aboutsummaryrefslogtreecommitdiffstats
path: root/util/repodiff.c
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-05-28 18:46:27 +0200
committerMattias Andrée <m@maandree.se>2026-05-28 18:46:27 +0200
commit55e9f58fa7c551aebfd6084b778cd942fa8b20ce (patch)
tree7177e49a9c3a3c2e9cd4f88e8507cb19c9f6c1b0 /util/repodiff.c
parentm fix (diff)
downloadrelease-scripts-55e9f58fa7c551aebfd6084b778cd942fa8b20ce.tar.gz
release-scripts-55e9f58fa7c551aebfd6084b778cd942fa8b20ce.tar.bz2
release-scripts-55e9f58fa7c551aebfd6084b778cd942fa8b20ce.tar.xz
Update (fixes support for a few edge cases, and leaves nothing laying around)
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
-rw-r--r--util/repodiff.c316
1 files changed, 316 insertions, 0 deletions
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;
+}