2 * A program to compare two mounted NTFS filesystems.
16 #include <attr/xattr.h>
22 # define DEBUG(format, ...) \
24 int __errno_save = errno; \
25 fprintf(stdout, "[%s %d] %s(): " format, \
26 __FILE__, __LINE__, __func__, ## __VA_ARGS__); \
29 errno = __errno_save; \
32 #define DEBUG(format, ...)
35 static void difference(const char *format, ...)
40 fputs("ntfs-cmp: ", stderr);
41 vfprintf(stderr, format, va);
48 static void error(const char *format, ...)
54 fputs("ntfs-cmp: ", stderr);
55 vfprintf(stderr, format, va);
56 fprintf(stderr, ": %s\n", strerror(err));
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. */
71 static struct node *tree = NULL;
73 static const char *root1, *root2;
75 static u64 do_lookup_ino(struct node *tree, u64 ino_from)
79 if (ino_from == tree->ino_from)
81 else if (ino_from < tree->ino_from)
82 return do_lookup_ino(tree->left, ino_from);
84 return do_lookup_ino(tree->right, ino_from);
88 static void do_insert(struct node *tree, struct node *node)
90 if (node->ino_from < tree->ino_from) {
92 return do_insert(tree->left, node);
97 return do_insert(tree->right, node);
103 static u64 lookup_ino(u64 ino_from)
105 return do_lookup_ino(tree, ino_from);
108 static void insert_ino(u64 ino_from, u64 ino_to)
110 struct node *node = malloc(sizeof(struct node));
112 error("Out of memory");
113 node->ino_from = ino_from;
114 node->ino_to = ino_to;
120 do_insert(tree, node);
124 /* Compares the "normal" contents of two files of size @size. */
125 static void cmp(const char *file1, const char *file2, size_t size)
128 char buf1[4096], buf2[4096];
129 ssize_t to_read = 4096;
130 fd1 = open(file1, O_RDONLY);
132 error("Could not open `%s'", file1);
133 fd2 = open(file2, O_RDONLY);
135 error("Could not open `%s'", file2);
136 for (; size; size -= to_read) {
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",
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,
158 DEBUG("cmp xattr \"%s\" of files %s, %s", xattr_name, file1, file2);
159 len1 = lgetxattr(file1, xattr_name, NULL, 0);
161 if (errno == ENOATTR) {
164 lgetxattr(file2, xattr_name, NULL, 0);
165 if (errno == ENOATTR)
168 difference("xattr `%s' exists on file `%s' "
169 "but not on file `%s'",
170 xattr_name, file1, file2);
172 error("Could not find attribute `%s' of `%s'",
176 error("Could not read xattr `%s' of `%s'",
183 error("Out of memory");
184 if (lgetxattr(file1, xattr_name, buf1, len1) != len1)
185 error("Could not read xattr `%s' of `%s'",
188 len2 = lgetxattr(file2, xattr_name, buf2, len1);
190 if (memcmp(buf1, buf2,
191 (max_size == 0 || len1 <= max_size) ? len1 : max_size))
193 difference("xattr `%s' of files `%s' and `%s' differs",
194 xattr_name, file1, file2);
198 error("Could not read xattr `%s' from `%s'",
202 difference("xattr `%s' of files `%s' and `%s' differs",
203 xattr_name, file1, file2);
209 /* Compares all alternate data streams of the files */
210 static void cmp_ads(const char *file1, const char *file2)
212 char _list1[256], _list2[sizeof(_list1)];
213 char *list1 = _list1, *list2 = _list2;
215 ssize_t len1, len2, tmp;
217 len1 = llistxattr(file1, list1, sizeof(_list1));
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);
227 error("Could not get xattr list of `%s'", file1);
229 error("xattr list of `%s' changed as we read it",
233 len2 = llistxattr(file2, list2, len1);
236 difference("`%s' and `%s' do not have the same "
237 "xattr list", file1, file2);
239 error("Could not get xattr list of `%s'", file2);
241 if (len1 != len2 || memcmp(list1, list2, len1))
242 difference("`%s' and `%s' do not have the same "
243 "xattr list", file1, file2);
245 pe = list1 + len1 - 1;
247 cmp_xattr(file1, file2, p, 0, false);
250 if (list1 != _list1) {
256 /* Compares special NTFS data of the files, as accessed through extended
258 static void special_cmp(const char *file1, const char *file2)
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);
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)
272 struct stat st1, st2;
273 u64 ino_from, ino_to;
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);
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",
289 if (st1.st_mode != st2.st_mode)
290 difference("Modes of `%s' and `%s' are not the same",
292 if (st1.st_atime != st2.st_atime)
293 difference("Access times of `%s' and `%s' are not the same",
295 if (st1.st_mtime != st2.st_mtime)
296 difference("Modification times times of `%s' and `%s' are not the same",
299 if (st1.st_ctime != st2.st_ctime)
300 difference("Status change times of `%s' and `%s' are not the same",
303 if (st1.st_nlink != st2.st_nlink)
304 difference("Link count of `%s' and `%s' are not the same",
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)) {
313 struct dirent **namelist1, **namelist2;
314 const char *dir1 = file1, *dir2 = file2;
316 ret1 = scandir(dir1, &namelist1, NULL, alphasort);
318 error("Error scanning directory `%s'", dir1);
319 ret2 = scandir(dir2, &namelist2, NULL, alphasort);
321 error("Error scanning directory `%s'", dir2);
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++) {
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,
338 name = namelist1[i]->d_name;
339 name_len = strlen(name);
340 if (!(name[0] == '.' &&
342 (name[1] == '.' && name[2] == '\0'))))
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);
355 file1[file1_len] = '\0';
356 file2[file2_len] = '\0';
360 int main(int argc, char **argv)
363 fprintf(stderr, "Usage: %s DIR1 DIR2", argv[0]);
368 strcpy(dir1, argv[1]);
369 strcpy(dir2, argv[2]);
372 ntfs_cmp(dir1, strlen(dir1), dir2, strlen(dir2));