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