]> wimlib.net Git - wimlib/blob - tests/ntfs-cmp.c
NTFS tests
[wimlib] / tests / ntfs-cmp.c
1 /* 
2  * A program to compare two mounted NTFS filesystems.
3  */
4
5 #include <stdarg.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <dirent.h>
12 #include <inttypes.h>
13 #include <unistd.h>
14 #include <stdbool.h>
15 #include <sys/stat.h>
16 #include <attr/xattr.h>
17 #include <assert.h>
18
19 typedef uint64_t u64;
20
21 #if 0
22 #       define DEBUG(format, ...)                                       \
23         ({                                                              \
24                 int __errno_save = errno;                               \
25                 fprintf(stdout, "[%s %d] %s(): " format,                \
26                         __FILE__, __LINE__, __func__, ## __VA_ARGS__);  \
27                 putchar('\n');                                          \
28                 fflush(stdout);                                         \
29                 errno = __errno_save;                                   \
30         })
31 #else
32 #define DEBUG(format, ...)
33 #endif
34
35 static void difference(const char *format, ...)
36 {
37         va_list va;
38         va_start(va, format);
39         fflush(stdout);
40         fputs("ntfs-cmp: ", stderr);
41         vfprintf(stderr, format, va);
42         putc('\n', stderr);
43         fflush(stderr);
44         va_end(va);
45         exit(1);
46 }
47
48 static void error(const char *format, ...)
49 {
50         va_list va;
51         int err = errno;
52         va_start(va, format);
53         fflush(stdout);
54         fputs("ntfs-cmp: ", stderr);
55         vfprintf(stderr, format, va);
56         fprintf(stderr, ": %s\n", strerror(err));
57         va_end(va);
58         exit(2);
59 }
60
61 /* This is just a binary tree that maps inode numbers in one NTFS tree to inode
62  * numbers in the other NTFS tree.  This is so we can tell if the hard link
63  * groups are the same between the two NTFS trees.  */
64 struct node {
65         u64 ino_from;
66         u64 ino_to;
67         struct node *left;
68         struct node *right;
69 };
70
71 static struct node *tree = NULL;
72
73 static const char *root1, *root2;
74
75 static u64 do_lookup_ino(struct node *tree, u64 ino_from)
76 {
77         if (!tree)
78                 return -1;
79         if (ino_from == tree->ino_from)
80                 return tree->ino_to;
81         else if (ino_from < tree->ino_from)
82                 return do_lookup_ino(tree->left, ino_from);
83         else
84                 return do_lookup_ino(tree->right, ino_from);
85 }
86
87
88 static void do_insert(struct node *tree, struct node *node)
89 {
90         if (node->ino_from < tree->ino_from) {
91                 if (tree->left)
92                         return do_insert(tree->left, node);
93                 else
94                         tree->left = node;
95         } else {
96                 if (tree->right)
97                         return do_insert(tree->right, node);
98                 else
99                         tree->right = node;
100         }
101 }
102
103 static u64 lookup_ino(u64 ino_from)
104 {
105         return do_lookup_ino(tree, ino_from);
106 }
107
108 static void insert_ino(u64 ino_from, u64 ino_to)
109 {
110         struct node *node = malloc(sizeof(struct node));
111         if (!node)
112                 error("Out of memory");
113         node->ino_from = ino_from;
114         node->ino_to   = ino_to;
115         node->left     = NULL;
116         node->right    = NULL;
117         if (!tree)
118                 tree = node;
119         else
120                 do_insert(tree, node);
121 }
122
123
124 /* Compares the "normal" contents of two files of size @size. */
125 static void cmp(const char *file1, const char *file2, size_t size)
126 {
127         int fd1, fd2;
128         char buf1[4096], buf2[4096];
129         ssize_t to_read = 4096;
130         fd1 = open(file1, O_RDONLY);
131         if (fd1 == -1)
132                 error("Could not open `%s'", file1);
133         fd2 = open(file2, O_RDONLY);
134         if (fd2 == -1)
135                 error("Could not open `%s'", file2);
136         for (; size; size -= to_read) {
137                 if (to_read > size)
138                         to_read = size;
139                 if (read(fd1, buf1, to_read) != to_read)
140                         error("Error reading `%s'", file1);
141                 if (read(fd2, buf2, to_read) != to_read)
142                         error("Error reading `%s'", file2);
143                 if (memcmp(buf1, buf2, to_read))
144                         difference("File contents of `%s' and `%s' differ",
145                                    file1, file2);
146         }
147         close(fd1);
148         close(fd2);
149 }
150
151 /* Compares an extended attribute of the files. */
152 static void cmp_xattr(const char *file1, const char *file2,
153                       const char *xattr_name, ssize_t max_size,
154                       bool missingok)
155 {
156         ssize_t len1, len2;
157         char *buf1, *buf2;
158         DEBUG("cmp xattr \"%s\" of files %s, %s", xattr_name, file1, file2);
159         len1 = lgetxattr(file1, xattr_name, NULL, 0);
160         if (len1 == -1) {
161                 if (errno == ENOATTR) {
162                         if (missingok) {
163                                 errno = 0;
164                                 lgetxattr(file2, xattr_name, NULL, 0);
165                                 if (errno == ENOATTR)
166                                         return;
167                                 else
168                                         difference("xattr `%s' exists on file `%s' "
169                                               "but not on file `%s'",
170                                               xattr_name, file1, file2);
171                         } else {
172                                 error("Could not find attribute `%s' of `%s'",
173                                       xattr_name, file1);
174                         }
175                 } else {
176                         error("Could not read xattr `%s' of `%s'",
177                               xattr_name, file1);
178                 }
179         }
180         buf1 = malloc(len1);
181         buf2 = malloc(len1);
182         if (!buf1 || !buf2)
183                 error("Out of memory");
184         if (lgetxattr(file1, xattr_name, buf1, len1) != len1)
185                 error("Could not read xattr `%s' of `%s'",
186                       xattr_name, file1);
187
188         len2 = lgetxattr(file2, xattr_name, buf2, len1);
189         if (len2 == len1) {
190                 if (memcmp(buf1, buf2,
191                            (max_size == 0 || len1 <= max_size) ? len1 : max_size))
192                 {
193                         difference("xattr `%s' of files `%s' and `%s' differs",
194                                    xattr_name, file1, file2);
195                 }
196         } else {
197                 if (len2 == -1) {
198                         error("Could not read xattr `%s' from `%s'",
199                               xattr_name, file2);
200                 }
201                 if (len1 != len2)
202                         difference("xattr `%s' of files `%s' and `%s' differs",
203                                    xattr_name, file1, file2);
204         }
205         free(buf1);
206         free(buf2);
207 }
208
209 /* Compares all alternate data streams of the files */
210 static void cmp_ads(const char *file1, const char *file2)
211 {
212         char _list1[256], _list2[sizeof(_list1)];
213         char *list1 = _list1, *list2 = _list2;
214         char *pe, *p;
215         ssize_t len1, len2, tmp;
216         errno = 0;
217         len1 = llistxattr(file1, list1, sizeof(_list1));
218         if (len1 == -1) {
219                 if (errno != ERANGE || ((len1 = llistxattr(file1, NULL, 0) == -1)))
220                         error("Could not get xattr list of `%s'", file1);
221                 list1 = malloc(len1);
222                 list2 = malloc(len1);
223                 if (!list1 || !list2)
224                         error("Out of memory");
225                 tmp = llistxattr(file1, list1, len1);
226                 if (tmp == -1)
227                         error("Could not get xattr list of `%s'", file1);
228                 if (tmp != len1)
229                         error("xattr list of `%s' changed as we read it",
230                               file1);
231         }
232         errno = 0;
233         len2 = llistxattr(file2, list2, len1);
234         if (len1 == -1) {
235                 if (errno == ERANGE)
236                         difference("`%s' and `%s' do not have the same "
237                                    "xattr list", file1, file2);
238                 else
239                         error("Could not get xattr list of `%s'", file2);
240         }
241         if (len1 != len2 || memcmp(list1, list2, len1))
242                 difference("`%s' and `%s' do not have the same "
243                            "xattr list", file1, file2);
244         p = list1;
245         pe = list1 + len1 - 1;
246         while (p < pe) {
247                 cmp_xattr(file1, file2, p, 0, false);
248                 p += strlen(p) + 1;
249         }
250         if (list1 != _list1) {
251                 free(list1);
252                 free(list2);
253         }
254 }
255
256 /* Compares special NTFS data of the files, as accessed through extended
257  * attributes. */
258 static void special_cmp(const char *file1, const char *file2)
259 {
260         cmp_xattr(file1, file2, "system.ntfs_acl", 0, false);
261         cmp_xattr(file1, file2, "system.ntfs_attrib", 0, false);
262         cmp_xattr(file1, file2, "system.ntfs_dos_name", 0, true);
263         cmp_xattr(file1, file2, "system.ntfs_reparse_data", 0, true);
264         cmp_xattr(file1, file2, "system.ntfs_times", 16, false);
265         cmp_ads(file1, file2);
266 }
267
268
269 /* Compares file1 on one NTFS volume to file2 on another NTFS volume. */
270 static void ntfs_cmp(char file1[], int file1_len, char file2[], int file2_len)
271 {
272         struct stat st1, st2;
273         u64 ino_from, ino_to;
274
275         DEBUG("cmp files %s, %s", file1, file2);
276         if (lstat(file1, &st1))
277                 error("Failed to stat `%s'", file1);
278         if (lstat(file2, &st2))
279                 error("Failed to stat `%s'", file2);
280         ino_from = st1.st_ino;
281         ino_to = lookup_ino(ino_from);
282         if (ino_to == -1)
283                 insert_ino(ino_from, st2.st_ino);
284         else if (ino_to != st2.st_ino)
285                 difference("Inode number on `%s' is wrong", file2);
286         if (st1.st_size != st2.st_size)
287                 difference("Sizes of `%s' and `%s' are not the same",
288                            file1, file2);
289         if (st1.st_mode != st2.st_mode)
290                 difference("Modes of `%s' and `%s' are not the same",
291                            file1, file2);
292         if (st1.st_atime != st2.st_atime)
293                 difference("Access times of `%s' and `%s' are not the same",
294                            file1, file2);
295         if (st1.st_mtime != st2.st_mtime)
296                 difference("Modification times times of `%s' and `%s' are not the same",
297                            file1, file2);
298 #if 0
299         if (st1.st_ctime != st2.st_ctime)
300                 difference("Status change times of `%s' and `%s' are not the same",
301                            file1, file2);
302 #endif
303         if (st1.st_nlink != st2.st_nlink)
304                 difference("Link count of `%s' and `%s' are not the same",
305                            file1, file2);
306         if (strcmp(file1, root1))
307                 special_cmp(file1, file2);
308         if (S_ISREG(st1.st_mode))
309                 cmp(file1, file2, st1.st_size);
310         else if (S_ISDIR(st1.st_mode)) {
311                 int ret1, ret2;
312                 int i;
313                 struct dirent **namelist1, **namelist2;
314                 const char *dir1 = file1, *dir2 = file2;
315
316                 ret1 = scandir(dir1, &namelist1, NULL, alphasort);
317                 if (ret1 == -1)
318                         error("Error scanning directory `%s'", dir1);
319                 ret2 = scandir(dir2, &namelist2, NULL, alphasort);
320                 if (ret2 == -1)
321                         error("Error scanning directory `%s'", dir2);
322                 if (ret1 != ret2)
323                         difference("Directories `%s' and `%s' do not contain "
324                                    "the same number of entries", dir1, dir2);
325                 file1[file1_len] = '/';
326                 file2[file2_len] = '/';
327                 for (i = 0; i < ret1; i++) {
328                         int name_len;
329                         const char *name;
330                         if (strcmp(namelist1[i]->d_name, namelist2[i]->d_name)) {
331                                 difference("Files `%s' and `%s' in directories "
332                                            "`%s' and `%s', respectively, do "
333                                            "not have the same name",
334                                            namelist1[i]->d_name,
335                                            namelist2[i]->d_name,
336                                            dir1, dir2);
337                         }
338                         name = namelist1[i]->d_name;
339                         name_len = strlen(name);
340                         if (!(name[0] == '.' &&
341                               (name[1] == '\0' ||
342                                (name[1] == '.' && name[2] == '\0'))))
343                         {
344                                 memcpy(file1 + file1_len + 1, name, name_len + 1);
345                                 memcpy(file2 + file2_len + 1, name, name_len + 1);
346                                 ntfs_cmp(file1, file1_len + 1 + name_len,
347                                          file2, file2_len + 1 + name_len);
348                         }
349
350                         free(namelist1[i]);
351                         free(namelist2[i]);
352                 }
353                 free(namelist1);
354                 free(namelist2);
355                 file1[file1_len] = '\0';
356                 file2[file2_len] = '\0';
357         }
358 }
359
360 int main(int argc, char **argv)
361 {
362         if (argc != 3) {
363                 fprintf(stderr, "Usage: %s DIR1 DIR2", argv[0]);
364                 return 2;
365         }
366         char dir1[4096];
367         char dir2[4096];
368         strcpy(dir1, argv[1]);
369         strcpy(dir2, argv[2]);
370         root1 = argv[1];
371         root2 = argv[2];
372         ntfs_cmp(dir1, strlen(dir1), dir2, strlen(dir2));
373         return 0;
374 }