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