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