2 * Compare directory trees (Windows version)
18 #define REPARSE_POINT_MAX_SIZE (16 * 1024)
21 win32_error_string(DWORD err_code)
23 static wchar_t buf[1024];
25 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err_code, 0,
30 static void __attribute__((noreturn))
31 error(const wchar_t *format, ...)
35 vfwprintf(stderr, format, va);
41 static void __attribute__((noreturn))
42 win32_error(const wchar_t *format, ...)
45 DWORD err = GetLastError();
48 vfwprintf(stderr, format, va);
49 fwprintf(stderr, L": %ls\n", win32_error_string(err));
61 static struct node *tree = NULL;
63 static u64 do_lookup_ino(struct node *tree, u64 ino_from)
67 if (ino_from == tree->ino_from)
69 else if (ino_from < tree->ino_from)
70 return do_lookup_ino(tree->left, ino_from);
72 return do_lookup_ino(tree->right, ino_from);
75 static void do_insert(struct node *tree, struct node *node)
77 if (node->ino_from < tree->ino_from) {
79 return do_insert(tree->left, node);
84 return do_insert(tree->right, node);
90 static u64 lookup_ino(u64 ino_from)
92 return do_lookup_ino(tree, ino_from);
95 static void insert_ino(u64 ino_from, u64 ino_to)
97 struct node *node = malloc(sizeof(struct node));
99 error(L"Out of memory");
100 node->ino_from = ino_from;
101 node->ino_to = ino_to;
107 do_insert(tree, node);
111 win32_open_file_readonly(const wchar_t *path)
113 HANDLE hFile = CreateFile(path,
114 GENERIC_READ | ACCESS_SYSTEM_SECURITY,
115 FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
118 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
120 if (hFile == INVALID_HANDLE_VALUE)
121 win32_error(L"Failed to open file %ls read-only", path);
126 get_reparse_data(HANDLE hFile, const wchar_t *path,
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
140 win32_error(L"Can't get reparse data from %ls", path);
141 return bytesReturned;
145 cmp_reparse_data(HANDLE hFile_1, const wchar_t *path_1,
146 HANDLE hFile_2, const wchar_t *path_2)
148 char rpdata_1[REPARSE_POINT_MAX_SIZE];
149 char rpdata_2[REPARSE_POINT_MAX_SIZE];
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",
161 struct win32_stream_wrapper {
162 struct win32_stream_wrapper *next;
163 WIN32_FIND_STREAM_DATA dat;
167 qsort_cmp_streams_by_name(const void *p1, const void *p2)
169 const WIN32_FIND_STREAM_DATA *s1 = p1, *s2 = p2;
170 return wcscmp(s1->cStreamName, s2->cStreamName);
173 static WIN32_FIND_STREAM_DATA *
174 get_stream_array(const wchar_t *path, size_t *nstreams_ret)
176 WIN32_FIND_STREAM_DATA dat;
177 WIN32_FIND_STREAM_DATA *array = NULL;
178 WIN32_FIND_STREAM_DATA *p;
180 struct win32_stream_wrapper *stream_list = NULL;
183 hFind = FindFirstStreamW(path, FindStreamInfoStandard, &dat, 0);
184 if (hFind != INVALID_HANDLE_VALUE) {
186 struct win32_stream_wrapper *wrapper;
188 wrapper = malloc(sizeof(*wrapper));
190 error(L"out of memory");
191 memcpy(&wrapper->dat, &dat, sizeof(dat));
192 wrapper->next = stream_list;
193 stream_list = wrapper;
195 } while (FindNextStreamW(hFind, &dat));
197 if (GetLastError() != ERROR_HANDLE_EOF)
198 win32_error(L"Can't lookup streams from %ls", path);
199 if (hFind != INVALID_HANDLE_VALUE)
201 array = malloc(nstreams * sizeof(array[0]));
203 while (stream_list) {
204 struct win32_stream_wrapper *next;
206 memcpy(p, &stream_list->dat, sizeof(*p));
207 next = stream_list->next;
212 assert(p - array == nstreams);
213 qsort(array, nstreams, sizeof(array[0]), qsort_cmp_streams_by_name);
214 *nstreams_ret = nstreams;
218 static const wchar_t *
219 fix_stream_name(wchar_t *stream_name)
223 /* The stream name should be returned as :NAME:TYPE */
224 if (stream_name[0] != L':')
226 colon = wcschr(stream_name + 1, L':');
229 if (wcscmp(colon + 1, L"$DATA"))
232 if (stream_name == colon - 1)
237 #define BUFSIZE 32768
240 cmp_data(HANDLE hFile_1, const wchar_t *path_1,
241 HANDLE hFile_2, const wchar_t *path_2, u64 size)
245 u64 bytes_remaining = size;
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)
256 win32_error(L"Error reading from %ls", path_1);
258 if (!ReadFile(hFile_2, buf_2, bytesToRead, &bytesRead, NULL) ||
259 bytesRead != bytesToRead)
261 win32_error(L"Error reading from %ls", path_2);
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;
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)
273 const wchar_t *stream_name;
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);
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);
287 stream_name = fix_stream_name(dat_1->cStreamName);
292 wcscpy(&path_1[path_1_len], stream_name);
293 wcscpy(&path_2[path_2_len], stream_name);
295 HANDLE hFile_1 = win32_open_file_readonly(path_1);
296 HANDLE hFile_2 = win32_open_file_readonly(path_2);
298 cmp_data(hFile_1, path_1, hFile_2, path_2,
299 dat_1->StreamSize.QuadPart);
301 CloseHandle(hFile_1);
302 CloseHandle(hFile_2);
303 path_1[path_1_len] = L'\0';
304 path_2[path_2_len] = L'\0';
308 cmp_streams(wchar_t *path_1, size_t path_1_len,
309 wchar_t *path_2, size_t path_2_len)
311 WIN32_FIND_STREAM_DATA *streams_1, *streams_2;
312 size_t nstreams_1, nstreams_2;
315 streams_1 = get_stream_array(path_1, &nstreams_1);
316 streams_2 = get_stream_array(path_2, &nstreams_2);
318 if (nstreams_1 != nstreams_2) {
319 error(L"%ls and %ls do not have the same number of streams",
323 for (i = 0; i < nstreams_1; i++)
324 cmp_stream(path_1, path_1_len, &streams_1[i],
325 path_2, path_2_len, &streams_2[i]);
330 struct win32_dentry_wrapper {
331 struct win32_dentry_wrapper *next;
336 qsort_cmp_dentries_by_name(const void *p1, const void *p2)
338 const WIN32_FIND_DATA *d1 = p1, *d2 = p2;
339 return wcscmp(d1->cFileName, d2->cFileName);
342 static WIN32_FIND_DATA *
343 get_dentry_array(wchar_t *path, size_t path_len, size_t *ndentries_ret)
346 WIN32_FIND_DATA *array = NULL;
348 size_t ndentries = 0;
349 struct win32_dentry_wrapper *dentry_list = NULL;
353 path[path_len] = L'\\';
354 path[path_len + 1] = L'*';
355 path[path_len + 2] = L'\0';
356 hFind = FindFirstFile(path, &dat);
357 path[path_len] = L'\0';
358 if (hFind != INVALID_HANDLE_VALUE) {
360 struct win32_dentry_wrapper *wrapper;
362 wrapper = malloc(sizeof(*wrapper));
364 error(L"out of memory");
365 memcpy(&wrapper->dat, &dat, sizeof(dat));
366 wrapper->next = dentry_list;
367 dentry_list = wrapper;
369 } while (FindNextFile(hFind, &dat));
371 err = GetLastError();
372 if (err != ERROR_NO_MORE_FILES && err != ERROR_FILE_NOT_FOUND)
373 win32_error(L"Can't lookup dentries from %ls", path);
374 if (hFind != INVALID_HANDLE_VALUE)
376 array = malloc(ndentries * sizeof(array[0]));
378 while (dentry_list) {
379 struct win32_dentry_wrapper *next;
381 memcpy(p, &dentry_list->dat, sizeof(*p));
382 next = dentry_list->next;
387 assert(p - array == ndentries);
388 qsort(array, ndentries, sizeof(array[0]), qsort_cmp_dentries_by_name);
389 *ndentries_ret = ndentries;
394 tree_cmp(wchar_t *path_1, size_t path_1_len, wchar_t *path_2, size_t path_2_len);
397 recurse_directory(wchar_t *path_1, size_t path_1_len,
398 wchar_t *path_2, size_t path_2_len)
400 WIN32_FIND_DATA *dentries_1, *dentries_2;
401 size_t ndentries_1, ndentries_2;
404 dentries_1 = get_dentry_array(path_1, path_1_len, &ndentries_1);
405 dentries_2 = get_dentry_array(path_2, path_2_len, &ndentries_2);
407 if (ndentries_1 != ndentries_2) {
408 error(L"%ls and %ls do not have the same number of dentries",
412 path_1[path_1_len] = L'\\';
413 path_2[path_2_len] = L'\\';
414 for (i = 0; i < ndentries_1; i++) {
415 size_t name_1_len, name_2_len;
417 name_1_len = wcslen(dentries_1[i].cFileName);
418 name_2_len = wcslen(dentries_2[i].cFileName);
419 wmemcpy(&path_1[path_1_len + 1], dentries_1[i].cFileName, name_1_len + 1);
420 wmemcpy(&path_2[path_2_len + 1], dentries_2[i].cFileName, name_2_len + 1);
422 if (wcscmp(dentries_1[i].cFileName,
423 dentries_2[i].cFileName))
424 error(L"%ls and %ls do not have the same name",
427 if (wcscmp(dentries_1[i].cAlternateFileName,
428 dentries_2[i].cAlternateFileName))
429 error(L"%ls and %ls do not have the same short name",
432 if (!wcscmp(dentries_1[i].cFileName, L".") ||
433 !wcscmp(dentries_2[i].cFileName, L".."))
435 tree_cmp(path_1, path_1_len + 1 + name_1_len,
436 path_2, path_2_len + 1 + name_2_len);
438 path_1[path_1_len] = L'\0';
439 path_2[path_2_len] = L'\0';
445 file_times_equal(const FILETIME *t1, const FILETIME *t2)
447 return t1->dwLowDateTime == t2->dwLowDateTime &&
448 t1->dwHighDateTime == t2->dwHighDateTime;
452 get_security(const wchar_t *path, size_t *len_ret)
455 DWORD requestedInformation = DACL_SECURITY_INFORMATION |
456 SACL_SECURITY_INFORMATION |
457 OWNER_SECURITY_INFORMATION |
458 GROUP_SECURITY_INFORMATION;
463 bret = GetFileSecurity(path, requestedInformation,
464 NULL, 0, &lenNeeded);
466 if (bret || GetLastError() != ERROR_INSUFFICIENT_BUFFER)
468 descr = malloc(lenNeeded);
470 error(L"out of memory");
471 if (!GetFileSecurity(path, requestedInformation, descr, lenNeeded,
474 *len_ret = lenNeeded;
477 win32_error(L"Can't read security descriptor of %ls", path);
481 get_security_descriptor_string(PSECURITY_DESCRIPTOR desc)
485 ConvertSecurityDescriptorToStringSecurityDescriptor(desc,
487 OWNER_SECURITY_INFORMATION |
488 GROUP_SECURITY_INFORMATION |
489 DACL_SECURITY_INFORMATION |
490 SACL_SECURITY_INFORMATION,
497 cmp_security(const wchar_t *path_1, const wchar_t *path_2)
499 void *descr_1, *descr_2;
501 const wchar_t *str_1, *str_2;
503 descr_1 = get_security(path_1, &len_1);
504 descr_2 = get_security(path_2, &len_2);
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);
518 tree_cmp(wchar_t *path_1, size_t path_1_len, wchar_t *path_2, size_t path_2_len)
520 HANDLE hFile_1, hFile_2;
521 BY_HANDLE_FILE_INFORMATION file_info_1, file_info_2;
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);
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);
540 attribs = file_info_1.dwFileAttributes;
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);
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);
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);
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);
568 if (!file_times_equal(&file_info_1.ftCreationTime, &file_info_2.ftCreationTime))
569 error(L"Creation times on %ls and %ls differ",
572 if (!file_times_equal(&file_info_1.ftLastWriteTime, &file_info_2.ftLastWriteTime))
573 error(L"Last write times on %ls and %ls differ",
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);
587 enable_privilege(const wchar_t *privilege)
591 TOKEN_PRIVILEGES newState;
593 if (!OpenProcessToken(GetCurrentProcess(),
594 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
595 win32_error(L"Failed to open process token");
597 if (!LookupPrivilegeValueW(NULL, privilege, &luid))
598 win32_error(L"Failed to look up privileges %ls", privilege);
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);
608 int wmain(int argc, wchar_t **argv, wchar_t **envp)
610 wchar_t dir_1[32769], dir_2[32769];
614 fwprintf(stderr, L"Usage: win32-tree-cmp DIR1 DIR2\n");
618 enable_privilege(SE_BACKUP_NAME);
619 enable_privilege(SE_SECURITY_NAME);
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);