diff options
Diffstat (limited to 'check/check-icon-listing.c')
-rw-r--r-- | check/check-icon-listing.c | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/check/check-icon-listing.c b/check/check-icon-listing.c new file mode 100644 index 0000000..af9eb1f --- /dev/null +++ b/check/check-icon-listing.c @@ -0,0 +1,398 @@ +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +enum filetype { + ERROR, + NO_SUCH_FILE, + REGULAR, + SYMLINK, + OTHER_TYPE +}; + +static size_t lineno = 0; +static char *text = NULL; +static size_t textlen = 0; +static int exitstatus = 0; +static char *filebuf = NULL; +static char *targetbuf = NULL; +static char *relbuf = NULL; + +static void +loadicons(void) +{ + int fd; + size_t size = 0; + ssize_t r; + + fd = open("icons.mk", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Failed to open icons.mk: %s\n", strerror(errno)); + exit(1); + } + + for (;;) { + if (textlen == size) { + size += 128UL << 10; + text = realloc(text, size); + if (!text) { + fprintf(stderr, "Failed to allocate enough memory to load icons.mk\n"); + exit(1); + } + } + + r = read(fd, &text[textlen], size - textlen); + if (r < 0) { + fprintf(stderr, "Failed to read icons.mk: %s\n", strerror(errno)); + exit(1); + } else if (!r) { + break; + } else { + textlen += (size_t)r; + } + } + + close(fd); +} + +static char * +nextline(void) +{ + static size_t pos = 0; + char *line = &text[pos]; + + if (pos == textlen) + return NULL; + + lineno += 1; + + for (; pos < textlen; pos++) { + if (!text[pos]) { + fprintf(stderr, "Line %zu in icons.mk contains NUL byte\n", lineno); + exitstatus = 1; + text[pos] = '^'; + } else if (text[pos] == '\n') { + text[pos++] = '\0'; + break; + } + } + + return line; +} + +static int +iscomment(char *line) +{ + while (*line == ' ' || *line == '\t') + line++; + return *line == '#' || !*line; +} + +static void +remove_line_continuation(char *line) +{ + size_t n = strlen(line); + if (n && line[n - 1] == '\\') + line[n - 1] = '\0'; +} + +static void +rstrip(char *line) +{ + size_t n = strlen(line); + while (n && (line[n - 1] == ' ' || line[n - 1] == '\t')) + line[--n] = '\0'; +} + +static size_t +getindent(char **linep) +{ + size_t indent = 0; + char *line = *linep; + int warned_sp = 0; + + for (;; line++) { + if (*line == ' ') { + if (!warned_sp) { + fprintf(stderr, "Line %zu in icons.mk contains SP character instead of HT\n", lineno); + warned_sp = 1; + } + exitstatus = 1; + indent += 1; + } else if (*line == '\t') { + indent += 8; + } else { + break; + } + } + + *linep = line; + return (indent + 7) / 8; +} + +static char * +getfile(char *icon) +{ + static size_t size = 0; + size_t req = strlen(icon) + sizeof("scalable/.svg"); + if (req > size) { + size = req; + filebuf = realloc(filebuf, size); + if (!filebuf) { + fprintf(stderr, "Failed to allocate enough memory to validate icons.mk\n"); + exit(1); + } + } + stpcpy(stpcpy(stpcpy(filebuf, "scalable/"), icon), ".svg"); + return filebuf; +} + +static enum filetype +getlink(char *file, char **target_out) +{ + static size_t size = 0; + ssize_t r; + struct stat st; + + if (!size) { + grow_and_try_again: + size += 4096; + targetbuf = realloc(targetbuf, size); + if (!targetbuf) { + fprintf(stderr, "Failed to allocate enough memory to validate icons.mk\n"); + exit(1); + } + } + + r = readlink(file, targetbuf, size - 1); + if (r >= 0) { + if (r >= size - 2) + goto grow_and_try_again; + while (r && !targetbuf[r - 1]) + r -= 1; + targetbuf[r] = '\0'; + *target_out = targetbuf; + return SYMLINK; + } else if (errno == ENOENT) { + *target_out = NULL; + return NO_SUCH_FILE; + } else if (errno == EINVAL) { + *target_out = NULL; + if (!lstat(file, &st)) + return S_ISREG(st.st_mode) ? REGULAR : OTHER_TYPE; + fprintf(stderr, "Failure at line %zu in icons.mk: lstat %s: %s\n", lineno, file, strerror(errno)); + exitstatus = 1; + return ERROR; + } else { + fprintf(stderr, "Failure at line %zu in icons.mk: readlink %s: %s\n", lineno, file, strerror(errno)); + exitstatus = 1; + *target_out = NULL; + return ERROR; + } +} + +static char * +rel(char *to, char *from) +{ + static size_t size = 0; + size_t i, req, up = 0; + char *p; + + size_t off = 0; + for (i = 0; to[i]; i++) { + if (to[i] != from[i]) + break; + if (to[i] == '/') + off = i + 1; + } + to = &to[off]; + from = &from[off]; + + while ((from = strchr(from, '/'))) { + from = &from[1]; + up += 1; + } + + if (!up) + return to; + + req = up * 3 + strlen(to) + 1; + if (req > size) { + size += 4096; + relbuf = realloc(relbuf, size = req); + if (!relbuf) { + fprintf(stderr, "Failed to allocate enough memory to validate icons.mk\n"); + exit(1); + } + } + + p = relbuf; + while (up--) + p = stpcpy(p, "../"); + stpcpy(p, to); + return relbuf; +} + +int +main(void) +{ + char *line; + char *file; + char *target; + char *goal; + int ret = 0; + size_t count = 0; + size_t indent; + char **stack = NULL; + size_t stacksize = 0; + size_t stacklen = 0; + struct stat st1, st2; + const char *diff; + size_t len; + int fixed; + + loadicons(); + + while ((line = nextline())) { + remove_line_continuation(line); + if (iscomment(line)) + continue; + break; + } + + while ((line = nextline())) { + remove_line_continuation(line); + if (iscomment(line)) + continue; + + count += 1; + rstrip(line); + indent = getindent(&line); + if (strchr(line, ' ') || strchr(line, '\t')) { + fprintf(stderr, "Line %zu in icons.mk contains unexpected whitespace\n", lineno); + exitstatus = 1; + } + + if (!indent) { + fprintf(stderr, "Line %zu in icons.mk is not indented\n", lineno); + exitstatus = 1; + break; + } + indent -= 1; + if (indent > stacklen) { + fprintf(stderr, "Line %zu in icons.mk (%s) is overindented\n", lineno, line); + exitstatus = 1; + break; + } + if (stacksize <= indent) { + stacksize += 32; + stack = realloc(stack, stacksize * sizeof(*stack)); + if (!stack) { + fprintf(stderr, "Failed to allocate enough memory to validate icons.mk\n"); + exit(1); + } + } + stack[indent] = line; + stacklen = indent + 1; + + file = getfile(line); + switch (getlink(file, &target)) { + case ERROR: + continue; + case NO_SUCH_FILE: + fprintf(stderr, "%s is listed but does not exist\n", line); + exitstatus = 1; + continue; + case REGULAR: + if (indent) { + fprintf(stderr, "%s is not a symlink but is listed as linking to %s\n", line, stack[indent - 1]); + exitstatus = 1; + continue; + } + break; + case SYMLINK: + if (!indent) { + fprintf(stderr, "%s is a symlink but not indented\n", line); + exitstatus = 1; + } + len = strlen(target); + if (len < 5 || strcmp(&target[len - 4], ".svg")) { + fprintf(stderr, "target of %s (%s) is not a .svg-file\n", line, target); + exitstatus = 1; + continue; + } + target[len -= 4] = '\0'; + break; + case OTHER_TYPE: + default: + fprintf(stderr, "%s is listed as an icon but is not a regular file\n", line); + exitstatus = 1; + continue; + } + + if (indent) { + if (stat(file, &st1)) { + if (errno == ENOENT) { + fprintf(stderr, "%s is a dangling symlink\n", line); + } else { + fprintf(stderr, "Failure at line %zu in icons.mk: stat %s: %s\n", + lineno, line, strerror(errno)); + } + exitstatus = 1; + continue; + } + + file = getfile(stack[indent - 1]); + if (stat(file, &st2)) { + fprintf(stderr, "Failure at line %zu in icons.mk (%s): stat %s: %s\n", + lineno, line, file, strerror(errno)); + } + + if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) + diff = "same real file"; + else + diff = "different real file"; + + fixed = 0; + while (target[0] == '.' && target[1] == '/') { + target = &target[2]; + fixed = 1; + } + goal = rel(stack[indent - 1], line); + if (strcmp(target, goal)) { + fprintf(stderr, "%s links to %s.svg but should link to %s.svg [%s] (%s)\n", + line, target, goal, stack[indent - 1], diff); + exitstatus = 1; + continue; + } + if (fixed) { + fprintf(stderr, "Fixing symlink %s\n", line); + file = getfile(line); + *strchr(target, '\0') = '.'; /* restore ".svg" at the end of the string */ + if (unlink(file)) { + fprintf(stderr, "... failed to unlink\n"); + } else if (symlink(target, file)) { + fprintf(stderr, "... failed to create symlink (%s -> %s)\n", file, target); + exitstatus = 1; + break; + } + } + } + } + + if (!count) { + fprintf(stderr, "No icons are listed in icons.mk\n"); + exitstatus = 1; + } + + free(stack); + free(text); + free(filebuf); + free(targetbuf); + free(relbuf); + return exitstatus; +} |