tests/tests-common.sh: fix references to /dev/null
[wimlib] / tests / win32-tree-cmp.c
1 /*
2  * Compare directory trees (Windows version)
3  */
4
5 #include <assert.h>
6 #include <inttypes.h>
7 #include <stdarg.h>
8 #include <stdbool.h>
9 #include <stdio.h>
10 #include <wchar.h>
11
12 #include <windows.h>
13 #include <sddl.h>
14
15 typedef uint8_t  u8;
16 typedef uint16_t u16;
17 typedef uint32_t u32;
18 typedef uint64_t u64;
19
20 #define REPARSE_POINT_MAX_SIZE (16 * 1024)
21
22 #define ARRAY_LEN(array)        (sizeof(array) / sizeof((array)[0]))
23
24 static wchar_t *
25 win32_error_string(DWORD err_code)
26 {
27         static wchar_t buf[1024];
28         buf[0] = L'\0';
29         FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
30                       NULL, err_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
31                       buf, ARRAY_LEN(buf), NULL);
32         return buf;
33 }
34
35 static void __attribute__((noreturn))
36 fatal_win32_error(const wchar_t *format, ...)
37 {
38         va_list va;
39         DWORD err = GetLastError();
40
41         va_start(va, format);
42         fputws(L"FATAL ERROR: ", stderr);
43         vfwprintf(stderr, format, va);
44         fwprintf(stderr, L": %ls\n", win32_error_string(err));
45         va_end(va);
46
47         exit(1);
48 }
49
50 static unsigned long difference_count = 0;
51
52 static void
53 difference(const wchar_t *format, ...)
54 {
55         va_list va;
56
57         va_start(va, format);
58         fputws(L"DIFFERENCE: ", stderr);
59         vfwprintf(stderr, format, va);
60         putwc(L'\n', stderr);
61         va_end(va);
62
63         difference_count++;
64 }
65
66 struct inode_mapping_node {
67         u64 key;
68         u64 value;
69         struct inode_mapping_node *left;
70         struct inode_mapping_node *right;
71 };
72
73 static struct inode_mapping_node *inode_map = NULL;
74
75 #define INODE_NOT_SEEN_YET ((u64)-1)
76
77 static u64
78 do_lookup_ino(struct inode_mapping_node *tree, u64 key)
79 {
80         if (!tree)
81                 return INODE_NOT_SEEN_YET;
82         if (key < tree->key)
83                 return do_lookup_ino(tree->left, key);
84         if (key > tree->key)
85                 return do_lookup_ino(tree->right, key);
86         return tree->value;
87 }
88
89 static void
90 do_insert(struct inode_mapping_node *tree, struct inode_mapping_node *node)
91 {
92         if (node->key < tree->key) {
93                 if (tree->left)
94                         return do_insert(tree->left, node);
95                 tree->left = node;
96                 return;
97         }
98         if (node->key > tree->key) {
99                 if (tree->right)
100                         return do_insert(tree->right, node);
101                 tree->right = node;
102                 return;
103         }
104         assert(0);
105 }
106
107 static u64
108 lookup_ino(u64 key)
109 {
110         return do_lookup_ino(inode_map, key);
111 }
112
113 static void
114 insert_ino(u64 key, u64 value)
115 {
116         struct inode_mapping_node *node = malloc(sizeof(*node));
117
118         node->key = key;
119         node->value = value;
120         node->left = NULL;
121         node->right = NULL;
122         if (!inode_map)
123                 inode_map = node;
124         else
125                 do_insert(inode_map, node);
126 }
127
128 static HANDLE
129 open_file(const wchar_t *path)
130 {
131         HANDLE hFile = CreateFile(path,
132                                   GENERIC_READ | ACCESS_SYSTEM_SECURITY,
133                                   FILE_SHARE_VALID_FLAGS,
134                                   NULL,
135                                   OPEN_EXISTING,
136                                   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
137                                   NULL);
138         if (hFile == INVALID_HANDLE_VALUE)
139                 fatal_win32_error(L"Failed to open file %ls read-only", path);
140         return hFile;
141 }
142
143 static size_t
144 get_reparse_data(HANDLE hFile, const wchar_t *path, char *rpbuf)
145 {
146         DWORD bytesReturned = 0;
147         if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, NULL, 0,
148                              rpbuf, REPARSE_POINT_MAX_SIZE, &bytesReturned, NULL))
149                 fatal_win32_error(L"Can't get reparse data from %ls", path);
150         return bytesReturned;
151 }
152
153 static void
154 cmp_reparse_data(HANDLE hFile_1, const wchar_t *path_1,
155                  HANDLE hFile_2, const wchar_t *path_2)
156 {
157         char rpbuf_1[REPARSE_POINT_MAX_SIZE];
158         char rpbuf_2[REPARSE_POINT_MAX_SIZE];
159         size_t len_1;
160         size_t len_2;
161
162         len_1 = get_reparse_data(hFile_1, path_1, rpbuf_1);
163         len_2 = get_reparse_data(hFile_2, path_2, rpbuf_2);
164         if (len_1 != len_2 || memcmp(rpbuf_1, rpbuf_2, len_1))
165                 difference(L"Reparse point buffers for %ls and %ls differ",
166                            path_1, path_2);
167 }
168
169 struct win32_stream_wrapper {
170         struct win32_stream_wrapper *next;
171         WIN32_FIND_STREAM_DATA dat;
172 };
173
174 static int
175 cmp_FIND_STREAM_DATA_by_name(const void *p1, const void *p2)
176 {
177         const WIN32_FIND_STREAM_DATA *s1 = p1;
178         const WIN32_FIND_STREAM_DATA *s2 = p2;
179         return wcscmp(s1->cStreamName, s2->cStreamName);
180 }
181
182 static WIN32_FIND_STREAM_DATA *
183 get_stream_array(const wchar_t *path, size_t *nstreams_ret)
184 {
185         WIN32_FIND_STREAM_DATA dat;
186         WIN32_FIND_STREAM_DATA *array = NULL;
187         WIN32_FIND_STREAM_DATA *p;
188         size_t nstreams = 0;
189         struct win32_stream_wrapper *stream_list = NULL;
190         HANDLE hFind;
191
192         hFind = FindFirstStreamW(path, FindStreamInfoStandard, &dat, 0);
193         if (hFind != INVALID_HANDLE_VALUE) {
194                 do {
195                         struct win32_stream_wrapper *wrapper;
196
197                         wrapper = malloc(sizeof(*wrapper));
198                         memcpy(&wrapper->dat, &dat, sizeof(dat));
199                         wrapper->next = stream_list;
200                         stream_list = wrapper;
201                         nstreams++;
202                 } while (FindNextStreamW(hFind, &dat));
203         }
204         if (GetLastError() != ERROR_HANDLE_EOF)
205                 fatal_win32_error(L"Can't lookup streams from %ls", path);
206         if (hFind != INVALID_HANDLE_VALUE)
207                 FindClose(hFind);
208         array = malloc(nstreams * sizeof(array[0]));
209         p = array;
210         while (stream_list) {
211                 struct win32_stream_wrapper *next;
212
213                 memcpy(p, &stream_list->dat, sizeof(*p));
214                 next = stream_list->next;
215                 free(stream_list);
216                 stream_list = next;
217                 p++;
218         }
219         assert(p - array == nstreams);
220         qsort(array, nstreams, sizeof(array[0]), cmp_FIND_STREAM_DATA_by_name);
221         *nstreams_ret = nstreams;
222         return array;
223 }
224
225 static const wchar_t *
226 fix_stream_name(wchar_t *stream_name)
227 {
228         wchar_t *colon;
229
230         /* The stream name should be returned as :NAME:TYPE */
231         if (stream_name[0] != L':')
232                 return NULL;
233         colon = wcschr(stream_name + 1, L':');
234         if (!colon)
235                 return NULL;
236         if (wcscmp(colon + 1, L"$DATA"))
237                 return NULL;
238         *colon = L'\0';
239         if (stream_name == colon - 1)
240                 stream_name = colon;
241         return stream_name;
242 }
243
244 #define BUFSIZE 32768
245
246 static void
247 cmp_data(HANDLE hFile_1, const wchar_t *path_1,
248          HANDLE hFile_2, const wchar_t *path_2, u64 size)
249 {
250         u8 buf_1[BUFSIZE];
251         u8 buf_2[BUFSIZE];
252         u64 bytes_remaining = size;
253         DWORD bytesRead;
254         DWORD bytesToRead;
255
256         while (bytes_remaining) {
257                 bytesToRead = BUFSIZE;
258                 if (bytesToRead > bytes_remaining)
259                         bytesToRead = bytes_remaining;
260                 if (!ReadFile(hFile_1, buf_1, bytesToRead, &bytesRead, NULL) ||
261                     bytesRead != bytesToRead)
262                 {
263                         fatal_win32_error(L"Error reading from %ls", path_1);
264                 }
265                 if (!ReadFile(hFile_2, buf_2, bytesToRead, &bytesRead, NULL) ||
266                     bytesRead != bytesToRead)
267                 {
268                         fatal_win32_error(L"Error reading from %ls", path_2);
269                 }
270                 if (memcmp(buf_1, buf_2, bytesToRead))
271                         difference(L"Data of %ls and %ls differs", path_1, path_2);
272                 bytes_remaining -= bytesToRead;
273         }
274 }
275
276 static void
277 cmp_stream(wchar_t *path_1, size_t path_1_len, WIN32_FIND_STREAM_DATA *dat_1,
278            wchar_t *path_2, size_t path_2_len, WIN32_FIND_STREAM_DATA *dat_2)
279 {
280         const wchar_t *stream_name;
281
282         /* Compare stream names  */
283         if (wcscmp(dat_1->cStreamName, dat_2->cStreamName)) {
284                 difference(L"Data streams %ls%ls and %ls%ls are not named the same",
285                            path_1, dat_1->cStreamName,
286                            path_2, dat_2->cStreamName);
287                 return;
288         }
289
290         /* Compare stream sizes  */
291         if (dat_1->StreamSize.QuadPart != dat_2->StreamSize.QuadPart) {
292                 difference(L"Data streams %ls%ls (%"PRIu64" bytes) and %ls%ls "
293                            "(%"PRIu64" bytes) are not the same size",
294                            path_1, dat_1->cStreamName, dat_1->StreamSize.QuadPart,
295                            path_2, dat_2->cStreamName, dat_2->StreamSize.QuadPart);
296                 return;
297         }
298
299         /* Compare stream data  */
300
301         stream_name = fix_stream_name(dat_1->cStreamName);
302
303         if (!stream_name) {
304                 fwprintf(stderr, L"WARNING: unrecognized stream name format %ls\n",
305                          dat_1->cStreamName);
306                 return;
307         }
308
309         wcscpy(&path_1[path_1_len], stream_name);
310         wcscpy(&path_2[path_2_len], stream_name);
311
312         HANDLE hFile_1 = open_file(path_1);
313         HANDLE hFile_2 = open_file(path_2);
314
315         cmp_data(hFile_1, path_1, hFile_2, path_2, dat_1->StreamSize.QuadPart);
316
317         CloseHandle(hFile_1);
318         CloseHandle(hFile_2);
319         path_1[path_1_len] = L'\0';
320         path_2[path_2_len] = L'\0';
321 }
322
323 static void
324 cmp_streams(wchar_t *path_1, size_t path_1_len,
325             wchar_t *path_2, size_t path_2_len)
326 {
327         WIN32_FIND_STREAM_DATA *streams_1, *streams_2;
328         size_t nstreams_1, nstreams_2;
329         size_t i;
330
331         streams_1 = get_stream_array(path_1, &nstreams_1);
332         streams_2 = get_stream_array(path_2, &nstreams_2);
333
334         if (nstreams_1 != nstreams_2) {
335                 difference(L"%ls and %ls do not have the same number of streams "
336                            "(%lu vs %lu)",
337                            path_1, path_2, nstreams_1, nstreams_2);
338                 goto out;
339         }
340
341         for (i = 0; i < nstreams_1; i++)
342                 cmp_stream(path_1, path_1_len, &streams_1[i],
343                            path_2, path_2_len, &streams_2[i]);
344
345 out:
346         free(streams_1);
347         free(streams_2);
348 }
349
350 struct win32_dentry_wrapper {
351         struct win32_dentry_wrapper *next;
352         WIN32_FIND_DATA dat;
353 };
354
355 static int
356 qsort_cmp_dentries_by_name(const void *p1, const void *p2)
357 {
358         const WIN32_FIND_DATA *d1 = p1;
359         const WIN32_FIND_DATA *d2 = p2;
360         return wcscmp(d1->cFileName, d2->cFileName);
361 }
362
363 static WIN32_FIND_DATA *
364 get_dentry_array(wchar_t *path, size_t path_len, size_t *ndentries_ret)
365 {
366         WIN32_FIND_DATA dat;
367         WIN32_FIND_DATA *array = NULL;
368         WIN32_FIND_DATA *p;
369         size_t ndentries = 0;
370         struct win32_dentry_wrapper *dentry_list = NULL;
371         HANDLE hFind;
372         DWORD err;
373
374         path[path_len] = L'\\';
375         path[path_len + 1] = L'*';
376         path[path_len + 2] = L'\0';
377         hFind = FindFirstFile(path, &dat);
378         path[path_len] = L'\0';
379         if (hFind != INVALID_HANDLE_VALUE) {
380                 do {
381                         struct win32_dentry_wrapper *wrapper;
382
383                         wrapper = malloc(sizeof(*wrapper));
384                         memcpy(&wrapper->dat, &dat, sizeof(dat));
385                         wrapper->next = dentry_list;
386                         dentry_list = wrapper;
387                         ndentries++;
388                 } while (FindNextFile(hFind, &dat));
389         }
390         err = GetLastError();
391         if (err != ERROR_NO_MORE_FILES && err != ERROR_FILE_NOT_FOUND)
392                 fatal_win32_error(L"Can't lookup dentries from %ls", path);
393         if (hFind != INVALID_HANDLE_VALUE)
394                 FindClose(hFind);
395         array = malloc(ndentries * sizeof(array[0]));
396         p = array;
397         while (dentry_list) {
398                 struct win32_dentry_wrapper *next;
399
400                 memcpy(p, &dentry_list->dat, sizeof(*p));
401                 next = dentry_list->next;
402                 free(dentry_list);
403                 dentry_list = next;
404                 p++;
405         }
406         assert(p - array == ndentries);
407         qsort(array, ndentries, sizeof(array[0]), qsort_cmp_dentries_by_name);
408         *ndentries_ret = ndentries;
409         return array;
410 }
411
412 static void
413 tree_cmp(wchar_t *path_1, size_t path_1_len, wchar_t *path_2, size_t path_2_len);
414
415 static void
416 recurse_directory(wchar_t *path_1, size_t path_1_len,
417                   wchar_t *path_2, size_t path_2_len)
418 {
419         WIN32_FIND_DATA *dentries_1, *dentries_2;
420         size_t ndentries_1, ndentries_2;
421         size_t i;
422
423         dentries_1 = get_dentry_array(path_1, path_1_len, &ndentries_1);
424         dentries_2 = get_dentry_array(path_2, path_2_len, &ndentries_2);
425
426         if (ndentries_1 != ndentries_2) {
427                 difference(L"Directories %ls and %ls do not contain the "
428                            "same number of entries", path_1, path_2);
429                 goto out;
430         }
431
432         path_1[path_1_len] = L'\\';
433         path_2[path_2_len] = L'\\';
434         for (i = 0; i < ndentries_1; i++) {
435                 size_t name_1_len, name_2_len;
436
437                 name_1_len = wcslen(dentries_1[i].cFileName);
438                 name_2_len = wcslen(dentries_2[i].cFileName);
439                 wmemcpy(&path_1[path_1_len + 1], dentries_1[i].cFileName, name_1_len + 1);
440                 wmemcpy(&path_2[path_2_len + 1], dentries_2[i].cFileName, name_2_len + 1);
441
442                 if (wcscmp(dentries_1[i].cFileName,
443                            dentries_2[i].cFileName))
444                         difference(L"%ls and %ls do not have the same name",
445                                    path_1, path_2);
446
447                 if (wcscmp(dentries_1[i].cAlternateFileName,
448                            dentries_2[i].cAlternateFileName))
449                         difference(L"%ls and %ls do not have the same short name "
450                                    "(%ls vs. %ls)", path_1, path_2,
451                                    dentries_1[i].cAlternateFileName,
452                                    dentries_2[i].cAlternateFileName);
453
454                 if (!wcscmp(dentries_1[i].cFileName, L".") ||
455                     !wcscmp(dentries_1[i].cFileName, L".."))
456                         continue;
457                 tree_cmp(path_1, path_1_len + 1 + name_1_len,
458                          path_2, path_2_len + 1 + name_2_len);
459         }
460
461 out:
462         path_1[path_1_len] = L'\0';
463         path_2[path_2_len] = L'\0';
464         free(dentries_1);
465         free(dentries_2);
466 }
467
468 static bool
469 file_times_equal(const FILETIME *t1, const FILETIME *t2)
470 {
471         return t1->dwLowDateTime == t2->dwLowDateTime &&
472                t1->dwHighDateTime == t2->dwHighDateTime;
473 }
474
475 static void *
476 get_security(const wchar_t *path, size_t *len_ret)
477 {
478         DWORD lenNeeded;
479         DWORD requestedInformation = DACL_SECURITY_INFORMATION |
480                                      SACL_SECURITY_INFORMATION |
481                                      OWNER_SECURITY_INFORMATION |
482                                      GROUP_SECURITY_INFORMATION |
483                                      BACKUP_SECURITY_INFORMATION;
484         void *descr;
485         BOOL bret;
486
487         bret = GetFileSecurity(path, requestedInformation, NULL, 0, &lenNeeded);
488
489         if (bret || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
490                 goto err;
491         descr = malloc(lenNeeded);
492         if (!GetFileSecurity(path, requestedInformation, descr, lenNeeded,
493                              &lenNeeded))
494                 goto err;
495         *len_ret = lenNeeded;
496         return descr;
497 err:
498         fatal_win32_error(L"Can't read security descriptor of %ls", path);
499 }
500
501 static wchar_t *
502 get_security_descriptor_string(PSECURITY_DESCRIPTOR desc)
503 {
504         wchar_t *str;
505         ConvertSecurityDescriptorToStringSecurityDescriptor(desc,
506                                                             SDDL_REVISION_1,
507                                                             OWNER_SECURITY_INFORMATION |
508                                                                     GROUP_SECURITY_INFORMATION |
509                                                                     DACL_SECURITY_INFORMATION |
510                                                                     SACL_SECURITY_INFORMATION |
511                                                                     BACKUP_SECURITY_INFORMATION,
512                                                             &str,
513                                                             NULL);
514         return str;
515 }
516
517 static void
518 cmp_security(const wchar_t *path_1, const wchar_t *path_2)
519 {
520         void *descr_1, *descr_2;
521         size_t len_1, len_2;
522         const wchar_t *str_1, *str_2;
523
524         descr_1 = get_security(path_1, &len_1);
525         descr_2 = get_security(path_2, &len_2);
526
527         if (len_1 != len_2 || memcmp(descr_1, descr_2, len_1)) {
528                 str_1 = get_security_descriptor_string(descr_1);
529                 str_2 = get_security_descriptor_string(descr_2);
530                 difference(L"%ls and %ls do not have the same security "
531                            "descriptor:\n\t%ls\nvs.\n\t%ls",
532                            path_1, path_2, str_1, str_2);
533         }
534         free(descr_1);
535         free(descr_2);
536 }
537
538 static const struct {
539         uint32_t flag;
540         const wchar_t *name;
541 } file_attr_flags[] = {
542         {FILE_ATTRIBUTE_READONLY,            L"READONLY"},
543         {FILE_ATTRIBUTE_HIDDEN,              L"HIDDEN"},
544         {FILE_ATTRIBUTE_SYSTEM,              L"SYSTEM"},
545         {FILE_ATTRIBUTE_DIRECTORY,           L"DIRECTORY"},
546         {FILE_ATTRIBUTE_ARCHIVE,             L"ARCHIVE"},
547         {FILE_ATTRIBUTE_DEVICE,              L"DEVICE"},
548         {FILE_ATTRIBUTE_NORMAL,              L"NORMAL"},
549         {FILE_ATTRIBUTE_TEMPORARY,           L"TEMPORARY"},
550         {FILE_ATTRIBUTE_SPARSE_FILE,         L"SPARSE_FILE"},
551         {FILE_ATTRIBUTE_REPARSE_POINT,       L"REPARSE_POINT"},
552         {FILE_ATTRIBUTE_COMPRESSED,          L"COMPRESSED"},
553         {FILE_ATTRIBUTE_OFFLINE,             L"OFFLINE"},
554         {FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, L"NOT_CONTENT_INDEXED"},
555         {FILE_ATTRIBUTE_ENCRYPTED,           L"ENCRYPTED"},
556         {FILE_ATTRIBUTE_VIRTUAL,             L"VIRTUAL"},
557 };
558
559 static void
560 cmp_attributes(const wchar_t *path_1, const BY_HANDLE_FILE_INFORMATION *file_info_1,
561                const wchar_t *path_2, const BY_HANDLE_FILE_INFORMATION *file_info_2)
562 {
563         u32 attrib_1 = file_info_1->dwFileAttributes;
564         u32 attrib_2 = file_info_2->dwFileAttributes;
565         u32 differences = attrib_1 ^ attrib_2;
566
567         if (!differences)
568                 return;
569
570         difference(L"Attributes for %ls (0x%"PRIx32") differ "
571                    "from attributes for %ls (0x%"PRIx32"):",
572                    path_1, attrib_1, path_2, attrib_2);
573         for (size_t i = 0; i < ARRAY_LEN(file_attr_flags); i++) {
574                 if (differences & file_attr_flags[i].flag) {
575                         const wchar_t *set_path;
576                         const wchar_t *unset_path;
577                         if (attrib_1 & file_attr_flags[i].flag) {
578                                 set_path = path_1;
579                                 unset_path = path_2;
580                         } else {
581                                 set_path = path_2;
582                                 unset_path = path_1;
583                         }
584                         fwprintf(stderr, L"\t%ls has FILE_ATTRIBUTE_%ls set but %ls does not\n",
585                                  set_path, file_attr_flags[i].name, unset_path);
586                 }
587         }
588 }
589
590 static void
591 tree_cmp(wchar_t *path_1, size_t path_1_len, wchar_t *path_2, size_t path_2_len)
592 {
593         HANDLE hFile_1, hFile_2;
594         BY_HANDLE_FILE_INFORMATION file_info_1, file_info_2;
595         u64 size_1, size_2;
596         u64 ino_1, ino_2;
597         u64 ino_to;
598         DWORD common_attribs;
599
600         /* Open each file.  */
601         hFile_1 = open_file(path_1);
602         hFile_2 = open_file(path_2);
603
604         /* Get basic file information.  */
605         if (!GetFileInformationByHandle(hFile_1, &file_info_1))
606                 fatal_win32_error(L"Failed to get file information for %ls", path_1);
607         if (!GetFileInformationByHandle(hFile_2, &file_info_2))
608                 fatal_win32_error(L"Failed to get file information for %ls", path_2);
609
610         /* Compare file attributes.  */
611         cmp_attributes(path_1, &file_info_1, path_2, &file_info_2);
612
613         common_attribs = file_info_1.dwFileAttributes & file_info_2.dwFileAttributes;
614
615         /* Compare file sizes, unless the files are both directories in which
616          * cases the sizes can legitimately differ.  */
617         if (!(common_attribs & FILE_ATTRIBUTE_DIRECTORY)) {
618                 size_1 = ((u64)file_info_1.nFileSizeHigh << 32) | file_info_1.nFileSizeLow;
619                 size_2 = ((u64)file_info_2.nFileSizeHigh << 32) | file_info_2.nFileSizeLow;
620                 if (size_1 != size_2)
621                         difference(L"Size for %ls (%"PRIu64") differs from size for %ls (%"PRIu64")",
622                                    path_1, size_1, path_2, size_2);
623         }
624
625         /* Compare file times.  */
626         if (!file_times_equal(&file_info_1.ftCreationTime, &file_info_2.ftCreationTime))
627                 difference(L"Creation times on %ls and %ls differ",
628                            path_1, path_2);
629
630         if (!file_times_equal(&file_info_1.ftLastWriteTime, &file_info_2.ftLastWriteTime))
631                 difference(L"Last write times on %ls and %ls differ",
632                            path_1, path_2);
633
634         /* If we've detected a hard link in tree 1, check that we've detected
635          * the same hard link in tree 2.  */
636         ino_1 = ((u64)file_info_1.nFileIndexHigh << 32) | file_info_1.nFileIndexLow;
637         ino_2 = ((u64)file_info_2.nFileIndexHigh << 32) | file_info_2.nFileIndexLow;
638         ino_to = lookup_ino(ino_1);
639         if (ino_to == INODE_NOT_SEEN_YET)
640                 insert_ino(ino_1, ino_2);
641         else if (ino_to != ino_2)
642                 difference(L"%ls and %ls are hard linked differently", path_1, path_2);
643
644         /* Compare security descriptors.  */
645         cmp_security(path_1, path_2);
646
647         /* Compare data streams.  */
648         cmp_streams(path_1, path_1_len, path_2, path_2_len);
649
650         /* Compare reparse data (if both files are reparse points)  */
651         if (common_attribs & FILE_ATTRIBUTE_REPARSE_POINT)
652                 cmp_reparse_data(hFile_1, path_1, hFile_2, path_2);
653
654         /* Recurse to directory (if both files are directories)  */
655         if ((common_attribs & FILE_ATTRIBUTE_DIRECTORY) &&
656             !(common_attribs & FILE_ATTRIBUTE_REPARSE_POINT))
657                 recurse_directory(path_1, path_1_len, path_2, path_2_len);
658
659         CloseHandle(hFile_1);
660         CloseHandle(hFile_2);
661 }
662
663 static void
664 enable_privilege(const wchar_t *privilege)
665 {
666         HANDLE hToken;
667         LUID luid;
668         TOKEN_PRIVILEGES newState;
669
670         if (!OpenProcessToken(GetCurrentProcess(),
671                               TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
672                 fatal_win32_error(L"Failed to open process token");
673
674         if (!LookupPrivilegeValue(NULL, privilege, &luid))
675                 fatal_win32_error(L"Failed to look up privileges %ls", privilege);
676
677         newState.PrivilegeCount = 1;
678         newState.Privileges[0].Luid = luid;
679         newState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
680         if (!AdjustTokenPrivileges(hToken, FALSE, &newState, 0, NULL, NULL))
681                 fatal_win32_error(L"Failed to acquire privilege %ls", privilege);
682         CloseHandle(hToken);
683 }
684
685 int
686 wmain(int argc, wchar_t **argv)
687 {
688         wchar_t *path_1 = malloc(32768 * sizeof(wchar_t));
689         wchar_t *path_2 = malloc(32768 * sizeof(wchar_t));
690         size_t len_1, len_2;
691
692         if (argc != 3) {
693                 fwprintf(stderr, L"Usage: win32-tree-cmp DIR1 DIR2\n");
694                 return 2;
695         }
696
697         enable_privilege(SE_BACKUP_NAME);
698         enable_privilege(SE_SECURITY_NAME);
699
700         len_1 = wcslen(argv[1]);
701         len_2 = wcslen(argv[2]);
702         wmemcpy(path_1, argv[1], len_1 + 1);
703         wmemcpy(path_2, argv[2], len_2 + 1);
704
705         tree_cmp(path_1, len_1, path_2, len_2);
706
707         if (difference_count) {
708                 fwprintf(stderr, L"Found %lu differences; exiting with failure status.\n",
709                          difference_count);
710                 return 1;
711         }
712         return 0;
713 }