2 * win32_capture.c - Windows-specific code for capturing files into a WIM image.
4 * This now uses the native Windows NT API a lot and not just Win32.
8 * Copyright (C) 2013, 2014, 2015 Eric Biggers
10 * This file is free software; you can redistribute it and/or modify it under
11 * the terms of the GNU Lesser General Public License as published by the Free
12 * Software Foundation; either version 3 of the License, or (at your option) any
15 * This file is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this file; if not, see http://www.gnu.org/licenses/.
30 #include "wimlib/win32_common.h"
32 #include "wimlib/assert.h"
33 #include "wimlib/blob_table.h"
34 #include "wimlib/capture.h"
35 #include "wimlib/dentry.h"
36 #include "wimlib/encoding.h"
37 #include "wimlib/endianness.h"
38 #include "wimlib/error.h"
39 #include "wimlib/paths.h"
40 #include "wimlib/reparse.h"
41 #include "wimlib/wof.h"
43 struct winnt_scan_ctx {
45 unsigned long num_get_sd_access_denied;
46 unsigned long num_get_sacl_priv_notheld;
48 /* True if WOF is definitely not attached to the volume being scanned;
49 * false if it may be */
50 bool wof_not_attached;
53 static inline const wchar_t *
54 printable_path(const wchar_t *full_path)
56 /* Skip over \\?\ or \??\ */
61 * If cur_dir is not NULL, open an existing file relative to the already-open
64 * Otherwise, open the file specified by @path, which must be a Windows NT
68 winnt_openat(HANDLE cur_dir, const wchar_t *path, size_t path_nchars,
69 ACCESS_MASK perms, HANDLE *h_ret)
72 OBJECT_ATTRIBUTES attr;
76 name.Length = path_nchars * sizeof(wchar_t);
77 name.MaximumLength = name.Length + sizeof(wchar_t);
78 name.Buffer = (wchar_t *)path;
80 attr.Length = sizeof(attr);
81 attr.RootDirectory = cur_dir;
82 attr.ObjectName = &name;
84 attr.SecurityDescriptor = NULL;
85 attr.SecurityQualityOfService = NULL;
88 status = (*func_NtOpenFile)(h_ret, perms, &attr, &iosb,
89 FILE_SHARE_VALID_FLAGS,
90 FILE_OPEN_REPARSE_POINT |
91 FILE_OPEN_FOR_BACKUP_INTENT |
92 FILE_SYNCHRONOUS_IO_NONALERT |
93 FILE_SEQUENTIAL_ONLY);
94 if (!NT_SUCCESS(status)) {
95 /* Try requesting fewer permissions */
96 if (status == STATUS_ACCESS_DENIED ||
97 status == STATUS_PRIVILEGE_NOT_HELD) {
98 if (perms & ACCESS_SYSTEM_SECURITY) {
99 perms &= ~ACCESS_SYSTEM_SECURITY;
102 if (perms & READ_CONTROL) {
103 perms &= ~READ_CONTROL;
111 /* Read the first @size bytes from the file, or named data stream of a file,
112 * described by @blob. */
114 read_winnt_stream_prefix(const struct blob_descriptor *blob, u64 size,
115 const struct read_blob_callbacks *cbs)
124 /* This is an NT namespace path. */
125 path = blob->file_on_disk;
127 status = winnt_openat(NULL, path, wcslen(path),
128 FILE_READ_DATA | SYNCHRONIZE, &h);
129 if (!NT_SUCCESS(status)) {
130 winnt_error(status, L"\"%ls\": Can't open for reading",
131 printable_path(path));
132 return WIMLIB_ERR_OPEN;
136 bytes_remaining = size;
137 while (bytes_remaining) {
138 IO_STATUS_BLOCK iosb;
142 count = min(sizeof(buf), bytes_remaining);
144 status = (*func_NtReadFile)(h, NULL, NULL, NULL,
145 &iosb, buf, count, NULL, NULL);
146 if (unlikely(!NT_SUCCESS(status))) {
147 if (status == STATUS_END_OF_FILE) {
148 ERROR("\"%ls\": File was concurrently truncated",
149 printable_path(path));
150 ret = WIMLIB_ERR_CONCURRENT_MODIFICATION_DETECTED;
152 winnt_error(status, L"\"%ls\": Error reading data",
153 printable_path(path));
154 ret = WIMLIB_ERR_READ;
159 bytes_read = iosb.Information;
161 bytes_remaining -= bytes_read;
162 ret = call_consume_chunk(buf, bytes_read, cbs);
170 struct win32_encrypted_read_ctx {
171 const struct read_blob_callbacks *cbs;
177 win32_encrypted_export_cb(unsigned char *data, void *_ctx, unsigned long len)
179 struct win32_encrypted_read_ctx *ctx = _ctx;
181 size_t bytes_to_consume = min(len, ctx->bytes_remaining);
183 if (bytes_to_consume == 0)
184 return ERROR_SUCCESS;
186 ret = call_consume_chunk(data, bytes_to_consume, ctx->cbs);
188 ctx->wimlib_err_code = ret;
189 /* It doesn't matter what error code is returned here, as long
190 * as it isn't ERROR_SUCCESS. */
191 return ERROR_READ_FAULT;
193 ctx->bytes_remaining -= bytes_to_consume;
194 return ERROR_SUCCESS;
198 read_win32_encrypted_file_prefix(const struct blob_descriptor *blob,
200 const struct read_blob_callbacks *cbs)
202 struct win32_encrypted_read_ctx export_ctx;
208 if (blob->file_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY)
209 flags |= CREATE_FOR_DIR;
211 export_ctx.cbs = cbs;
212 export_ctx.wimlib_err_code = 0;
213 export_ctx.bytes_remaining = size;
215 err = OpenEncryptedFileRaw(blob->file_on_disk, flags, &file_ctx);
216 if (err != ERROR_SUCCESS) {
218 L"Failed to open encrypted file \"%ls\" for raw read",
219 printable_path(blob->file_on_disk));
220 return WIMLIB_ERR_OPEN;
222 err = ReadEncryptedFileRaw(win32_encrypted_export_cb,
223 &export_ctx, file_ctx);
224 if (err != ERROR_SUCCESS) {
225 ret = export_ctx.wimlib_err_code;
228 L"Failed to read encrypted file \"%ls\"",
229 printable_path(blob->file_on_disk));
230 ret = WIMLIB_ERR_READ;
232 } else if (export_ctx.bytes_remaining != 0) {
233 ERROR("Only could read %"PRIu64" of %"PRIu64" bytes from "
234 "encrypted file \"%ls\"",
235 size - export_ctx.bytes_remaining, size,
236 printable_path(blob->file_on_disk));
237 ret = WIMLIB_ERR_READ;
241 CloseEncryptedFileRaw(file_ctx);
246 * Load the short name of a file into a WIM dentry.
248 static noinline_for_stack NTSTATUS
249 winnt_get_short_name(HANDLE h, struct wim_dentry *dentry)
251 /* It's not any harder to just make the NtQueryInformationFile() system
252 * call ourselves, and it saves a dumb call to FindFirstFile() which of
253 * course has to create its own handle. */
255 IO_STATUS_BLOCK iosb;
256 u8 buf[128] _aligned_attribute(8);
257 const FILE_NAME_INFORMATION *info;
259 status = (*func_NtQueryInformationFile)(h, &iosb, buf, sizeof(buf),
260 FileAlternateNameInformation);
261 info = (const FILE_NAME_INFORMATION *)buf;
262 if (NT_SUCCESS(status) && info->FileNameLength != 0) {
263 dentry->d_short_name = utf16le_dupz(info->FileName,
264 info->FileNameLength);
265 if (!dentry->d_short_name)
266 return STATUS_NO_MEMORY;
267 dentry->d_short_name_nbytes = info->FileNameLength;
273 * Load the security descriptor of a file into the corresponding inode and the
274 * WIM image's security descriptor set.
276 static noinline_for_stack NTSTATUS
277 winnt_get_security_descriptor(HANDLE h, struct wim_inode *inode,
278 struct wim_sd_set *sd_set,
279 struct winnt_scan_ctx *ctx, int add_flags)
281 SECURITY_INFORMATION requestedInformation;
282 u8 _buf[4096] _aligned_attribute(8);
289 * LABEL_SECURITY_INFORMATION is needed on Windows Vista and 7 because
290 * Microsoft decided to add mandatory integrity labels to the SACL but
291 * not have them returned by SACL_SECURITY_INFORMATION.
293 * BACKUP_SECURITY_INFORMATION is needed on Windows 8 because Microsoft
294 * decided to add even more stuff to the SACL and still not have it
295 * returned by SACL_SECURITY_INFORMATION; but they did remember that
296 * backup applications exist and simply want to read the stupid thing
297 * once and for all, so they added a flag to read the entire security
300 * Older versions of Windows tolerate these new flags being passed in.
302 requestedInformation = OWNER_SECURITY_INFORMATION |
303 GROUP_SECURITY_INFORMATION |
304 DACL_SECURITY_INFORMATION |
305 SACL_SECURITY_INFORMATION |
306 LABEL_SECURITY_INFORMATION |
307 BACKUP_SECURITY_INFORMATION;
310 bufsize = sizeof(_buf);
313 * We need the file's security descriptor in
314 * SECURITY_DESCRIPTOR_RELATIVE format, and we currently have a handle
315 * opened with as many relevant permissions as possible. At this point,
316 * on Windows there are a number of options for reading a file's
317 * security descriptor:
319 * GetFileSecurity(): This takes in a path and returns the
320 * SECURITY_DESCRIPTOR_RELATIVE. Problem: this uses an internal handle,
321 * not ours, and the handle created internally doesn't specify
322 * FILE_FLAG_BACKUP_SEMANTICS. Therefore there can be access denied
323 * errors on some files and directories, even when running as the
326 * GetSecurityInfo(): This takes in a handle and returns the security
327 * descriptor split into a bunch of different parts. This should work,
328 * but it's dumb because we have to put the security descriptor back
331 * BackupRead(): This can read the security descriptor, but this is a
332 * difficult-to-use API, probably only works as the Administrator, and
333 * the format of the returned data is not well documented.
335 * NtQuerySecurityObject(): This is exactly what we need, as it takes
336 * in a handle and returns the security descriptor in
337 * SECURITY_DESCRIPTOR_RELATIVE format. Only problem is that it's a
338 * ntdll function and therefore not officially part of the Win32 API.
341 while (!(NT_SUCCESS(status = (*func_NtQuerySecurityObject)(h,
342 requestedInformation,
343 (PSECURITY_DESCRIPTOR)buf,
348 case STATUS_BUFFER_TOO_SMALL:
349 wimlib_assert(buf == _buf);
350 buf = MALLOC(len_needed);
352 return STATUS_NO_MEMORY;
353 bufsize = len_needed;
355 case STATUS_PRIVILEGE_NOT_HELD:
356 case STATUS_ACCESS_DENIED:
357 if (add_flags & WIMLIB_ADD_FLAG_STRICT_ACLS) {
359 /* Permission denied in STRICT_ACLS mode, or
363 if (requestedInformation & SACL_SECURITY_INFORMATION) {
364 /* Try again without the SACL. */
365 ctx->num_get_sacl_priv_notheld++;
366 requestedInformation &= ~(SACL_SECURITY_INFORMATION |
367 LABEL_SECURITY_INFORMATION |
368 BACKUP_SECURITY_INFORMATION);
371 /* Fake success (useful when capturing as
372 * non-Administrator). */
373 ctx->num_get_sd_access_denied++;
374 status = STATUS_SUCCESS;
379 /* Add the security descriptor to the WIM image, and save its ID in
381 inode->i_security_id = sd_set_add_sd(sd_set, buf, len_needed);
382 if (unlikely(inode->i_security_id < 0))
383 status = STATUS_NO_MEMORY;
385 if (unlikely(buf != _buf))
391 winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
394 size_t full_path_nchars,
395 const wchar_t *filename,
396 size_t filename_nchars,
397 struct capture_params *params,
398 struct winnt_scan_ctx *ctx);
401 winnt_recurse_directory(HANDLE h,
403 size_t full_path_nchars,
404 struct wim_dentry *parent,
405 struct capture_params *params,
406 struct winnt_scan_ctx *ctx)
409 const size_t bufsize = 8192;
410 IO_STATUS_BLOCK iosb;
414 buf = MALLOC(bufsize);
416 return WIMLIB_ERR_NOMEM;
418 /* Using NtQueryDirectoryFile() we can re-use the same open handle,
419 * which we opened with FILE_FLAG_BACKUP_SEMANTICS. */
421 while (NT_SUCCESS(status = (*func_NtQueryDirectoryFile)(h, NULL, NULL, NULL,
423 FileNamesInformation,
424 FALSE, NULL, FALSE)))
426 const FILE_NAMES_INFORMATION *info = buf;
428 if (!should_ignore_filename(info->FileName,
429 info->FileNameLength / 2))
433 struct wim_dentry *child;
435 p = full_path + full_path_nchars;
436 /* Only add a backslash if we don't already have
437 * one. This prevents a duplicate backslash
438 * from being added when the path to the capture
439 * dir had a trailing backslash. */
440 if (*(p - 1) != L'\\')
443 p = wmempcpy(filename, info->FileName,
444 info->FileNameLength / 2);
447 ret = winnt_build_dentry_tree_recursive(
453 info->FileNameLength / 2,
457 full_path[full_path_nchars] = L'\0';
461 attach_scanned_tree(parent, child, params->blob_table);
463 if (info->NextEntryOffset == 0)
465 info = (const FILE_NAMES_INFORMATION *)
466 ((const u8 *)info + info->NextEntryOffset);
470 if (unlikely(status != STATUS_NO_MORE_FILES)) {
471 winnt_error(status, L"\"%ls\": Can't read directory",
472 printable_path(full_path));
473 ret = WIMLIB_ERR_READ;
480 /* Reparse point fixup status code */
481 #define RP_FIXED (-1)
484 file_has_ino_and_dev(HANDLE h, u64 ino, u64 dev)
487 IO_STATUS_BLOCK iosb;
488 FILE_INTERNAL_INFORMATION int_info;
489 FILE_FS_VOLUME_INFORMATION vol_info;
491 status = (*func_NtQueryInformationFile)(h, &iosb,
492 &int_info, sizeof(int_info),
493 FileInternalInformation);
494 if (!NT_SUCCESS(status))
497 if (int_info.IndexNumber.QuadPart != ino)
500 status = (*func_NtQueryVolumeInformationFile)(h, &iosb,
501 &vol_info, sizeof(vol_info),
502 FileFsVolumeInformation);
503 if (!(NT_SUCCESS(status) || status == STATUS_BUFFER_OVERFLOW))
506 if (iosb.Information <
507 offsetof(FILE_FS_VOLUME_INFORMATION, VolumeSerialNumber) +
508 sizeof(vol_info.VolumeSerialNumber))
511 return (vol_info.VolumeSerialNumber == dev);
515 * This is the Windows equivalent of unix_relativize_link_target(); see there
516 * for general details. This version works with an "absolute" Windows link
517 * target, specified from the root of the Windows kernel object namespace. Note
518 * that we have to open directories with a trailing slash when present because
519 * \??\E: opens the E: device itself and not the filesystem root directory.
521 static const wchar_t *
522 winnt_relativize_link_target(const wchar_t *target, size_t target_nbytes,
526 OBJECT_ATTRIBUTES attr;
527 IO_STATUS_BLOCK iosb;
529 const wchar_t *target_end;
532 target_end = target + (target_nbytes / sizeof(wchar_t));
535 if (target_end == target)
538 /* No leading slash??? */
539 if (target[0] != L'\\')
543 if ((target_end - target) >= 2 &&
544 target[0] == L'\\' && target[1] == L'\\')
547 attr.Length = sizeof(attr);
548 attr.RootDirectory = NULL;
549 attr.ObjectName = &name;
551 attr.SecurityDescriptor = NULL;
552 attr.SecurityQualityOfService = NULL;
554 name.Buffer = (wchar_t *)target;
559 const wchar_t *orig_p = p;
561 /* Skip non-backslashes */
562 while (p != target_end && *p != L'\\')
565 /* Skip backslashes */
566 while (p != target_end && *p == L'\\')
569 /* Append path component */
570 name.Length += (p - orig_p) * sizeof(wchar_t);
571 name.MaximumLength = name.Length;
573 /* Try opening the file */
574 status = (*func_NtOpenFile) (&h,
575 FILE_READ_ATTRIBUTES | FILE_TRAVERSE,
578 FILE_SHARE_VALID_FLAGS,
579 FILE_OPEN_FOR_BACKUP_INTENT);
581 if (NT_SUCCESS(status)) {
582 /* Reset root directory */
583 if (attr.RootDirectory)
584 (*func_NtClose)(attr.RootDirectory);
585 attr.RootDirectory = h;
586 name.Buffer = (wchar_t *)p;
589 if (file_has_ino_and_dev(h, ino, dev))
590 goto out_close_root_dir;
592 } while (p != target_end);
597 if (attr.RootDirectory)
598 (*func_NtClose)(attr.RootDirectory);
599 while (p > target && *(p - 1) == L'\\')
605 winnt_rpfix_progress(struct capture_params *params, const wchar_t *path,
606 const struct link_reparse_point *link, int scan_status)
608 size_t print_name_nchars = link->print_name_nbytes / sizeof(wchar_t);
609 wchar_t print_name0[print_name_nchars + 1];
611 wmemcpy(print_name0, link->print_name, print_name_nchars);
612 print_name0[print_name_nchars] = L'\0';
614 params->progress.scan.cur_path = printable_path(path);
615 params->progress.scan.symlink_target = print_name0;
616 return do_capture_progress(params, scan_status, NULL);
620 winnt_try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p,
621 const wchar_t *path, struct capture_params *params)
623 struct link_reparse_point link;
624 const wchar_t *rel_target;
627 if (parse_link_reparse_point(rpbuf, *rpbuflen_p, &link)) {
628 /* Couldn't understand the reparse data; don't do the fixup. */
633 * Don't do reparse point fixups on relative symbolic links.
635 * On Windows, a relative symbolic link is supposed to be identifiable
636 * by having reparse tag WIM_IO_REPARSE_TAG_SYMLINK and flags
637 * SYMBOLIC_LINK_RELATIVE. We will use this information, although this
638 * may not always do what the user expects, since drive-relative
639 * symbolic links such as "\Users\Public" have SYMBOLIC_LINK_RELATIVE
640 * set, in addition to truly relative symbolic links such as "Users" or
641 * "Users\Public". However, WIMGAPI (as of Windows 8.1) has this same
644 * Otherwise, as far as I can tell, the targets of symbolic links that
645 * are NOT relative, as well as junctions (note: a mountpoint is the
646 * sames thing as a junction), must be NT namespace paths, for example:
648 * - \??\e:\Users\Public
649 * - \DosDevices\e:\Users\Public
650 * - \Device\HardDiskVolume4\Users\Public
651 * - \??\Volume{c47cb07c-946e-4155-b8f7-052e9cec7628}\Users\Public
652 * - \DosDevices\Volume{c47cb07c-946e-4155-b8f7-052e9cec7628}\Users\Public
654 if (link_is_relative_symlink(&link))
657 rel_target = winnt_relativize_link_target(link.substitute_name,
658 link.substitute_name_nbytes,
659 params->capture_root_ino,
660 params->capture_root_dev);
662 if (rel_target == link.substitute_name) {
663 /* Target points outside of the tree being captured or had an
664 * unrecognized path format. Don't adjust it. */
665 return winnt_rpfix_progress(params, path, &link,
666 WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK);
669 /* We have an absolute target pointing within the directory being
670 * captured. @rel_target is the suffix of the link target that is the
671 * part relative to the directory being captured.
673 * We will cut off the prefix before this part (which is the path to the
674 * directory being captured) and add a dummy prefix. Since the process
675 * will need to be reversed when applying the image, it doesn't matter
676 * what exactly the prefix is, as long as it looks like an absolute
679 static const wchar_t prefix[6] = L"\\??\\X:";
680 static const size_t num_unprintable_chars = 4;
682 size_t rel_target_nbytes =
683 link.substitute_name_nbytes - ((const u8 *)rel_target -
684 (const u8 *)link.substitute_name);
686 wchar_t tmp[(sizeof(prefix) + rel_target_nbytes) / sizeof(wchar_t)];
688 memcpy(tmp, prefix, sizeof(prefix));
689 memcpy(tmp + ARRAY_LEN(prefix), rel_target, rel_target_nbytes);
691 link.substitute_name = tmp;
692 link.substitute_name_nbytes = sizeof(tmp);
694 link.print_name = link.substitute_name + num_unprintable_chars;
695 link.print_name_nbytes = link.substitute_name_nbytes -
696 (num_unprintable_chars * sizeof(wchar_t));
698 if (make_link_reparse_point(&link, rpbuf, rpbuflen_p))
701 ret = winnt_rpfix_progress(params, path, &link,
702 WIMLIB_SCAN_DENTRY_FIXED_SYMLINK);
708 /* Load the reparse data of a file into the corresponding WIM inode. If the
709 * reparse point is a symbolic link or junction with an absolute target and
710 * RPFIX mode is enabled, then also rewrite its target to be relative to the
712 static noinline_for_stack int
713 winnt_load_reparse_data(HANDLE h, struct wim_inode *inode,
714 const wchar_t *full_path, struct capture_params *params)
716 struct reparse_buffer_disk rpbuf;
717 DWORD bytes_returned;
721 if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
722 /* See comment above assign_stream_types_encrypted() */
723 WARNING("Ignoring reparse data of encrypted file \"%ls\"",
724 printable_path(full_path));
728 if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT,
729 NULL, 0, &rpbuf, REPARSE_POINT_MAX_SIZE,
730 &bytes_returned, NULL))
732 win32_error(GetLastError(), L"\"%ls\": Can't get reparse point",
733 printable_path(full_path));
734 return WIMLIB_ERR_READLINK;
737 rpbuflen = bytes_returned;
739 if (unlikely(rpbuflen < REPARSE_DATA_OFFSET)) {
740 ERROR("\"%ls\": reparse point buffer is too short",
741 printable_path(full_path));
742 return WIMLIB_ERR_INVALID_REPARSE_DATA;
745 if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX) {
746 ret = winnt_try_rpfix(&rpbuf, &rpbuflen, full_path, params);
748 inode->i_rp_flags &= ~WIM_RP_FLAG_NOT_FIXED;
753 inode->i_reparse_tag = le32_to_cpu(rpbuf.rptag);
754 inode->i_rp_reserved = le16_to_cpu(rpbuf.rpreserved);
756 if (!inode_add_stream_with_data(inode,
757 STREAM_TYPE_REPARSE_POINT,
760 rpbuflen - REPARSE_DATA_OFFSET,
762 return WIMLIB_ERR_NOMEM;
768 win32_tally_encrypted_size_cb(unsigned char *_data, void *_size_ret,
771 *(u64*)_size_ret += len;
772 return ERROR_SUCCESS;
776 win32_get_encrypted_file_size(const wchar_t *path, bool is_dir, u64 *size_ret)
784 flags |= CREATE_FOR_DIR;
786 err = OpenEncryptedFileRaw(path, flags, &file_ctx);
787 if (err != ERROR_SUCCESS) {
789 L"Failed to open encrypted file \"%ls\" for raw read",
790 printable_path(path));
791 return WIMLIB_ERR_OPEN;
794 err = ReadEncryptedFileRaw(win32_tally_encrypted_size_cb,
796 if (err != ERROR_SUCCESS) {
798 L"Failed to read raw encrypted data from \"%ls\"",
799 printable_path(path));
800 ret = WIMLIB_ERR_READ;
804 CloseEncryptedFileRaw(file_ctx);
809 winnt_scan_efsrpc_raw_data(struct wim_inode *inode, const wchar_t *nt_path,
810 struct list_head *unhashed_blobs)
812 struct blob_descriptor *blob;
813 struct wim_inode_stream *strm;
816 blob = new_blob_descriptor();
820 blob->file_on_disk = WCSDUP(nt_path);
821 if (!blob->file_on_disk)
823 blob->blob_location = BLOB_WIN32_ENCRYPTED;
825 /* OpenEncryptedFileRaw() expects a Win32 name. */
826 wimlib_assert(!wmemcmp(blob->file_on_disk, L"\\??\\", 4));
827 blob->file_on_disk[1] = L'\\';
829 blob->file_inode = inode;
831 ret = win32_get_encrypted_file_size(blob->file_on_disk,
832 (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY),
837 /* Empty EFSRPC data does not make sense */
838 wimlib_assert(blob->size != 0);
840 strm = inode_add_stream(inode, STREAM_TYPE_EFSRPC_RAW_DATA,
841 NO_STREAM_NAME, blob);
845 prepare_unhashed_blob(blob, inode, strm->stream_id, unhashed_blobs);
849 ret = WIMLIB_ERR_NOMEM;
851 free_blob_descriptor(blob);
856 get_data_stream_name(wchar_t *raw_stream_name, size_t raw_stream_name_nchars,
857 wchar_t **stream_name_ret, size_t *stream_name_nchars_ret)
859 const wchar_t *sep, *type, *end;
861 /* The stream name should be returned as :NAME:TYPE */
862 if (raw_stream_name_nchars < 1)
864 if (raw_stream_name[0] != L':')
868 raw_stream_name_nchars--;
870 end = raw_stream_name + raw_stream_name_nchars;
872 sep = wmemchr(raw_stream_name, L':', raw_stream_name_nchars);
880 if (wmemcmp(type, L"$DATA", 5))
883 *stream_name_ret = raw_stream_name;
884 *stream_name_nchars_ret = sep - raw_stream_name;
888 /* Build the path to the data stream. For unnamed streams, this is simply the
889 * path to the file. For named streams, this is the path to the file, followed
890 * by a colon, followed by the stream name. */
892 build_data_stream_path(const wchar_t *path, size_t path_nchars,
893 const wchar_t *stream_name, size_t stream_name_nchars)
895 size_t stream_path_nchars;
896 wchar_t *stream_path;
899 stream_path_nchars = path_nchars;
900 if (stream_name_nchars)
901 stream_path_nchars += 1 + stream_name_nchars;
903 stream_path = MALLOC((stream_path_nchars + 1) * sizeof(wchar_t));
905 p = wmempcpy(stream_path, path, path_nchars);
906 if (stream_name_nchars) {
908 p = wmempcpy(p, stream_name, stream_name_nchars);
916 winnt_scan_data_stream(const wchar_t *path, size_t path_nchars,
917 wchar_t *raw_stream_name, size_t raw_stream_name_nchars,
919 struct wim_inode *inode, struct list_head *unhashed_blobs)
921 wchar_t *stream_name;
922 size_t stream_name_nchars;
923 struct blob_descriptor *blob;
924 struct wim_inode_stream *strm;
926 /* Given the raw stream name (which is something like
927 * :streamname:$DATA), extract just the stream name part (streamname).
928 * Ignore any non-$DATA streams. */
929 if (!get_data_stream_name(raw_stream_name, raw_stream_name_nchars,
930 &stream_name, &stream_name_nchars))
933 stream_name[stream_name_nchars] = L'\0';
935 /* If the stream is non-empty, set up a blob descriptor for it. */
936 if (stream_size != 0) {
937 blob = new_blob_descriptor();
940 blob->file_on_disk = build_data_stream_path(path,
944 if (!blob->file_on_disk)
946 blob->blob_location = BLOB_IN_WINNT_FILE_ON_DISK;
947 blob->size = stream_size;
948 blob->file_inode = inode;
953 strm = inode_add_stream(inode, STREAM_TYPE_DATA, stream_name, blob);
957 prepare_unhashed_blob(blob, inode, strm->stream_id, unhashed_blobs);
961 free_blob_descriptor(blob);
962 return WIMLIB_ERR_NOMEM;
966 * Load information about the data streams of an open file into a WIM inode.
968 * We use the NtQueryInformationFile() system call instead of FindFirstStream()
969 * and FindNextStream(). This is done for two reasons:
971 * - FindFirstStream() opens its own handle to the file or directory and
972 * apparently does so without specifying FILE_FLAG_BACKUP_SEMANTICS, thereby
973 * causing access denied errors on certain files (even when running as the
975 * - FindFirstStream() and FindNextStream() is only available on Windows Vista
976 * and later, whereas the stream support in NtQueryInformationFile() was
977 * already present in Windows XP.
979 static noinline_for_stack int
980 winnt_scan_data_streams(HANDLE h, const wchar_t *path, size_t path_nchars,
981 struct wim_inode *inode, struct list_head *unhashed_blobs,
982 u64 file_size, u32 vol_flags)
985 u8 _buf[4096] _aligned_attribute(8);
988 IO_STATUS_BLOCK iosb;
990 FILE_STREAM_INFORMATION *info;
993 bufsize = sizeof(_buf);
995 if (!(vol_flags & FILE_NAMED_STREAMS))
998 /* Get a buffer containing the stream information. */
999 while (!NT_SUCCESS(status = (*func_NtQueryInformationFile)(h,
1003 FileStreamInformation)))
1007 case STATUS_BUFFER_OVERFLOW:
1013 newbuf = MALLOC(bufsize);
1015 newbuf = REALLOC(buf, bufsize);
1017 ret = WIMLIB_ERR_NOMEM;
1023 case STATUS_NOT_IMPLEMENTED:
1024 case STATUS_NOT_SUPPORTED:
1025 case STATUS_INVALID_INFO_CLASS:
1029 L"\"%ls\": Failed to query stream information",
1030 printable_path(path));
1031 ret = WIMLIB_ERR_READ;
1036 if (iosb.Information == 0) {
1037 /* No stream information. */
1042 /* Parse one or more stream information structures. */
1043 info = (FILE_STREAM_INFORMATION *)buf;
1045 /* Load the stream information. */
1046 ret = winnt_scan_data_stream(path, path_nchars,
1048 info->StreamNameLength / 2,
1049 info->StreamSize.QuadPart,
1050 inode, unhashed_blobs);
1054 if (info->NextEntryOffset == 0) {
1055 /* No more stream information. */
1058 /* Advance to next stream information. */
1059 info = (FILE_STREAM_INFORMATION *)
1060 ((u8 *)info + info->NextEntryOffset);
1066 /* The volume does not support named streams. Only capture the unnamed
1068 if (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
1069 FILE_ATTRIBUTE_REPARSE_POINT))
1076 wchar_t stream_name[] = L"::$DATA";
1077 ret = winnt_scan_data_stream(path, path_nchars, stream_name, 7,
1078 file_size, inode, unhashed_blobs);
1081 /* Free buffer if allocated on heap. */
1082 if (unlikely(buf != _buf))
1087 static noinline_for_stack u64
1088 get_sort_key(HANDLE h)
1090 STARTING_VCN_INPUT_BUFFER in = { .StartingVcn.QuadPart = 0 };
1091 RETRIEVAL_POINTERS_BUFFER out;
1092 DWORD bytesReturned;
1094 if (!DeviceIoControl(h, FSCTL_GET_RETRIEVAL_POINTERS,
1097 &bytesReturned, NULL))
1100 if (out.ExtentCount < 1)
1103 return out.Extents[0].Lcn.QuadPart;
1107 set_sort_key(struct wim_inode *inode, u64 sort_key)
1109 for (unsigned i = 0; i < inode->i_num_streams; i++) {
1110 struct wim_inode_stream *strm = &inode->i_streams[i];
1111 struct blob_descriptor *blob = stream_blob_resolved(strm);
1112 if (blob && (blob->blob_location == BLOB_IN_WINNT_FILE_ON_DISK ||
1113 blob->blob_location == BLOB_WIN32_ENCRYPTED))
1114 blob->sort_key = sort_key;
1119 should_try_to_use_wimboot_hash(const struct wim_inode *inode,
1120 const struct winnt_scan_ctx *ctx,
1121 const struct capture_params *params)
1123 /* Directories and encrypted files aren't valid for external backing. */
1124 if (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
1125 FILE_ATTRIBUTE_ENCRYPTED))
1128 /* If the file is a reparse point, then try the hash fixup if it's a WOF
1129 * reparse point and we're in WIMBOOT mode. Otherwise, try the hash
1130 * fixup if WOF may be attached. */
1131 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
1132 return (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_WOF) &&
1133 (params->add_flags & WIMLIB_ADD_FLAG_WIMBOOT);
1134 return !ctx->wof_not_attached;
1138 * This function implements an optimization for capturing files from a
1139 * filesystem with a backing WIM(s). If a file is WIM-backed, then we can
1140 * retrieve the SHA-1 message digest of its original contents from its reparse
1141 * point. This may eliminate the need to read the file's data and/or allow the
1142 * file's data to be immediately deduplicated with existing data in the WIM.
1144 * If WOF is attached, then this function is merely an optimization, but
1145 * potentially a very effective one. If WOF is detached, then this function
1146 * really causes WIM-backed files to be, effectively, automatically
1147 * "dereferenced" when possible; the unnamed data stream is updated to reference
1148 * the original contents and the reparse point is removed.
1150 * This function returns 0 if the fixup succeeded or was intentionally not
1151 * executed. Otherwise it returns an error code.
1153 static noinline_for_stack int
1154 try_to_use_wimboot_hash(HANDLE h, struct wim_inode *inode,
1155 struct blob_table *blob_table,
1156 struct winnt_scan_ctx *ctx, const wchar_t *full_path)
1158 struct wim_inode_stream *reparse_strm = NULL;
1159 struct wim_inode_stream *strm;
1160 struct blob_descriptor *blob;
1161 u8 hash[SHA1_HASH_SIZE];
1164 if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
1165 struct reparse_buffer_disk rpbuf;
1167 struct wof_external_info wof_info;
1168 struct wim_provider_rpdata wim_info;
1169 } *rpdata = (void *)rpbuf.rpdata;
1170 struct blob_descriptor *reparse_blob;
1172 /* The file has a WOF reparse point, so WOF must be detached.
1173 * We can read the reparse point directly. */
1174 ctx->wof_not_attached = true;
1175 reparse_strm = inode_get_unnamed_stream(inode, STREAM_TYPE_REPARSE_POINT);
1176 reparse_blob = stream_blob_resolved(reparse_strm);
1178 if (!reparse_blob || reparse_blob->size < sizeof(*rpdata))
1179 return 0; /* Not a WIM-backed file */
1181 ret = read_blob_into_buf(reparse_blob, rpdata);
1185 if (rpdata->wof_info.version != WOF_CURRENT_VERSION ||
1186 rpdata->wof_info.provider != WOF_PROVIDER_WIM ||
1187 rpdata->wim_info.version != 2)
1188 return 0; /* Not a WIM-backed file */
1190 /* Okay, this is a WIM backed file. Get its SHA-1 hash. */
1191 copy_hash(hash, rpdata->wim_info.unnamed_data_stream_hash);
1194 struct wof_external_info wof_info;
1195 struct wim_provider_external_info wim_info;
1197 IO_STATUS_BLOCK iosb;
1200 /* WOF may be attached. Try reading this file's external
1202 status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb,
1203 FSCTL_GET_EXTERNAL_BACKING,
1204 NULL, 0, &out, sizeof(out));
1206 /* Is WOF not attached? */
1207 if (status == STATUS_INVALID_DEVICE_REQUEST) {
1208 ctx->wof_not_attached = true;
1212 /* Is this file not externally backed? */
1213 if (status == STATUS_OBJECT_NOT_EXTERNALLY_BACKED)
1216 /* Does this file have an unknown type of external backing that
1217 * needed a larger information buffer? */
1218 if (status == STATUS_BUFFER_TOO_SMALL)
1221 /* Was there some other failure? */
1222 if (status != STATUS_SUCCESS) {
1224 L"\"%ls\": FSCTL_GET_EXTERNAL_BACKING failed",
1226 return WIMLIB_ERR_STAT;
1229 /* Is this file backed by a WIM? */
1230 if (out.wof_info.version != WOF_CURRENT_VERSION ||
1231 out.wof_info.provider != WOF_PROVIDER_WIM ||
1232 out.wim_info.version != WIM_PROVIDER_CURRENT_VERSION)
1235 /* Okay, this is a WIM backed file. Get its SHA-1 hash. */
1236 copy_hash(hash, out.wim_info.unnamed_data_stream_hash);
1239 /* If the file's unnamed data stream is nonempty, then fill in its hash
1240 * and deduplicate it if possible.
1242 * With WOF detached, we require that the blob *must* de-duplicable for
1243 * any action can be taken, since without WOF we can't fall back to
1244 * getting the "dereferenced" data by reading the stream (the real
1245 * stream is sparse and contains all zeroes). */
1246 strm = inode_get_unnamed_data_stream(inode);
1247 if (strm && (blob = stream_blob_resolved(strm))) {
1248 struct blob_descriptor **back_ptr;
1250 if (reparse_strm && !lookup_blob(blob_table, hash))
1252 back_ptr = retrieve_pointer_to_unhashed_blob(blob);
1253 copy_hash(blob->hash, hash);
1254 if (after_blob_hashed(blob, back_ptr, blob_table) != blob)
1255 free_blob_descriptor(blob);
1258 /* Remove the reparse point, if present. */
1260 inode_remove_stream(inode, reparse_strm, blob_table);
1261 inode->i_attributes &= ~(FILE_ATTRIBUTE_REPARSE_POINT |
1262 FILE_ATTRIBUTE_SPARSE_FILE);
1263 if (inode->i_attributes == 0)
1264 inode->i_attributes = FILE_ATTRIBUTE_NORMAL;
1270 static noinline_for_stack u32
1271 get_volume_information(HANDLE h, const wchar_t *full_path,
1272 struct capture_params *params)
1274 FILE_FS_ATTRIBUTE_INFORMATION attr_info;
1275 FILE_FS_VOLUME_INFORMATION vol_info;
1276 IO_STATUS_BLOCK iosb;
1280 /* Get volume flags */
1281 status = (*func_NtQueryVolumeInformationFile)(h, &iosb,
1284 FileFsAttributeInformation);
1285 if (likely((NT_SUCCESS(status) || status == STATUS_BUFFER_OVERFLOW) &&
1286 (iosb.Information >=
1287 offsetof(FILE_FS_ATTRIBUTE_INFORMATION,
1288 FileSystemAttributes) +
1289 sizeof(attr_info.FileSystemAttributes))))
1291 vol_flags = attr_info.FileSystemAttributes;
1293 winnt_warning(status, L"\"%ls\": Can't get volume attributes",
1294 printable_path(full_path));
1298 /* Get volume ID. */
1299 status = (*func_NtQueryVolumeInformationFile)(h, &iosb,
1302 FileFsVolumeInformation);
1303 if (likely((NT_SUCCESS(status) || status == STATUS_BUFFER_OVERFLOW) &&
1304 (iosb.Information >=
1305 offsetof(FILE_FS_VOLUME_INFORMATION,
1306 VolumeSerialNumber) +
1307 sizeof(vol_info.VolumeSerialNumber))))
1309 params->capture_root_dev = vol_info.VolumeSerialNumber;
1311 winnt_warning(status, L"\"%ls\": Can't get volume ID",
1312 printable_path(full_path));
1313 params->capture_root_dev = 0;
1322 u64 last_write_time;
1323 u64 last_access_time;
1328 static noinline_for_stack NTSTATUS
1329 get_file_info(HANDLE h, struct file_info *info)
1331 IO_STATUS_BLOCK iosb;
1333 FILE_ALL_INFORMATION all_info;
1335 status = (*func_NtQueryInformationFile)(h, &iosb, &all_info,
1337 FileAllInformation);
1339 if (unlikely(!NT_SUCCESS(status) && status != STATUS_BUFFER_OVERFLOW))
1342 info->attributes = all_info.BasicInformation.FileAttributes;
1343 info->num_links = all_info.StandardInformation.NumberOfLinks;
1344 info->creation_time = all_info.BasicInformation.CreationTime.QuadPart;
1345 info->last_write_time = all_info.BasicInformation.LastWriteTime.QuadPart;
1346 info->last_access_time = all_info.BasicInformation.LastAccessTime.QuadPart;
1347 info->ino = all_info.InternalInformation.IndexNumber.QuadPart;
1348 info->end_of_file = all_info.StandardInformation.EndOfFile.QuadPart;
1349 return STATUS_SUCCESS;
1353 winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
1356 size_t full_path_nchars,
1357 const wchar_t *filename,
1358 size_t filename_nchars,
1359 struct capture_params *params,
1360 struct winnt_scan_ctx *ctx)
1362 struct wim_dentry *root = NULL;
1363 struct wim_inode *inode = NULL;
1367 struct file_info file_info;
1368 ACCESS_MASK requestedPerms;
1371 ret = try_exclude(full_path, params);
1372 if (unlikely(ret < 0)) /* Excluded? */
1374 if (unlikely(ret > 0)) /* Error? */
1377 /* Open the file. */
1378 requestedPerms = FILE_READ_DATA |
1379 FILE_READ_ATTRIBUTES |
1381 ACCESS_SYSTEM_SECURITY |
1384 status = winnt_openat(cur_dir,
1385 (cur_dir ? filename : full_path),
1386 (cur_dir ? filename_nchars : full_path_nchars),
1389 if (unlikely(!NT_SUCCESS(status))) {
1390 if (status == STATUS_DELETE_PENDING) {
1391 WARNING("\"%ls\": Deletion pending; skipping file",
1392 printable_path(full_path));
1396 if (status == STATUS_ACCESS_DENIED &&
1397 (requestedPerms & FILE_READ_DATA)) {
1398 /* This happens on encrypted files. */
1399 requestedPerms &= ~FILE_READ_DATA;
1403 winnt_error(status, L"\"%ls\": Can't open file",
1404 printable_path(full_path));
1405 if (status == STATUS_FVE_LOCKED_VOLUME)
1406 ret = WIMLIB_ERR_FVE_LOCKED_VOLUME;
1408 ret = WIMLIB_ERR_OPEN;
1412 /* Get information about the file. */
1413 status = get_file_info(h, &file_info);
1414 if (!NT_SUCCESS(status)) {
1415 winnt_error(status, L"\"%ls\": Can't get file information",
1416 printable_path(full_path));
1417 ret = WIMLIB_ERR_STAT;
1421 if (unlikely(!(requestedPerms & FILE_READ_DATA)) &&
1422 !(file_info.attributes & FILE_ATTRIBUTE_ENCRYPTED))
1424 ERROR("\"%ls\": Permission to read data was denied",
1425 printable_path(full_path));
1426 ret = WIMLIB_ERR_OPEN;
1430 if (unlikely(!cur_dir)) {
1431 /* Root of tree being captured; get volume information. */
1432 ctx->vol_flags = get_volume_information(h, full_path, params);
1433 params->capture_root_ino = file_info.ino;
1437 /* Create a WIM dentry with an associated inode, which may be shared.
1439 * However, we need to explicitly check for directories and files with
1440 * only 1 link and refuse to hard link them. This is because Windows
1441 * has a bug where it can return duplicate File IDs for files and
1442 * directories on the FAT filesystem.
1444 * Since we don't follow mount points on Windows, we don't need to query
1445 * the volume ID per-file. Just once, for the root, is enough. But we
1446 * can't simply pass 0, because then there could be inode collisions
1447 * among multiple calls to win32_build_dentry_tree() that are scanning
1448 * files on different volumes. */
1449 ret = inode_table_new_dentry(params->inode_table,
1452 params->capture_root_dev,
1453 (file_info.num_links <= 1),
1458 /* Get the short (DOS) name of the file. */
1459 status = winnt_get_short_name(h, root);
1461 /* If we can't read the short filename for any reason other than
1462 * out-of-memory, just ignore the error and assume the file has no short
1463 * name. This shouldn't be an issue, since the short names are
1464 * essentially obsolete anyway. */
1465 if (unlikely(status == STATUS_NO_MEMORY)) {
1466 ret = WIMLIB_ERR_NOMEM;
1470 inode = root->d_inode;
1472 if (inode->i_nlink > 1) {
1473 /* Shared inode (hard link); skip reading per-inode information.
1478 inode->i_attributes = file_info.attributes;
1479 inode->i_creation_time = file_info.creation_time;
1480 inode->i_last_write_time = file_info.last_write_time;
1481 inode->i_last_access_time = file_info.last_access_time;
1483 /* Get the file's security descriptor, unless we are capturing in
1484 * NO_ACLS mode or the volume does not support security descriptors. */
1485 if (!(params->add_flags & WIMLIB_ADD_FLAG_NO_ACLS)
1486 && (ctx->vol_flags & FILE_PERSISTENT_ACLS))
1488 status = winnt_get_security_descriptor(h, inode,
1489 params->sd_set, ctx,
1491 if (!NT_SUCCESS(status)) {
1493 L"\"%ls\": Can't read security descriptor",
1494 printable_path(full_path));
1495 ret = WIMLIB_ERR_STAT;
1500 /* If this is a reparse point, load the reparse data. */
1501 if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
1502 ret = winnt_load_reparse_data(h, inode, full_path, params);
1507 sort_key = get_sort_key(h);
1509 if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) {
1510 /* Load information about the raw encrypted data. This is
1511 * needed for any directory or non-directory that has
1512 * FILE_ATTRIBUTE_ENCRYPTED set.
1514 * Note: since OpenEncryptedFileRaw() fails with
1515 * ERROR_SHARING_VIOLATION if there are any open handles to the
1516 * file, we have to close the file and re-open it later if
1520 ret = winnt_scan_efsrpc_raw_data(inode, full_path,
1521 params->unhashed_blobs);
1526 * Load information about data streams (unnamed and named).
1528 * Skip this step for encrypted files, since the data from
1529 * ReadEncryptedFileRaw() already contains all data streams (and
1530 * they do in fact all get restored by WriteEncryptedFileRaw().)
1532 * Note: WIMGAPI (as of Windows 8.1) gets wrong and stores both
1533 * the EFSRPC data and the named data stream(s)...!
1535 ret = winnt_scan_data_streams(h,
1539 params->unhashed_blobs,
1540 file_info.end_of_file,
1546 if (unlikely(should_try_to_use_wimboot_hash(inode, ctx, params))) {
1547 ret = try_to_use_wimboot_hash(h, inode, params->blob_table, ctx,
1553 set_sort_key(inode, sort_key);
1555 if (inode_is_directory(inode)) {
1557 /* Directory: recurse to children. */
1560 /* Re-open handle that was closed to read raw encrypted
1562 status = winnt_openat(cur_dir,
1564 filename : full_path),
1566 filename_nchars : full_path_nchars),
1567 FILE_LIST_DIRECTORY | SYNCHRONIZE,
1569 if (!NT_SUCCESS(status)) {
1571 L"\"%ls\": Can't re-open file",
1572 printable_path(full_path));
1573 ret = WIMLIB_ERR_OPEN;
1577 ret = winnt_recurse_directory(h,
1588 params->progress.scan.cur_path = printable_path(full_path);
1590 ret = do_capture_progress(params, WIMLIB_SCAN_DENTRY_OK, inode);
1592 ret = do_capture_progress(params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
1596 if (unlikely(ret)) {
1597 free_dentry_tree(root, params->blob_table);
1599 ret = report_capture_error(params, ret, full_path);
1606 winnt_do_scan_warnings(const wchar_t *path, const struct winnt_scan_ctx *ctx)
1608 if (likely(ctx->num_get_sacl_priv_notheld == 0 &&
1609 ctx->num_get_sd_access_denied == 0))
1612 WARNING("Scan of \"%ls\" complete, but with one or more warnings:", path);
1613 if (ctx->num_get_sacl_priv_notheld != 0) {
1614 WARNING("- Could not capture SACL (System Access Control List)\n"
1615 " on %lu files or directories.",
1616 ctx->num_get_sacl_priv_notheld);
1618 if (ctx->num_get_sd_access_denied != 0) {
1619 WARNING("- Could not capture security descriptor at all\n"
1620 " on %lu files or directories.",
1621 ctx->num_get_sd_access_denied);
1623 WARNING("To fully capture all security descriptors, run the program\n"
1624 " with Administrator rights.");
1627 #define WINDOWS_NT_MAX_PATH 32768
1629 /* Win32 version of capturing a directory tree. */
1631 win32_build_dentry_tree(struct wim_dentry **root_ret,
1632 const wchar_t *root_disk_path,
1633 struct capture_params *params)
1637 UNICODE_STRING ntpath;
1638 struct winnt_scan_ctx ctx;
1639 size_t ntpath_nchars;
1641 /* WARNING: There is no check for overflow later when this buffer is
1642 * being used! But it's as long as the maximum path length understood
1643 * by Windows NT (which is NOT the same as MAX_PATH). */
1644 path = MALLOC((WINDOWS_NT_MAX_PATH + 1) * sizeof(wchar_t));
1646 return WIMLIB_ERR_NOMEM;
1648 ret = win32_path_to_nt_path(root_disk_path, &ntpath);
1652 if (ntpath.Length < 4 * sizeof(wchar_t) ||
1653 ntpath.Length > WINDOWS_NT_MAX_PATH * sizeof(wchar_t) ||
1654 wmemcmp(ntpath.Buffer, L"\\??\\", 4))
1656 ERROR("\"%ls\": unrecognized path format", root_disk_path);
1657 ret = WIMLIB_ERR_INVALID_PARAM;
1659 ntpath_nchars = ntpath.Length / sizeof(wchar_t);
1660 wmemcpy(path, ntpath.Buffer, ntpath_nchars);
1661 path[ntpath_nchars] = L'\0';
1663 params->capture_root_nchars = ntpath_nchars;
1664 if (path[ntpath_nchars - 1] == L'\\')
1665 params->capture_root_nchars--;
1668 HeapFree(GetProcessHeap(), 0, ntpath.Buffer);
1672 memset(&ctx, 0, sizeof(ctx));
1674 ret = winnt_build_dentry_tree_recursive(root_ret, NULL,
1675 path, ntpath_nchars,
1676 L"", 0, params, &ctx);
1680 winnt_do_scan_warnings(root_disk_path, &ctx);
1684 #endif /* __WIN32__ */