#include #include #include #include #include #include #include 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; }