diff options
Diffstat (limited to 'check')
-rw-r--r-- | check/check-icon-listing.c | 398 | ||||
-rwxr-xr-x | check/check-icons-listing | 89 | ||||
-rwxr-xr-x | check/find-duplicates | 9 |
3 files changed, 407 insertions, 89 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; +} diff --git a/check/check-icons-listing b/check/check-icons-listing deleted file mode 100755 index 404e089..0000000 --- a/check/check-icons-listing +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/sh -set -e - -rel () { - to="$1" - from="$2" - while test $(printf '%s\n' "$to" "$from" | grep / | wc -l) = 2; do - todir="$(printf '%s\n' "$to" | cut -d / -f 1)" - fromdir="$(printf '%s\n' "$from" | cut -d / -f 1)" - if test ! "$todir" = "$fromdir"; then - break - fi - to="$(printf '%s\n' "$to" | cut -d / -f 2-)" - from="$(printf '%s\n' "$frm" | cut -d / -f 2-)" - done - while test -n "$(printf '%s\n' "$from" | grep /)"; do - from="$(printf '%s\n' "$from" | cut -d / -f 2-)" - to="../$to" - done - printf '%s\n' "$to" -} - -lines=$(sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed 1d | wc -l) -test ! $lines = 0 - -test $(sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed 1d | sed 's/\t/ /g' | grep '^ ' | wc -l) = $lines - -test $(sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed '1s/^.*$//' | grep -n ' ' | wc -l) = 0 - -stack="" -i=0 -sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed 1d | sed 's/^\t/x/' | while read L; do - i=$(( i + 1 )) - printf 'Checking %i of %i\n\033[A' $i $lines >&2 - tabs=0 - while test ! $(printf '%s\n' "$L" | sed -n '/^x\t/p' | wc -l) = 0; do - L="$(printf '%s\n' "$L" | sed 's/^x\t/x/')" - tabs=$(( tabs + 1 )) - done - L="$(printf '%s\n' "$L" | sed 's/^x//')" - f="scalable/$L.svg" - if test ! $(printf '%s\n' $f | wc -l) = 1; then - printf '\033[K%s contains whitespace\n' "$L" >&2 - exit 1 - fi - tabsplus1=$(( tabs + 1 )) - if test -z "$(printf '%s\n' $stack x | sed -n ${tabsplus1}p)"; then - printf '\033[K%s is overtabulated\n' "$L" >&2 - exit 1 - else - stack="$(printf '%s\n' $stack | head -n $tabs; printf '%s\n' "$L")" - fi - if test ! -e "$f"; then - if test -L "$f"; then - printf '\033[K%s is a dangling symlink\n' "$L" >&2 - else - printf '\033[K%s is listed but does not exist\n' "$L" >&2 - fi - exit 1 - fi - if test $tabs = 0; then - if test -L "$f"; then - printf '\033[K%s is a symlink but not indented\n' "$L" >&2 - exit 1 - fi - else - goal_="$(printf '%s\n' $stack | sed -n "${tabs}p")" - if test ! -L "$f"; then - printf '\033[K%s is not a symlink but listed as linking to %s\n' "$L" "$goal" >&2 - exit 1 - fi - target="$(realpath -- "$f")" - goal="$(realpath -- "scalable/${goal_}.svg")" - if test "$target" = "$goal"; then - diff="same real file" - else - diff="different real file" - fi - target="$(readlink -- "$f")" - goal="$(rel "${goal_}.svg" "$L.svg")" - if test "$target" = "./$goal"; then - ln -sf -- "$target" "$f" - elif test ! "$target" = "$goal"; then - printf '\033[K%s links to %s but should link to %s (%s)\n' "$L" "$target" "$goal" "$diff" >&2 - exit 1 - fi - fi -done -printf '\033[K' >&2 diff --git a/check/find-duplicates b/check/find-duplicates new file mode 100755 index 0000000..2517fb6 --- /dev/null +++ b/check/find-duplicates @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +dups="$(sed 's/\\$//' < icons.mk | sed '/^\s*\(#.*\|\)$/d' | sed 1d | tr -d '\t ' | sort | uniq -d)" + +if test -n "$dups"; then + printf 'The following files have been listed in icons.mk multiple times:\n%s\n' "$dups" >&2 + exit 1 +fi |