aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--util/.gitignore18
-rw-r--r--util/.makeenv2
-rw-r--r--util/Makefile36
-rw-r--r--util/README80
-rw-r--r--util/config.mk10
-rwxr-xr-xutil/gen-checksums23
-rwxr-xr-xutil/get-and-check17
-rwxr-xr-xutil/get-checksums42
-rwxr-xr-xutil/order-checksums16
-rw-r--r--util/repodiff.c316
-rw-r--r--util/tmpmount.c44
-rwxr-xr-xutil/validate-tarball27
12 files changed, 631 insertions, 0 deletions
diff --git a/util/.gitignore b/util/.gitignore
new file mode 100644
index 0000000..88f9aca
--- /dev/null
+++ b/util/.gitignore
@@ -0,0 +1,18 @@
+*\#*
+*~
+*.o
+*.a
+*.t
+*.lo
+*.to
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+/tmpmount
+/repodiff
diff --git a/util/.makeenv b/util/.makeenv
new file mode 100644
index 0000000..f345ae5
--- /dev/null
+++ b/util/.makeenv
@@ -0,0 +1,2 @@
+MAKEENV_OPTS_OPT_ATTACHED_ARG = j
+CC = gcc-extreme -std=c99
diff --git a/util/Makefile b/util/Makefile
new file mode 100644
index 0000000..d5e3a6c
--- /dev/null
+++ b/util/Makefile
@@ -0,0 +1,36 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+BIN =\
+ tmpmount\
+ repodiff
+
+OBJ =\
+ tmpmount.o\
+ repodiff.o
+
+HDR =
+
+all: $(BIN)
+$(OBJ): $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+tmpmount: tmpmount.o
+ $(CC) -o $@ tmpmount.o $(LDFLAGS)
+ $(ASROOT) sh -c "chown -- '0:0' $@ && chmod -- 4755 $@"
+
+repodiff: repodiff.o
+ $(CC) -o $@ repodiff.o $(LDFLAGS)
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda
+ -rm -f -- $(BIN)
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+.PHONY: all clean
diff --git a/util/README b/util/README
new file mode 100644
index 0000000..b4ce9b4
--- /dev/null
+++ b/util/README
@@ -0,0 +1,80 @@
+tmpmount mountpoint command [argument] ...
+
+ Mounts a tmpfs at `mountpoint` and execs into
+ `command [argument] ...`. The tmpfs is private
+ to the process and it's children.
+
+
+repodiff directory-1 file-list-1 directory-2 file-list-2
+
+ Checks for differences betweens the files listed in
+ file-list-1, which are relative to directory-1, and
+ files listed in file-list-2, which are relative to
+ directory-2. Will only check for things supported
+ by git: file listing, file names, file content,
+ symlink target, and executable by owner. (Files
+ that are not regular files, symbolic links or
+ directories will cause failure.)
+
+ file-list-1 and file-list-2 use <nul> termination
+ rather then <newline> termination, meaning that
+ if the file listed is created with file(1),
+ `-print0` should have been used, or if with
+ git-ls-files(1), `-z` should have been used.
+
+ If a difference is found 1 is returned, 0 otherwise.
+ Exit value 2 is used to signal runtime error.
+
+
+validate-tarball tarball git-dir work-dir
+
+ Validates the contents a tarball againts a known
+ good directory (git-dir). An empty directory
+ (work-dir) shall be provided as a space for
+ temporary files.
+
+
+order-checksums
+
+ Sorts standard input, removed duplicates checksums
+ and output the checksums lines primarily ordered
+ by the hash algorithms in a particular order and
+ secondarily by the checksums alphabetically sorted.
+
+
+get-checksums tarball
+
+ Calculates the checksums of a tarball.
+
+ A special argument can be provided for printing
+ the order the hash algorithms are output in
+
+
+get-and-check tarball-url reference-dir work-dir
+
+ Downloads tarball-url and uses ./validate-tarball
+ to validate its contents against a known good
+ directory (reference-dir). An empty directory
+ (work-dir) shall be provided as a space for
+ temporary files. If the content of the tarball
+ matches git-tracked files in reference-dir,
+ ./get-checksums is used to output the tarball's
+ checksums.
+
+
+gen-checksums tarball git-dir < version-info-file > checksum-listing
+
+ This brings all of the above together. The tarball,
+ which is assumed to be the tarball for the static release
+ is validates against a known good directory (git-dir),
+ for which the tarball is supposted to be generated from,
+ but this check validates that git itself is not manipulating
+ the tarballs. Then it reads stdin for URLs in lines that
+ begin with "Tarball: " (however, the URL for the static
+ release is discarded as it is assumed to not exist yet
+ but be the same file as input as the argument). These
+ URLs are used to download release tarballs from all
+ mirrors; they are also validates against the known good
+ directory. The checksums for all tarballs, both the
+ one provided in the command line and those downloaded,
+ are output to stdout.
diff --git a/util/config.mk b/util/config.mk
new file mode 100644
index 0000000..d78941c
--- /dev/null
+++ b/util/config.mk
@@ -0,0 +1,10 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS = -lsimple
+
+ASROOT = asroot
diff --git a/util/gen-checksums b/util/gen-checksums
new file mode 100755
index 0000000..bb1f981
--- /dev/null
+++ b/util/gen-checksums
@@ -0,0 +1,23 @@
+#!/bin/sh
+# See LICENSE file for copyright and license details.
+
+set -e
+
+if test ! $# = 2 || test -t 0 || test -t 1; then
+ printf 'usage: %s tarball git-dir < version-info-file > checksum-listing\n' "$0" >&2
+fi
+
+tarball="$1"
+gitdir="$2"
+
+utildir="$(dirname -- "$0")"
+(cd -- "${utildir}" && (make >/dev/null 2>/dev/null || make))
+
+"${utildir}"/tmpmount /var/empty "${utildir}"/validate-tarball "${tarball}" "${gitdir}" /var/empty
+
+grep '^Tarball: ' | cut -d ' ' -f 2 | grep -v '^https://maandree.se/static/' | (
+ "${utildir}"/get-checksums "${tarball}"
+ while read -r url; do
+ "${utildir}"/tmpmount /var/empty "${utildir}"/get-and-check "${url}" "${gitdir}" /var/empty
+ done
+) | "${utildir}"/order-checksums
diff --git a/util/get-and-check b/util/get-and-check
new file mode 100755
index 0000000..da9f1aa
--- /dev/null
+++ b/util/get-and-check
@@ -0,0 +1,17 @@
+#!/bin/sh
+# See LICENSE file for copyright and license details.
+
+set -e
+
+if ! test $# = 3; then
+ printf 'usage: %s tarball-url reference-dir work-dir\n' "$0" >&2
+ exit 1
+fi
+
+utildir="$(dirname -- "$0")"
+tarball="$3/download-$$.tar.gz"
+
+curl -sL -- "$1" > "${tarball}"
+"${utildir}"/validate-tarball "${tarball}" "$2" "$3"
+"${utildir}"/get-checksums "${tarball}"
+rm -f -- "${tarball}"
diff --git a/util/get-checksums b/util/get-checksums
new file mode 100755
index 0000000..9d1b86a
--- /dev/null
+++ b/util/get-checksums
@@ -0,0 +1,42 @@
+#!/bin/sh
+# See LICENSE file for copyright and license details.
+
+set -e
+
+gensums () {
+ gensum sha224sum SHA224
+ gensum sha256sum SHA256
+ gensum sha384sum SHA384
+ gensum sha512sum SHA512
+ gensum sha512-224sum SHA512/224
+ gensum sha512-256sum SHA512/256
+ gensum sha3-224sum SHA3-224
+ gensum sha3-256sum SHA3-256
+ gensum sha3-384sum SHA3-384
+ gensum sha3-512sum SHA3-512
+ gensum b2sum BLAKE2b
+}
+
+if test $# = 1 && test "$1" = '-- output checksum order --'; then
+ gensum () { printf '%s\n' "$2"; }
+ gensums
+ exit 0
+fi
+
+if ! test $# = 1; then
+ printf 'usage: %s tarball\n' "$0" >&2
+ exit 1
+fi
+
+tarball="$1"
+
+gensum () {
+ tool="$1"
+ name="$2"
+
+ sum="$(${tool} < "${tarball}" | cut -d ' ' -f 1)"
+ test -n "${sum}"
+ printf '%s checksum: %s\n' "${name}" "${sum}"
+}
+
+gensums
diff --git a/util/order-checksums b/util/order-checksums
new file mode 100755
index 0000000..00abca9
--- /dev/null
+++ b/util/order-checksums
@@ -0,0 +1,16 @@
+#!/bin/sh
+# See LICENSE file for copyright and license details.
+
+set -e
+
+if ! test $# = 0; then
+ printf 'usage: %s < unordered-checksum-table > ordered-checksum-table \n' "$0" >&2
+ exit 1
+fi
+
+utildir="$(dirname -- "$0")"
+
+text="$(sort -u)"
+"${utildir}"/get-checksums '-- output checksum order --' | while read sum; do
+ printf '%s\n' "${text}" | grep "^${sum} checksum:"
+done
diff --git a/util/repodiff.c b/util/repodiff.c
new file mode 100644
index 0000000..b27aef6
--- /dev/null
+++ b/util/repodiff.c
@@ -0,0 +1,316 @@
+/* See LICENSE file for copyright and license details. */
+#include <libsimple.h>
+#include <libsimple-arg.h>
+
+NUSAGE(2, "directory-1 file-list-1 directory-2 file-list-2");
+
+
+LIBSIMPLE_PURE
+static int
+parse_fd(const char *s, int dash_fd)
+{
+ int fd, d;
+
+ if (streq(s, "/dev/stdin"))
+ return STDIN_FILENO;
+ if (streq(s, "/dev/stdout"))
+ return STDOUT_FILENO;
+ if (streq(s, "/dev/stderr"))
+ return STDERR_FILENO;
+ if (streq(s, "-"))
+ return dash_fd;
+
+ if (strstarts(s, "/proc/self/fd/"))
+ s = &s[sizeof("/proc/self/fd/") - 1u];
+ else if (strstarts(s, "/dev/fd/"))
+ s = &s[sizeof("/dev/fd/") - 1u];
+ else
+ return -1;
+
+ if ('0' > *s || *s > '9')
+ return -1;
+ fd = (int)(*s++ - '0');
+ while ('0' <= *s && *s <= '9') {
+ d = (int)(*s - '0');
+ if (fd > (INT_MAX - d) / 10)
+ return -1;
+ fd = fd * 10 + d;
+ }
+
+ return *s ? -1 : fd;
+}
+
+
+static char **
+get_file_list(const char *path, size_t *n_out)
+{
+ char *buf = NULL;
+ size_t bufsize = 0u;
+ size_t len = 0u;
+ size_t off, i, j, d;
+ int fd, do_close;
+ ssize_t r;
+ char **list = NULL;
+ size_t listsize = 0u;
+
+ *n_out = 0u;
+
+ do_close = 0;
+ fd = parse_fd(path, STDIN_FILENO);
+ if (fd < 0) {
+ do_close = 1;
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ eprintf("open %s O_RDONLY:", path);
+ }
+
+ for (;;) {
+ if (len == bufsize)
+ buf = erealloc(buf, bufsize += (size_t)8 << 10);
+ r = read(fd, &buf[len], bufsize - len);
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ eprintf("read %s:", path);
+ }
+ len += (size_t)r;
+ }
+
+ if (do_close)
+ close(fd);
+
+ buf = erealloc(buf, len + 1u);
+ buf[len++] = '\0';
+
+ off = 0u;
+ for (i = 0u; i < len; i++) {
+ if (buf[i])
+ continue;
+ if (i == off)
+ goto next;
+ while (buf[off] == '/')
+ off += 1u;
+ if (buf[off + 0u] == '.' && buf[off + 1u] == '/')
+ off += 2u;
+ if (!buf[off])
+ goto next;
+ if (buf[off + 0u] == '.' && !buf[off + 1u])
+ goto next;
+
+ d = buf[i - 1u] == '/' ? 1u : 0u;
+ buf[i - d] = '\0';
+ if (!buf[off])
+ goto next;
+
+ if (*n_out == listsize)
+ list = ereallocarray(list, listsize += 128u, sizeof(*list));
+ list[(*n_out)++] = estrdup(&buf[off]);
+ for (j = i - 1u - d; j > off; j--) {
+ if (buf[j] != '/')
+ continue;
+ buf[j] = '\0';
+ if (*n_out == listsize)
+ list = ereallocarray(list, listsize += 128u, sizeof(*list));
+ list[(*n_out)++] = estrdup(&buf[off]);
+ }
+
+ next:
+ off = i + 1u;
+ }
+
+ free(buf);
+ return list;
+}
+
+
+static size_t
+uniq(char **list, size_t n)
+{
+ size_t r, w = 1u;
+ if (!n)
+ return 0u;
+ for (r = 1u; r < n; r++) {
+ if (strcmp(list[r], list[w - 1u]))
+ list[w++] = list[r];
+ else
+ free(list[r]);
+ }
+ return w;
+}
+
+
+static int
+files_equal(int fd1, const char *dir1, int fd2, const char *dir2, const char *path)
+{
+ static char buf1[8192];
+ static char buf2[sizeof(buf1)];
+ size_t len1, len2;
+ ssize_t r;
+
+ do {
+ len1 = 0u;
+ while (len1 < sizeof(buf1)) {
+ r = read(fd1, &buf1[len1], sizeof(buf1) - len1);
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ eprintf("read %s/%s:", dir1, path);
+ }
+ len1 += (size_t)r;
+ }
+
+ len2 = 0u;
+ while (len2 < sizeof(buf2)) {
+ r = read(fd2, &buf2[len2], sizeof(buf2));
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ eprintf("read %s/%s:", dir2, path);
+ }
+ len2 += (size_t)r;
+ }
+
+ if (len1 != len2 || memcmp(buf1, buf2, len1))
+ return 0;
+ } while (len1 && len2);
+
+ return !len1 && !len2;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ struct stat st1, st2;
+ const char *dir1, *dir2;
+ char **files1, **files2;
+ size_t nfiles1, nfiles2, i, j;
+ char *target1, *target2;
+ int dirfd1, dirfd2, cmp;
+ int fd1, fd2;
+ int ret = 0;
+
+ libsimple_default_failure_exit = 2;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc != 4)
+ usage();
+
+ dir1 = argv[0];
+ dir2 = argv[2];
+ dirfd1 = open(dir1, O_PATH);
+ if (dirfd1 < 0)
+ eprintf("open %s O_PATH", dir1);
+ dirfd2 = open(dir2, O_PATH);
+ if (dirfd2 < 0)
+ eprintf("open %s O_PATH", dir2);
+
+ files1 = get_file_list(argv[1], &nfiles1);
+ files2 = get_file_list(argv[3], &nfiles2);
+ libsimple_qsort_str((void *)files1, nfiles1);
+ libsimple_qsort_str((void *)files2, nfiles2);
+ nfiles1 = uniq(files1, nfiles1);
+ nfiles2 = uniq(files2, nfiles2);
+
+ i = j = 0u;
+ while (i < nfiles1 || j < nfiles2) {
+ if (i == nfiles1) {
+ exclusive_to_dir2:
+ weprintf("%s exists only in %s\n", files2[j], dir2);
+ j++;
+ ret = 1;
+ continue;
+ }
+ if (j == nfiles2) {
+ exclusive_to_dir1:
+ weprintf("%s exists only in %s\n", files1[i], dir1);
+ i++;
+ ret = 1;
+ continue;
+ }
+
+ cmp = strcmp(files1[i], files2[j]);
+ if (cmp < 0)
+ goto exclusive_to_dir1;
+ if (cmp > 1)
+ goto exclusive_to_dir2;
+
+ if (fstatat(dirfd1, files1[i], &st1, AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT))
+ eprintf("fstatat %s %s AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT:", dir1, files1[i]);
+ if (fstatat(dirfd2, files2[j], &st2, AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT))
+ eprintf("fstatat %s %s AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT:", dir2, files2[j]);
+
+ if ((st1.st_mode & S_IFMT) != (st2.st_mode & S_IFMT)) {
+ weprintf("%s has different file types in %s and %s\n", files1[i], dir1, dir2);
+ ret = 1;
+ goto next;
+ }
+
+ switch (st1.st_mode & S_IFMT) {
+ case S_IFREG:
+ if ((st1.st_mode ^ st2.st_mode) & S_IXUSR) {
+ weprintf("%s is executable in one of but in not both of %s and %s\n", files1[i], dir1, dir2);
+ ret = 1;
+ }
+ if (st1.st_size != st2.st_size) {
+ weprintf("%s has different sizes in %s and %s\n", files1[i], dir1, dir2);
+ ret = 1;
+ goto next;
+ }
+ fd1 = openat(dirfd1, files1[i], O_RDONLY | O_NOFOLLOW);
+ if (fd1 < 0)
+ eprintf("openat %s %s O_RDONLY|O_NOFOLLOW:", dir1, files1[i]);
+ fd2 = openat(dirfd2, files2[j], O_RDONLY | O_NOFOLLOW);
+ if (fd2 < 0)
+ eprintf("openat %s %s O_RDONLY|O_NOFOLLOW:", dir2, files2[j]);
+ if (!files_equal(fd1, dir1, fd2, dir2, files1[i])) {
+ weprintf("%s has different contents in %s and %s\n", files1[i], dir1, dir2);
+ ret = 1;
+ }
+ close(fd1);
+ close(fd2);
+ break;
+
+ case S_IFLNK:
+ target1 = libsimple_ereadlinkat(dirfd1, files1[i]);
+ target2 = libsimple_ereadlinkat(dirfd2, files2[j]);
+ if (strcmp(target1, target2)) {
+ weprintf("%s has different targets in %s and %s\n", files1[i], dir1, dir2);
+ ret = 1;
+ }
+ free(target1);
+ free(target2);
+ break;
+
+ case S_IFDIR:
+ break;
+
+ default:
+ eprintf("%s has unsupported file type\n", files1[i]);
+ }
+
+ next:
+ i++;
+ j++;
+ }
+
+ close(dirfd1);
+ close(dirfd2);
+ while (nfiles1--)
+ free(files1[nfiles1]);
+ while (nfiles2--)
+ free(files2[nfiles2]);
+ free(files1);
+ free(files2);
+ return ret;
+}
diff --git a/util/tmpmount.c b/util/tmpmount.c
new file mode 100644
index 0000000..75e225d
--- /dev/null
+++ b/util/tmpmount.c
@@ -0,0 +1,44 @@
+/* See LICENSE file for copyright and license details. */
+#include <sys/mount.h>
+#include <sched.h>
+#include <libsimple.h>
+#include <libsimple-arg.h>
+
+NUSAGE(125, "mountpoint utility [argument] ...");
+
+
+int
+main(int argc, char *argv[])
+{
+ const char *mountpoint;
+
+ libsimple_default_failure_exit = 125;
+
+ ARGBEGIN {
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc < 2)
+ usage();
+
+ mountpoint = *argv++;
+ argc--;
+
+ if (unshare(CLONE_NEWNS))
+ eprintf("unshare CLONE_NEWNS:");
+ if (mount("none", "/", NULL, MS_REC | MS_SLAVE, NULL))
+ eprintf("mount none / NULL MS_REC|MS_SLAVE NULL:");
+
+ if (mount("tmpfs", mountpoint, "tmpfs", 0, NULL))
+ eprintf("mount tmpfs %s tmpfs 0 NULL:", mountpoint);
+
+ if (setegid(getgid()))
+ eprintf("setegid <real group>:");
+ if (seteuid(getuid()))
+ eprintf("seteuid <real user>:");
+
+ execvp(argv[0], argv);
+ enprintf(errno == ENOENT ? 127 : 126, "execvp %s:", argv[0]);
+ return 0;
+}
diff --git a/util/validate-tarball b/util/validate-tarball
new file mode 100755
index 0000000..14aff6d
--- /dev/null
+++ b/util/validate-tarball
@@ -0,0 +1,27 @@
+#!/bin/sh
+# See LICENSE file for copyright and license details.
+
+set -e
+
+if ! test $# = 3; then
+ printf 'usage: %s tarball git-dir work-dir\n' "$0" >&2
+ exit 1
+fi
+
+utildir="$(dirname -- "$0")"
+
+dir="$3/$$.tmpdir"
+rm -rf -- "${dir}"
+mkdir -- "${dir}"
+
+gunzip < "$1" | (cd -- "${dir}" && tar -x)
+
+(cd -- "$2" && git ls-files -z) > "$3/listing-1"
+(cd -- "${dir}"/*/ && find -print0) > "$3/listing-2"
+
+set +e
+"${utildir}"/repodiff "$2" "$3/listing-1" "${dir}"/* "$3/listing-2"
+ret=$?
+
+rm -rf -- "${dir}"
+exit $ret