+/* Called when starting to read a blob for extraction on Windows */
+static int
+begin_extract_blob(struct blob_descriptor *blob, void *_ctx)
+{
+ struct win32_apply_ctx *ctx = _ctx;
+ const struct blob_extraction_target *targets = blob_extraction_targets(blob);
+ int ret;
+
+ ctx->num_open_handles = 0;
+ ctx->data_buffer_ptr = NULL;
+ INIT_LIST_HEAD(&ctx->reparse_dentries);
+ INIT_LIST_HEAD(&ctx->encrypted_dentries);
+
+ for (u32 i = 0; i < blob->out_refcnt; i++) {
+ const struct wim_inode *inode = targets[i].inode;
+ const struct wim_inode_stream *strm = targets[i].stream;
+ struct wim_dentry *dentry;
+
+ /* A copy of the blob needs to be extracted to @inode. */
+
+ if (ctx->common.supported_features.hard_links) {
+ dentry = inode_first_extraction_dentry(inode);
+ ret = begin_extract_blob_instance(blob, dentry, strm, ctx);
+ ret = check_apply_error(dentry, ctx, ret);
+ if (ret)
+ goto fail;
+ } else {
+ /* Hard links not supported. Extract the blob
+ * separately to each alias of the inode. */
+ inode_for_each_extraction_alias(dentry, inode) {
+ ret = begin_extract_blob_instance(blob, dentry, strm, ctx);
+ ret = check_apply_error(dentry, ctx, ret);
+ if (ret)
+ goto fail;
+ }
+ }
+ }
+
+ return 0;
+
+fail:
+ close_handles(ctx);
+ return ret;
+}
+
+/* Called when the next chunk of a blob has been read for extraction on Windows
+ */
+static int
+extract_chunk(const void *chunk, size_t size, void *_ctx)
+{
+ struct win32_apply_ctx *ctx = _ctx;
+
+ /* Write the data chunk to each open handle */
+ for (unsigned i = 0; i < ctx->num_open_handles; i++) {
+ u8 *bufptr = (u8 *)chunk;
+ size_t bytes_remaining = size;
+ NTSTATUS status;
+ while (bytes_remaining) {
+ ULONG count = min(0xFFFFFFFF, bytes_remaining);
+
+ status = NtWriteFile(ctx->open_handles[i],
+ NULL, NULL, NULL,
+ &ctx->iosb, bufptr, count,
+ NULL, NULL);
+ if (!NT_SUCCESS(status)) {
+ winnt_error(status, L"Error writing data to target volume");
+ return WIMLIB_ERR_WRITE;
+ }
+ bufptr += ctx->iosb.Information;
+ bytes_remaining -= ctx->iosb.Information;
+ }
+ }
+
+ /* Copy the data chunk into the buffer (if needed) */
+ if (ctx->data_buffer_ptr)
+ ctx->data_buffer_ptr = mempcpy(ctx->data_buffer_ptr,
+ chunk, size);
+ return 0;
+}
+
+static int
+get_system_compression_format(int extract_flags)
+{
+ if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K)
+ return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K;
+
+ if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K)
+ return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS8K;
+
+ if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K)
+ return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS16K;
+
+ return FILE_PROVIDER_COMPRESSION_FORMAT_LZX;
+}
+
+
+static const wchar_t *
+get_system_compression_format_string(int format)
+{
+ switch (format) {
+ case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K:
+ return L"XPRESS4K";
+ case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS8K:
+ return L"XPRESS8K";
+ case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS16K:
+ return L"XPRESS16K";
+ default:
+ return L"LZX";
+ }
+}
+
+static NTSTATUS
+set_system_compression(HANDLE h, int format)
+{
+ NTSTATUS status;
+ struct {
+ struct wof_external_info wof_info;
+ struct file_provider_external_info file_info;
+ } in = {
+ .wof_info = {
+ .version = WOF_CURRENT_VERSION,
+ .provider = WOF_PROVIDER_FILE,
+ },
+ .file_info = {
+ .version = FILE_PROVIDER_CURRENT_VERSION,
+ .compression_format = format,
+ },
+ };
+
+ /* We intentionally use NtFsControlFile() rather than DeviceIoControl()
+ * here because the "compressing this object would not save space"
+ * status code does not map to a valid Win32 error code on older
+ * versions of Windows (before Windows 10?). This can be a problem if
+ * the WOFADK driver is being used rather than the regular WOF, since
+ * WOFADK can be used on older versions of Windows. */
+ status = winnt_fsctl(h, FSCTL_SET_EXTERNAL_BACKING,
+ &in, sizeof(in), NULL, 0, NULL);
+
+ if (status == 0xC000046F) /* "Compressing this object would not save space." */
+ return STATUS_SUCCESS;
+
+ return status;
+}
+
+/* Hard-coded list of files which the Windows bootloader may need to access
+ * before the WOF driver has been loaded. */
+static wchar_t *bootloader_pattern_strings[] = {
+ L"*winload.*",
+ L"*winresume.*",
+ L"\\Windows\\AppPatch\\drvmain.sdb",
+ L"\\Windows\\Boot\\DVD\\*",
+ L"\\Windows\\Boot\\EFI\\*",
+ L"\\Windows\\bootstat.dat",
+ L"\\Windows\\Fonts\\vgaoem.fon",
+ L"\\Windows\\Fonts\\vgasys.fon",
+ L"\\Windows\\INF\\errata.inf",
+ L"\\Windows\\System32\\config\\*",
+ L"\\Windows\\System32\\ntkrnlpa.exe",
+ L"\\Windows\\System32\\ntoskrnl.exe",
+ L"\\Windows\\System32\\bootvid.dll",
+ L"\\Windows\\System32\\ci.dll",
+ L"\\Windows\\System32\\hal*.dll",
+ L"\\Windows\\System32\\mcupdate_AuthenticAMD.dll",
+ L"\\Windows\\System32\\mcupdate_GenuineIntel.dll",
+ L"\\Windows\\System32\\pshed.dll",
+ L"\\Windows\\System32\\apisetschema.dll",
+ L"\\Windows\\System32\\api-ms-win*.dll",
+ L"\\Windows\\System32\\ext-ms-win*.dll",
+ L"\\Windows\\System32\\KernelBase.dll",
+ L"\\Windows\\System32\\drivers\\*.sys",
+ L"\\Windows\\System32\\*.nls",
+ L"\\Windows\\System32\\kbd*.dll",
+ L"\\Windows\\System32\\kd*.dll",
+ L"\\Windows\\System32\\clfs.sys",
+ L"\\Windows\\System32\\CodeIntegrity\\driver.stl",
+};
+
+static const struct string_list bootloader_patterns = {
+ .strings = bootloader_pattern_strings,
+ .num_strings = ARRAY_LEN(bootloader_pattern_strings),
+};
+
+static NTSTATUS
+set_system_compression_on_inode(struct wim_inode *inode, int format,
+ struct win32_apply_ctx *ctx)
+{
+ bool retried = false;
+ NTSTATUS status;
+ HANDLE h;
+
+ /* If it may be needed for compatibility with the Windows bootloader,
+ * force this file to XPRESS4K or uncompressed format. The bootloader
+ * of Windows 10 supports XPRESS4K only; older versions don't support
+ * system compression at all. */
+ if (!is_image_windows_10_or_later(ctx) ||
+ format != FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K)
+ {
+ /* We need to check the patterns against every name of the
+ * inode, in case any of them match. */
+ struct wim_dentry *dentry;
+ inode_for_each_extraction_alias(dentry, inode) {
+ bool incompatible;
+ bool warned;
+
+ if (calculate_dentry_full_path(dentry)) {
+ ERROR("Unable to compute file path!");
+ return STATUS_NO_MEMORY;
+ }
+
+ incompatible = match_pattern_list(dentry->d_full_path,
+ &bootloader_patterns);
+ FREE(dentry->d_full_path);
+ dentry->d_full_path = NULL;
+
+ if (!incompatible)
+ continue;
+
+ warned = (ctx->num_system_compression_exclusions++ > 0);
+
+ if (is_image_windows_10_or_later(ctx)) {
+ /* Force to XPRESS4K */
+ if (!warned) {
+ WARNING("For compatibility with the "
+ "Windows bootloader, some "
+ "files are being\n"
+ " compacted "
+ "using the XPRESS4K format "
+ "instead of the %"TS" format\n"
+ " you requested.",
+ get_system_compression_format_string(format));
+ }
+ format = FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K;
+ break;
+ } else {
+ /* Force to uncompressed */
+ if (!warned) {
+ WARNING("For compatibility with the "
+ "Windows bootloader, some "
+ "files will not\n"
+ " be compressed with"
+ " system compression "
+ "(\"compacted\").");
+ }
+ return STATUS_SUCCESS;
+ }
+
+ }
+ }
+
+ /* Open the extracted file. */
+ status = create_file(&h, GENERIC_READ | GENERIC_WRITE, NULL,
+ 0, FILE_OPEN, 0,
+ inode_first_extraction_dentry(inode), ctx);
+
+ if (!NT_SUCCESS(status))
+ return status;
+retry:
+ /* Compress the file. If the attempt fails with "invalid device
+ * request", then attach wof.sys (or wofadk.sys) and retry. */
+ status = set_system_compression(h, format);
+ if (unlikely(status == STATUS_INVALID_DEVICE_REQUEST && !retried)) {
+ wchar_t drive_path[7];
+ if (!win32_get_drive_path(ctx->common.target, drive_path) &&
+ win32_try_to_attach_wof(drive_path + 4)) {
+ retried = true;
+ goto retry;
+ }
+ }
+
+ NtClose(h);
+ return status;
+}
+
+/*
+ * This function is called when doing a "compact-mode" extraction and we just
+ * finished extracting a blob to one or more locations. For each location that
+ * was the unnamed data stream of a file, this function compresses the
+ * corresponding file using System Compression, if allowed.
+ *
+ * Note: we're doing the compression immediately after extracting the data
+ * rather than during a separate compression pass. This way should be faster
+ * since the operating system should still have the file's data cached.
+ *
+ * Note: we're having the operating system do the compression, which is not
+ * ideal because wimlib could create the compressed data faster and more
+ * efficiently (the compressed data format is identical to a WIM resource). But
+ * we seemingly don't have a choice because WOF prevents applications from
+ * creating its reparse points.
+ */
+static void
+handle_system_compression(struct blob_descriptor *blob, struct win32_apply_ctx *ctx)
+{
+ const struct blob_extraction_target *targets = blob_extraction_targets(blob);
+
+ const int format = get_system_compression_format(ctx->common.extract_flags);
+
+ for (u32 i = 0; i < blob->out_refcnt; i++) {
+ struct wim_inode *inode = targets[i].inode;
+ struct wim_inode_stream *strm = targets[i].stream;
+ NTSTATUS status;
+
+ if (!stream_is_unnamed_data_stream(strm))
+ continue;
+
+ if (will_externally_back_inode(inode, ctx, NULL, false) != 0)
+ continue;
+
+ status = set_system_compression_on_inode(inode, format, ctx);
+ if (likely(NT_SUCCESS(status)))
+ continue;
+
+ if (status == STATUS_INVALID_DEVICE_REQUEST) {
+ WARNING(
+ "The request to compress the extracted files using System Compression\n"
+" will not be honored because the operating system or target volume\n"
+" does not support it. System Compression is only supported on\n"
+" Windows 10 and later, and only on NTFS volumes.");
+ ctx->common.extract_flags &= ~COMPACT_FLAGS;
+ return;
+ }
+
+ ctx->num_system_compression_failures++;
+ if (ctx->num_system_compression_failures < 10) {
+ winnt_warning(status, L"\"%ls\": Failed to compress "
+ "extracted file using System Compression",
+ current_path(ctx));
+ } else if (ctx->num_system_compression_failures == 10) {
+ WARNING("Suppressing further warnings about "
+ "System Compression failures.");
+ }
+ }
+}
+
+/* Called when a blob has been fully read for extraction on Windows */
+static int
+end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
+{
+ struct win32_apply_ctx *ctx = _ctx;
+ int ret;
+ const struct wim_dentry *dentry;
+
+ close_handles(ctx);
+
+ if (status)
+ return status;
+
+ if (unlikely(ctx->common.extract_flags & COMPACT_FLAGS))
+ handle_system_compression(blob, ctx);
+
+ if (likely(!ctx->data_buffer_ptr))
+ return 0;
+
+ if (!list_empty(&ctx->reparse_dentries)) {
+ if (blob->size > REPARSE_DATA_MAX_SIZE) {
+ dentry = list_first_entry(&ctx->reparse_dentries,
+ struct wim_dentry, d_tmp_list);
+ build_extraction_path(dentry, ctx);
+ ERROR("Reparse data of \"%ls\" has size "
+ "%"PRIu64" bytes (exceeds %u bytes)",
+ current_path(ctx), blob->size,
+ REPARSE_DATA_MAX_SIZE);
+ ret = WIMLIB_ERR_INVALID_REPARSE_DATA;
+ return check_apply_error(dentry, ctx, ret);
+ }
+ /* Reparse data */
+ memcpy(ctx->rpbuf.rpdata, ctx->data_buffer, blob->size);
+
+ list_for_each_entry(dentry, &ctx->reparse_dentries, d_tmp_list) {
+
+ /* Reparse point header */
+ complete_reparse_point(&ctx->rpbuf, dentry->d_inode,
+ blob->size);
+
+ ret = set_reparse_point(dentry, &ctx->rpbuf,
+ REPARSE_DATA_OFFSET + blob->size,
+ ctx);
+ ret = check_apply_error(dentry, ctx, ret);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (!list_empty(&ctx->encrypted_dentries)) {
+ ctx->encrypted_size = blob->size;
+ list_for_each_entry(dentry, &ctx->encrypted_dentries, d_tmp_list) {
+ ret = extract_encrypted_file(dentry, ctx);
+ ret = check_apply_error(dentry, ctx, ret);
+ if (ret)
+ return ret;
+ /* Re-open the target directory if needed. */
+ ret = open_target_directory(ctx);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/* Attributes that can't be set directly */
+#define SPECIAL_ATTRIBUTES \
+ (FILE_ATTRIBUTE_REPARSE_POINT | \
+ FILE_ATTRIBUTE_DIRECTORY | \
+ FILE_ATTRIBUTE_ENCRYPTED | \
+ FILE_ATTRIBUTE_SPARSE_FILE | \
+ FILE_ATTRIBUTE_COMPRESSED)
+
+static void
+set_object_id(HANDLE h, const struct wim_inode *inode,
+ struct win32_apply_ctx *ctx)
+{
+ const void *object_id;
+ u32 len;
+ NTSTATUS status;
+
+ if (!ctx->common.supported_features.object_ids)
+ return;
+
+ object_id = inode_get_object_id(inode, &len);
+ if (likely(object_id == NULL)) /* No object ID? */
+ return;
+
+ status = winnt_fsctl(h, FSCTL_SET_OBJECT_ID,
+ object_id, len, NULL, 0, NULL);
+ if (NT_SUCCESS(status))
+ return;
+
+ /* Object IDs must be unique within the filesystem. A duplicate might
+ * occur if an image containing object IDs is applied twice to the same
+ * filesystem. Arguably, the user should be warned in this case; but
+ * the reality seems to be that nothing important cares about object IDs
+ * except the Distributed Link Tracking Service... so for now these
+ * failures are just ignored. */
+ if (status == STATUS_DUPLICATE_NAME ||
+ status == STATUS_OBJECT_NAME_COLLISION)
+ return;
+
+ ctx->num_object_id_failures++;
+ if (ctx->num_object_id_failures < 10) {
+ winnt_warning(status, L"Can't set object ID on \"%ls\"",
+ current_path(ctx));
+ } else if (ctx->num_object_id_failures == 10) {
+ WARNING("Suppressing further warnings about failure to set "
+ "object IDs.");
+ }
+}
+
+/* Set the security descriptor @desc, of @desc_size bytes, on the file with open
+ * handle @h. */
+static NTSTATUS
+set_security_descriptor(HANDLE h, const void *_desc,
+ size_t desc_size, struct win32_apply_ctx *ctx)
+{
+ SECURITY_INFORMATION info;
+ NTSTATUS status;
+ SECURITY_DESCRIPTOR_RELATIVE *desc;
+
+ /*
+ * Ideally, we would just pass in the security descriptor buffer as-is.
+ * But it turns out that Windows can mess up the security descriptor
+ * even when using the low-level NtSetSecurityObject() function:
+ *
+ * - Windows will clear SE_DACL_AUTO_INHERITED if it is set in the
+ * passed buffer. To actually get Windows to set
+ * SE_DACL_AUTO_INHERITED, the application must set the non-persistent
+ * flag SE_DACL_AUTO_INHERIT_REQ. As usual, Microsoft didn't bother
+ * to properly document either of these flags. It's unclear how
+ * important SE_DACL_AUTO_INHERITED actually is, but to be safe we use
+ * the SE_DACL_AUTO_INHERIT_REQ workaround to set it if needed.
+ *
+ * - The above also applies to the equivalent SACL flags,
+ * SE_SACL_AUTO_INHERITED and SE_SACL_AUTO_INHERIT_REQ.
+ *
+ * - If the application says that it's setting
+ * DACL_SECURITY_INFORMATION, then Windows sets SE_DACL_PRESENT in the
+ * resulting security descriptor, even if the security descriptor the
+ * application provided did not have a DACL. This seems to be
+ * unavoidable, since omitting DACL_SECURITY_INFORMATION would cause a
+ * default DACL to remain. Fortunately, this behavior seems harmless,
+ * since the resulting DACL will still be "null" --- but it will be
+ * "the other representation of null".
+ *
+ * - The above also applies to SACL_SECURITY_INFORMATION and
+ * SE_SACL_PRESENT. Again, it's seemingly unavoidable but "harmless"
+ * that Windows changes the representation of a "null SACL".
+ */
+ if (likely(desc_size <= STACK_MAX)) {
+ desc = alloca(desc_size);
+ } else {
+ desc = MALLOC(desc_size);
+ if (!desc)
+ return STATUS_NO_MEMORY;
+ }
+
+ memcpy(desc, _desc, desc_size);
+
+ if (likely(desc_size >= 4)) {
+
+ if (desc->Control & SE_DACL_AUTO_INHERITED)
+ desc->Control |= SE_DACL_AUTO_INHERIT_REQ;
+
+ if (desc->Control & SE_SACL_AUTO_INHERITED)
+ desc->Control |= SE_SACL_AUTO_INHERIT_REQ;
+ }
+
+ /*
+ * More API insanity. We want to set the entire security descriptor
+ * as-is. But all available APIs require specifying the specific parts
+ * of the security descriptor being set. Especially annoying is that
+ * mandatory integrity labels are part of the SACL, but they aren't set
+ * with SACL_SECURITY_INFORMATION. Instead, applications must also
+ * specify LABEL_SECURITY_INFORMATION (Windows Vista, Windows 7) or
+ * BACKUP_SECURITY_INFORMATION (Windows 8). But at least older versions
+ * of Windows don't error out if you provide these newer flags...
+ *
+ * Also, if the process isn't running as Administrator, then it probably
+ * doesn't have SE_RESTORE_PRIVILEGE. In this case, it will always get
+ * the STATUS_PRIVILEGE_NOT_HELD error by trying to set the SACL, even
+ * if the security descriptor it provided did not have a SACL. By
+ * default, in this case we try to recover and set as much of the
+ * security descriptor as possible --- potentially excluding the DACL, and
+ * even the owner, as well as the SACL.
+ */
+
+ info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION |
+ LABEL_SECURITY_INFORMATION | BACKUP_SECURITY_INFORMATION;
+
+
+ /*
+ * It's also worth noting that SetFileSecurity() is unusable because it
+ * doesn't request "backup semantics" when it opens the file internally.
+ * NtSetSecurityObject() seems to be the best function to use in backup
+ * applications. (SetSecurityInfo() should also work, but it's harder
+ * to use and must call NtSetSecurityObject() internally anyway.
+ * BackupWrite() is theoretically usable as well, but it's inflexible
+ * and poorly documented.)
+ */
+
+retry:
+ status = NtSetSecurityObject(h, info, desc);
+ if (NT_SUCCESS(status))
+ goto out_maybe_free_desc;
+
+ /* Failed to set the requested parts of the security descriptor. If the
+ * error was permissions-related, try to set fewer parts of the security
+ * descriptor, unless WIMLIB_EXTRACT_FLAG_STRICT_ACLS is enabled. */
+ if ((status == STATUS_PRIVILEGE_NOT_HELD ||
+ status == STATUS_ACCESS_DENIED) &&
+ !(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
+ {
+ if (info & SACL_SECURITY_INFORMATION) {
+ info &= ~(SACL_SECURITY_INFORMATION |
+ LABEL_SECURITY_INFORMATION |
+ BACKUP_SECURITY_INFORMATION);
+ ctx->partial_security_descriptors++;
+ goto retry;
+ }
+ if (info & DACL_SECURITY_INFORMATION) {
+ info &= ~DACL_SECURITY_INFORMATION;
+ goto retry;
+ }
+ if (info & OWNER_SECURITY_INFORMATION) {
+ info &= ~OWNER_SECURITY_INFORMATION;
+ goto retry;
+ }
+ /* Nothing left except GROUP, and if we removed it we
+ * wouldn't have anything at all. */
+ }
+
+ /* No part of the security descriptor could be set, or
+ * WIMLIB_EXTRACT_FLAG_STRICT_ACLS is enabled and the full security
+ * descriptor could not be set. */
+ if (!(info & SACL_SECURITY_INFORMATION))
+ ctx->partial_security_descriptors--;
+ ctx->no_security_descriptors++;
+
+out_maybe_free_desc:
+ if (unlikely(desc_size > STACK_MAX))
+ FREE(desc);
+ return status;
+}
+
+/* Set metadata on the open file @h from the WIM inode @inode. */
+static int
+do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode,
+ struct win32_apply_ctx *ctx)
+{
+ FILE_BASIC_INFORMATION info;
+ NTSTATUS status;
+
+ /* Set the file's object ID if present and object IDs are supported by
+ * the filesystem. */
+ set_object_id(h, inode, ctx);
+
+ /* Set the file's security descriptor if present and we're not in
+ * NO_ACLS mode */
+ if (inode_has_security_descriptor(inode) &&
+ !(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS))
+ {
+ const struct wim_security_data *sd;
+ const void *desc;
+ size_t desc_size;
+
+ sd = wim_get_current_security_data(ctx->common.wim);
+ desc = sd->descriptors[inode->i_security_id];
+ desc_size = sd->sizes[inode->i_security_id];
+
+ status = set_security_descriptor(h, desc, desc_size, ctx);
+ if (!NT_SUCCESS(status) &&
+ (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS))
+ {
+ winnt_error(status,
+ L"Can't set security descriptor on \"%ls\"",
+ current_path(ctx));
+ return WIMLIB_ERR_SET_SECURITY;
+ }
+ }
+
+ /* Set attributes and timestamps */
+ info.CreationTime.QuadPart = inode->i_creation_time;
+ info.LastAccessTime.QuadPart = inode->i_last_access_time;
+ info.LastWriteTime.QuadPart = inode->i_last_write_time;
+ info.ChangeTime.QuadPart = 0;
+ if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) {
+ info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ } else {
+ info.FileAttributes = inode->i_attributes & ~SPECIAL_ATTRIBUTES;
+ if (info.FileAttributes == 0)
+ info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ status = NtSetInformationFile(h, &ctx->iosb, &info, sizeof(info),
+ FileBasicInformation);
+ /* On FAT volumes we get STATUS_INVALID_PARAMETER if we try to set
+ * attributes on the root directory. (Apparently because FAT doesn't
+ * actually have a place to store those attributes!) */
+ if (!NT_SUCCESS(status)
+ && !(status == STATUS_INVALID_PARAMETER &&
+ dentry_is_root(inode_first_extraction_dentry(inode))))
+ {
+ winnt_error(status, L"Can't set basic metadata on \"%ls\"",
+ current_path(ctx));
+ return WIMLIB_ERR_SET_ATTRIBUTES;
+ }
+
+ return 0;
+}
+
+static int
+apply_metadata_to_file(const struct wim_dentry *dentry,
+ struct win32_apply_ctx *ctx)
+{
+ const struct wim_inode *inode = dentry->d_inode;
+ DWORD perms;
+ HANDLE h;
+ NTSTATUS status;
+ int ret;
+
+ perms = FILE_WRITE_ATTRIBUTES | WRITE_DAC |
+ WRITE_OWNER | ACCESS_SYSTEM_SECURITY;
+
+ build_extraction_path(dentry, ctx);
+
+ /* Open a handle with as many relevant permissions as possible. */
+ while (!NT_SUCCESS(status = do_create_file(&h, perms, NULL,
+ 0, FILE_OPEN, 0, ctx)))
+ {
+ if (status == STATUS_PRIVILEGE_NOT_HELD ||
+ status == STATUS_ACCESS_DENIED)
+ {
+ if (perms & ACCESS_SYSTEM_SECURITY) {
+ perms &= ~ACCESS_SYSTEM_SECURITY;
+ continue;
+ }
+ if (perms & WRITE_DAC) {
+ perms &= ~WRITE_DAC;
+ continue;
+ }
+ if (perms & WRITE_OWNER) {
+ perms &= ~WRITE_OWNER;
+ continue;
+ }
+ }
+ winnt_error(status, L"Can't open \"%ls\" to set metadata",
+ current_path(ctx));
+ return WIMLIB_ERR_OPEN;
+ }
+
+ ret = do_apply_metadata_to_file(h, inode, ctx);
+
+ NtClose(h);
+
+ return ret;
+}
+
+static int
+apply_metadata(struct list_head *dentry_list, struct win32_apply_ctx *ctx)
+{
+ const struct wim_dentry *dentry;
+ int ret;
+
+ /* We go in reverse so that metadata is set on all a directory's
+ * children before the directory itself. This avoids any potential
+ * problems with attributes, timestamps, or security descriptors. */
+ list_for_each_entry_reverse(dentry, dentry_list, d_extraction_list_node)
+ {
+ ret = apply_metadata_to_file(dentry, ctx);
+ ret = check_apply_error(dentry, ctx, ret);
+ if (ret)
+ return ret;
+ ret = report_file_metadata_applied(&ctx->common);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/* Issue warnings about problems during the extraction for which warnings were
+ * not already issued (due to the high number of potential warnings if we issued
+ * them per-file). */
+static void
+do_warnings(const struct win32_apply_ctx *ctx)
+{
+ if (ctx->partial_security_descriptors == 0
+ && ctx->no_security_descriptors == 0
+ && ctx->num_set_short_name_failures == 0
+ #if 0
+ && ctx->num_remove_short_name_failures == 0
+ #endif
+ )
+ return;
+
+ WARNING("Extraction to \"%ls\" complete, but with one or more warnings:",
+ ctx->common.target);
+ if (ctx->num_set_short_name_failures) {
+ WARNING("- Could not set short names on %lu files or directories",
+ ctx->num_set_short_name_failures);
+ }
+#if 0
+ if (ctx->num_remove_short_name_failures) {
+ WARNING("- Could not remove short names on %lu files or directories"
+ " (This is expected on Vista and earlier)",
+ ctx->num_remove_short_name_failures);
+ }
+#endif
+ if (ctx->partial_security_descriptors) {
+ WARNING("- Could only partially set the security descriptor\n"
+ " on %lu files or directories.",
+ ctx->partial_security_descriptors);
+ }
+ if (ctx->no_security_descriptors) {
+ WARNING("- Could not set security descriptor at all\n"
+ " on %lu files or directories.",
+ ctx->no_security_descriptors);
+ }
+ if (ctx->partial_security_descriptors || ctx->no_security_descriptors) {
+ WARNING("To fully restore all security descriptors, run the program\n"
+ " with Administrator rights.");
+ }
+}
+
+static u64
+count_dentries(const struct list_head *dentry_list)
+{
+ const struct list_head *cur;
+ u64 count = 0;
+
+ list_for_each(cur, dentry_list)
+ count++;
+
+ return count;
+}
+
+/* Extract files from a WIM image to a directory on Windows */
+static int
+win32_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
+{
+ int ret;
+ struct win32_apply_ctx *ctx = (struct win32_apply_ctx *)_ctx;
+ u64 dentry_count;
+
+ ret = prepare_target(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ if (unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) {
+ ret = start_wimboot_extraction(dentry_list, ctx);
+ if (ret)
+ goto out;
+ }
+
+ ctx->windows_build_number = xml_get_windows_build_number(ctx->common.wim->xml_info,
+ ctx->common.wim->current_image);
+
+ dentry_count = count_dentries(dentry_list);
+
+ ret = start_file_structure_phase(&ctx->common, dentry_count);
+ if (ret)
+ goto out;
+
+ ret = create_directories(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ ret = create_nondirectories(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ ret = end_file_structure_phase(&ctx->common);
+ if (ret)
+ goto out;
+
+ struct read_blob_callbacks cbs = {
+ .begin_blob = begin_extract_blob,
+ .consume_chunk = extract_chunk,
+ .end_blob = end_extract_blob,
+ .ctx = ctx,
+ };
+ ret = extract_blob_list(&ctx->common, &cbs);
+ if (ret)
+ goto out;
+
+ ret = start_file_metadata_phase(&ctx->common, dentry_count);
+ if (ret)
+ goto out;
+
+ ret = apply_metadata(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ ret = end_file_metadata_phase(&ctx->common);
+ if (ret)
+ goto out;
+
+ if (unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) {
+ ret = end_wimboot_extraction(ctx);
+ if (ret)
+ goto out;
+ }
+
+ do_warnings(ctx);
+out:
+ close_target_directory(ctx);
+ if (ctx->target_ntpath.Buffer)
+ HeapFree(GetProcessHeap(), 0, ctx->target_ntpath.Buffer);
+ FREE(ctx->pathbuf.Buffer);
+ FREE(ctx->print_buffer);
+ FREE(ctx->wimboot.wims);
+ if (ctx->prepopulate_pats) {
+ FREE(ctx->prepopulate_pats->strings);
+ FREE(ctx->prepopulate_pats);
+ }
+ FREE(ctx->mem_prepopulate_pats);
+ FREE(ctx->data_buffer);
+ return ret;
+}
+
+const struct apply_operations win32_apply_ops = {
+ .name = "Windows",
+ .get_supported_features = win32_get_supported_features,
+ .extract = win32_extract,
+ .will_back_from_wim = win32_will_back_from_wim,
+ .context_size = sizeof(struct win32_apply_ctx),
+};
+