2 * Copyright (c) 2014 Eric Biggers. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
22 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 /*****************************************************************************/
39 /* Size of WIM resource (stream) hash fields */
40 #define RESOURCE_HASH_SIZE 20
43 #define ARRAY_LEN(A) (sizeof(A) / sizeof((A)[0]))
45 /*****************************************************************************/
47 /* Definitions for WOF (Windows Overlay File System Filter) */
49 #define WOF_CURRENT_VERSION 1
50 #define WOF_PROVIDER_WIM 1
51 #define WIM_PROVIDER_CURRENT_VERSION 1
53 /* Identifies a backing provider for a specific overlay service version. */
54 struct wof_external_info {
56 /* Version of the overlay service supported by the backing provider.
57 * Set to WOF_CURRENT_VERSION. */
60 /* Identifier for the backing provider. Example value:
61 * WOF_PROVIDER_WIM. */
65 struct wim_provider_external_info {
67 /* Set to WIM_PROVIDER_CURRENT_VERSION. */
70 /* 0 when WIM provider active, otherwise
71 * WIM_PROVIDER_EXTERNAL_FLAG_NOT_ACTIVE or
72 * WIM_PROVIDER_EXTERNAL_FLAG_SUSPENDED. */
75 /* Integer ID that identifies the WIM. Get this with the
76 * FSCTL_ADD_OVERLAY ioctl. */
77 uint64_t data_source_id;
79 /* SHA1 message digest of the file's unnamed data stream. */
80 uint8_t resource_hash[RESOURCE_HASH_SIZE];
84 * --- FSCTL_GET_EXTERNAL_BACKING ---
86 * Get external backing information for the specified file.
88 * DeviceType: 9 (FILE_DEVICE_FILE_SYSTEM)
89 * Access: 0 (FILE_ANY_ACCESS)
91 * Method: 0 (METHOD_BUFFERED)
94 * Output buffer: 'struct wof_external_info' followed by provider-specific data
95 * ('struct wim_provider_external_info' in the case of WIM).
97 #define FSCTL_GET_EXTERNAL_BACKING 0x90310
99 #ifndef STATUS_OBJECT_NOT_EXTERNALLY_BACKED
100 # define STATUS_OBJECT_NOT_EXTERNALLY_BACKED 0xC000046D
103 /*****************************************************************************/
105 /* Global counters, updated during the directory tree scan */
108 uint64_t sharing_violations;
109 uint64_t directories;
110 uint64_t nondirectories;
111 uint64_t externally_backed_files;
112 uint64_t bytes_checksummed;
113 uint64_t next_bytes_checksummed_progress;
114 uint64_t checksum_mismatches;
117 #define BYTES_PER_PROGRESS 100000000
119 /*****************************************************************************/
121 /* Handle to ntdll.dll */
122 static HMODULE hNtdll;
124 /* Functions loaded from ntdll.dll */
126 static DWORD (WINAPI *func_RtlNtStatusToDosError)(NTSTATUS status);
128 static NTSTATUS (WINAPI *func_NtOpenFile) (PHANDLE FileHandle,
129 ACCESS_MASK DesiredAccess,
130 POBJECT_ATTRIBUTES ObjectAttributes,
131 PIO_STATUS_BLOCK IoStatusBlock,
135 static NTSTATUS (WINAPI *func_NtQueryInformationFile)(HANDLE FileHandle,
136 PIO_STATUS_BLOCK IoStatusBlock,
137 PVOID FileInformation,
139 FILE_INFORMATION_CLASS FileInformationClass);
141 static NTSTATUS (WINAPI *func_NtQueryObject) (HANDLE Handle,
142 OBJECT_INFORMATION_CLASS ObjectInformationClass,
143 PVOID ObjectInformation,
144 ULONG ObjectInformationLength,
145 PULONG ReturnLength);
147 static NTSTATUS (WINAPI *func_NtFsControlFile) (HANDLE FileHandle,
149 PIO_APC_ROUTINE ApcRoutine,
151 PIO_STATUS_BLOCK IoStatusBlock,
154 ULONG InputBufferLength,
156 ULONG OutputBufferLength);
158 static NTSTATUS (WINAPI *func_NtQueryDirectoryFile) (HANDLE FileHandle,
160 PIO_APC_ROUTINE ApcRoutine,
162 PIO_STATUS_BLOCK IoStatusBlock,
163 PVOID FileInformation,
165 FILE_INFORMATION_CLASS FileInformationClass,
166 BOOLEAN ReturnSingleEntry,
167 PUNICODE_STRING FileName,
168 BOOLEAN RestartScan);
170 static NTSTATUS (WINAPI *func_NtClose) (HANDLE Handle);
172 /*****************************************************************************/
174 /* Retrieves a human-readable error string for the specified Win32 error code. */
176 win32_error_string(DWORD err_code)
178 static wchar_t buf[1024];
182 len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err_code, 0,
183 buf, ARRAY_LEN(buf), NULL);
184 if (len > 0 && buf[len - 1] == L'\n')
186 if (len > 0 && buf[len - 1] == L'\r')
188 if (len > 0 && buf[len - 1] == L'.')
193 /* Retrieves a human-readable error string for the specified NTSTATUS error
196 nt_error_string(NTSTATUS status)
198 static wchar_t buf[1024];
199 wsprintf(buf, L"status 0x%08x: %ls",
200 (uint32_t)status, win32_error_string(func_RtlNtStatusToDosError(status)));
204 /* Translate the specified unsigned number into string form, with commas. */
206 u64_to_pretty_string(uint64_t num)
208 static char bufs[4][30];
209 static int which_buf = 0;
211 which_buf = (which_buf + 1) % ARRAY_LEN(bufs);
213 char *p = &bufs[which_buf][ARRAY_LEN(bufs[0]) - 1];
214 unsigned int comma_count = 3;
222 if (comma_count == 0) {
227 *--p = '0' + (num % 10);
228 } while ((num /= 10) != 0);
233 /* Prints the specified error message and exits the program with failure status.
235 static void __attribute__((format(printf, 1, 2)))
236 fatal(const char *fmt, ...)
241 fputs("ERROR: ", stderr);
242 vfprintf(stderr, fmt, va);
249 /* Prints the specified warning message. */
250 static void __attribute__((format(printf, 1, 2)))
251 warn(const char *fmt, ...)
256 fputs("WARNING: ", stderr);
257 vfprintf(stderr, fmt, va);
262 /* Like malloc(), but abort the program on failure. */
270 fatal("Out of memory");
274 /* @path is a NT namespace name beginning with \Device\
275 * Try to replace the device with the corresponding DOS device,
276 * e.g. \Device\HardDiskVolume1\Windows => C:\Windows
277 * Note that there seems to be no easy way to do this.
280 replace_nt_device(wchar_t *path)
283 wchar_t nt_device[1000];
285 size_t nt_device_nchars;
288 tmp = wcschr(path + 9, L'\\');
290 nt_device_nchars = tmp - path;
292 nt_device_nchars = wcslen(path);
294 if (!wcsncmp(path, L"\\Device\\Mup\\", 12)) {
295 /* Network path, like \Device\Mup\192.168.0.1\somedir\somefile */
303 /* Go through each possible drive letter and see if the reverse mapping
305 for (drive[0] = 'A'; drive[0] <= 'Z'; drive[0]++) {
306 ret = QueryDosDevice(drive, nt_device, ARRAY_LEN(nt_device));
309 if (!wcsncmp(nt_device, path, nt_device_nchars)) {
310 path[--nt_device_nchars] = drive[1];
311 path[--nt_device_nchars] = drive[0];
312 return &path[nt_device_nchars];
315 /* Nothing matched. Just keep the NT namespace path. */
319 /* Given an open handle and a path relative to it (both optional, but at least
320 * one must be specified), return a statically allocated human-readable form of
322 static const wchar_t *
323 printable_path(HANDLE h, const wchar_t *path)
325 static uint8_t bufs[2][sizeof(OBJECT_NAME_INFORMATION) + 32768 * sizeof(wchar_t)]
326 __attribute__((aligned(8)));
327 static int buf_index = 0;
335 buf = bufs[buf_index];
336 buf_index = (buf_index + 1) % ARRAY_LEN(bufs);
339 res = wcscpy((wchar_t *)buf, path);
343 status = (*func_NtQueryObject)(h, ObjectNameInformation,
344 buf, sizeof(bufs[0]), &return_length);
345 if (!NT_SUCCESS(status)) {
346 res = wcscpy((wchar_t *)buf, path);
351 OBJECT_NAME_INFORMATION *info = (OBJECT_NAME_INFORMATION *)buf;
353 /* Strip trailing slash and append file name */
355 i = info->Name.Length / sizeof(wchar_t);
356 if (i > 0 && info->Name.Buffer[i - 1] != L'\\')
357 info->Name.Buffer[i++] = L'\\';
358 wcscpy(&info->Name.Buffer[i], path);
361 res = info->Name.Buffer;
365 if (!wcsncmp(res, L"\\Device\\", 8))
366 res = replace_nt_device(res);
367 else if (!wcsncmp(res, L"\\??\\", 4))
372 static const wchar_t *
373 prettify_path(const wchar_t *path)
375 return printable_path(NULL, path);
378 static const wchar_t *
379 handle_to_path(HANDLE h)
381 return printable_path(h, NULL);
384 /* Load ntdll.dll and some native functions from it. */
388 hNtdll = LoadLibrary(L"ntdll.dll");
391 fatal("Can't load ntdll.dll");
393 #define NTDLL_SYM(name) { (void **)&func_##name, #name }
394 static const struct ntdll_sym {
398 NTDLL_SYM(RtlNtStatusToDosError),
399 NTDLL_SYM(NtOpenFile),
400 NTDLL_SYM(NtQueryInformationFile),
401 NTDLL_SYM(NtQueryObject),
402 NTDLL_SYM(NtFsControlFile),
403 NTDLL_SYM(NtQueryDirectoryFile),
409 for (const struct ntdll_sym *sym = ntdll_syms; sym->name; sym++) {
410 void *addr = (void*)GetProcAddress(hNtdll, sym->name);
412 fatal("Can't find %s in ntdll.dll", sym->name);
413 *(sym->func_ptr) = addr;
418 checksum_file(HANDLE h, uint64_t expected_size, uint8_t hash[RESOURCE_HASH_SIZE])
423 uint64_t actual_size = 0;
427 if (!ReadFile(h, buf, sizeof(buf), &bytesRead, NULL)) {
428 warn("Error reading \"%ls\": %ls",
429 handle_to_path(h), win32_error_string(GetLastError()));
436 sha1_update(&ctx, buf, bytesRead);
437 actual_size += bytesRead;
440 sha1_final(hash, &ctx);
441 counters.bytes_checksummed += actual_size;
442 if (actual_size != expected_size) {
443 warn("Actual file size (%s) does not match expected file size (%s): \"%ls\"",
444 u64_to_pretty_string(actual_size),
445 u64_to_pretty_string(expected_size),
448 if (counters.bytes_checksummed > counters.next_bytes_checksummed_progress) {
449 printf("%s MB checksummed...\n",
450 u64_to_pretty_string(counters.next_bytes_checksummed_progress / 1000000));
451 counters.next_bytes_checksummed_progress += BYTES_PER_PROGRESS;
457 /* Given an open handle to any file on the volume being scanned, detect if the
458 * WOF driver is available. If not, print an error message and abort. */
463 IO_STATUS_BLOCK iosb;
465 status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb,
466 FSCTL_GET_EXTERNAL_BACKING,
469 if (status == STATUS_OBJECT_NOT_EXTERNALLY_BACKED ||
470 status == STATUS_BUFFER_TOO_SMALL)
475 fatal("\"%ls\": The Windows Overlay File System Filter is not running.",
480 /* It is unknown whether the file is externally backed or not. */
481 BACKING_UNKNOWN = 0x0,
483 /* The file is not externally backed. */
484 BACKING_INTERNAL = 0x1,
486 /* The file is externally backed. */
487 BACKING_EXTERNAL = 0x2,
489 /* The file is externally backed, specifically in a WIM file. */
490 BACKING_EXTERNAL_WIM = 0x4 | BACKING_EXTERNAL,
493 /* Determines the externaly backing status of a file.
496 * Open handle to the file.
498 * If return value is BACKING_EXTERNAL_WIM, this will be set to the data
499 * source ID of the backing WIM.
501 * If return value is BACKING_EXTERNAL_WIM, this will be filled in with the
502 * SHA1 message digest of the file's contents (unnamed data stream, which
503 * is being backed in the WIM).
505 * Returns one of the 'enum backing' values.
508 get_external_backing(HANDLE h, uint64_t *wim_id_ret,
509 uint8_t resource_hash_ret[RESOURCE_HASH_SIZE])
512 struct wof_external_info wof_info;
513 struct wim_provider_external_info wim_info;
516 IO_STATUS_BLOCK iosb;
518 status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb,
519 FSCTL_GET_EXTERNAL_BACKING,
520 NULL, 0, &out, sizeof(out));
522 if (status == STATUS_OBJECT_NOT_EXTERNALLY_BACKED)
523 return BACKING_INTERNAL;
525 if (status == STATUS_BUFFER_TOO_SMALL ||
526 status == STATUS_BUFFER_OVERFLOW)
527 return BACKING_EXTERNAL;
529 if (!NT_SUCCESS(status)) {
530 warn("\"%ls\": FSCTL_GET_EXTERNAL_BACKING failed (%ls)",
531 handle_to_path(h), nt_error_string(status));
533 return BACKING_UNKNOWN;
536 if (iosb.Information < sizeof(struct wof_external_info)) {
537 warn("\"%ls\": weird results from FSCTL_GET_EXTERNAL_BACKING",
540 return BACKING_UNKNOWN;
543 if (out.wof_info.provider == WOF_PROVIDER_WIM) {
544 *wim_id_ret = out.wim_info.data_source_id;
545 memcpy(resource_hash_ret, out.wim_info.resource_hash, RESOURCE_HASH_SIZE);
546 return BACKING_EXTERNAL_WIM;
549 return BACKING_EXTERNAL;
553 verify(HANDLE cur_dir, const wchar_t *path, size_t path_nchars);
556 recurse_directory(HANDLE h)
559 const size_t bufsize = 32768;
561 IO_STATUS_BLOCK iosb;
563 buf = xmalloc(bufsize + sizeof(wchar_t));
565 while (NT_SUCCESS(status = (*func_NtQueryDirectoryFile)(h, NULL, NULL, NULL,
567 FileNamesInformation,
568 FALSE, NULL, FALSE)))
570 FILE_NAMES_INFORMATION *info;
572 info = (FILE_NAMES_INFORMATION *)buf;
574 if (!(info->FileNameLength == 2 && info->FileName[0] == L'.') &&
575 !(info->FileNameLength == 4 && info->FileName[0] == L'.' &&
576 info->FileName[1] == L'.'))
578 wchar_t save = info->FileName[info->FileNameLength /
581 info->FileName[info->FileNameLength /
582 sizeof(wchar_t)] = L'\0';
584 verify(h, info->FileName,
585 info->FileNameLength / sizeof(wchar_t));
587 info->FileName[info->FileNameLength /
588 sizeof(wchar_t)] = save;
590 if (info->NextEntryOffset == 0)
592 info = (FILE_NAMES_INFORMATION *)
593 ((uint8_t *)info + info->NextEntryOffset);
598 if (status != STATUS_NO_MORE_FILES) {
599 warn("\"%ls\": Can't read directory (%ls)",
600 handle_to_path(h), nt_error_string(status));
606 open_file(HANDLE cur_dir, const wchar_t *path, size_t path_nchars, HANDLE *h_ret)
609 OBJECT_ATTRIBUTES attr;
610 IO_STATUS_BLOCK iosb;
612 name.Length = path_nchars * sizeof(wchar_t);
613 name.MaximumLength = name.Length + sizeof(wchar_t);
614 name.Buffer = (wchar_t *)path;
616 attr.Length = sizeof(attr);
617 attr.RootDirectory = cur_dir;
618 attr.ObjectName = &name;
620 attr.SecurityDescriptor = NULL;
621 attr.SecurityQualityOfService = NULL;
623 return (*func_NtOpenFile)(h_ret,
624 FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
627 FILE_SHARE_VALID_FLAGS,
628 FILE_OPEN_REPARSE_POINT |
629 FILE_OPEN_FOR_BACKUP_INTENT |
630 FILE_SYNCHRONOUS_IO_NONALERT);
633 /* Query "all" metadata about the specified file. */
635 query_all_file_info(HANDLE h, FILE_ALL_INFORMATION *file_info)
637 IO_STATUS_BLOCK iosb;
638 return (*func_NtQueryInformationFile)(h,
641 sizeof(FILE_ALL_INFORMATION),
648 * Parent directory, or NULL if first iteration.
650 * Basename of current file, or the full name if first iteration.
652 * Number of characters valid in @path (will be null-terminated as well).
654 * Metadata for this file or directory if available from parent directory,
655 * otherwise NULL. This will be used if the file itself cannot be opened
656 * due to a sharing violation.
659 verify(HANDLE cur_dir, const wchar_t *path, size_t path_nchars)
663 FILE_ALL_INFORMATION file_info;
665 status = open_file(cur_dir, path, path_nchars, &h);
667 if (!NT_SUCCESS(status)) {
668 if (status == STATUS_SHARING_VIOLATION) {
669 counters.sharing_violations++;
671 if (cur_dir == NULL) {
672 fatal("\"%ls\": Can't open file (%ls)",
673 printable_path(cur_dir, path),
674 nt_error_string(status));
676 warn("\"%ls\": Can't open file (%ls)",
677 printable_path(cur_dir, path),
678 nt_error_string(status));
687 status = query_all_file_info(h, &file_info);
689 if (!NT_SUCCESS(status) && status != STATUS_BUFFER_OVERFLOW) {
690 warn("\"%ls\": Can't read metadata (%ls)",
691 handle_to_path(h), nt_error_string(status));
696 if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
697 counters.directories++;
698 recurse_directory(h);
700 enum backing backing;
702 uint8_t resource_hash[RESOURCE_HASH_SIZE];
703 uint8_t actual_hash[RESOURCE_HASH_SIZE];
705 counters.nondirectories++;
707 backing = get_external_backing(h, &wim_id, resource_hash);
709 if (backing & BACKING_EXTERNAL) {
710 counters.externally_backed_files++;
711 if (backing == BACKING_EXTERNAL_WIM) {
713 file_info.StandardInformation.EndOfFile.QuadPart,
716 if (memcmp(resource_hash, actual_hash, RESOURCE_HASH_SIZE)) {
717 warn("CHECKSUM MISMATCH: path=\"%ls\"", handle_to_path(h));
718 counters.checksum_mismatches++;
722 warn("Ignoring \"%ls\": externally backed, but not by WIM archive",
733 enable_privilege(const wchar_t *privilege)
737 TOKEN_PRIVILEGES newState;
739 if (OpenProcessToken(GetCurrentProcess(),
740 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
742 if (LookupPrivilegeValue(NULL, privilege, &luid)) {
743 newState.PrivilegeCount = 1;
744 newState.Privileges[0].Luid = luid;
745 newState.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
746 AdjustTokenPrivileges(hToken, FALSE, &newState, 0, NULL, NULL);
755 puts("--------------------------------------------------------------------------------");
759 wmain(int argc, wchar_t **argv)
762 size_t fullpath_nchars;
763 const wchar_t *prefix = L"\\??\\";
764 const size_t prefix_nchars = wcslen(prefix);
767 enable_privilege(SE_BACKUP_NAME);
772 fprintf(stderr, "Usage: %ls DIR\n", argv[0]);
776 /* Prepare the initial path. */
777 fullpath = xmalloc(32768 * sizeof(wchar_t));
779 wcscpy(fullpath, prefix);
780 ret = GetFullPathName(argv[1], 32768 - prefix_nchars,
781 fullpath + prefix_nchars, NULL);
783 fatal("\"%ls\": Can't get full path (%ls)",
784 argv[1], win32_error_string(GetLastError()));
786 fullpath_nchars = prefix_nchars + ret;
788 counters.next_bytes_checksummed_progress = BYTES_PER_PROGRESS;
790 /* Scan the directory tree. */
792 printf("Verifying \"%ls\"\n", prettify_path(fullpath));
793 verify(NULL, fullpath, fullpath_nchars);
796 /* Print statistics. */
798 printf("Errors: %s (%s were sharing violations)\n",
799 u64_to_pretty_string(counters.errors),
800 u64_to_pretty_string(counters.sharing_violations));
801 printf("File counts: %s dirs, %s nondirs (%s externally backed)\n",
802 u64_to_pretty_string(counters.directories),
803 u64_to_pretty_string(counters.nondirectories),
804 u64_to_pretty_string(counters.externally_backed_files));
805 printf("%s bytes checksummed; %s mismatches\n",
806 u64_to_pretty_string(counters.bytes_checksummed),
807 u64_to_pretty_string(counters.checksum_mismatches));
810 /* Cleanup and exit. */