X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_apply.c;h=c182862ea42af8e4483a687df00d665222184216;hp=972224efad978188b191c217508fe734ba2fd2a8;hb=b3562d219976321899ea77bf36c25adf756c7447;hpb=d5447ec541b245fa91dac72ce2822bdaafc30f6c diff --git a/src/win32_apply.c b/src/win32_apply.c index 972224ef..c182862e 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -27,388 +27,414 @@ # include "config.h" #endif -#include /* for SetSecurityInfo() */ - #include "wimlib/win32_common.h" #include "wimlib/apply.h" +#include "wimlib/capture.h" #include "wimlib/dentry.h" -#include "wimlib/endianness.h" #include "wimlib/error.h" #include "wimlib/lookup_table.h" -#include "wimlib/metadata.h" -#include "wimlib/reparse.h" -#include "wimlib/security.h" +#include "wimlib/paths.h" +#include "wimlib/textfile.h" +#include "wimlib/xml.h" +#include "wimlib/wim.h" +#include "wimlib/wimboot.h" + +static void +ctx_save_data_source_id(struct apply_ctx *ctx, u64 data_source_id) +{ + ctx->private[0] = data_source_id & 0xFFFFFFFF; + ctx->private[1] = data_source_id >> 32; +} + +static u64 +ctx_get_data_source_id(const struct apply_ctx *ctx) +{ + return (u32)ctx->private[0] | ((u64)(u32)ctx->private[1] << 32); +} -#define MAX_CREATE_HARD_LINK_WARNINGS 5 -#define MAX_CREATE_SOFT_LINK_WARNINGS 5 +static void +set_prepopulate_pats(struct apply_ctx *ctx, struct string_set *s) +{ + ctx->private[2] = (intptr_t)s; +} -#define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1 -#define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1 +static struct string_set * +get_prepopulate_pats(struct apply_ctx *ctx) +{ + return (struct string_set *)(ctx->private[2]); +} -static const wchar_t *apply_access_denied_msg = -L"If you are not running this program as the administrator, you may\n" - " need to do so, so that all data and metadata can be extracted\n" - " exactly as the origignal copy. However, if you do not care that\n" - " the security descriptors are extracted correctly, you could run\n" - " `wimlib-imagex apply' with the --no-acls flag instead.\n" - ; +static void +free_prepopulate_pats(struct apply_ctx *ctx) +{ + struct string_set *s; + s = get_prepopulate_pats(ctx); + if (s) { + FREE(s->strings); + FREE(s); + } + set_prepopulate_pats(ctx, NULL); + + FREE((void *)ctx->private[3]); + ctx->private[3] = (intptr_t)NULL; +} static int -win32_extract_try_rpfix(u8 *rpbuf, - u16 *rpbuflen_p, - const wchar_t *extract_root_realpath, - unsigned extract_root_realpath_nchars) +load_prepopulate_pats(struct apply_ctx *ctx) { - struct reparse_data rpdata; - wchar_t *target; - size_t target_nchars; - size_t stripped_nchars; - wchar_t *stripped_target; - wchar_t stripped_target_nchars; int ret; + struct wim_dentry *dentry; + struct wim_lookup_table_entry *lte; + struct string_set *s; + const tchar *path = WIMLIB_WIM_PATH_SEPARATOR_STRING T("Windows") + WIMLIB_WIM_PATH_SEPARATOR_STRING T("System32") + WIMLIB_WIM_PATH_SEPARATOR_STRING T("WimBootCompress.ini"); + void *buf; + void *mem; + struct text_file_section sec; + + dentry = get_dentry(ctx->wim, path, WIMLIB_CASE_INSENSITIVE); + if (!dentry || + (dentry->d_inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_REPARSE_POINT | + FILE_ATTRIBUTE_ENCRYPTED)) || + !(lte = inode_unnamed_lte(dentry->d_inode, ctx->wim->lookup_table))) + { + WARNING("%"TS" does not exist in WIM image!", path); + return WIMLIB_ERR_PATH_DOES_NOT_EXIST; + } - utf16lechar *new_target; - utf16lechar *new_print_name; - size_t new_target_nchars; - size_t new_print_name_nchars; - utf16lechar *p; - - ret = parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata); + ret = read_full_stream_into_alloc_buf(lte, &buf); if (ret) return ret; - if (extract_root_realpath[0] == L'\0' || - extract_root_realpath[1] != L':' || - extract_root_realpath[2] != L'\\') - { - ERROR("Can't understand full path format \"%ls\". " - "Try turning reparse point fixups off...", - extract_root_realpath); - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; + s = CALLOC(1, sizeof(struct string_set)); + if (!s) { + FREE(buf); + return WIMLIB_ERR_NOMEM; } - ret = parse_substitute_name(rpdata.substitute_name, - rpdata.substitute_name_nbytes, - rpdata.rptag); - if (ret < 0) - return 0; - stripped_nchars = ret; - target = rpdata.substitute_name; - target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar); - stripped_target = target + stripped_nchars; - stripped_target_nchars = target_nchars - stripped_nchars; - - new_target = alloca((6 + extract_root_realpath_nchars + - stripped_target_nchars) * sizeof(utf16lechar)); - - p = new_target; - if (stripped_nchars == 6) { - /* Include \??\ prefix if it was present before */ - p = wmempcpy(p, L"\\??\\", 4); - } + sec.name = T("PrepopulateList"); + sec.strings = s; - /* Print name excludes the \??\ if present. */ - new_print_name = p; - if (stripped_nchars != 0) { - /* Get drive letter from real path to extract root, if a drive - * letter was present before. */ - *p++ = extract_root_realpath[0]; - *p++ = extract_root_realpath[1]; + ret = do_load_text_file(path, buf, lte->size, &mem, &sec, 1, + LOAD_TEXT_FILE_REMOVE_QUOTES | + LOAD_TEXT_FILE_NO_WARNINGS, + mangle_pat); + FREE(buf); + if (ret) { + FREE(s); + return ret; } - /* Copy the rest of the extract root */ - p = wmempcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2); + set_prepopulate_pats(ctx, s); + ctx->private[3] = (intptr_t)mem; + return 0; +} - /* Append the stripped target */ - p = wmempcpy(p, stripped_target, stripped_target_nchars); - new_target_nchars = p - new_target; - new_print_name_nchars = p - new_print_name; +static bool +in_prepopulate_list(struct wim_dentry *dentry, + struct apply_ctx *ctx) +{ + struct string_set *pats; + const tchar *path; - if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE || - new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE) - { - ERROR("Path names too long to do reparse point fixup!"); - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; - } - rpdata.substitute_name = new_target; - rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar); - rpdata.print_name = new_print_name; - rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar); - return make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p); + pats = get_prepopulate_pats(ctx); + if (!pats) + return false; + path = dentry_full_path(dentry); + if (!path) + return false; + + return match_pattern(path, path_basename(path), pats); } -/* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on - * an extracted reparse point. */ static int -win32_set_reparse_data(HANDLE h, - const struct wim_inode *inode, - const struct wim_lookup_table_entry *lte, - const wchar_t *path, - struct apply_args *args) +win32_start_extract(const wchar_t *path, struct apply_ctx *ctx) { int ret; - u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8); - DWORD bytesReturned; - u16 rpbuflen; - - DEBUG("Setting reparse data on \"%ls\"", path); + unsigned vol_flags; + bool supports_SetFileShortName; - ret = wim_inode_get_reparse_data(inode, rpbuf, &rpbuflen); + ret = win32_get_vol_flags(path, &vol_flags, &supports_SetFileShortName); if (ret) return ret; - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX && - (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) && - !inode->i_not_rpfixed) + ctx->supported_features.archive_files = 1; + ctx->supported_features.hidden_files = 1; + ctx->supported_features.system_files = 1; + + if (vol_flags & FILE_FILE_COMPRESSION) + ctx->supported_features.compressed_files = 1; + + if (vol_flags & FILE_SUPPORTS_ENCRYPTION) { + ctx->supported_features.encrypted_files = 1; + ctx->supported_features.encrypted_directories = 1; + } + + ctx->supported_features.not_context_indexed_files = 1; + + if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) + ctx->supported_features.sparse_files = 1; + + if (vol_flags & FILE_NAMED_STREAMS) + ctx->supported_features.named_data_streams = 1; + + if (vol_flags & FILE_SUPPORTS_HARD_LINKS) + ctx->supported_features.hard_links = 1; + + if (vol_flags & FILE_SUPPORTS_REPARSE_POINTS) { + ctx->supported_features.reparse_points = 1; + if (win32func_CreateSymbolicLinkW) + ctx->supported_features.symlink_reparse_points = 1; + } + + if (vol_flags & FILE_PERSISTENT_ACLS) + ctx->supported_features.security_descriptors = 1; + + if (supports_SetFileShortName) + ctx->supported_features.short_names = 1; + + if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) { + + ret = load_prepopulate_pats(ctx); + if (ret == WIMLIB_ERR_NOMEM) + return ret; + + u64 data_source_id; + + if (!wim_info_get_wimboot(ctx->wim->wim_info, + ctx->wim->current_image)) + WARNING("Image is not marked as WIMBoot compatible!"); + + ret = wimboot_alloc_data_source_id(ctx->wim->filename, + ctx->wim->current_image, + path, &data_source_id); + if (ret) { + free_prepopulate_pats(ctx); + return ret; + } + + ctx_save_data_source_id(ctx, data_source_id); + } + + return 0; +} + +static int +win32_finish_extract(struct apply_ctx *ctx) +{ + free_prepopulate_pats(ctx); + return 0; +} + +/* Delete a non-directory file, working around Windows quirks. */ +static BOOL +win32_delete_file_wrapper(const wchar_t *path) +{ + DWORD err; + DWORD attrib; + + if (DeleteFile(path)) + return TRUE; + + err = GetLastError(); + attrib = GetFileAttributes(path); + if ((attrib != INVALID_FILE_ATTRIBUTES) && + (attrib & FILE_ATTRIBUTE_READONLY)) { - ret = win32_extract_try_rpfix(rpbuf, - &rpbuflen, - args->target_realpath, - args->target_realpath_len); - if (ret) - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; + /* Try again with FILE_ATTRIBUTE_READONLY cleared. */ + attrib &= ~FILE_ATTRIBUTE_READONLY; + if (SetFileAttributes(path, attrib)) { + if (DeleteFile(path)) + return TRUE; + else + err = GetLastError(); + } } - /* Set the reparse data on the open file using the - * FSCTL_SET_REPARSE_POINT ioctl. - * - * There are contradictions in Microsoft's documentation for this: - * - * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED, - * lpOverlapped is ignored." - * - * --- So setting lpOverlapped to NULL is okay since it's ignored. + SetLastError(err); + return FALSE; +} + + +/* Create a normal file, overwriting one already present. */ +static int +win32_create_file(const wchar_t *path, struct apply_ctx *ctx, u64 *cookie_ret) +{ + HANDLE h; + + /* Notes: * - * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an - * operation returns no output data and lpOutBuffer is NULL, - * DeviceIoControl makes use of lpBytesReturned. After such an - * operation, the value of lpBytesReturned is meaningless." + * WRITE_OWNER and WRITE_DAC privileges are required for some reason, + * even through we're creating a new file. * - * --- So lpOverlapped not really ignored, as it affects another - * parameter. This is the actual behavior: lpBytesReturned must be - * specified, even though lpBytesReturned is documented as: + * FILE_FLAG_OPEN_REPARSE_POINT is required to prevent an existing + * reparse point from redirecting the creation of the new file + * (potentially to an arbitrary location). * - * "Not used with this operation; set to NULL." + * CREATE_ALWAYS could be used instead of CREATE_NEW. However, there + * are quirks that would need to be handled (e.g. having to set + * FILE_ATTRIBUTE_HIDDEN and/or FILE_ATTRIBUTE_SYSTEM if the existing + * file had them specified, and/or having to clear + * FILE_ATTRIBUTE_READONLY on the existing file). It's simpler to just + * call win32_delete_file_wrapper() to delete the existing file in such + * a way that already handles the FILE_ATTRIBUTE_READONLY quirk. */ - if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf, - rpbuflen, - NULL, 0, - &bytesReturned /* lpBytesReturned */, - NULL /* lpOverlapped */)) - { +retry: + h = CreateFile(path, WRITE_OWNER | WRITE_DAC, 0, NULL, CREATE_NEW, + FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (h == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - { - args->num_soft_links_failed++; - if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) { - WARNING("Can't set reparse data on \"%ls\": Access denied!\n" - " You may be trying to extract a symbolic " - "link without the\n" - " SeCreateSymbolicLink privilege, which by " - "default non-Administrator\n" - " accounts do not have.", path); - } - if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) { - WARNING("Suppressing further warnings regarding failure to extract\n" - " reparse points due to insufficient privileges..."); - } - } else { - ERROR("Failed to set reparse data on \"%ls\"", path); - win32_error(err); - if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) - return WIMLIB_ERR_LINK; - else - return WIMLIB_ERR_WRITE; - } + + if (err == ERROR_FILE_EXISTS && win32_delete_file_wrapper(path)) + goto retry; + set_errno_from_win32_error(err); + return WIMLIB_ERR_OPEN; } + CloseHandle(h); return 0; } -/* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the - * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */ static int -win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path) +win32_create_directory(const wchar_t *path, struct apply_ctx *ctx, + u64 *cookie_ret) { - DWORD bytesReturned; - if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, - &format, sizeof(USHORT), - NULL, 0, - &bytesReturned, NULL)) - { - /* Could be a warning only, but we only call this if the volume - * supports compression. So I'm calling this an error. */ - DWORD err = GetLastError(); - ERROR("Failed to set compression flag on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; - } + if (!CreateDirectory(path, NULL)) + if (GetLastError() != ERROR_ALREADY_EXISTS) + goto error; return 0; + +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_MKDIR; } -/* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */ static int -win32_set_sparse(HANDLE hFile, const wchar_t *path) +win32_create_hardlink(const wchar_t *oldpath, const wchar_t *newpath, + struct apply_ctx *ctx) { - DWORD bytesReturned; - if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE, - NULL, 0, - NULL, 0, - &bytesReturned, NULL)) - { - /* Could be a warning only, but we only call this if the volume - * supports sparse files. So I'm calling this an error. */ - DWORD err = GetLastError(); - WARNING("Failed to set sparse flag on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; + if (!CreateHardLink(newpath, oldpath, NULL)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + goto error; + if (!win32_delete_file_wrapper(newpath)) + goto error; + if (!CreateHardLink(newpath, oldpath, NULL)) + goto error; } return 0; + +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_LINK; } -/* - * Sets the security descriptor on an extracted file. - */ static int -win32_set_security_data(const struct wim_inode *inode, - HANDLE hFile, - const wchar_t *path, - struct apply_args *args) +win32_create_symlink(const wchar_t *oldpath, const wchar_t *newpath, + struct apply_ctx *ctx) { - PSECURITY_DESCRIPTOR descriptor; - unsigned long n; - DWORD err; - const struct wim_security_data *sd; + if (!(*win32func_CreateSymbolicLinkW)(newpath, oldpath, 0)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + goto error; + if (!win32_delete_file_wrapper(newpath)) + goto error; + if (!(*win32func_CreateSymbolicLinkW)(newpath, oldpath, 0)) + goto error; + } + return 0; - SECURITY_INFORMATION securityInformation = 0; +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_LINK; +} - void *owner = NULL; - void *group = NULL; - ACL *dacl = NULL; - ACL *sacl = NULL; +static int +win32_extract_wim_chunk(const void *buf, size_t len, void *arg) +{ + HANDLE h = (HANDLE)arg; + DWORD nbytes_written; - BOOL owner_defaulted; - BOOL group_defaulted; - BOOL dacl_present; - BOOL dacl_defaulted; - BOOL sacl_present; - BOOL sacl_defaulted; + if (unlikely(!WriteFile(h, buf, len, &nbytes_written, NULL))) + goto error; + if (unlikely(nbytes_written != len)) + goto error; + return 0; - sd = wim_const_security_data(args->w); - descriptor = sd->descriptors[inode->i_security_id]; +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_WRITE; +} - GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted); - if (owner) - securityInformation |= OWNER_SECURITY_INFORMATION; +static int +win32_extract_stream(const wchar_t *path, const wchar_t *stream_name, + size_t stream_name_nchars, + struct wim_lookup_table_entry *lte, struct apply_ctx *ctx) +{ + DWORD creationDisposition = OPEN_EXISTING; + wchar_t *stream_path = (wchar_t*)path; + HANDLE h; + int ret; - GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted); - if (group) - securityInformation |= GROUP_SECURITY_INFORMATION; + if (stream_name_nchars) { + creationDisposition = CREATE_ALWAYS; + stream_path = alloca(sizeof(wchar_t) * + (wcslen(path) + 1 + + wcslen(stream_name) + 1)); + tsprintf(stream_path, L"%ls:%ls", path, stream_name); + } - GetSecurityDescriptorDacl(descriptor, &dacl_present, - &dacl, &dacl_defaulted); - if (dacl) - securityInformation |= DACL_SECURITY_INFORMATION; + h = CreateFile(stream_path, FILE_WRITE_DATA, 0, NULL, + creationDisposition, FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT, + NULL); + if (h == INVALID_HANDLE_VALUE) + goto error; - GetSecurityDescriptorSacl(descriptor, &sacl_present, - &sacl, &sacl_defaulted); - if (sacl) - securityInformation |= SACL_SECURITY_INFORMATION; + ret = 0; + if (!lte) + goto out_close_handle; + ret = extract_stream(lte, lte->size, win32_extract_wim_chunk, h); +out_close_handle: + if (!CloseHandle(h)) + goto error; + if (ret && !errno) + errno = -1; + return ret; -again: - if (securityInformation == 0) - return 0; - if (SetSecurityInfo(hFile, SE_FILE_OBJECT, - securityInformation, owner, group, dacl, sacl)) - return 0; - err = GetLastError(); - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) - goto fail; - switch (err) { - case ERROR_PRIVILEGE_NOT_HELD: - if (securityInformation & SACL_SECURITY_INFORMATION) { - n = args->num_set_sacl_priv_notheld++; - securityInformation &= ~SACL_SECURITY_INFORMATION; - sacl = NULL; - if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) { - WARNING( -"We don't have enough privileges to set the full security\n" -" descriptor on \"%ls\"!\n", path); - if (args->num_set_sd_access_denied + - args->num_set_sacl_priv_notheld == 1) - { - WARNING("%ls", apply_access_denied_msg); - } - WARNING("Re-trying with SACL omitted.\n", path); - } else if (n == MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) { - WARNING( -"Suppressing further 'privileges not held' error messages when setting\n" -" security descriptors."); - } - goto again; - } - /* Fall through */ - case ERROR_INVALID_OWNER: - case ERROR_ACCESS_DENIED: - n = args->num_set_sd_access_denied++; - if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) { - WARNING("Failed to set security descriptor on \"%ls\": " - "Access denied!\n", path); - if (args->num_set_sd_access_denied + - args->num_set_sacl_priv_notheld == 1) - { - WARNING("%ls", apply_access_denied_msg); - } - } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) { - WARNING( -"Suppressing further access denied error messages when setting\n" -" security descriptors"); - } - return 0; - default: -fail: - ERROR("Failed to set security descriptor on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; - } +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_WRITE; } - static int -win32_extract_chunk(const void *buf, size_t len, void *arg) +win32_extract_unnamed_stream(file_spec_t file, + struct wim_lookup_table_entry *lte, + struct apply_ctx *ctx, + struct wim_dentry *dentry) { - HANDLE hStream = arg; - - DWORD nbytes_written; - wimlib_assert(len <= 0xffffffff); - - if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) || - nbytes_written != len) + if (ctx->extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT + && lte + && lte->resource_location == RESOURCE_IN_WIM + && lte->rspec->wim == ctx->wim + && !in_prepopulate_list(dentry, ctx)) { - DWORD err = GetLastError(); - ERROR("WriteFile(): write error"); - win32_error(err); - return WIMLIB_ERR_WRITE; + return wimboot_set_pointer(file.path, + ctx_get_data_source_id(ctx), + lte->hash); } - return 0; + + return win32_extract_stream(file.path, NULL, 0, lte, ctx); } static int -do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte) +win32_extract_named_stream(file_spec_t file, const wchar_t *stream_name, + size_t stream_name_nchars, + struct wim_lookup_table_entry *lte, struct apply_ctx *ctx) { - return extract_wim_resource(lte, wim_resource_size(lte), - win32_extract_chunk, hStream); + return win32_extract_stream(file.path, stream_name, + stream_name_nchars, lte, ctx); } struct win32_encrypted_extract_ctx { @@ -417,909 +443,472 @@ struct win32_encrypted_extract_ctx { }; static DWORD WINAPI -win32_encrypted_import_cb(unsigned char *data, void *_ctx, +win32_encrypted_import_cb(unsigned char *data, void *_import_ctx, unsigned long *len_p) { - struct win32_encrypted_extract_ctx *ctx = _ctx; + struct win32_encrypted_extract_ctx *import_ctx = _import_ctx; unsigned long len = *len_p; - const struct wim_lookup_table_entry *lte = ctx->lte; + const struct wim_lookup_table_entry *lte = import_ctx->lte; - len = min(len, wim_resource_size(lte) - ctx->offset); + len = min(len, lte->size - import_ctx->offset); - if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data)) + if (read_partial_wim_stream_into_buf(lte, len, import_ctx->offset, data)) return ERROR_READ_FAULT; - ctx->offset += len; + import_ctx->offset += len; *len_p = len; return ERROR_SUCCESS; } -/* Create an encrypted file and extract the raw encrypted data to it. - * - * @path: Path to encrypted file to create. - * @lte: WIM lookup_table entry for the raw encrypted data. - * - * This is separate from do_win32_extract_stream() because the WIM is supposed - * to contain the *raw* encrypted data, which needs to be extracted ("imported") - * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and - * CloseEncryptedFileRaw(). - * - * Returns 0 on success; nonzero on failure. - */ static int -do_win32_extract_encrypted_stream(const wchar_t *path, - const struct wim_lookup_table_entry *lte) +win32_extract_encrypted_stream(const wchar_t *path, + struct wim_lookup_table_entry *lte, + struct apply_ctx *ctx) { void *file_ctx; + DWORD err; int ret; + struct win32_encrypted_extract_ctx extract_ctx; - DEBUG("Opening file \"%ls\" to extract raw encrypted data", path); - - ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx); - if (ret) { - ERROR("Failed to open \"%ls\" to write raw encrypted data", path); - win32_error(ret); - return WIMLIB_ERR_OPEN; + err = OpenEncryptedFileRaw(path, CREATE_FOR_IMPORT, &file_ctx); + if (err != ERROR_SUCCESS) { + set_errno_from_win32_error(err); + ret = WIMLIB_ERR_OPEN; + goto out; } - if (lte) { - struct win32_encrypted_extract_ctx ctx; - - ctx.lte = lte; - ctx.offset = 0; - ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx); - if (ret == ERROR_SUCCESS) { - ret = 0; - } else { - ret = WIMLIB_ERR_WRITE; - ERROR("Failed to extract encrypted file \"%ls\"", path); - } + extract_ctx.lte = lte; + extract_ctx.offset = 0; + err = WriteEncryptedFileRaw(win32_encrypted_import_cb, &extract_ctx, + file_ctx); + if (err != ERROR_SUCCESS) { + set_errno_from_win32_error(err); + ret = WIMLIB_ERR_WRITE; + goto out_close; } + + ret = 0; +out_close: CloseEncryptedFileRaw(file_ctx); +out: return ret; } -static bool -path_is_root_of_drive(const wchar_t *path) +static BOOL +win32_set_special_file_attributes(const wchar_t *path, u32 attributes) { - if (*path == L'\0') - return false; + HANDLE h; + DWORD err; + USHORT compression_format = COMPRESSION_FORMAT_DEFAULT; + DWORD bytes_returned; - if (!wcsncmp(path, L"\\\\?\\", 4)) - path += 4; + h = win32_open_existing_file(path, GENERIC_READ | GENERIC_WRITE); + if (h == INVALID_HANDLE_VALUE) + goto error; - if (*path != L'/' && *path != L'\\') { - if (*(path + 1) == L':') - path += 2; - else - return false; - } - while (*path == L'/' || *path == L'\\') - path++; - return (*path == L'\0'); -} + if (attributes & FILE_ATTRIBUTE_SPARSE_FILE) + if (!DeviceIoControl(h, FSCTL_SET_SPARSE, + NULL, 0, + NULL, 0, + &bytes_returned, NULL)) + goto error_close_handle; -static inline DWORD -win32_mask_attributes(DWORD i_attributes) -{ - return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE | - FILE_ATTRIBUTE_COMPRESSED | - FILE_ATTRIBUTE_REPARSE_POINT | - FILE_ATTRIBUTE_DIRECTORY | - FILE_ATTRIBUTE_ENCRYPTED | - FILE_FLAG_DELETE_ON_CLOSE | - FILE_FLAG_NO_BUFFERING | - FILE_FLAG_OPEN_NO_RECALL | - FILE_FLAG_OVERLAPPED | - FILE_FLAG_RANDOM_ACCESS | - /*FILE_FLAG_SESSION_AWARE |*/ - FILE_FLAG_SEQUENTIAL_SCAN | - FILE_FLAG_WRITE_THROUGH); -} + if (attributes & FILE_ATTRIBUTE_COMPRESSED) + if (!DeviceIoControl(h, FSCTL_SET_COMPRESSION, + &compression_format, sizeof(USHORT), + NULL, 0, + &bytes_returned, NULL)) + goto error_close_handle; -static inline DWORD -win32_get_create_flags_and_attributes(DWORD i_attributes) -{ - /* - * Some attributes cannot be set by passing them to CreateFile(). In - * particular: - * - * FILE_ATTRIBUTE_DIRECTORY: - * CreateDirectory() must be called instead of CreateFile(). - * - * FILE_ATTRIBUTE_SPARSE_FILE: - * Needs an ioctl. - * See: win32_set_sparse(). - * - * FILE_ATTRIBUTE_COMPRESSED: - * Not clear from the documentation, but apparently this needs an - * ioctl as well. - * See: win32_set_compressed(). - * - * FILE_ATTRIBUTE_REPARSE_POINT: - * Needs an ioctl, with the reparse data specified. - * See: win32_set_reparse_data(). - * - * In addition, clear any file flags in the attributes that we don't - * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and - * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application. - */ - return win32_mask_attributes(i_attributes) | - FILE_FLAG_OPEN_REPARSE_POINT | - FILE_FLAG_BACKUP_SEMANTICS; -} + if (!CloseHandle(h)) + goto error; -/* Set compression and/or sparse attributes on a stream, if supported by the - * volume. */ -static int -win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode, - struct wim_lookup_table_entry *unnamed_stream_lte, - const wchar_t *path, unsigned vol_flags) -{ - int ret; + if (attributes & FILE_ATTRIBUTE_ENCRYPTED) + if (!EncryptFile(path)) + goto error; - if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) { - if (vol_flags & FILE_FILE_COMPRESSION) { - ret = win32_set_compression_state(hFile, - COMPRESSION_FORMAT_DEFAULT, - path); - if (ret) - return ret; - } else { - DEBUG("Cannot set compression attribute on \"%ls\": " - "volume does not support transparent compression", - path); - } - } + return TRUE; - if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) { - if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) { - DEBUG("Setting sparse flag on \"%ls\"", path); - ret = win32_set_sparse(hFile, path); - if (ret) - return ret; - } else { - DEBUG("Cannot set sparse attribute on \"%ls\": " - "volume does not support sparse files", - path); - } - } - return 0; +error_close_handle: + err = GetLastError(); + CloseHandle(h); + SetLastError(err); +error: + return FALSE; } -/* Pre-create directories; extract encrypted streams */ static int -win32_begin_extract_unnamed_stream(const struct wim_inode *inode, - const struct wim_lookup_table_entry *lte, - const wchar_t *path, - DWORD *creationDisposition_ret, - unsigned int vol_flags) +win32_set_file_attributes(const wchar_t *path, u32 attributes, + struct apply_ctx *ctx, unsigned pass) { - DWORD err; - int ret; - - /* Directories must be created with CreateDirectoryW(). Then the call - * to CreateFileW() will merely open the directory that was already - * created rather than creating a new file. */ - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { - if (!path_is_root_of_drive(path)) { - if (!CreateDirectoryW(path, NULL)) { - err = GetLastError(); - if (err != ERROR_ALREADY_EXISTS) { - ERROR("Failed to create directory \"%ls\"", - path); - win32_error(err); - return WIMLIB_ERR_MKDIR; - } - } - DEBUG("Created directory \"%ls\"", path); - } - *creationDisposition_ret = OPEN_EXISTING; - } - if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED && - vol_flags & FILE_SUPPORTS_ENCRYPTION) - { - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { - unsigned remaining_sharing_violations = 100; - while (!EncryptFile(path)) { - if (remaining_sharing_violations && - err == ERROR_SHARING_VIOLATION) - { - WARNING("Couldn't encrypt directory \"%ls\" " - "due to sharing violation; re-trying " - "after 100 ms", path); - Sleep(100); - remaining_sharing_violations--; - } else { - err = GetLastError(); - ERROR("Failed to encrypt directory \"%ls\"", - path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - } - } else { - ret = do_win32_extract_encrypted_stream(path, lte); - if (ret) - return ret; - DEBUG("Extracted encrypted file \"%ls\"", path); - } - *creationDisposition_ret = OPEN_EXISTING; - } + u32 special_attributes = + FILE_ATTRIBUTE_REPARSE_POINT | + FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_SPARSE_FILE | + FILE_ATTRIBUTE_COMPRESSED | + FILE_ATTRIBUTE_ENCRYPTED; + u32 actual_attributes; + + /* Delay setting FILE_ATTRIBUTE_READONLY on the initial pass (when files + * are created, but data not extracted); otherwise the system will + * refuse access to the file even if the process has SeRestorePrivilege. + */ + if (pass == 0) + attributes &= ~FILE_ATTRIBUTE_READONLY; - /* Set file attributes if we created the file. Otherwise, we haven't - * created the file set and we will set the attributes in the call to - * CreateFileW(). - * - * The FAT filesystem does not let you change the attributes of the root - * directory, so treat that as a special case and do not set attributes. - * */ - if (*creationDisposition_ret == OPEN_EXISTING && - !path_is_root_of_drive(path)) - { - if (!SetFileAttributesW(path, - win32_mask_attributes(inode->i_attributes))) - { - err = GetLastError(); - ERROR("Failed to set attributes on \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - } - return 0; -} + if (!SetFileAttributes(path, attributes & ~special_attributes)) + goto error; -/* Set security descriptor and extract stream data or reparse data (skip the - * unnamed data stream of encrypted files, which was already extracted). */ -static int -win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry, - const struct wim_lookup_table_entry *lte, - const wchar_t *stream_path, - const wchar_t *stream_name_utf16, - struct apply_args *args) -{ - int ret = 0; - const struct wim_inode *inode = dentry->d_inode; - if (stream_name_utf16 == NULL) { - /* Unnamed stream. */ - - /* Set security descriptor, unless the extract_flags indicate - * not to or the volume does not supported it. Note that this - * is only done when the unnamed stream is being extracted, as - * security descriptors are per-file and not per-stream. */ - if (inode->i_security_id >= 0 && - !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS) - && (args->vol_flags & FILE_PERSISTENT_ACLS)) - { - ret = win32_set_security_data(inode, h, stream_path, args); - if (ret) - return ret; - } + if (pass != 0) + return 0; - /* Handle reparse points. The data for them needs to be set - * using a special ioctl. Note that the reparse point may have - * been created using CreateFileW() in the case of - * non-directories or CreateDirectoryW() in the case of - * directories; but the ioctl works either way. Also, it is - * only this step that actually sets the - * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it - * using SetFileAttributesW() or CreateFileW(). - * - * If the volume does not support reparse points we simply - * ignore the reparse data. (N.B. the code currently doesn't - * actually reach this case because reparse points are skipped - * entirely on such volumes.) */ - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { - if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) { - ret = win32_set_reparse_data(h, inode, - lte, stream_path, - args); - if (ret) - return ret; - } else { - DEBUG("Cannot set reparse data on \"%ls\": volume " - "does not support reparse points", stream_path); - } - } else if (lte != NULL && - !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION && - inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) + if (attributes & (FILE_ATTRIBUTE_SPARSE_FILE | + FILE_ATTRIBUTE_ENCRYPTED | + FILE_ATTRIBUTE_COMPRESSED)) + if (!win32_set_special_file_attributes(path, attributes)) + goto error; + + /* If file is not supposed to be encrypted or compressed, remove + * defaulted encrypted or compressed attributes (from creating file in + * encrypted or compressed directory). */ + actual_attributes = GetFileAttributes(path); + if (actual_attributes == INVALID_FILE_ATTRIBUTES) + goto error; + + if ((actual_attributes & FILE_ATTRIBUTE_ENCRYPTED) && + !(attributes & FILE_ATTRIBUTE_ENCRYPTED)) + if (!DecryptFile(path, 0)) + goto error; + if ((actual_attributes & FILE_ATTRIBUTE_COMPRESSED) && + !(attributes & FILE_ATTRIBUTE_COMPRESSED)) + { + HANDLE h; + DWORD bytes_returned; + USHORT compression_format = COMPRESSION_FORMAT_NONE; + + h = win32_open_existing_file(path, GENERIC_READ | GENERIC_WRITE); + if (h == INVALID_HANDLE_VALUE) + goto error; + + if (!DeviceIoControl(h, FSCTL_SET_COMPRESSION, + &compression_format, sizeof(USHORT), + NULL, 0, + &bytes_returned, NULL)) { - /* Extract the data of the unnamed stream, unless the - * lookup table entry is NULL (indicating an empty - * stream for which no data needs to be extracted), or - * the stream is encrypted and therefore was already - * extracted as a special case. */ - ret = do_win32_extract_stream(h, lte); - if (ret) - return ret; + DWORD err = GetLastError(); + CloseHandle(h); + SetLastError(err); + goto error; } - if (dentry_has_short_name(dentry)) - SetFileShortNameW(h, dentry->short_name); - else if (running_on_windows_7_or_later()) - SetFileShortNameW(h, L""); - } else { - /* Extract the data for a named data stream. */ - if (lte != NULL) { - DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")", - stream_path, wim_resource_size(lte)); - ret = do_win32_extract_stream(h, lte); - } + if (!CloseHandle(h)) + goto error; } - return ret; -} -static int -win32_decrypt_file(HANDLE open_handle, const wchar_t *path) -{ - DWORD err; - /* We cannot call DecryptFileW() while there is an open handle to the - * file. So close it first. */ - if (!CloseHandle(open_handle)) { - err = GetLastError(); - ERROR("Failed to close handle for \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) { - err = GetLastError(); - ERROR("Failed to decrypt file \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } return 0; + +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_SET_ATTRIBUTES; } -/* - * Create and extract a stream to a file, or create a directory, using the - * Windows API. - * - * This handles reparse points, directories, alternate data streams, encrypted - * files, compressed files, etc. - * - * @dentry: WIM dentry for the file or directory being extracted. - * - * @path: Path to extract the file to. - * - * @stream_name_utf16: - * Name of the stream, or NULL if the stream is unnamed. This will - * be called with a NULL stream_name_utf16 before any non-NULL - * stream_name_utf16's. - * - * @lte: WIM lookup table entry for the stream. May be NULL to indicate - * a stream of length 0. - * - * @args: Additional apply context, including flags indicating supported - * volume features. - * - * Returns 0 on success; nonzero on failure. - */ static int -win32_extract_stream(const struct wim_dentry *dentry, - const wchar_t *path, - const wchar_t *stream_name_utf16, - struct wim_lookup_table_entry *lte, - struct apply_args *args) +win32_set_reparse_data(const wchar_t *path, const u8 *rpbuf, u16 rpbuflen, + struct apply_ctx *ctx) { - wchar_t *stream_path; HANDLE h; - int ret; DWORD err; - DWORD creationDisposition = CREATE_ALWAYS; - DWORD requestedAccess; - BY_HANDLE_FILE_INFORMATION file_info; - unsigned remaining_sharing_violations = 1000; - const struct wim_inode *inode = dentry->d_inode; - - if (stream_name_utf16) { - /* Named stream. Create a buffer that contains the UTF-16LE - * string [./]path:stream_name_utf16. This is needed to - * create and open the stream using CreateFileW(). I'm not - * aware of any other APIs to do this. Note: the '$DATA' suffix - * seems to be unneeded. Additional note: a "./" prefix needs - * to be added when the path is not absolute to avoid ambiguity - * with drive letters. */ - size_t stream_path_nchars; - size_t path_nchars; - size_t stream_name_nchars; - const wchar_t *prefix; - - path_nchars = wcslen(path); - stream_name_nchars = wcslen(stream_name_utf16); - stream_path_nchars = path_nchars + 1 + stream_name_nchars; - if (path[0] != cpu_to_le16(L'\0') && - path[0] != cpu_to_le16(L'/') && - path[0] != cpu_to_le16(L'\\') && - path[1] != cpu_to_le16(L':')) - { - prefix = L"./"; - stream_path_nchars += 2; - } else { - prefix = L""; - } - stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t)); - swprintf(stream_path, L"%ls%ls:%ls", - prefix, path, stream_name_utf16); - } else { - /* Unnamed stream; its path is just the path to the file itself. - * */ - stream_path = (wchar_t*)path; - - ret = win32_begin_extract_unnamed_stream(inode, lte, path, - &creationDisposition, - args->vol_flags); - if (ret) - goto fail; - } + DWORD bytes_returned; - DEBUG("Opening \"%ls\"", stream_path); - /* DELETE access is needed for SetFileShortNameW(), for some reason. */ - requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE | - ACCESS_SYSTEM_SECURITY; -try_open_again: - /* Open the stream to be extracted. Depending on what we have set - * creationDisposition to, we may be creating this for the first time, - * or we may be opening on existing stream we already created using - * CreateDirectoryW() or OpenEncryptedFileRawW(). */ - h = CreateFileW(stream_path, - requestedAccess, - FILE_SHARE_READ, - NULL, - creationDisposition, - win32_get_create_flags_and_attributes(inode->i_attributes), - NULL); - if (h == INVALID_HANDLE_VALUE) { - err = GetLastError(); - if (err == ERROR_ACCESS_DENIED && - path_is_root_of_drive(stream_path)) - { - ret = 0; - goto out; - } - if ((err == ERROR_PRIVILEGE_NOT_HELD || - err == ERROR_ACCESS_DENIED) && - (requestedAccess & ACCESS_SYSTEM_SECURITY)) - { - /* Try opening the file again without privilege to - * modify SACL. */ - requestedAccess &= ~ACCESS_SYSTEM_SECURITY; - goto try_open_again; - } - if (err == ERROR_SHARING_VIOLATION) { - if (remaining_sharing_violations) { - --remaining_sharing_violations; - /* This can happen when restoring encrypted directories - * for some reason. Probably a bug in EncryptFile(). */ - WARNING("Couldn't open \"%ls\" due to sharing violation; " - "re-trying after 100ms", stream_path); - Sleep(100); - goto try_open_again; - } else { - ERROR("Too many sharing violations; giving up..."); - } - } else { - if (creationDisposition == OPEN_EXISTING) - ERROR("Failed to open \"%ls\"", stream_path); - else - ERROR("Failed to create \"%ls\"", stream_path); - win32_error(err); - } - ret = WIMLIB_ERR_OPEN; - goto fail; - } + h = win32_open_existing_file(path, GENERIC_WRITE); + if (h == INVALID_HANDLE_VALUE) + goto error; - /* Check the attributes of the file we just opened, and remove - * encryption or compression if either was set by default but is not - * supposed to be set based on the WIM inode attributes. */ - if (!GetFileInformationByHandle(h, &file_info)) { - err = GetLastError(); - ERROR("Failed to get attributes of \"%ls\"", stream_path); - win32_error(err); - ret = WIMLIB_ERR_STAT; - goto fail_close_handle; - } + if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, + (void*)rpbuf, rpbuflen, + NULL, 0, &bytes_returned, NULL)) + goto error_close_handle; - /* Remove encryption? */ - if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED && - !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) - { - /* File defaulted to encrypted due to being in an encrypted - * directory, but is not actually supposed to be encrypted. - * - * This is a workaround, because I'm not aware of any way to - * directly (e.g. with CreateFileW()) create an unencrypted file - * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */ - ret = win32_decrypt_file(h, stream_path); - if (ret) - goto fail; /* win32_decrypt_file() closed the handle. */ - creationDisposition = OPEN_EXISTING; - goto try_open_again; - } + if (!CloseHandle(h)) + goto error; - /* Remove compression? */ - if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED && - !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)) - { - /* Similar to the encrypted case, above, if the file defaulted - * to compressed due to being in an compressed directory, but is - * not actually supposed to be compressed, explicitly set the - * compression format to COMPRESSION_FORMAT_NONE. */ - ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE, - stream_path); - if (ret) - goto fail_close_handle; - } - - /* Set compression and/or sparse attributes if needed */ - ret = win32_set_special_stream_attributes(h, inode, lte, path, - args->vol_flags); + return 0; - if (ret) - goto fail_close_handle; - - /* At this point we have at least created the needed stream with the - * appropriate attributes. We have yet to set the appropriate security - * descriptor and actually extract the stream data (other than for - * extracted files, which were already extracted). - * win32_finish_extract_stream() handles these additional steps. */ - ret = win32_finish_extract_stream(h, dentry, lte, stream_path, - stream_name_utf16, args); - if (ret) - goto fail_close_handle; - - /* Done extracting the stream. Close the handle and return. */ - DEBUG("Closing \"%ls\"", stream_path); - if (!CloseHandle(h)) { - err = GetLastError(); - ERROR("Failed to close \"%ls\"", stream_path); - win32_error(err); - ret = WIMLIB_ERR_WRITE; - goto fail; - } - ret = 0; - goto out; -fail_close_handle: +error_close_handle: + err = GetLastError(); CloseHandle(h); -fail: - ERROR("Error extracting \"%ls\"", stream_path); -out: - return ret; + SetLastError(err); +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_WRITE; /* XXX: need better error code */ } -/* - * Creates a file, directory, or reparse point and extracts all streams to it - * (unnamed data stream and/or reparse point stream, plus any alternate data - * streams). Handles sparse, compressed, and/or encrypted files. - * - * @dentry: WIM dentry for this file or directory. - * @path: UTF-16LE external path to extract the inode to. - * @args: Additional extraction context. - * - * Returns 0 on success; nonzero on failure. - */ static int -win32_extract_streams(const struct wim_dentry *dentry, - const wchar_t *path, struct apply_args *args) +win32_set_short_name(const wchar_t *path, const wchar_t *short_name, + size_t short_name_nchars, struct apply_ctx *ctx) { - struct wim_lookup_table_entry *unnamed_lte; - int ret; - const struct wim_inode *inode = dentry->d_inode; - - /* First extract the unnamed stream. */ - - unnamed_lte = inode_unnamed_lte_resolved(inode); - ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args); - if (ret) - goto out; + HANDLE h; + DWORD err; - /* Extract any named streams, if supported by the volume. */ + h = win32_open_existing_file(path, GENERIC_WRITE | DELETE); + if (h == INVALID_HANDLE_VALUE) + goto error; - if (!(args->vol_flags & FILE_NAMED_STREAMS)) - goto out; - for (u16 i = 0; i < inode->i_num_ads; i++) { - const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i]; - - /* Skip the unnamed stream if it's in the ADS entries (we - * already extracted it...) */ - if (ads_entry->stream_name_nbytes == 0) - continue; - - /* Skip special UNIX data entries (see documentation for - * WIMLIB_ADD_FLAG_UNIX_DATA) */ - if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES - && !memcmp(ads_entry->stream_name, - WIMLIB_UNIX_DATA_TAG_UTF16LE, - WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES)) - continue; - - /* Extract the named stream */ - ret = win32_extract_stream(dentry, - path, - ads_entry->stream_name, - ads_entry->lte, - args); - if (ret) - break; + if (short_name_nchars) { + if (!SetFileShortName(h, short_name)) + goto error_close_handle; + } else if (running_on_windows_7_or_later()) { + if (!SetFileShortName(h, L"")) + goto error_close_handle; } -out: - return ret; -} -static int -dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore) -{ - dentry->d_inode->i_visited = 0; - return 0; -} + if (!CloseHandle(h)) + goto error; -static int -dentry_get_features(struct wim_dentry *dentry, void *_features_p) -{ - DWORD features = 0; - DWORD *features_p = _features_p; - struct wim_inode *inode = dentry->d_inode; - - if (inode->i_visited) { - features |= FILE_SUPPORTS_HARD_LINKS; - } else { - inode->i_visited = 1; - if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) - features |= FILE_SUPPORTS_SPARSE_FILES; - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) - features |= FILE_SUPPORTS_REPARSE_POINTS; - for (unsigned i = 0; i < inode->i_num_ads; i++) - if (inode->i_ads_entries[i].stream_name_nbytes) - features |= FILE_NAMED_STREAMS; - if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) - features |= FILE_SUPPORTS_ENCRYPTION; - if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) - features |= FILE_FILE_COMPRESSION; - if (inode->i_security_id != -1) - features |= FILE_PERSISTENT_ACLS; - } - *features_p |= features; return 0; + +error_close_handle: + err = GetLastError(); + CloseHandle(h); + SetLastError(err); +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_WRITE; /* XXX: need better error code */ } -/* If not done already, load the supported feature flags for the volume onto - * which the image is being extracted, and warn the user about any missing - * features that could be important. */ -static int -win32_check_vol_flags(const wchar_t *output_path, - struct wim_dentry *root, struct apply_args *args) +static DWORD +do_win32_set_security_descriptor(HANDLE h, const wchar_t *path, + SECURITY_INFORMATION info, + PSECURITY_DESCRIPTOR desc) { - DWORD dentry_features = 0; - DWORD missing_features; - - if (args->have_vol_flags) - return 0; - - for_dentry_in_tree(root, dentry_clear_inode_visited, NULL); - for_dentry_in_tree(root, dentry_get_features, &dentry_features); - - win32_get_vol_flags(output_path, &args->vol_flags); - args->have_vol_flags = true; - - missing_features = dentry_features & ~args->vol_flags; - - /* Warn the user about data that may not be extracted. */ - if (missing_features & FILE_SUPPORTS_SPARSE_FILES) - WARNING("Volume does not support sparse files!\n" - " Sparse files will be extracted as non-sparse."); - if (missing_features & FILE_SUPPORTS_REPARSE_POINTS) - WARNING("Volume does not support reparse points!\n" - " Reparse point data will not be extracted."); - if (missing_features & FILE_NAMED_STREAMS) { - WARNING("Volume does not support named data streams!\n" - " Named data streams will not be extracted."); - } - if (missing_features & FILE_SUPPORTS_ENCRYPTION) { - WARNING("Volume does not support encryption!\n" - " Encrypted files will be extracted as raw data."); - } - if (missing_features & FILE_FILE_COMPRESSION) { - WARNING("Volume does not support transparent compression!\n" - " Compressed files will be extracted as non-compressed."); - } - if (missing_features & FILE_PERSISTENT_ACLS) { - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) { - ERROR("Strict ACLs requested, but the volume does not " - "support ACLs!"); - return WIMLIB_ERR_VOLUME_LACKS_FEATURES; - } else { - WARNING("Volume does not support persistent ACLS!\n" - " File permissions will not be extracted."); - } - } - if (running_on_windows_7_or_later() && - (missing_features & FILE_SUPPORTS_HARD_LINKS)) - { - WARNING("Volume does not support hard links!\n" - " Hard links will be extracted as duplicate files."); +#ifdef WITH_NTDLL + if (func_NtSetSecurityObject) { + return (*func_RtlNtStatusToDosError)( + (*func_NtSetSecurityObject)(h, info, desc)); } - return 0; +#endif + if (SetFileSecurity(path, info, desc)) + return ERROR_SUCCESS; + else + return GetLastError(); } /* - * Try extracting a hard link. - * - * @output_path: Path to link to be extracted. + * Set an arbitrary security descriptor on an arbitrary file (or directory), + * working around bugs and design flaws in the Windows operating system. * - * @inode: WIM inode that the link is to; inode->i_extracted_file - * the path to a name of the file that has already been - * extracted (we use this to create the hard link). - * - * @args: Additional apply context, used here to keep track of - * the number of times creating a hard link failed due to - * ERROR_INVALID_FUNCTION. This error should indicate that hard - * links are not supported by the volume, and we would like to - * warn the user a few times, but not too many times. - * - * Returns 0 if the hard link was successfully extracted. Returns - * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly - * being unsupported by the volume. Returns a negative value if creating the - * hard link failed due to ERROR_INVALID_FUNCTION. + * On success, return 0. On failure, return WIMLIB_ERR_SET_SECURITY and set + * errno. Note: if WIMLIB_EXTRACT_FLAG_STRICT_ACLS is not set in + * ctx->extract_flags, this function succeeds iff any part of the security + * descriptor was successfully set. */ static int -win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode, - struct apply_args *args) +win32_set_security_descriptor(const wchar_t *path, const u8 *desc, + size_t desc_size, struct apply_ctx *ctx) { - DWORD err; - - /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS), - * but it's only available on Windows 7 and later. - * - * Otherwise, CreateHardLinkW() will apparently return - * ERROR_INVALID_FUNCTION if the volume does not support hard links. */ - - DEBUG("Creating hard link \"%ls => %ls\"", - output_path, inode->i_extracted_file); - - if (running_on_windows_7_or_later() && - !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS)) - goto hard_links_unsupported; - - if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL)) - return 0; + SECURITY_INFORMATION info; + HANDLE h; + int ret; - err = GetLastError(); - if (err != ERROR_INVALID_FUNCTION) { - ERROR("Can't create hard link \"%ls => %ls\"", - output_path, inode->i_extracted_file); - win32_error(err); - return WIMLIB_ERR_LINK; - } -hard_links_unsupported: - args->num_hard_links_failed++; - if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) { - if (running_on_windows_7_or_later()) - { - WARNING("Extracting duplicate copy of \"%ls\" " - "rather than hard link", output_path); - } else { - WARNING("Can't create hard link \"%ls\" => \"%ls\":\n" - " Volume does not support hard links!\n" - " Falling back to extracting a copy of the file.", - output_path, inode->i_extracted_file); + /* 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; + + h = INVALID_HANDLE_VALUE; + + /* 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".) */ + +#ifdef WITH_NTDLL + if (func_NtSetSecurityObject) { + DWORD dwDesiredAccess; + + /* Open a handle for NtSetSecurityObject() with as many relevant + * access rights as possible. + * + * We don't know which rights will be actually granted. It + * could be less than what is needed to actually assign the full + * security descriptor, especially if the process is running as + * a non-Administrator. However, by default we just do the best + * we can, unless WIMLIB_EXTRACT_FLAG_STRICT_ACLS has been + * enabled. The MAXIMUM_ALLOWED access right is seemingly + * designed for this use case; however, it does not work + * properly in all cases: it can cause CreateFile() to fail with + * ERROR_ACCESS_DENIED, even though by definition + * MAXIMUM_ALLOWED access only requests access rights that are + * *not* denied. (Needless to say, MS does not document this + * bug.) */ + + dwDesiredAccess = WRITE_DAC | + WRITE_OWNER | + ACCESS_SYSTEM_SECURITY; + for (;;) { + DWORD err; + + h = win32_open_existing_file(path, dwDesiredAccess); + if (h != INVALID_HANDLE_VALUE) + break; + err = GetLastError(); + if (err == ERROR_ACCESS_DENIED || + err == ERROR_PRIVILEGE_NOT_HELD) + { + /* Don't increment partial_security_descriptors + * here or check WIMLIB_EXTRACT_FLAG_STRICT_ACLS + * here. It will be done later if needed; here + * we are just trying to get as many relevant + * access rights as possible. */ + if (dwDesiredAccess & ACCESS_SYSTEM_SECURITY) { + dwDesiredAccess &= ~ACCESS_SYSTEM_SECURITY; + continue; + } + if (dwDesiredAccess & WRITE_DAC) { + dwDesiredAccess &= ~WRITE_DAC; + continue; + } + if (dwDesiredAccess & WRITE_OWNER) { + dwDesiredAccess &= ~WRITE_OWNER; + continue; + } + } + /* Other error, or couldn't open the file even with no + * access rights specified. Something else must be + * wrong. */ + set_errno_from_win32_error(err); + return WIMLIB_ERR_SET_SECURITY; } } - if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) - WARNING("Suppressing further hard linking warnings..."); - return -1; -} - -/* Extract a file, directory, reparse point, or hard link to an - * already-extracted file using the Win32 API */ -int -win32_do_apply_dentry(const wchar_t *output_path, - size_t output_path_num_chars, - struct wim_dentry *dentry, - struct apply_args *args) -{ - int ret; - struct wim_inode *inode = dentry->d_inode; +#endif - ret = win32_check_vol_flags(output_path, dentry, args); - if (ret) - return ret; - if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) { - /* Linked file, with another name already extracted. Create a - * hard link. */ - ret = win32_try_hard_link(output_path, inode, args); - if (ret >= 0) - return ret; - /* Negative return value from win32_try_hard_link() indicates - * that hard links are probably not supported by the volume. - * Fall back to extracting a copy of the file. */ - } + /* Try setting the security descriptor. */ + for (;;) { + DWORD err; - /* If this is a reparse point and the volume does not support reparse - * points, just skip it completely. */ - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT && - !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) - { - WARNING("Not extracting reparse point \"%ls\"", output_path); - dentry->not_extracted = 1; - } else { - /* Create the file, directory, or reparse point, and extract the - * data streams. */ - ret = win32_extract_streams(dentry, output_path, args); - if (ret) - return ret; - } - if (inode->i_extracted_file == NULL) { - const struct wim_lookup_table_entry *lte; - - /* Tally bytes extracted, including all alternate data streams, - * unless we extracted a hard link (or, at least extracted a - * name that was supposed to be a hard link) */ - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - lte = inode_stream_lte_resolved(inode, i); - if (lte) - args->progress.extract.completed_bytes += - wim_resource_size(lte); + err = do_win32_set_security_descriptor(h, path, info, + (PSECURITY_DESCRIPTOR)desc); + if (err == ERROR_SUCCESS) { + ret = 0; + break; } - if (inode->i_nlink > 1) { - /* Save extracted path for a later call to - * CreateHardLinkW() if this inode has multiple links. - * */ - inode->i_extracted_file = WCSDUP(output_path); - if (!inode->i_extracted_file) - return WIMLIB_ERR_NOMEM; + + /* 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 ((err == ERROR_PRIVILEGE_NOT_HELD || + err == ERROR_ACCESS_DENIED) && + !(ctx->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) + { + if (info & SACL_SECURITY_INFORMATION) { + info &= ~SACL_SECURITY_INFORMATION; + ctx->partial_security_descriptors++; + continue; + } + if (info & DACL_SECURITY_INFORMATION) { + info &= ~DACL_SECURITY_INFORMATION; + continue; + } + if (info & OWNER_SECURITY_INFORMATION) { + info &= ~OWNER_SECURITY_INFORMATION; + continue; + } + /* Nothing left except GROUP, and if we removed it we + * wouldn't have anything at all. */ } - } - return 0; + /* 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--; + set_errno_from_win32_error(err); + ret = WIMLIB_ERR_SET_SECURITY; + break; + } + + /* Close handle opened for NtSetSecurityObject(). */ +#ifdef WITH_NTDLL + if (func_NtSetSecurityObject) + CloseHandle(h); +#endif + return ret; } -/* Set timestamps on an extracted file using the Win32 API */ -int -win32_do_apply_dentry_timestamps(const wchar_t *path, - size_t path_num_chars, - struct wim_dentry *dentry, - struct apply_args *args) +static int +win32_set_timestamps(const wchar_t *path, u64 creation_time, + u64 last_write_time, u64 last_access_time, + struct apply_ctx *ctx) { - DWORD err; HANDLE h; - const struct wim_inode *inode = dentry->d_inode; - - /* Windows doesn't let you change the timestamps of the root directory - * (at least on FAT, which is dumb but expected since FAT doesn't store - * any metadata about the root directory...) */ - if (path_is_root_of_drive(path)) - return 0; + DWORD err; + FILETIME creationTime = {.dwLowDateTime = creation_time & 0xffffffff, + .dwHighDateTime = creation_time >> 32}; + FILETIME lastAccessTime = {.dwLowDateTime = last_access_time & 0xffffffff, + .dwHighDateTime = last_access_time >> 32}; + FILETIME lastWriteTime = {.dwLowDateTime = last_write_time & 0xffffffff, + .dwHighDateTime = last_write_time >> 32}; - DEBUG("Opening \"%ls\" to set timestamps", path); h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES); - if (h == INVALID_HANDLE_VALUE) { - err = GetLastError(); - goto fail; - } + if (h == INVALID_HANDLE_VALUE) + goto error; - FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff, - .dwHighDateTime = inode->i_creation_time >> 32}; - FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff, - .dwHighDateTime = inode->i_last_access_time >> 32}; - FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff, - .dwHighDateTime = inode->i_last_write_time >> 32}; + if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) + goto error_close_handle; + + if (!CloseHandle(h)) + goto error; - DEBUG("Calling SetFileTime() on \"%ls\"", path); - if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) { - err = GetLastError(); - CloseHandle(h); - goto fail; - } - DEBUG("Closing \"%ls\"", path); - if (!CloseHandle(h)) { - err = GetLastError(); - goto fail; - } - goto out; -fail: - /* Only warn if setting timestamps failed; still return 0. */ - WARNING("Can't set timestamps on \"%ls\"", path); - win32_error(err); -out: return 0; + +error_close_handle: + err = GetLastError(); + CloseHandle(h); + SetLastError(err); +error: + set_errno_from_GetLastError(); + return WIMLIB_ERR_SET_TIMESTAMPS; } +const struct apply_operations win32_apply_ops = { + .name = L"Win32", + + .target_is_root = win32_path_is_root_of_drive, + .start_extract = win32_start_extract, + .finish_extract = win32_finish_extract, + .abort_extract = win32_finish_extract, + .create_file = win32_create_file, + .create_directory = win32_create_directory, + .create_hardlink = win32_create_hardlink, + .create_symlink = win32_create_symlink, + .extract_unnamed_stream = win32_extract_unnamed_stream, + .extract_named_stream = win32_extract_named_stream, + .extract_encrypted_stream = win32_extract_encrypted_stream, + .set_file_attributes = win32_set_file_attributes, + .set_reparse_data = win32_set_reparse_data, + .set_short_name = win32_set_short_name, + .set_security_descriptor = win32_set_security_descriptor, + .set_timestamps = win32_set_timestamps, + + .path_prefix = L"\\\\?\\", + .path_prefix_nchars = 4, + .path_separator = L'\\', + .path_max = 32768, + + .requires_realtarget_in_paths = 1, + .realpath_works_on_nonexisting_files = 1, + .root_directory_is_special = 1, + .requires_final_set_attributes_pass = 1, + .extract_encrypted_stream_creates_file = 1, + .requires_short_name_reordering = 1, /* TODO: check if this is really needed */ +}; + #endif /* __WIN32__ */