diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | difftool/Makemodule.am | 6 | ||||
-rw-r--r-- | difftool/compare_dir.c | 97 | ||||
-rw-r--r-- | difftool/compare_file.c | 77 | ||||
-rw-r--r-- | difftool/difftool.h | 41 | ||||
-rw-r--r-- | difftool/fscompare.c | 118 | ||||
-rw-r--r-- | difftool/node_compare.c | 99 | ||||
-rw-r--r-- | difftool/util.c | 26 |
9 files changed, 467 insertions, 0 deletions
@@ -24,3 +24,4 @@ sqfs2tar tar2sqfs test_* test-* +fscompare diff --git a/Makefile.am b/Makefile.am index 155ac08..4fb779d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,6 +4,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/include -D_GNU_SOURCE AM_CFLAGS = $(WARN_CFLAGS) noinst_LIBRARIES = +noinst_PROGRAMS = bin_PROGRAMS = dist_man1_MANS = check_PROGRAMS = @@ -16,4 +17,5 @@ include lib/Makemodule.am include tar/Makemodule.am include mkfs/Makemodule.am include unpack/Makemodule.am +include difftool/Makemodule.am include tests/Makemodule.am diff --git a/difftool/Makemodule.am b/difftool/Makemodule.am new file mode 100644 index 0000000..4a03495 --- /dev/null +++ b/difftool/Makemodule.am @@ -0,0 +1,6 @@ +fscompare_SOURCES = difftool/fscompare.c difftool/difftool.h difftool/util.c +fscompare_SOURCES += difftool/compare_dir.c difftool/node_compare.c +fscompare_SOURCES += difftool/compare_file.c +fscompare_LDADD = libfstree.a libutil.a + +noinst_PROGRAMS += fscompare diff --git a/difftool/compare_dir.c b/difftool/compare_dir.c new file mode 100644 index 0000000..8d4a186 --- /dev/null +++ b/difftool/compare_dir.c @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * compare_dir.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "difftool.h" + +int compare_dir_entries(tree_node_t *a, tree_node_t *b) +{ + tree_node_t *ait = a->data.dir->children, *aprev = NULL; + tree_node_t *bit = b->data.dir->children, *bprev = NULL; + int ret, result = 0; + char *path, arrow; + + while (ait != NULL && bit != NULL) { + ret = strcmp(ait->name, bit->name); + + if (ret < 0) { + result = 1; + path = node_path(ait); + if (path == NULL) + return -1; + fprintf(stdout, "< %s\n", path); + free(path); + + if (aprev == NULL) { + a->data.dir->children = ait->next; + free(ait); + ait = a->data.dir->children; + } else { + aprev->next = ait->next; + free(ait); + ait = aprev->next; + } + } else if (ret > 0) { + result = 1; + path = node_path(bit); + if (path == NULL) + return -1; + fprintf(stdout, "> %s\n", path); + free(path); + + if (bprev == NULL) { + b->data.dir->children = bit->next; + free(bit); + bit = b->data.dir->children; + } else { + bprev->next = bit->next; + free(bit); + bit = bprev->next; + } + } else { + aprev = ait; + ait = ait->next; + + bprev = bit; + bit = bit->next; + } + } + + if (ait != NULL || bit != NULL) { + result = 1; + + if (ait != NULL) { + if (aprev == NULL) { + a->data.dir->children = NULL; + } else { + aprev->next = NULL; + } + arrow = '<'; + } else { + if (bprev == NULL) { + b->data.dir->children = NULL; + } else { + bprev->next = NULL; + } + arrow = '>'; + ait = bit; + } + + while (ait != NULL) { + path = node_path(ait); + if (path == NULL) { + result = -1; + } else { + fprintf(stdout, "%c %s\n", arrow, path); + free(path); + } + aprev = ait; + ait = ait->next; + free(aprev); + } + } + + return result; +} diff --git a/difftool/compare_file.c b/difftool/compare_file.c new file mode 100644 index 0000000..c424220 --- /dev/null +++ b/difftool/compare_file.c @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * compare_file.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "difftool.h" + +int compare_files(file_info_t *a, file_info_t *b, const char *path) +{ + uint64_t offset, size, diff; + int ret = 0, afd, bfd; + void *aptr, *bptr; + + if (a->size != b->size) + return 1; + + if (pushd(first_path)) + return -1; + afd = open(path, O_RDONLY); + if (afd < 0) { + fprintf(stderr, "%s/%s: %s\n", first_path, path, + strerror(errno)); + return -1; + } + if (popd()) + goto fail_afd; + + if (pushd(second_path)) + goto fail_afd; + bfd = open(path, O_RDONLY); + if (bfd < 0) { + fprintf(stderr, "%s/%s: %s\n", second_path, path, + strerror(errno)); + goto fail_afd; + } + if (popd()) + goto fail_bfd; + + size = a->size; + + for (offset = 0; offset < size; offset += diff) { + diff = size - offset; + if (diff > MAX_WINDOW_SIZE) + diff = MAX_WINDOW_SIZE; + + aptr = mmap(NULL, diff, PROT_READ, MAP_SHARED, afd, offset); + if (aptr == MAP_FAILED) { + fprintf(stderr, "mmap %s/%s: %s\n", first_path, path, + strerror(errno)); + goto fail_bfd; + } + + bptr = mmap(NULL, diff, PROT_READ, MAP_SHARED, bfd, offset); + if (bptr == MAP_FAILED) { + fprintf(stderr, "mmap %s/%s: %s\n", second_path, path, + strerror(errno)); + goto fail_bfd; + } + + ret = memcmp(aptr, bptr, diff); + munmap(aptr, diff); + munmap(bptr, diff); + + if (ret != 0) + break; + } + + close(afd); + close(bfd); + return ret; +fail_bfd: + close(bfd); +fail_afd: + close(afd); + return -1; +} diff --git a/difftool/difftool.h b/difftool/difftool.h new file mode 100644 index 0000000..0dea6d5 --- /dev/null +++ b/difftool/difftool.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * difftool.h + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#ifndef DIFFTOOL_H +#define DIFFTOOL_H + +#include "config.h" +#include "fstree.h" +#include "util.h" + +#include <sys/mman.h> +#include <unistd.h> +#include <stdlib.h> +#include <getopt.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#define MAX_WINDOW_SIZE (1024 * 1024 * 4) + +extern const char *first_path; +extern const char *second_path; +extern int compare_flags; + +enum { + COMPARE_NO_PERM = 0x01, + COMPARE_NO_OWNER = 0x02, +}; + +int compare_dir_entries(tree_node_t *a, tree_node_t *b); + +char *node_path(tree_node_t *n); + +int compare_files(file_info_t *a, file_info_t *b, const char *path); + +int node_compare(tree_node_t *a, tree_node_t *b); + +#endif /* DIFFTOOL_H */ diff --git a/difftool/fscompare.c b/difftool/fscompare.c new file mode 100644 index 0000000..c925adc --- /dev/null +++ b/difftool/fscompare.c @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * fscompare.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "difftool.h" + +static struct option long_opts[] = { + { "no-owner", no_argument, NULL, 'O' }, + { "no-permissions", no_argument, NULL, 'P' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, +}; + +static const char *short_opts = "OPhV"; + +static const char *usagestr = +"Usage: fscompare [OPTIONS...] <first> <second>\n" +"\n" +"Compare two directories, making sure that each contains the same entries.\n" +"If an entry is a directory, comparison recurses into the directory.\n" +"\n" +"Possible options:\n" +"\n" +" --no-owner, -O Do not compare file owners.\n" +" --no-permissions, -P Do not compare permission bits.\n" +"\n" +" --help, -h Print help text and exit.\n" +" --version, -V Print version information and exit.\n" +"\n"; + +const char *first_path; +const char *second_path; +int compare_flags = 0; + +static void process_options(int argc, char **argv) +{ + int i; + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'O': + compare_flags |= COMPARE_NO_OWNER; + break; + case 'P': + compare_flags |= COMPARE_NO_PERM; + break; + case 'h': + fputs(usagestr, stdout); + exit(EXIT_SUCCESS); + case 'V': + print_version(); + exit(EXIT_SUCCESS); + default: + goto fail_arg; + } + } + + if (optind >= argc) { + fputs("Missing arguments: first directory\n", stderr); + goto fail_arg; + } + + first_path = argv[optind++]; + + if (optind >= argc) { + fputs("Missing arguments: second directory\n", stderr); + goto fail_arg; + } + + second_path = argv[optind++]; + + if (optind < argc) { + fputs("Unknown extra arguments\n", stderr); + goto fail_arg; + } + return; +fail_arg: + fprintf(stderr, "Try `fscompare --help' for more information.\n"); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + int ret = EXIT_FAILURE; + fstree_t afs, bfs; + + process_options(argc, argv); + + if (fstree_init(&afs, 512, NULL)) + return EXIT_FAILURE; + + if (fstree_init(&bfs, 512, NULL)) + goto out_afs; + + if (fstree_from_dir(&afs, first_path, false)) + goto out_bfs; + + if (fstree_from_dir(&bfs, second_path, false)) + goto out_bfs; + + tree_node_sort_recursive(afs.root); + tree_node_sort_recursive(bfs.root); + + if (node_compare(afs.root, bfs.root) == 0) + ret = EXIT_SUCCESS; + +out_bfs: + fstree_cleanup(&bfs); +out_afs: + fstree_cleanup(&afs); + return ret; +} diff --git a/difftool/node_compare.c b/difftool/node_compare.c new file mode 100644 index 0000000..fae35ab --- /dev/null +++ b/difftool/node_compare.c @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * node_compare.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "difftool.h" + +int node_compare(tree_node_t *a, tree_node_t *b) +{ + char *path = node_path(a); + tree_node_t *ait, *bit; + int ret, status = 0; + + if (path == NULL) + return -1; + + if ((a->mode & S_IFMT) != (b->mode & S_IFMT)) { + fprintf(stdout, "%s has a different type\n", path); + free(path); + return 1; + } + + if (!(compare_flags & COMPARE_NO_PERM)) { + if ((a->mode & ~S_IFMT) != (b->mode & ~S_IFMT)) { + fprintf(stdout, "%s has different permissions\n", + path); + status = 1; + } + } + + if (!(compare_flags & COMPARE_NO_OWNER)) { + if (a->uid != b->uid || a->gid != b->gid) { + fprintf(stdout, "%s has different ownership\n", path); + status = 1; + } + } + + switch (a->mode & S_IFMT) { + case S_IFSOCK: + case S_IFIFO: + break; + case S_IFBLK: + case S_IFCHR: + if (a->data.devno != b->data.devno) { + fprintf(stdout, "%s has different device number\n", + path); + status = 1; + } + break; + case S_IFLNK: + if (strcmp(a->data.slink_target, b->data.slink_target) != 0) { + fprintf(stdout, "%s has a different link target\n", + path); + } + break; + case S_IFDIR: + ret = compare_dir_entries(a, b); + if (ret < 0) { + status = -1; + break; + } + if (ret > 0) + status = 1; + + free(path); + path = NULL; + + ait = a->data.dir->children; + bit = b->data.dir->children; + + while (ait != NULL && bit != NULL) { + ret = node_compare(ait, bit); + if (ret < 0) + return -1; + if (ret > 0) + status = 1; + + ait = ait->next; + bit = bit->next; + } + break; + case S_IFREG: + ret = compare_files(a->data.file, b->data.file, path); + if (ret < 0) { + status = -1; + } else if (ret > 0) { + fprintf(stdout, "regular file %s differs\n", path); + status = 1; + } + break; + default: + fprintf(stdout, "%s has unknown type, ignoring\n", path); + break; + } + + free(path); + return status; +} diff --git a/difftool/util.c b/difftool/util.c new file mode 100644 index 0000000..64ee7d4 --- /dev/null +++ b/difftool/util.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * util.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "difftool.h" + +char *node_path(tree_node_t *n) +{ + char *path = fstree_get_path(n); + + if (path == NULL) { + perror("get path"); + return NULL; + } + + if (canonicalize_name(path)) { + fputs("[BUG] canonicalization of fstree_get_path failed!!\n", + stderr); + free(path); + return NULL; + } + + return path; +} |