ntfs-cmp program
authorEric Biggers <ebiggers3@gmail.com>
Fri, 31 Aug 2012 02:36:46 +0000 (21:36 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Fri, 31 Aug 2012 02:36:46 +0000 (21:36 -0500)
Makefile.am
tests/ntfs-cmp.c [new file with mode: 0644]
tests/test-imagex-ntfs

index 6ba0010..9c36d5a 100644 (file)
@@ -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 (file)
index 0000000..6c43a41
--- /dev/null
@@ -0,0 +1,363 @@
+/* Compares two mounted NTFS filesystems. */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <attr/xattr.h>
+#include <assert.h>
+
+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;
+}
index bbc88b7..eb061b1 100755 (executable)
@@ -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'
+
+       
+