aboutsummaryrefslogblamecommitdiffstats
path: root/check/check-icon-listing.c
blob: af9eb1fd68c76f50b56b4262075d12faf0c289d9 (plain) (tree)













































































































































































































































































































































































































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