+/* Called when a stream has been fully read for extraction on Windows */
+static int
+end_extract_stream(struct wim_lookup_table_entry *stream, 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 (likely(!ctx->data_buffer_ptr))
+ return 0;
+
+ if (!list_empty(&ctx->reparse_dentries)) {
+ if (stream->size > REPARSE_DATA_MAX_SIZE) {
+ dentry = list_first_entry(&ctx->reparse_dentries,
+ struct wim_dentry, tmp_list);
+ build_extraction_path(dentry, ctx);
+ ERROR("Reparse data of \"%ls\" has size "
+ "%"PRIu64" bytes (exceeds %u bytes)",
+ current_path(ctx), stream->size,
+ REPARSE_DATA_MAX_SIZE);
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+ }
+ /* In the WIM format, reparse streams are just the reparse data
+ * and omit the header. But we can reconstruct the header. */
+ memcpy(ctx->rpbuf.rpdata, ctx->data_buffer, stream->size);
+ ctx->rpbuf.rpdatalen = stream->size;
+ ctx->rpbuf.rpreserved = 0;
+ list_for_each_entry(dentry, &ctx->reparse_dentries, tmp_list) {
+ ctx->rpbuf.rptag = dentry->d_inode->i_reparse_tag;
+ ret = set_reparse_data(dentry, &ctx->rpbuf,
+ stream->size + REPARSE_DATA_OFFSET,
+ ctx);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (!list_empty(&ctx->encrypted_dentries)) {
+ ctx->encrypted_size = stream->size;
+ list_for_each_entry(dentry, &ctx->encrypted_dentries, tmp_list) {
+ ret = extract_encrypted_file(dentry, 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)
+
+/* 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;
+
+ /* We really just want to set entire the security descriptor as-is, but
+ * all available APIs require specifying the specific parts of the
+ * descriptor being set. Start out by requesting all parts be set. If
+ * permissions problems are encountered, fall back to omitting some
+ * parts (first the SACL, then the DACL, then the owner), unless the
+ * WIMLIB_EXTRACT_FLAG_STRICT_ACLS flag has been enabled. */
+ info = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION;
+
+ /* Prefer NtSetSecurityObject() to SetFileSecurity(). SetFileSecurity()
+ * itself necessarily uses NtSetSecurityObject() as the latter is the
+ * underlying system call for setting security information, but
+ * SetFileSecurity() opens the handle with NtCreateFile() without
+ * FILE_OPEN_FILE_BACKUP_INTENT. Hence, access checks are done and due
+ * to the Windows security model, even a process running as the
+ * Administrator can have access denied. (Of course, this not mentioned
+ * in the MS "documentation".) */
+retry:
+ status = (*func_NtSetSecurityObject)(h, info, (PSECURITY_DESCRIPTOR)desc);
+ if (NT_SUCCESS(status))
+ return status;
+ /* 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;
+ 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++;
+ 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 security descriptor if present and not in NO_ACLS mode */
+ if (inode->i_security_id >= 0 &&
+ !(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))
+ {
+ set_errno_from_nt_status(status);
+ ERROR_WITH_ERRNO("Can't set security descriptor "
+ "on \"%ls\" (status=0x%08"PRIx32")",
+ current_path(ctx), (u32)status);
+ 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 = 0;
+ else
+ info.FileAttributes = inode->i_attributes & ~SPECIAL_ATTRIBUTES;
+
+ status = (*func_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))))
+ {
+ set_errno_from_nt_status(status);
+ ERROR_WITH_ERRNO("Can't set basic metadata on \"%ls\" "
+ "(status=0x%08"PRIx32")",
+ current_path(ctx), (u32)status);
+ 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;
+ }
+ }
+ set_errno_from_nt_status(status);
+ ERROR_WITH_ERRNO("Can't open \"%ls\" to set metadata "
+ "(status=0x%08"PRIx32")",
+ current_path(ctx), (u32)status);
+ return WIMLIB_ERR_OPEN;
+ }
+
+ ret = do_apply_metadata_to_file(h, inode, ctx);
+
+ (*func_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);
+ 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)
+ return;
+
+ WARNING("Extraction to \"%ls\" complete, but with one or more warnings:",
+ ctx->common.target);
+ if (ctx->partial_security_descriptors != 0) {
+ WARNING("- Could only partially set the security descriptor\n"
+ " on %lu files or directories.",
+ ctx->partial_security_descriptors);
+ }
+ if (ctx->no_security_descriptors != 0) {
+ WARNING("- Could not set security descriptor at all\n"
+ " on %lu files or directories.",
+ ctx->no_security_descriptors);
+ }
+ WARNING("To fully restore all security descriptors, run the program\n"
+ " with Administrator rights.");
+}
+
+/* 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;
+
+ ret = prepare_target(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) {
+ ret = start_wimboot_extraction(ctx);
+ if (ret)
+ goto out;
+ }
+
+ ret = create_directories(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ ret = create_nondirectories(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ struct read_stream_list_callbacks cbs = {
+ .begin_stream = begin_extract_stream,
+ .begin_stream_ctx = ctx,
+ .consume_chunk = extract_chunk,
+ .consume_chunk_ctx = ctx,
+ .end_stream = end_extract_stream,
+ .end_stream_ctx = ctx,
+ };
+ ret = extract_stream_list(&ctx->common, &cbs);
+ if (ret)
+ goto out;
+
+ ret = apply_metadata(dentry_list, ctx);
+ if (ret)
+ goto out;
+
+ do_warnings(ctx);
+out:
+ if (ctx->h_target)
+ (*func_NtClose)(ctx->h_target);
+ if (ctx->target_ntpath.Buffer)
+ HeapFree(GetProcessHeap(), 0, ctx->target_ntpath.Buffer);
+ FREE(ctx->pathbuf.Buffer);
+ FREE(ctx->print_buffer);
+ if (ctx->wimboot.prepopulate_pats) {
+ FREE(ctx->wimboot.prepopulate_pats->strings);
+ FREE(ctx->wimboot.prepopulate_pats);
+ }
+ FREE(ctx->wimboot.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,
+ .context_size = sizeof(struct win32_apply_ctx),
+};
+