]> wimlib.net Git - wimlib/blob - tools/wimboot-verify/wimboot-verify.c
Add wimboot-verify to tools/
[wimlib] / tools / wimboot-verify / wimboot-verify.c
1 /*
2  * Copyright (c) 2014 Eric Biggers.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  *
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.
14  *
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.
26  */
27
28 #include <inttypes.h>
29 #include <ntstatus.h>
30 #include <stdarg.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <windows.h>
34 #include <winternl.h>
35 #include "sha1.h"
36
37 /*****************************************************************************/
38
39 /* Size of WIM resource (stream) hash fields  */
40 #define RESOURCE_HASH_SIZE 20
41
42 /* Useful macros  */
43 #define ARRAY_LEN(A) (sizeof(A) / sizeof((A)[0]))
44
45 /*****************************************************************************/
46
47 /* Definitions for WOF (Windows Overlay File System Filter)  */
48
49 #define WOF_CURRENT_VERSION     1
50 #define WOF_PROVIDER_WIM        1
51 #define WIM_PROVIDER_CURRENT_VERSION 1
52
53 /* Identifies a backing provider for a specific overlay service version.  */
54 struct wof_external_info {
55
56         /* Version of the overlay service supported by the backing provider.
57          * Set to WOF_CURRENT_VERSION.  */
58         uint32_t version;
59
60         /* Identifier for the backing provider.  Example value:
61          * WOF_PROVIDER_WIM.  */
62         uint32_t provider;
63 };
64
65 struct wim_provider_external_info {
66
67         /* Set to WIM_PROVIDER_CURRENT_VERSION.  */
68         uint32_t version;
69
70         /* 0 when WIM provider active, otherwise
71          * WIM_PROVIDER_EXTERNAL_FLAG_NOT_ACTIVE or
72          * WIM_PROVIDER_EXTERNAL_FLAG_SUSPENDED.  */
73         uint32_t flags;
74
75         /* Integer ID that identifies the WIM.  Get this with the
76          * FSCTL_ADD_OVERLAY ioctl.  */
77         uint64_t data_source_id;
78
79         /* SHA1 message digest of the file's unnamed data stream.  */
80         uint8_t resource_hash[RESOURCE_HASH_SIZE];
81 };
82
83 /*
84  * --- FSCTL_GET_EXTERNAL_BACKING ---
85  *
86  * Get external backing information for the specified file.
87  *
88  * DeviceType: 9 (FILE_DEVICE_FILE_SYSTEM)
89  * Access:     0 (FILE_ANY_ACCESS)
90  * Function:   196
91  * Method:     0 (METHOD_BUFFERED)
92  *
93  * Input buffer: None
94  * Output buffer:  'struct wof_external_info' followed by provider-specific data
95  * ('struct wim_provider_external_info' in the case of WIM).
96  */
97 #define FSCTL_GET_EXTERNAL_BACKING 0x90310
98
99 #ifndef STATUS_OBJECT_NOT_EXTERNALLY_BACKED
100 #  define STATUS_OBJECT_NOT_EXTERNALLY_BACKED 0xC000046D
101 #endif
102
103 /*****************************************************************************/
104
105 /* Global counters, updated during the directory tree scan  */
106 static struct {
107         uint64_t errors;
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;
115 } counters;
116
117 #define BYTES_PER_PROGRESS 100000000
118
119 /*****************************************************************************/
120
121 /* Handle to ntdll.dll  */
122 static HMODULE hNtdll;
123
124 /* Functions loaded from ntdll.dll  */
125
126 static DWORD (WINAPI *func_RtlNtStatusToDosError)(NTSTATUS status);
127
128 static NTSTATUS (WINAPI *func_NtOpenFile) (PHANDLE FileHandle,
129                                            ACCESS_MASK DesiredAccess,
130                                            POBJECT_ATTRIBUTES ObjectAttributes,
131                                            PIO_STATUS_BLOCK IoStatusBlock,
132                                            ULONG ShareAccess,
133                                            ULONG OpenOptions);
134
135 static NTSTATUS (WINAPI *func_NtQueryInformationFile)(HANDLE FileHandle,
136                                                       PIO_STATUS_BLOCK IoStatusBlock,
137                                                       PVOID FileInformation,
138                                                       ULONG Length,
139                                                       FILE_INFORMATION_CLASS FileInformationClass);
140
141 static NTSTATUS (WINAPI *func_NtQueryObject) (HANDLE Handle,
142                                               OBJECT_INFORMATION_CLASS ObjectInformationClass,
143                                               PVOID ObjectInformation,
144                                               ULONG ObjectInformationLength,
145                                               PULONG ReturnLength);
146
147 static NTSTATUS (WINAPI *func_NtFsControlFile) (HANDLE FileHandle,
148                                                 HANDLE Event,
149                                                 PIO_APC_ROUTINE ApcRoutine,
150                                                 PVOID ApcContext,
151                                                 PIO_STATUS_BLOCK IoStatusBlock,
152                                                 ULONG FsControlCode,
153                                                 PVOID InputBuffer,
154                                                 ULONG InputBufferLength,
155                                                 PVOID OutputBuffer,
156                                                 ULONG OutputBufferLength);
157
158 static NTSTATUS (WINAPI *func_NtQueryDirectoryFile) (HANDLE FileHandle,
159                                                      HANDLE Event,
160                                                      PIO_APC_ROUTINE ApcRoutine,
161                                                      PVOID ApcContext,
162                                                      PIO_STATUS_BLOCK IoStatusBlock,
163                                                      PVOID FileInformation,
164                                                      ULONG Length,
165                                                      FILE_INFORMATION_CLASS FileInformationClass,
166                                                      BOOLEAN ReturnSingleEntry,
167                                                      PUNICODE_STRING FileName,
168                                                      BOOLEAN RestartScan);
169
170 static NTSTATUS (WINAPI *func_NtClose) (HANDLE Handle);
171
172 /*****************************************************************************/
173
174 /* Retrieves a human-readable error string for the specified Win32 error code.  */
175 static wchar_t *
176 win32_error_string(DWORD err_code)
177 {
178         static wchar_t buf[1024];
179         size_t len;
180
181         buf[0] = L'\0';
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')
185                 buf[--len] = L'\0';
186         if (len > 0 && buf[len - 1] == L'\r')
187                 buf[--len] = L'\0';
188         if (len > 0 && buf[len - 1] == L'.')
189                 buf[--len] = L'\0';
190         return buf;
191 }
192
193 /* Retrieves a human-readable error string for the specified NTSTATUS error
194  * code.  */
195 static wchar_t *
196 nt_error_string(NTSTATUS status)
197 {
198         static wchar_t buf[1024];
199         wsprintf(buf, L"status 0x%08x: %ls",
200                  (uint32_t)status, win32_error_string(func_RtlNtStatusToDosError(status)));
201         return buf;
202 }
203
204 /* Translate the specified unsigned number into string form, with commas.  */
205 static const char *
206 u64_to_pretty_string(uint64_t num)
207 {
208         static char bufs[4][30];
209         static int which_buf = 0;
210
211         which_buf = (which_buf + 1) % ARRAY_LEN(bufs);
212
213         char *p = &bufs[which_buf][ARRAY_LEN(bufs[0]) - 1];
214         unsigned int comma_count = 3;
215
216         if (num == 0) {
217                 *--p = '0';
218                 return p;
219         }
220
221         do {
222                 if (comma_count == 0) {
223                         *--p = ',';
224                         comma_count = 3;
225                 }
226                 --comma_count;
227                 *--p = '0' + (num % 10);
228         } while ((num /= 10) != 0);
229
230         return p;
231 }
232
233 /* Prints the specified error message and exits the program with failure status.
234  */
235 static void __attribute__((format(printf, 1, 2)))
236 fatal(const char *fmt, ...)
237 {
238         va_list va;
239
240         va_start(va, fmt);
241         fputs("ERROR: ", stderr);
242         vfprintf(stderr, fmt, va);
243         fputc('\n', stderr);
244         va_end(va);
245
246         exit(1);
247 }
248
249 /* Prints the specified warning message.  */
250 static void __attribute__((format(printf, 1, 2)))
251 warn(const char *fmt, ...)
252 {
253         va_list va;
254
255         va_start(va, fmt);
256         fputs("WARNING: ", stderr);
257         vfprintf(stderr, fmt, va);
258         fputc('\n', stderr);
259         va_end(va);
260 }
261
262 /* Like malloc(), but abort the program on failure.  */
263 static void *
264 xmalloc(size_t size)
265 {
266         void *p;
267
268         p = malloc(size);
269         if (!p)
270                 fatal("Out of memory");
271         return p;
272 }
273
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.
278  */
279 static wchar_t *
280 replace_nt_device(wchar_t *path)
281 {
282         wchar_t drive[3];
283         wchar_t nt_device[1000];
284         wchar_t *tmp;
285         size_t nt_device_nchars;
286         DWORD ret;
287
288         tmp = wcschr(path + 9, L'\\');
289         if (tmp)
290                 nt_device_nchars = tmp - path;
291         else
292                 nt_device_nchars = wcslen(path);
293
294         if (!wcsncmp(path, L"\\Device\\Mup\\", 12)) {
295                 /* Network path, like \Device\Mup\192.168.0.1\somedir\somefile  */
296                 path[10] = L'\\';
297                 return path + 10;
298         }
299
300         drive[1] = L':';
301         drive[2] = L'\0';
302
303         /* Go through each possible drive letter and see if the reverse mapping
304          * matches... */
305         for (drive[0] = 'A'; drive[0] <= 'Z'; drive[0]++) {
306                 ret = QueryDosDevice(drive, nt_device, ARRAY_LEN(nt_device));
307                 if (!ret)
308                         continue;
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];
313                 }
314         }
315         /* Nothing matched.  Just keep the NT namespace path.  */
316         return path;
317 }
318
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
321  * the full path.  */
322 static const wchar_t *
323 printable_path(HANDLE h, const wchar_t *path)
324 {
325         static uint8_t bufs[2][sizeof(OBJECT_NAME_INFORMATION) + 32768 * sizeof(wchar_t)]
326                            __attribute__((aligned(8)));
327         static int buf_index = 0;
328
329         uint8_t *buf;
330         NTSTATUS status;
331         ULONG return_length;
332         size_t i;
333         wchar_t *res;
334
335         buf = bufs[buf_index];
336         buf_index = (buf_index + 1) % ARRAY_LEN(bufs);
337
338         if (h == NULL) {
339                 res = wcscpy((wchar_t *)buf, path);
340                 goto out_nt_to_dos;
341         }
342
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);
347                 goto out_nt_to_dos;
348         }
349
350         {
351                 OBJECT_NAME_INFORMATION *info = (OBJECT_NAME_INFORMATION *)buf;
352
353                 /* Strip trailing slash and append file name  */
354                 if (path) {
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);
359                 }
360
361                 res = info->Name.Buffer;
362         }
363
364 out_nt_to_dos:
365         if (!wcsncmp(res, L"\\Device\\", 8))
366                 res = replace_nt_device(res);
367         else if (!wcsncmp(res, L"\\??\\", 4))
368                 res += 4;
369         return res;
370 }
371
372 static const wchar_t *
373 prettify_path(const wchar_t *path)
374 {
375         return printable_path(NULL, path);
376 }
377
378 static const wchar_t *
379 handle_to_path(HANDLE h)
380 {
381         return printable_path(h, NULL);
382 }
383
384 /* Load ntdll.dll and some native functions from it.  */
385 static void
386 init_ntdll(void)
387 {
388         hNtdll = LoadLibrary(L"ntdll.dll");
389
390         if (!hNtdll)
391                 fatal("Can't load ntdll.dll");
392
393 #define NTDLL_SYM(name) { (void **)&func_##name, #name }
394         static const struct ntdll_sym {
395                 void **func_ptr;
396                 const char *name;
397         } ntdll_syms[] = {
398                 NTDLL_SYM(RtlNtStatusToDosError),
399                 NTDLL_SYM(NtOpenFile),
400                 NTDLL_SYM(NtQueryInformationFile),
401                 NTDLL_SYM(NtQueryObject),
402                 NTDLL_SYM(NtFsControlFile),
403                 NTDLL_SYM(NtQueryDirectoryFile),
404                 NTDLL_SYM(NtClose),
405                 {NULL, NULL},
406         };
407 #undef NTDLL_SYM
408
409         for (const struct ntdll_sym *sym = ntdll_syms; sym->name; sym++) {
410                 void *addr = (void*)GetProcAddress(hNtdll, sym->name);
411                 if (!addr)
412                         fatal("Can't find %s in ntdll.dll", sym->name);
413                 *(sym->func_ptr) = addr;
414         }
415 }
416
417 static bool
418 checksum_file(HANDLE h, uint64_t expected_size, uint8_t hash[RESOURCE_HASH_SIZE])
419 {
420         uint8_t buf[32768];
421         DWORD bytesRead;
422         SHA_CTX ctx;
423         uint64_t actual_size = 0;
424
425         sha1_init(&ctx);
426         for (;;) {
427                 if (!ReadFile(h, buf, sizeof(buf), &bytesRead, NULL)) {
428                         warn("Error reading \"%ls\": %ls",
429                              handle_to_path(h), win32_error_string(GetLastError()));
430                         counters.errors++;
431                         return false;
432                 }
433                 if (bytesRead == 0)
434                         break;
435
436                 sha1_update(&ctx, buf, bytesRead);
437                 actual_size += bytesRead;
438         }
439
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),
446                      handle_to_path(h));
447         }
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;
452         }
453
454         return true;
455 }
456
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.  */
459 static void
460 detect_wof(HANDLE h)
461 {
462         NTSTATUS status;
463         IO_STATUS_BLOCK iosb;
464
465         status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb,
466                                          FSCTL_GET_EXTERNAL_BACKING,
467                                          NULL, 0, NULL, 0);
468
469         if (status == STATUS_OBJECT_NOT_EXTERNALLY_BACKED ||
470             status == STATUS_BUFFER_TOO_SMALL)
471         {
472                 return;
473         }
474
475         fatal("\"%ls\": The Windows Overlay File System Filter is not running.",
476               handle_to_path(h));
477 }
478
479 enum backing {
480         /* It is unknown whether the file is externally backed or not.  */
481         BACKING_UNKNOWN = 0x0,
482
483         /* The file is not externally backed.  */
484         BACKING_INTERNAL = 0x1,
485
486         /* The file is externally backed.  */
487         BACKING_EXTERNAL = 0x2,
488
489         /* The file is externally backed, specifically in a WIM file.  */
490         BACKING_EXTERNAL_WIM = 0x4 | BACKING_EXTERNAL,
491 };
492
493 /* Determines the externaly backing status of a file.
494  *
495  * @h
496  *      Open handle to the file.
497  * @wim_id_ret
498  *      If return value is BACKING_EXTERNAL_WIM, this will be set to the data
499  *      source ID of the backing WIM.
500  * @resource_hash_ret
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).
504  *
505  * Returns one of the 'enum backing' values.
506  */
507 static enum backing
508 get_external_backing(HANDLE h, uint64_t *wim_id_ret,
509                      uint8_t resource_hash_ret[RESOURCE_HASH_SIZE])
510 {
511         struct {
512                 struct wof_external_info wof_info;
513                 struct wim_provider_external_info wim_info;
514         } out;
515         NTSTATUS status;
516         IO_STATUS_BLOCK iosb;
517
518         status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb,
519                                          FSCTL_GET_EXTERNAL_BACKING,
520                                          NULL, 0, &out, sizeof(out));
521
522         if (status == STATUS_OBJECT_NOT_EXTERNALLY_BACKED)
523                 return BACKING_INTERNAL;
524
525         if (status == STATUS_BUFFER_TOO_SMALL ||
526             status == STATUS_BUFFER_OVERFLOW)
527                 return BACKING_EXTERNAL;
528
529         if (!NT_SUCCESS(status)) {
530                 warn("\"%ls\": FSCTL_GET_EXTERNAL_BACKING failed (%ls)",
531                      handle_to_path(h), nt_error_string(status));
532                 counters.errors++;
533                 return BACKING_UNKNOWN;
534         }
535
536         if (iosb.Information < sizeof(struct wof_external_info)) {
537                 warn("\"%ls\": weird results from FSCTL_GET_EXTERNAL_BACKING",
538                      handle_to_path(h));
539                 counters.errors++;
540                 return BACKING_UNKNOWN;
541         }
542
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;
547         }
548
549         return BACKING_EXTERNAL;
550 }
551
552 static void
553 verify(HANDLE cur_dir, const wchar_t *path, size_t path_nchars);
554
555 static void
556 recurse_directory(HANDLE h)
557 {
558         uint8_t *buf;
559         const size_t bufsize = 32768;
560         NTSTATUS status;
561         IO_STATUS_BLOCK iosb;
562
563         buf = xmalloc(bufsize + sizeof(wchar_t));
564
565         while (NT_SUCCESS(status = (*func_NtQueryDirectoryFile)(h, NULL, NULL, NULL,
566                                                                 &iosb, buf, bufsize,
567                                                                 FileNamesInformation,
568                                                                 FALSE, NULL, FALSE)))
569         {
570                 FILE_NAMES_INFORMATION *info;
571
572                 info = (FILE_NAMES_INFORMATION *)buf;
573                 for (;;) {
574                         if (!(info->FileNameLength == 2 && info->FileName[0] == L'.') &&
575                             !(info->FileNameLength == 4 && info->FileName[0] == L'.' &&
576                                                            info->FileName[1] == L'.'))
577                         {
578                                 wchar_t save = info->FileName[info->FileNameLength /
579                                                                 sizeof(wchar_t)];
580
581                                 info->FileName[info->FileNameLength /
582                                                         sizeof(wchar_t)] = L'\0';
583
584                                 verify(h, info->FileName,
585                                        info->FileNameLength / sizeof(wchar_t));
586
587                                 info->FileName[info->FileNameLength /
588                                                         sizeof(wchar_t)] = save;
589                         }
590                         if (info->NextEntryOffset == 0)
591                                 break;
592                         info = (FILE_NAMES_INFORMATION *)
593                                         ((uint8_t *)info + info->NextEntryOffset);
594                 }
595         }
596
597         free(buf);
598         if (status != STATUS_NO_MORE_FILES) {
599                 warn("\"%ls\": Can't read directory (%ls)",
600                      handle_to_path(h), nt_error_string(status));
601                 counters.errors++;
602         }
603 }
604
605 static NTSTATUS
606 open_file(HANDLE cur_dir, const wchar_t *path, size_t path_nchars, HANDLE *h_ret)
607 {
608         UNICODE_STRING name;
609         OBJECT_ATTRIBUTES attr;
610         IO_STATUS_BLOCK iosb;
611
612         name.Length = path_nchars * sizeof(wchar_t);
613         name.MaximumLength = name.Length + sizeof(wchar_t);
614         name.Buffer = (wchar_t *)path;
615
616         attr.Length = sizeof(attr);
617         attr.RootDirectory = cur_dir;
618         attr.ObjectName = &name;
619         attr.Attributes = 0;
620         attr.SecurityDescriptor = NULL;
621         attr.SecurityQualityOfService = NULL;
622
623         return (*func_NtOpenFile)(h_ret,
624                                   FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
625                                   &attr,
626                                   &iosb,
627                                   FILE_SHARE_VALID_FLAGS,
628                                   FILE_OPEN_REPARSE_POINT |
629                                           FILE_OPEN_FOR_BACKUP_INTENT |
630                                           FILE_SYNCHRONOUS_IO_NONALERT);
631 }
632
633 /* Query "all" metadata about the specified file.  */
634 static NTSTATUS
635 query_all_file_info(HANDLE h, FILE_ALL_INFORMATION *file_info)
636 {
637         IO_STATUS_BLOCK iosb;
638         return (*func_NtQueryInformationFile)(h,
639                                               &iosb,
640                                               file_info,
641                                               sizeof(FILE_ALL_INFORMATION),
642                                               FileAllInformation);
643 }
644
645 /* Recursive scan.
646  *
647  * @cur_dir
648  *      Parent directory, or NULL if first iteration.
649  * @path
650  *      Basename of current file, or the full name if first iteration.
651  * @path_nchars
652  *      Number of characters valid in @path (will be null-terminated as well).
653  * @d_meta
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.
657  */
658 static void
659 verify(HANDLE cur_dir, const wchar_t *path, size_t path_nchars)
660 {
661         HANDLE h;
662         NTSTATUS status;
663         FILE_ALL_INFORMATION file_info;
664
665         status = open_file(cur_dir, path, path_nchars, &h);
666
667         if (!NT_SUCCESS(status)) {
668                 if (status == STATUS_SHARING_VIOLATION) {
669                         counters.sharing_violations++;
670                 } else {
671                         if (cur_dir == NULL) {
672                                 fatal("\"%ls\": Can't open file (%ls)",
673                                       printable_path(cur_dir, path),
674                                       nt_error_string(status));
675                         }
676                         warn("\"%ls\": Can't open file (%ls)",
677                              printable_path(cur_dir, path),
678                              nt_error_string(status));
679                 }
680                 counters.errors++;
681                 return;
682         }
683
684         if (cur_dir == NULL)
685                 detect_wof(h);
686
687         status = query_all_file_info(h, &file_info);
688
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));
692                 counters.errors++;
693                 goto out_close;
694         }
695
696         if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
697                 counters.directories++;
698                 recurse_directory(h);
699         } else {
700                 enum backing backing;
701                 uint64_t wim_id;
702                 uint8_t resource_hash[RESOURCE_HASH_SIZE];
703                 uint8_t actual_hash[RESOURCE_HASH_SIZE];
704
705                 counters.nondirectories++;
706
707                 backing = get_external_backing(h, &wim_id, resource_hash);
708
709                 if (backing & BACKING_EXTERNAL) {
710                         counters.externally_backed_files++;
711                         if (backing == BACKING_EXTERNAL_WIM) {
712                                 if (checksum_file(h,
713                                                   file_info.StandardInformation.EndOfFile.QuadPart,
714                                                   actual_hash))
715                                 {
716                                         if (memcmp(resource_hash, actual_hash, RESOURCE_HASH_SIZE)) {
717                                                 warn("CHECKSUM MISMATCH: path=\"%ls\"", handle_to_path(h));
718                                                 counters.checksum_mismatches++;
719                                         }
720                                 }
721                         } else {
722                                 warn("Ignoring \"%ls\": externally backed, but not by WIM archive",
723                                      handle_to_path(h));
724                         }
725                 }
726         }
727
728 out_close:
729         (*func_NtClose)(h);
730 }
731
732 static void
733 enable_privilege(const wchar_t *privilege)
734 {
735         HANDLE hToken;
736         LUID luid;
737         TOKEN_PRIVILEGES newState;
738
739         if (OpenProcessToken(GetCurrentProcess(),
740                              TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
741         {
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);
747                 }
748                 CloseHandle(hToken);
749         }
750 }
751
752 static void
753 pline(void)
754 {
755         puts("--------------------------------------------------------------------------------");
756 }
757
758 int
759 wmain(int argc, wchar_t **argv)
760 {
761         wchar_t *fullpath;
762         size_t fullpath_nchars;
763         const wchar_t *prefix = L"\\??\\";
764         const size_t prefix_nchars = wcslen(prefix);
765         DWORD ret;
766
767         enable_privilege(SE_BACKUP_NAME);
768
769         init_ntdll();
770
771         if (argc != 2) {
772                 fprintf(stderr, "Usage: %ls DIR\n", argv[0]);
773                 return 2;
774         }
775
776         /* Prepare the initial path.  */
777         fullpath = xmalloc(32768 * sizeof(wchar_t));
778
779         wcscpy(fullpath, prefix);
780         ret = GetFullPathName(argv[1], 32768 - prefix_nchars,
781                               fullpath + prefix_nchars, NULL);
782         if (ret == 0) {
783                 fatal("\"%ls\": Can't get full path (%ls)",
784                       argv[1], win32_error_string(GetLastError()));
785         }
786         fullpath_nchars = prefix_nchars + ret;
787
788         counters.next_bytes_checksummed_progress = BYTES_PER_PROGRESS;
789
790         /* Scan the directory tree.  */
791         pline();
792         printf("Verifying \"%ls\"\n", prettify_path(fullpath));
793         verify(NULL, fullpath, fullpath_nchars);
794         pline();
795
796         /* Print statistics.  */
797
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));
808         printf("\n");
809
810         /* Cleanup and exit.  */
811         free(fullpath);
812
813         return 0;
814 }