diff options
| author | Mattias Andrée <m@maandree.se> | 2026-05-28 18:46:27 +0200 |
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2026-05-28 18:46:27 +0200 |
| commit | 55e9f58fa7c551aebfd6084b778cd942fa8b20ce (patch) | |
| tree | 7177e49a9c3a3c2e9cd4f88e8507cb19c9f6c1b0 /util/repodiff.c | |
| parent | m fix (diff) | |
| download | release-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 'util/repodiff.c')
| -rw-r--r-- | util/repodiff.c | 316 |
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; +} |
