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