From cdf10fe68524c0a8d1faab3fbf80f6e90916f4eb Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 30 Aug 2012 21:36:46 -0500 Subject: [PATCH] ntfs-cmp program --- Makefile.am | 2 + tests/ntfs-cmp.c | 363 +++++++++++++++++++++++++++++++++++++++++ tests/test-imagex-ntfs | 116 ++++--------- 3 files changed, 399 insertions(+), 82 deletions(-) create mode 100644 tests/ntfs-cmp.c diff --git a/Makefile.am b/Makefile.am index 6ba00106..9c36d5a1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -122,6 +122,8 @@ man1_MANS = \ $(man1_MANS): config.status +check_PROGRAMS = tests/ntfs-cmp +tests_ntfs_cmp_SOURCES = tests/ntfs-cmp.c dist_check_SCRIPTS = tests/test-imagex tests/test-imagex-ntfs TESTS = $(dist_check_SCRIPTS) diff --git a/tests/ntfs-cmp.c b/tests/ntfs-cmp.c new file mode 100644 index 00000000..6c43a41d --- /dev/null +++ b/tests/ntfs-cmp.c @@ -0,0 +1,363 @@ +/* Compares two mounted NTFS filesystems. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef uint64_t u64; + +#if 0 +# define DEBUG(format, ...) \ + ({ \ + int __errno_save = errno; \ + fprintf(stdout, "[%s %d] %s(): " format, \ + __FILE__, __LINE__, __func__, ## __VA_ARGS__); \ + putchar('\n'); \ + fflush(stdout); \ + errno = __errno_save; \ + }) +#else +#define DEBUG(format, ...) +#endif + +static void difference(const char *format, ...) +{ + va_list va; + va_start(va, format); + fflush(stdout); + fputs("ntfs-cmp: ", stderr); + vfprintf(stderr, format, va); + putc('\n', stderr); + fflush(stderr); + va_end(va); + exit(1); +} + +static void error(const char *format, ...) +{ + va_list va; + int err = errno; + va_start(va, format); + fflush(stdout); + fputs("ntfs-cmp: ", stderr); + vfprintf(stderr, format, va); + fprintf(stderr, ": %s\n", strerror(err)); + va_end(va); + exit(2); +} + +struct node { + u64 ino_from; + u64 ino_to; + struct node *left; + struct node *right; +}; + +static struct node *tree = NULL; + +static const char *root1, *root2; + +static u64 do_lookup_ino(struct node *tree, u64 ino_from) +{ + if (!tree) + return -1; + if (ino_from == tree->ino_from) + return tree->ino_to; + else if (ino_from < tree->ino_from) + return do_lookup_ino(tree->left, ino_from); + else + return do_lookup_ino(tree->right, ino_from); +} + + +static void do_insert(struct node *tree, struct node *node) +{ + if (node->ino_from < tree->ino_from) { + if (tree->left) + return do_insert(tree->left, node); + else + tree->left = node; + } else { + if (tree->right) + return do_insert(tree->right, node); + else + tree->right = node; + } +} + +static u64 lookup_ino(u64 ino_from) +{ + return do_lookup_ino(tree, ino_from); +} + +static void insert_ino(u64 ino_from, u64 ino_to) +{ + struct node *node = malloc(sizeof(struct node)); + if (!node) + error("Out of memory"); + node->ino_from = ino_from; + node->ino_to = ino_to; + node->left = NULL; + node->right = NULL; + if (!tree) + tree = node; + else + do_insert(tree, node); +} + + +static void cmp(const char *file1, const char *file2, size_t size) +{ + int fd1, fd2; + char buf1[4096], buf2[4096]; + ssize_t to_read = 4096; + fd1 = open(file1, O_RDONLY); + if (fd1 == -1) + error("Could not open `%s'", file1); + fd2 = open(file2, O_RDONLY); + if (fd2 == -1) + error("Could not open `%s'", file2); + for (; size; size -= to_read) { + if (to_read > size) + to_read = size; + if (read(fd1, buf1, to_read) != to_read) + error("Error reading `%s'", file1); + if (read(fd2, buf2, to_read) != to_read) + error("Error reading `%s'", file2); + if (memcmp(buf1, buf2, to_read)) + difference("File contents of `%s' and `%s' differ", + file1, file2); + } + close(fd1); + close(fd2); +} + +static void cmp_xattr(const char *file1, const char *file2, + const char *xattr_name, ssize_t max_size, + bool missingok) +{ + ssize_t len1, len2; + char *buf1, *buf2; + DEBUG("cmp xattr \"%s\" of files %s, %s", xattr_name, file1, file2); + len1 = lgetxattr(file1, xattr_name, NULL, 0); + if (len1 == -1) { + if (errno == ENOATTR) { + if (missingok) { + errno = 0; + lgetxattr(file2, xattr_name, NULL, 0); + if (errno == ENOATTR) + return; + else + error("xattr `%s' exists on file `%s' " + "but not on file `%s'", + xattr_name, file1, file2); + } else { + error("Could not find attribute `%s' of `%s'", + xattr_name, file1); + } + } else { + error("Could not read xattr `%s' of `%s'", + xattr_name, file1); + } + } + buf1 = malloc(len1); + buf2 = malloc(len1); + if (!buf1 || !buf2) + error("Out of memory"); + if (lgetxattr(file1, xattr_name, buf1, len1) != len1) + error("Could not read xattr `%s' of `%s'", + xattr_name, file1); + + len2 = lgetxattr(file2, xattr_name, buf2, len1); + if (len2 == len1) { + if (memcmp(buf1, buf2, + (max_size == 0 || len1 <= max_size) ? len1 : max_size)) + { + difference("xattr `%s' of files `%s' and `%s' differs", + xattr_name, file1, file2); + } + } else { + if (len2 == -1) { + error("Could not read xattr `%s' from `%s'", + xattr_name, file2); + } + if (len1 != len2) + difference("xattr `%s' of files `%s' and `%s' differs", + xattr_name, file1, file2); + } + free(buf1); + free(buf2); +} + +static void cmp_ads(const char *file1, const char *file2) +{ + char _list1[256], _list2[sizeof(_list1)]; + char *list1 = _list1, *list2 = _list2; + char *pe, *p; + ssize_t len1, len2, tmp; + errno = 0; + len1 = llistxattr(file1, list1, sizeof(_list1)); + if (len1 == -1) { + if (errno != ERANGE || ((len1 = llistxattr(file1, NULL, 0) == -1))) + error("Could not get xattr list of `%s'", file1); + list1 = malloc(len1); + list2 = malloc(len1); + if (!list1 || !list2) + error("Out of memory"); + tmp = llistxattr(file1, list1, len1); + if (tmp == -1) + error("Could not get xattr list of `%s'", file1); + if (tmp != len1) + error("xattr list of `%s' changed as we read it", + file1); + } + errno = 0; + len2 = llistxattr(file2, list2, len1); + if (len1 == -1) { + if (errno == ERANGE) + difference("`%s' and `%s' do not have the same " + "xattr list", file1, file2); + else + error("Could not get xattr list of `%s'", file2); + } + if (len1 != len2 || memcmp(list1, list2, len1)) + difference("`%s' and `%s' do not have the same " + "xattr list", file1, file2); + p = list1; + pe = list1 + len1 - 1; + while (p < pe) { + cmp_xattr(file1, file2, p, 0, false); + p += strlen(p); + } + if (list1 != _list1) { + free(list1); + free(list2); + } +} + +static void special_cmp(const char *file1, const char *file2) +{ + cmp_xattr(file1, file2, "system.ntfs_acl", 0, false); + cmp_xattr(file1, file2, "system.ntfs_attrib", 0, false); + cmp_xattr(file1, file2, "system.ntfs_dos_name", 0, true); + cmp_xattr(file1, file2, "system.ntfs_reparse_data", 0, true); + cmp_xattr(file1, file2, "system.ntfs_times", 16, false); + cmp_ads(file1, file2); +} + + +static void ntfs_cmp(char file1[], int file1_len, char file2[], int file2_len) +{ + struct stat st1, st2; + u64 ino_from, ino_to; + + DEBUG("cmp files %s, %s", file1, file2); + if (lstat(file1, &st1)) + error("Failed to stat `%s'", file1); + if (lstat(file2, &st2)) + error("Failed to stat `%s'", file2); + ino_from = st1.st_ino; + ino_to = lookup_ino(ino_from); + if (ino_to == -1) + insert_ino(ino_from, st2.st_ino); + else if (ino_to != st2.st_ino) + difference("Inode number on `%s' is wrong", file2); + if (st1.st_size != st2.st_size) + difference("Sizes of `%s' and `%s' are not the same", + file1, file2); + if (st1.st_mode != st2.st_mode) + difference("Modes of `%s' and `%s' are not the same", + file1, file2); + if (st1.st_atime != st2.st_atime) + difference("Access times of `%s' and `%s' are not the same", + file1, file2); + if (st1.st_mtime != st2.st_mtime) + difference("Modification times times of `%s' and `%s' are not the same", + file1, file2); +#if 0 + if (st1.st_ctime != st2.st_ctime) + difference("Status change times of `%s' and `%s' are not the same", + file1, file2); +#endif + if (st1.st_nlink != st2.st_nlink) + difference("Link count of `%s' and `%s' are not the same", + file1, file2); + if (strcmp(file1, root1)) + special_cmp(file1, file2); + if (S_ISREG(st1.st_mode)) + cmp(file1, file2, st1.st_size); + else if (S_ISDIR(st1.st_mode)) { + int ret1, ret2; + int i; + struct dirent **namelist1, **namelist2; + const char *dir1 = file1, *dir2 = file2; + + ret1 = scandir(dir1, &namelist1, NULL, alphasort); + if (ret1 == -1) + error("Error scanning directory `%s'", dir1); + ret2 = scandir(dir2, &namelist2, NULL, alphasort); + if (ret2 == -1) + error("Error scanning directory `%s'", dir2); + if (ret1 != ret2) + difference("Directories `%s' and `%s' do not contain " + "the same number of entries", dir1, dir2); + file1[file1_len] = '/'; + file2[file2_len] = '/'; + for (i = 0; i < ret1; i++) { + int name_len; + const char *name; + if (strcmp(namelist1[i]->d_name, namelist2[i]->d_name)) { + difference("Files `%s' and `%s' in directories " + "`%s' and `%s', respectively, do " + "not have the same name", + namelist1[i]->d_name, + namelist2[i]->d_name, + dir1, dir2); + } + name = namelist1[i]->d_name; + name_len = strlen(name); + if (!(name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')))) + { + memcpy(file1 + file1_len + 1, name, name_len + 1); + memcpy(file2 + file2_len + 1, name, name_len + 1); + ntfs_cmp(file1, file1_len + 1 + name_len, + file2, file2_len + 1 + name_len); + } + + free(namelist1[i]); + free(namelist2[i]); + } + free(namelist1); + free(namelist2); + file1[file1_len] = '\0'; + file2[file2_len] = '\0'; + } +} + +int main(int argc, char **argv) +{ + if (argc != 3) { + fprintf(stderr, "Usage: %s DIR1 DIR2", argv[0]); + return 2; + } + char dir1[4096]; + char dir2[4096]; + strcpy(dir1, argv[1]); + strcpy(dir2, argv[2]); + root1 = argv[1]; + root2 = argv[2]; + ntfs_cmp(dir1, strlen(dir1), dir2, strlen(dir2)); + return 0; +} diff --git a/tests/test-imagex-ntfs b/tests/test-imagex-ntfs index bbc88b74..eb061b1a 100755 --- a/tests/test-imagex-ntfs +++ b/tests/test-imagex-ntfs @@ -11,25 +11,33 @@ cd tests imagex() { #echo "imagex $@" - ../imagex $@ #> /dev/null + ../imagex $@ > /dev/null } +__do_unmount() { + if ! fusermount -u $1; then + error "Failed to unmount \"$1\"" + fi + +} do_unmount() { if mountpoint $1 &> /dev/null; then - if ! fusermount -u $1; then - error "Failed to unmount \"$1\"" - fi + __do_unmount $1 fi } -do_mount() { - do_unmount $2 +__do_mount() { options="$3" if ! ntfs-3g ${options:+-o $options} $1 $2; then error "Could not mount NTFS volume \"$1\" on \"$2\". Make sure ntfs-3g is installed." fi } +do_mount() { + do_unmount $2 + __do_mount $1 $2 $3 +} + do_mkntfs() { if ! mkntfs --force --fast $1 &> /dev/null; then error "Could not create NTFS volume on \"$1\". Make sure ntfs-3g / ntfsprogs are installed" @@ -38,11 +46,12 @@ do_mkntfs() { init() { echo "Creating NTFS volumes and empty directories to use as mountpoints" - dd if=/dev/zero of=in.ntfs bs=4096 count=1000 &> /dev/null - dd if=/dev/zero of=out.ntfs bs=4096 count=1000 &> /dev/null + dd if=/dev/zero of=in.ntfs bs=4096 count=260 &> /dev/null + dd if=/dev/zero of=out.ntfs bs=4096 count=260 &> /dev/null mkdir in.mnt out.mnt do_mkntfs in.ntfs do_mkntfs out.ntfs + do_mount in.ntfs in.mnt } cleanup() { @@ -61,85 +70,22 @@ error() { exit 1 } -do_capture() { - do_unmount in.mnt +do_test() { + cd in.mnt + eval "$1" + cd .. + __do_unmount in.mnt if ! imagex capture in.ntfs ntfs.wim; then error "Failed to capture NTFS volume into a WIM" fi -} - -do_apply() { - do_unmount out.mnt - do_mkntfs out.ntfs if ! imagex apply ntfs.wim 1 out.ntfs; then error "Failed to apply WIM to NTFS volume" fi -} - -cmp_xattrs() { - infile=$1 - outfile=$2 - xattr=$3 - #echo "Comparing xattr $xattr of $infile and $outfile" - if test "$xattr" = "system.ntfs_times"; then - headnum=24 - else - headnum=1000000000 - fi - if eval getfattr --only-values -h -d -n $xattr $infile 2>/dev/null\ - | head -c $headnum > in.xattr; then - if eval getfattr --only-values -h -d -n $xattr $outfile 2>/dev/null\ - | head -c $headnum > out.xattr; then - if ! cmp in.xattr out.xattr; then - error "Extended attribute $xattr of $infile and $outfile differs" - fi - else - error "$infile has extended attribute $xattr, but $outfile doesn't" - fi - else - if eval getfattr --only-values -h -d -n $xattr $outfile 2>/dev/null\ - | head -c $headnum > out.xattr; then - error "$outfile has extended attribute $xattr, but $infile doesn't" - fi - fi -} - -# Captures in.ntfs, applies it to out.ntfs, and diffs the result including -# extended attributes -do_capture_and_apply() { - do_capture - do_apply - do_mount in.ntfs in.mnt ro - do_mount out.ntfs out.mnt ro - #if ! diff -r in.mnt out.mnt; then - #error "Recursive diff of original NTFS volume with applied NTFS volume failed" - #fi - for infile in `find in.mnt`; do - outfile=out.mnt${infile##in.mnt} - #echo "Comparing xattrs of $infile and $outfile" - if [ ! -L $infile -a ! -d $infile ]; then - if ! cmp $infile $outfile; then - error "Contents of $infile and $outfile differed" - fi - fi - cmp_xattrs $infile $outfile system.ntfs_attrib - cmp_xattrs $infile $outfile system.ntfs_reparse_data - cmp_xattrs $infile $outfile system.ntfs_acl - cmp_xattrs $infile $outfile system.ntfs_dos_name - cmp_xattrs $infile $outfile system.ntfs_times - done -} - -build_ntfs() { - do_unmount in.mnt - do_mkntfs in.ntfs - do_mount in.ntfs in.mnt - ( cd in.mnt; eval "$1" ) -} - -do_test() { - build_ntfs "$1" - do_capture_and_apply + __do_mount in.ntfs in.mnt + __do_mount out.ntfs out.mnt + ./ntfs-cmp in.mnt out.mnt + rm -rf out.mnt/* in.mnt/* + __do_unmount out.mnt } msg() { echo "Testing image capture and application of $1" @@ -149,7 +95,7 @@ cleanup init msg "Empty NTFS volume" -do_capture_and_apply +do_test "" msg "NTFS volume containing a single file" do_test "echo 1 > file" @@ -211,3 +157,9 @@ do_test 'echo 888 > file; ln file link2; echo 888 > file3' +msg "NTFS volume containing file with named data stream" +do_test 'echo 1 > file; + setfattr -n user.ads -v 2 file' + + + -- 2.43.0