diff options
Diffstat (limited to '')
| -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  | 
