+ close_handles(ctx);
+ return ret;
+}
+
+/* Called when the next chunk of a stream 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 = (*func_NtWriteFile)(ctx->open_handles[i],
+ NULL, NULL, NULL,
+ &ctx->iosb, bufptr, count,
+ NULL, NULL);
+ if (!NT_SUCCESS(status)) {
+ set_errno_from_nt_status(status);
+ ERROR_WITH_ERRNO("Error writing data to target "
+ "volume (status=0x%08"PRIx32")",
+ (u32)status);
+ 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;
+}
+
+/* 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;
+ }