/* See LICENSE file for copyright and license details. */ #include #include 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; }