2 * A program to compare directory trees
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
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.
30 #include <sys/types.h>
31 #ifdef HAVE_SYS_XATTR_H
32 # include <sys/xattr.h>
37 # define ENOATTR ENODATA
43 # define DEBUG(format, ...) \
45 int __errno_save = errno; \
46 fprintf(stdout, "[%s %d] %s(): " format, \
47 __FILE__, __LINE__, __func__, ## __VA_ARGS__); \
50 errno = __errno_save; \
53 #define DEBUG(format, ...)
55 static bool ntfs_mode = false;
57 static void difference(const char *format, ...)
62 fputs("tree-cmp: ", stderr);
63 vfprintf(stderr, format, va);
70 static void error(const char *format, ...)
76 fputs("tree-cmp: ", stderr);
77 vfprintf(stderr, format, va);
78 fprintf(stderr, ": %s\n", strerror(err));
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. */
93 static struct node *tree = NULL;
95 static const char *root1, *root2;
97 static u64 do_lookup_ino(struct node *tree, u64 ino_from)
101 if (ino_from == tree->ino_from)
103 else if (ino_from < tree->ino_from)
104 return do_lookup_ino(tree->left, ino_from);
106 return do_lookup_ino(tree->right, ino_from);
110 static void do_insert(struct node *tree, struct node *node)
112 if (node->ino_from < tree->ino_from) {
114 return do_insert(tree->left, node);
119 return do_insert(tree->right, node);
125 static u64 lookup_ino(u64 ino_from)
127 return do_lookup_ino(tree, ino_from);
130 static void insert_ino(u64 ino_from, u64 ino_to)
132 struct node *node = malloc(sizeof(struct node));
134 error("Out of memory");
135 node->ino_from = ino_from;
136 node->ino_to = ino_to;
142 do_insert(tree, node);
146 /* Compares the "normal" contents of two files of size @size. */
147 static void cmp(const char *file1, const char *file2, size_t size)
150 char buf1[4096], buf2[4096];
151 ssize_t to_read = 4096;
152 fd1 = open(file1, O_RDONLY);
154 error("Could not open `%s'", file1);
155 fd2 = open(file2, O_RDONLY);
157 error("Could not open `%s'", file2);
158 for (; size; size -= to_read) {
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",
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,
181 DEBUG("cmp xattr \"%s\" of files %s, %s", xattr_name, file1, file2);
182 len1 = lgetxattr(file1, xattr_name, NULL, 0);
184 if (errno == ENOATTR) {
187 lgetxattr(file2, xattr_name, NULL, 0);
188 if (errno == ENOATTR)
191 difference("xattr `%s' exists on file `%s' "
192 "but not on file `%s'",
193 xattr_name, file1, file2);
195 error("Could not find attribute `%s' of `%s'",
199 error("Could not read xattr `%s' of `%s'",
206 error("Out of memory");
207 if (lgetxattr(file1, xattr_name, buf1, len1) != len1)
208 error("Could not read xattr `%s' of `%s'",
211 len2 = lgetxattr(file2, xattr_name, buf2, len1);
213 if (memcmp(buf1, buf2,
214 (max_size == 0 || len1 <= max_size) ? len1 : max_size))
216 difference("xattr `%s' of files `%s' and `%s' differs",
217 xattr_name, file1, file2);
221 error("Could not read xattr `%s' from `%s'",
225 difference("xattr `%s' of files `%s' and `%s' differs",
226 xattr_name, file1, file2);
232 /* Compares all alternate data streams of the files */
233 static void cmp_ads(const char *file1, const char *file2)
235 char _list1[256], _list2[sizeof(_list1)];
236 char *list1 = _list1, *list2 = _list2;
238 ssize_t len1, len2, tmp;
240 len1 = llistxattr(file1, list1, sizeof(_list1));
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);
250 error("Could not get xattr list of `%s'", file1);
252 error("xattr list of `%s' changed as we read it",
256 len2 = llistxattr(file2, list2, len1);
259 difference("`%s' and `%s' do not have the same "
260 "xattr list", file1, file2);
262 error("Could not get xattr list of `%s'", file2);
264 if (len1 != len2 || memcmp(list1, list2, len1))
265 difference("`%s' and `%s' do not have the same "
266 "xattr list", file1, file2);
268 pe = list1 + len1 - 1;
270 cmp_xattr(file1, file2, p, 0, false);
273 if (list1 != _list1) {
278 #endif /* HAVE_SYS_XATTR_H */
280 /* Compares special NTFS data of the files, as accessed through extended
282 static void special_cmp(const char *file1, const char *file2)
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);
293 fprintf(stderr, "tree-cmp: Warning: cannot compare xattrs of `%s' and `%s'\n",
295 fprintf(stderr, " Extended attributes are not supported on this platform.\n");
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)
303 struct stat st1, st2;
304 u64 ino_from, ino_to;
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);
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",
321 if (S_ISREG(st1.st_mode) && st1.st_size != st2.st_size)
322 difference("Sizes of `%s' and `%s' are not the same",
325 if (ntfs_mode && st1.st_atime != st2.st_atime)
326 difference("Access times of `%s' (%x) and `%s' (%x) are "
328 file1, st1.st_atime, file2, st2.st_atime);
330 if (st1.st_mtime != st2.st_mtime)
331 difference("Modification times of `%s' (%x) and `%s' (%x) are "
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) "
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)) {
345 struct dirent **namelist1, **namelist2;
346 const char *dir1 = file1, *dir2 = file2;
348 ret1 = scandir(dir1, &namelist1, NULL, alphasort);
350 error("Error scanning directory `%s'", dir1);
351 ret2 = scandir(dir2, &namelist2, NULL, alphasort);
353 error("Error scanning directory `%s'", dir2);
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++) {
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,
370 name = namelist1[i]->d_name;
371 name_len = strlen(name);
372 if (!(name[0] == '.' &&
374 (name[1] == '.' && name[2] == '\0'))))
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);
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)];
392 ret1 = readlink(file1, buf1, sizeof(buf1));
394 error("Failed to get symlink target of `%s'", file1);
395 ret2 = readlink(file2, buf2, sizeof(buf2));
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",
404 int main(int argc, char **argv)
406 if (argc != 3 && argc != 4) {
407 fprintf(stderr, "Usage: %s DIR1 DIR2 [NTFS]", argv[0]);
410 if (argc > 3 && strcmp(argv[3], "NTFS") == 0)
415 strcpy(dir1, argv[1]);
416 strcpy(dir2, argv[2]);
419 tree_cmp(dir1, strlen(dir1), dir2, strlen(dir2));