X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_apply.c;h=0367719558a6d9a747b1b284f71672b5d5f41d77;hp=2f169a57dbdcb6ebd9f21a2b6022db974fbc54c5;hb=8b709192cd2811b83c248fbe61ca4f11ee9de797;hpb=bc38f3735bf6eaa2b856b1e05710b34bcfbbf787 diff --git a/src/win32_apply.c b/src/win32_apply.c index 2f169a57..03677195 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -3,22 +3,20 @@ */ /* - * Copyright (C) 2013, 2014 Eric Biggers + * Copyright (C) 2013, 2014, 2015 Eric Biggers * - * This file is part of wimlib, a library for working with WIM files. + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. * - * wimlib is free software; you can redistribute it and/or modify it under the - * terms of the GNU General Public License as published by the Free - * Software Foundation; either version 3 of the License, or (at your option) - * any later version. - * - * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - * A PARTICULAR PURPOSE. See the GNU General Public License for more + * This file is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * - * You should have received a copy of the GNU General Public License - * along with wimlib; if not, see http://www.gnu.org/licenses/. + * You should have received a copy of the GNU Lesser General Public License + * along with this file; if not, see http://www.gnu.org/licenses/. */ #ifdef __WIN32__ @@ -30,19 +28,20 @@ #include "wimlib/win32_common.h" #include "wimlib/apply.h" +#include "wimlib/assert.h" +#include "wimlib/blob_table.h" #include "wimlib/capture.h" /* for mangle_pat() and match_pattern_list() */ #include "wimlib/dentry.h" +#include "wimlib/encoding.h" #include "wimlib/error.h" -#include "wimlib/lookup_table.h" #include "wimlib/metadata.h" +#include "wimlib/paths.h" +#include "wimlib/pattern.h" #include "wimlib/reparse.h" #include "wimlib/textfile.h" #include "wimlib/xml.h" #include "wimlib/wimboot.h" - -/* TODO: Add workaround for when a stream needs to be extracted to more places - * than this */ -#define MAX_OPEN_HANDLES 32768 +#include "wimlib/wof.h" struct win32_apply_ctx { @@ -52,13 +51,27 @@ struct win32_apply_ctx { /* WIMBoot information, only filled in if WIMLIB_EXTRACT_FLAG_WIMBOOT * was provided */ struct { - u64 data_source_id; - struct string_set *prepopulate_pats; - void *mem_prepopulate_pats; - u8 wim_lookup_table_hash[SHA1_HASH_SIZE]; + /* This array contains the WIM files registered with WOF on the + * target volume for this extraction operation. All WIMStructs + * in this array are distinct and have ->filename != NULL. */ + struct wimboot_wim { + WIMStruct *wim; + u64 data_source_id; + u8 blob_table_hash[SHA1_HASH_SIZE]; + } *wims; + size_t num_wims; bool wof_running; + bool have_wrong_version_wims; + bool have_uncompressed_wims; + bool have_unsupported_compressed_resources; + bool have_huge_resources; } wimboot; + /* External backing information */ + struct string_set *prepopulate_pats; + void *mem_prepopulate_pats; + bool tried_to_load_prepopulate_list; + /* Open handle to the target directory */ HANDLE h_target; @@ -80,7 +93,7 @@ struct win32_apply_ctx { * target-relative NT paths */ wchar_t *print_buffer; - /* Allocated buffer for reading stream data when it cannot be extracted + /* Allocated buffer for reading blob data when it cannot be extracted * directly */ u8 *data_buffer; @@ -100,25 +113,25 @@ struct win32_apply_ctx { struct reparse_buffer_disk rpbuf; /* Temporary buffer for reparse data of "fixed" absolute symbolic links - * and junction */ + * and junctions */ struct reparse_buffer_disk rpfixbuf; /* Array of open handles to filesystem streams currently being written */ - HANDLE open_handles[MAX_OPEN_HANDLES]; + HANDLE open_handles[MAX_OPEN_FILES]; /* Number of handles in @open_handles currently open (filled in from the * beginning of the array) */ unsigned num_open_handles; - /* List of dentries, joined by @tmp_list, that need to have reparse data - * extracted as soon as the whole stream has been read into + /* List of dentries, joined by @d_tmp_list, that need to have reparse + * data extracted as soon as the whole blob has been read into * @data_buffer. */ struct list_head reparse_dentries; - /* List of dentries, joined by @tmp_list, that need to have raw - * encrypted data extracted as soon as the whole stream has been read - * into @data_buffer. */ + /* List of dentries, joined by @d_tmp_list, that need to have raw + * encrypted data extracted as soon as the whole blob has been read into + * @data_buffer. */ struct list_head encrypted_dentries; /* Number of files for which we didn't have permission to set the full @@ -128,6 +141,19 @@ struct win32_apply_ctx { /* Number of files for which we didn't have permission to set any part * of the security descriptor. */ unsigned long no_security_descriptors; + + /* Number of files for which we couldn't set the short name. */ + unsigned long num_set_short_name_failures; + + /* Number of files for which we couldn't remove the short name. */ + unsigned long num_remove_short_name_failures; + + /* Number of files on which we couldn't set System Compression. */ + unsigned long num_system_compression_failures; + + /* Have we tried to enable short name support on the target volume yet? + */ + bool tried_to_enable_short_names; }; /* Get the drive letter from a Windows path, or return the null character if the @@ -170,11 +196,9 @@ get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret, vol_flags_ret, filesystem_name, ARRAY_LEN(filesystem_name))) { - DWORD err = GetLastError(); - set_errno_from_win32_error(err); - WARNING_WITH_ERRNO("Failed to get volume information for " - "\"%ls\" (err=%"PRIu32")", - target, (u32)err); + win32_warning(GetLastError(), + L"Failed to get volume information for \"%ls\"", + target); return; } @@ -189,6 +213,30 @@ get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret, } } +static const wchar_t * +current_path(struct win32_apply_ctx *ctx); + +static void +build_extraction_path(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx); + +static int +report_dentry_apply_error(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx, int ret) +{ + build_extraction_path(dentry, ctx); + return report_apply_error(&ctx->common, ret, current_path(ctx)); +} + +static inline int +check_apply_error(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx, int ret) +{ + if (unlikely(ret)) + ret = report_dentry_apply_error(dentry, ctx, ret); + return ret; +} + static int win32_get_supported_features(const wchar_t *target, struct wim_features *supported_features) @@ -239,32 +287,55 @@ win32_get_supported_features(const wchar_t *target, return 0; } -/* Load the patterns from [PrepopulateList] of WimBootCompresse.ini in the WIM - * image being extracted. */ +#define COMPACT_FLAGS (WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K | \ + WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K | \ + WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K | \ + WIMLIB_EXTRACT_FLAG_COMPACT_LZX) + + + +/* + * If not done already, load the patterns from the [PrepopulateList] section of + * WimBootCompress.ini in the WIM image being extracted. + * + * Note: WimBootCompress.ini applies to both types of "external backing": + * + * - WIM backing ("WIMBoot" - Windows 8.1 and later) + * - File backing ("System Compression" - Windows 10 and later) + */ static int load_prepopulate_pats(struct win32_apply_ctx *ctx) { const wchar_t *path = L"\\Windows\\System32\\WimBootCompress.ini"; struct wim_dentry *dentry; - struct wim_lookup_table_entry *lte; + const struct blob_descriptor *blob; int ret; void *buf; struct string_set *s; void *mem; struct text_file_section sec; + if (ctx->tried_to_load_prepopulate_list) + return 0; + + ctx->tried_to_load_prepopulate_list = true; + dentry = get_dentry(ctx->common.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->common.wim->lookup_table))) + !(blob = inode_get_blob_for_unnamed_data_stream(dentry->d_inode, + ctx->common.wim->blob_table))) { - WARNING("%ls does not exist in WIM image!", path); + WARNING("%ls does not exist in the WIM image.\n" + " The default configuration will be used instead; it assumes that all\n" + " files are valid for external backing regardless of path, equivalent\n" + " to an empty [PrepopulateList] section.", path); return WIMLIB_ERR_PATH_DOES_NOT_EXIST; } - ret = read_full_stream_into_alloc_buf(lte, &buf); + ret = read_blob_into_alloc_buf(blob, &buf); if (ret) return ret; @@ -277,7 +348,7 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) sec.name = T("PrepopulateList"); sec.strings = s; - ret = do_load_text_file(path, buf, lte->size, &mem, &sec, 1, + ret = do_load_text_file(path, buf, blob->size, &mem, &sec, 1, LOAD_TEXT_FILE_REMOVE_QUOTES | LOAD_TEXT_FILE_NO_WARNINGS, mangle_pat); @@ -287,63 +358,439 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) FREE(s); return ret; } - ctx->wimboot.prepopulate_pats = s; - ctx->wimboot.mem_prepopulate_pats = mem; + ctx->prepopulate_pats = s; + ctx->mem_prepopulate_pats = mem; return 0; } -/* Returns %true if the path to @dentry matches a pattern in [PrepopulateList] - * of WimBootCompress.ini. Otherwise returns %false. - * - * @dentry must have had its full path calculated. */ +/* Returns %true if the specified absolute path to a file in the WIM image can + * be subject to external backing when extracted. Otherwise returns %false. */ static bool -in_prepopulate_list(struct wim_dentry *dentry, - const struct win32_apply_ctx *ctx) +can_externally_back_path(const wchar_t *path, const struct win32_apply_ctx *ctx) { - const struct string_set *pats = ctx->wimboot.prepopulate_pats; + /* Does the path match a pattern given in the [PrepopulateList] section + * of WimBootCompress.ini? */ + if (ctx->prepopulate_pats && match_pattern_list(path, ctx->prepopulate_pats)) + return false; + + /* Since we attempt to modify the SYSTEM registry after it's extracted + * (see end_wimboot_extraction()), it can't be extracted as externally + * backed. This extends to associated files such as SYSTEM.LOG that + * also must be writable in order to write to the registry. Normally, + * SYSTEM is in [PrepopulateList], and the SYSTEM.* files match patterns + * in [ExclusionList] and therefore are not captured in the WIM at all. + * However, a WIM that wasn't specifically captured in "WIMBoot mode" + * may contain SYSTEM.* files. So to make things "just work", hard-code + * the pattern. */ + if (match_path(path, L"\\Windows\\System32\\config\\SYSTEM*", false)) + return false; + + return true; +} + +/* Can the specified WIM resource be used as the source of an external backing + * for the wof.sys WIM provider? */ +static bool +is_resource_valid_for_external_backing(const struct wim_resource_descriptor *rdesc, + struct win32_apply_ctx *ctx) +{ + /* Must be the original WIM file format. This check excludes pipable + * resources and solid resources. It also excludes other resources + * contained in such files even if they would be otherwise compatible. + */ + if (rdesc->wim->hdr.magic != WIM_MAGIC || + rdesc->wim->hdr.wim_version != WIM_VERSION_DEFAULT) + { + ctx->wimboot.have_wrong_version_wims = true; + return false; + } - if (!pats || !pats->num_strings) + /* + * Whitelist of compression types and chunk sizes supported by + * Microsoft's WOF driver. + * + * Notes: + * - Uncompressed WIMs result in BSOD. However, this only applies to + * the WIM file itself, not to uncompressed resources in a WIM file + * that is otherwise compressed. + * - XPRESS 64K sometimes appears to work, but sometimes it causes + * reads to fail with STATUS_UNSUCCESSFUL. + */ + switch (rdesc->compression_type) { + case WIMLIB_COMPRESSION_TYPE_NONE: + if (rdesc->wim->compression_type == WIMLIB_COMPRESSION_TYPE_NONE) { + ctx->wimboot.have_uncompressed_wims = true; + return false; + } + break; + case WIMLIB_COMPRESSION_TYPE_XPRESS: + switch (rdesc->chunk_size) { + case 4096: + case 8192: + case 16384: + case 32768: + break; + default: + ctx->wimboot.have_unsupported_compressed_resources = true; + return false; + } + break; + case WIMLIB_COMPRESSION_TYPE_LZX: + switch (rdesc->chunk_size) { + case 32768: + break; + default: + ctx->wimboot.have_unsupported_compressed_resources = true; + return false; + } + break; + default: + ctx->wimboot.have_unsupported_compressed_resources = true; return false; + } + + /* Microsoft's WoF driver errors out if it tries to satisfy a read with + * ending offset >= 4 GiB from an externally backed file. */ + if (rdesc->uncompressed_size > 4200000000) { + ctx->wimboot.have_huge_resources = true; + return false; + } + + return true; +} + +#define EXTERNAL_BACKING_NOT_ENABLED -1 +#define EXTERNAL_BACKING_NOT_POSSIBLE -2 +#define EXTERNAL_BACKING_EXCLUDED -3 - return match_pattern_list(dentry->_full_path, - wcslen(dentry->_full_path), pats); +/* + * Determines whether the specified file will be externally backed. Returns a + * negative status code if no, 0 if yes, or a positive wimlib error code on + * error. If the file is excluded from external backing based on its path, then + * *excluded_dentry_ret is set to the dentry for the path that matched the + * exclusion rule. + * + * Note that this logic applies to both types of "external backing": + * + * - WIM backing ("WIMBoot" - Windows 8.1 and later) + * - File backing ("System Compression" - Windows 10 and later) + * + * However, in the case of WIM backing we also need to validate that the WIM + * resource that would be the source of the backing is supported by the wof.sys + * WIM provider. + */ +static int +will_externally_back_inode(struct wim_inode *inode, struct win32_apply_ctx *ctx, + const struct wim_dentry **excluded_dentry_ret, + bool wimboot_mode) +{ + struct wim_dentry *dentry; + struct blob_descriptor *blob; + int ret; + + if (load_prepopulate_pats(ctx) == WIMLIB_ERR_NOMEM) + return WIMLIB_ERR_NOMEM; + + if (inode->i_can_externally_back) + return 0; + + /* This may do redundant checks because the cached value + * i_can_externally_back is 2-state (as opposed to 3-state: + * unknown/no/yes). But most files can be externally backed, so this + * way is fine. */ + + if (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_REPARSE_POINT | + FILE_ATTRIBUTE_ENCRYPTED)) + return EXTERNAL_BACKING_NOT_POSSIBLE; + + blob = inode_get_blob_for_unnamed_data_stream_resolved(inode); + + if (!blob) + return EXTERNAL_BACKING_NOT_POSSIBLE; + + if (wimboot_mode && + (blob->blob_location != BLOB_IN_WIM || + !is_resource_valid_for_external_backing(blob->rdesc, ctx))) + return EXTERNAL_BACKING_NOT_POSSIBLE; + + /* + * We need to check the patterns in [PrepopulateList] against every name + * of the inode, in case any of them match. + */ + + inode_for_each_extraction_alias(dentry, inode) { + + ret = calculate_dentry_full_path(dentry); + if (ret) + return ret; + + if (!can_externally_back_path(dentry->d_full_path, ctx)) { + if (excluded_dentry_ret) + *excluded_dentry_ret = dentry; + return EXTERNAL_BACKING_EXCLUDED; + } + } + + inode->i_can_externally_back = 1; + return 0; } -/* Calculates the SHA-1 message digest of the WIM's lookup table. */ +/* + * Determines if the unnamed data stream of a file will be created as a WIM + * external backing (a "WIMBoot pointer file"), as opposed to a standard + * extraction. + */ static int -hash_lookup_table(WIMStruct *wim, u8 hash[SHA1_HASH_SIZE]) +win32_will_back_from_wim(struct wim_dentry *dentry, struct apply_ctx *_ctx) +{ + struct win32_apply_ctx *ctx = (struct win32_apply_ctx *)_ctx; + + if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) + return EXTERNAL_BACKING_NOT_ENABLED; + + return will_externally_back_inode(dentry->d_inode, ctx, NULL, true); +} + +/* Find the WOF registration information for the specified WIM file. */ +static struct wimboot_wim * +find_wimboot_wim(WIMStruct *wim_to_find, struct win32_apply_ctx *ctx) { - return wim_reshdr_to_hash(&wim->hdr.lookup_table_reshdr, wim, hash); + for (size_t i = 0; i < ctx->wimboot.num_wims; i++) + if (wim_to_find == ctx->wimboot.wims[i].wim) + return &ctx->wimboot.wims[i]; + + wimlib_assert(0); + return NULL; } -/* Prepare for doing a "WIMBoot" extraction by loading patterns from - * [PrepopulateList] of WimBootCompress.ini and allocating a WOF data source ID - * on the target volume. */ static int -start_wimboot_extraction(struct win32_apply_ctx *ctx) +set_backed_from_wim(HANDLE h, struct wim_inode *inode, struct win32_apply_ctx *ctx) { int ret; - WIMStruct *wim = ctx->common.wim; + const struct wim_dentry *excluded_dentry; + const struct blob_descriptor *blob; + const struct wimboot_wim *wimboot_wim; - ret = load_prepopulate_pats(ctx); - if (ret == WIMLIB_ERR_NOMEM) + ret = will_externally_back_inode(inode, ctx, &excluded_dentry, true); + if (ret > 0) /* Error. */ return ret; - if (!wim_info_get_wimboot(wim->wim_info, - wim->current_image)) - WARNING("Image is not marked as WIMBoot compatible!"); + if (ret < 0 && ret != EXTERNAL_BACKING_EXCLUDED) + return 0; /* Not externally backing, other than due to exclusion. */ + + if (unlikely(ret == EXTERNAL_BACKING_EXCLUDED)) { + /* Not externally backing due to exclusion. */ + union wimlib_progress_info info; + + build_extraction_path(excluded_dentry, ctx); + + info.wimboot_exclude.path_in_wim = excluded_dentry->d_full_path; + info.wimboot_exclude.extraction_path = current_path(ctx); + + return call_progress(ctx->common.progfunc, + WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE, + &info, ctx->common.progctx); + } + + /* Externally backing. */ + + blob = inode_get_blob_for_unnamed_data_stream_resolved(inode); + wimboot_wim = find_wimboot_wim(blob->rdesc->wim, ctx); + + if (unlikely(!wimboot_set_pointer(h, + blob, + wimboot_wim->data_source_id, + wimboot_wim->blob_table_hash, + ctx->wimboot.wof_running))) + { + const DWORD err = GetLastError(); + + build_extraction_path(inode_first_extraction_dentry(inode), ctx); + win32_error(err, L"\"%ls\": Couldn't set WIMBoot pointer data", + current_path(ctx)); + return WIMLIB_ERR_WIMBOOT; + } + return 0; +} + +/* Calculates the SHA-1 message digest of the WIM's blob table. */ +static int +hash_blob_table(WIMStruct *wim, u8 hash[SHA1_HASH_SIZE]) +{ + return wim_reshdr_to_hash(&wim->hdr.blob_table_reshdr, wim, hash); +} + +static int +register_wim_with_wof(WIMStruct *wim, struct win32_apply_ctx *ctx) +{ + struct wimboot_wim *p; + int ret; + + /* Check if already registered */ + for (size_t i = 0; i < ctx->wimboot.num_wims; i++) + if (wim == ctx->wimboot.wims[i].wim) + return 0; + + /* Not yet registered */ + + p = REALLOC(ctx->wimboot.wims, + (ctx->wimboot.num_wims + 1) * sizeof(ctx->wimboot.wims[0])); + if (!p) + return WIMLIB_ERR_NOMEM; + ctx->wimboot.wims = p; + + ctx->wimboot.wims[ctx->wimboot.num_wims].wim = wim; + + ret = hash_blob_table(wim, ctx->wimboot.wims[ctx->wimboot.num_wims].blob_table_hash); + if (ret) + return ret; - ret = hash_lookup_table(ctx->common.wim, - ctx->wimboot.wim_lookup_table_hash); + ret = wimboot_alloc_data_source_id(wim->filename, + wim->hdr.guid, + ctx->common.wim->current_image, + ctx->common.target, + &ctx->wimboot.wims[ctx->wimboot.num_wims].data_source_id, + &ctx->wimboot.wof_running); if (ret) return ret; - return wimboot_alloc_data_source_id(wim->filename, - wim->hdr.guid, - wim->current_image, - ctx->common.target, - &ctx->wimboot.data_source_id, - &ctx->wimboot.wof_running); + ctx->wimboot.num_wims++; + return 0; +} + +/* Prepare for doing a "WIMBoot" extraction by registering each source WIM file + * with WOF on the target volume. */ +static int +start_wimboot_extraction(struct list_head *dentry_list, struct win32_apply_ctx *ctx) +{ + int ret; + struct wim_dentry *dentry; + + if (!wim_info_get_wimboot(ctx->common.wim->wim_info, + ctx->common.wim->current_image)) + WARNING("The WIM image is not marked as WIMBoot compatible. This usually\n" + " means it is not intended to be used to back a Windows operating\n" + " system. Proceeding anyway."); + + list_for_each_entry(dentry, dentry_list, d_extraction_list_node) { + struct blob_descriptor *blob; + + ret = win32_will_back_from_wim(dentry, &ctx->common); + if (ret > 0) /* Error */ + return ret; + if (ret < 0) /* Won't externally back */ + continue; + + blob = inode_get_blob_for_unnamed_data_stream_resolved(dentry->d_inode); + ret = register_wim_with_wof(blob->rdesc->wim, ctx); + if (ret) + return ret; + } + + if (ctx->wimboot.have_wrong_version_wims) { + WARNING("At least one of the source WIM files uses a version of the WIM\n" +" file format that not supported by Microsoft's wof.sys driver.\n" +" Files whose data is contained in one of these WIM files will be\n" +" extracted as full files rather than externally backed."); + } + + if (ctx->wimboot.have_uncompressed_wims) { + WARNING("At least one of the source WIM files is uncompressed. Files whose\n" +" data is contained in an uncompressed WIM file will be extracted as\n" +" full files rather than externally backed, since uncompressed WIM\n" +" files are not supported by Microsoft's wof.sys driver."); + } + + if (ctx->wimboot.have_unsupported_compressed_resources) { + WARNING("At least one of the source WIM files uses a compression format that\n" +" is not supported by Microsoft's wof.sys driver. Files whose data is\n" +" contained in a compressed resource in one of these WIM files will be\n" +" extracted as full files rather than externally backed. (The\n" +" compression formats supported by wof.sys are: XPRESS 4K, XPRESS 8K,\n" +" XPRESS 16K, XPRESS 32K, and LZX 32K.)"); + } + + if (ctx->wimboot.have_huge_resources) { + WARNING("Some files exceeded 4.2 GB in size. Such files will be extracted\n" +" as full files rather than externally backed, since very large files\n" +" are not supported by Microsoft's wof.sys driver."); + } + + return 0; +} + +static void +build_win32_extraction_path(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx); + +/* Sets WimBoot=1 in the extracted SYSTEM registry hive. + * + * WIMGAPI does this, and it's possible that it's important. + * But I don't know exactly what this value means to Windows. */ +static int +end_wimboot_extraction(struct win32_apply_ctx *ctx) +{ + struct wim_dentry *dentry; + wchar_t subkeyname[32]; + LONG res; + LONG res2; + HKEY key; + DWORD value; + + dentry = get_dentry(ctx->common.wim, L"\\Windows\\System32\\config\\SYSTEM", + WIMLIB_CASE_INSENSITIVE); + + if (!dentry || !will_extract_dentry(dentry)) + goto out; + + if (!will_extract_dentry(wim_get_current_root_dentry(ctx->common.wim))) + goto out; + + /* Not bothering to use the native routines (e.g. NtLoadKey()) for this. + * If this doesn't work, you probably also have many other problems. */ + + build_win32_extraction_path(dentry, ctx); + + randomize_char_array_with_alnum(subkeyname, 20); + subkeyname[20] = L'\0'; + + res = RegLoadKey(HKEY_LOCAL_MACHINE, subkeyname, ctx->pathbuf.Buffer); + if (res) + goto out_check_res; + + wcscpy(&subkeyname[20], L"\\Setup"); + + res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, subkeyname, 0, NULL, + REG_OPTION_BACKUP_RESTORE, 0, NULL, &key, NULL); + if (res) + goto out_unload_key; + + value = 1; + + res = RegSetValueEx(key, L"WimBoot", 0, REG_DWORD, + (const BYTE *)&value, sizeof(DWORD)); + if (res) + goto out_close_key; + + res = RegFlushKey(key); + +out_close_key: + res2 = RegCloseKey(key); + if (!res) + res = res2; +out_unload_key: + subkeyname[20] = L'\0'; + RegUnLoadKey(HKEY_LOCAL_MACHINE, subkeyname); +out_check_res: + if (res) { + /* Warning only. */ + win32_warning(res, L"Failed to set \\Setup: dword \"WimBoot\"=1 " + "value in registry hive \"%ls\"", + ctx->pathbuf.Buffer); + } +out: + return 0; } /* Returns the number of wide characters needed to represent the path to the @@ -370,18 +817,21 @@ dentry_extraction_path_length(const struct wim_dentry *dentry) * * If the inode has no named data streams, this will be 0. Otherwise, this will * be 1 plus the length of the longest-named data stream, since the data stream - * name must be separated form the path by the ':' character. */ + * name must be separated from the path by the ':' character. */ static size_t inode_longest_named_data_stream_spec(const struct wim_inode *inode) { size_t max = 0; - for (u16 i = 0; i < inode->i_num_ads; i++) { - size_t len = inode->i_ads_entries[i].stream_name_nbytes; + for (unsigned i = 0; i < inode->i_num_streams; i++) { + const struct wim_inode_stream *strm = &inode->i_streams[i]; + if (!stream_is_named_data_stream(strm)) + continue; + size_t len = utf16le_len_chars(strm->stream_name); if (len > max) max = len; } if (max) - max = 1 + (max / sizeof(wchar_t)); + max += 1; return max; } @@ -466,7 +916,7 @@ build_extraction_path_with_ads(const struct wim_dentry *dentry, * The path is saved in ctx->pathbuf and will be null terminated. * * XXX: We could get rid of this if it wasn't needed for the file encryption - * APIs. */ + * APIs, and the registry manipulation in WIMBoot mode. */ static void build_win32_extraction_path(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) @@ -507,44 +957,19 @@ current_path(struct win32_apply_ctx *ctx) return ctx->print_buffer; } -/* - * Ensures the target directory exists and opens a handle to it, in preparation - * of using paths relative to it. - */ +/* Open handle to the target directory if it is not already open. If the target + * directory does not exist, this creates it. */ static int -prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx) +open_target_directory(struct win32_apply_ctx *ctx) { NTSTATUS status; - size_t path_max; - - /* Open handle to the target directory (possibly creating it). */ - if (func_RtlDosPathNameToNtPathName_U_WithStatus) { - status = (*func_RtlDosPathNameToNtPathName_U_WithStatus)(ctx->common.target, - &ctx->target_ntpath, - NULL, NULL); - } else { - if ((*func_RtlDosPathNameToNtPathName_U)(ctx->common.target, - &ctx->target_ntpath, - NULL, NULL)) - status = STATUS_SUCCESS; - else - status = STATUS_NO_MEMORY; - } - if (!NT_SUCCESS(status)) { - if (status == STATUS_NO_MEMORY) { - return WIMLIB_ERR_NOMEM; - } else { - ERROR("\"%ls\": invalid path name " - "(status=0x%08"PRIx32")", - ctx->common.target, (u32)status); - return WIMLIB_ERR_INVALID_PARAM; - } - } + if (ctx->h_target) + return 0; ctx->attr.Length = sizeof(ctx->attr); + ctx->attr.RootDirectory = NULL; ctx->attr.ObjectName = &ctx->target_ntpath; - status = (*func_NtCreateFile)(&ctx->h_target, FILE_TRAVERSE, &ctx->attr, @@ -558,29 +983,56 @@ prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx) FILE_OPEN_FOR_BACKUP_INTENT, NULL, 0); - if (!NT_SUCCESS(status)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't open or create directory \"%ls\" " - "(status=0x%08"PRIx32")", - ctx->common.target, (u32)status); + winnt_error(status, L"Can't open or create directory \"%ls\"", + ctx->common.target); return WIMLIB_ERR_OPENDIR; } + ctx->attr.RootDirectory = ctx->h_target; + ctx->attr.ObjectName = &ctx->pathbuf; + return 0; +} - path_max = compute_path_max(dentry_list); +static void +close_target_directory(struct win32_apply_ctx *ctx) +{ + if (ctx->h_target) { + (*func_NtClose)(ctx->h_target); + ctx->h_target = NULL; + ctx->attr.RootDirectory = NULL; + } +} + +/* + * Ensures the target directory exists and opens a handle to it, in preparation + * of using paths relative to it. + */ +static int +prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx) +{ + int ret; + size_t path_max; - /* Add some extra for building Win32 paths for the file encryption APIs - * ... */ - path_max += 2 + (ctx->target_ntpath.Length / sizeof(wchar_t)); + ret = win32_path_to_nt_path(ctx->common.target, &ctx->target_ntpath); + if (ret) + return ret; + + ret = open_target_directory(ctx); + if (ret) + return ret; + + path_max = compute_path_max(dentry_list); + /* Add some extra for building Win32 paths for the file encryption APIs, + * and ensure we have at least enough to potentially use a 8.3 name for + * the last component. */ + path_max += max(2 + (ctx->target_ntpath.Length / sizeof(wchar_t)), + 8 + 1 + 3); ctx->pathbuf.MaximumLength = path_max * sizeof(wchar_t); ctx->pathbuf.Buffer = MALLOC(ctx->pathbuf.MaximumLength); if (!ctx->pathbuf.Buffer) return WIMLIB_ERR_NOMEM; - ctx->attr.RootDirectory = ctx->h_target; - ctx->attr.ObjectName = &ctx->pathbuf; - ctx->print_buffer = MALLOC((ctx->common.target_nchars + 1 + path_max + 1) * sizeof(wchar_t)); if (!ctx->print_buffer) @@ -592,20 +1044,15 @@ prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx) /* When creating an inode that will have a short (DOS) name, we create it using * the long name associated with the short name. This ensures that the short * name gets associated with the correct long name. */ -static const struct wim_dentry * +static struct wim_dentry * first_extraction_alias(const struct wim_inode *inode) { - const struct list_head *next = inode->i_extraction_aliases.next; - const struct wim_dentry *dentry; + struct wim_dentry *dentry; - do { - dentry = list_entry(next, struct wim_dentry, - d_extraction_alias_node); + inode_for_each_extraction_alias(dentry, inode) if (dentry_has_short_name(dentry)) - break; - next = next->next; - } while (next != &inode->i_extraction_aliases); - return dentry; + return dentry; + return inode_first_extraction_dentry(inode); } /* @@ -663,79 +1110,112 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry, if (NT_SUCCESS(status)) return 0; - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't %s compression attribute on \"%ls\" " - "(status=0x%08"PRIx32")", - (compressed ? "set" : "clear"), - current_path(ctx), status); + winnt_error(status, L"Can't %s compression attribute on \"%ls\"", + (compressed ? "set" : "clear"), current_path(ctx)); return WIMLIB_ERR_SET_ATTRIBUTES; } -/* - * Clear FILE_ATTRIBUTE_ENCRYPTED if the file or directory is not supposed to be - * encrypted. - * - * You can provide FILE_ATTRIBUTE_ENCRYPTED to NtCreateFile() to set it on the - * created file. However, the file or directory will otherwise default to the - * encryption state of the parent directory. This function works around this - * limitation by using DecryptFile() to remove FILE_ATTRIBUTE_ENCRYPTED on files - * (and directories) that are not supposed to have it set. - * - * Regardless of whether it succeeds or fails, this function may close the - * handle to the file. If it does, it sets it to NULL. - */ -static int -maybe_clear_encryption_attribute(HANDLE *h_ret, const struct wim_dentry *dentry, - struct win32_apply_ctx *ctx) +/* Try to enable short name support on the target volume. If successful, return + * true. If unsuccessful, issue a warning and return false. */ +static bool +try_to_enable_short_names(const wchar_t *volume) { - if (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) - return 0; + HANDLE h; + FILE_FS_PERSISTENT_VOLUME_INFORMATION info; + BOOL bret; + DWORD bytesReturned; - if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) - return 0; + h = CreateFile(volume, GENERIC_WRITE, + FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (h == INVALID_HANDLE_VALUE) + goto fail; - if (!ctx->common.supported_features.encrypted_files) - return 0; + info.VolumeFlags = 0; + info.FlagMask = PERSISTENT_VOLUME_STATE_SHORT_NAME_CREATION_DISABLED; + info.Version = 1; + info.Reserved = 0; - FILE_BASIC_INFORMATION info; + bret = DeviceIoControl(h, FSCTL_SET_PERSISTENT_VOLUME_STATE, + &info, sizeof(info), NULL, 0, + &bytesReturned, NULL); + + CloseHandle(h); + + if (!bret) + goto fail; + return true; + +fail: + win32_warning(GetLastError(), + L"Failed to enable short name support on %ls", + volume + 4); + return false; +} + +static NTSTATUS +remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) +{ + wchar_t *name; + wchar_t *end; NTSTATUS status; - BOOL bret; + HANDLE h; + size_t bufsize = offsetof(FILE_NAME_INFORMATION, FileName) + + (13 * sizeof(wchar_t)); + u8 buf[bufsize] _aligned_attribute(8); + bool retried = false; + FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf; - /* Get current attributes */ - status = (*func_NtQueryInformationFile)(*h_ret, &ctx->iosb, - &info, sizeof(info), - FileBasicInformation); - if (NT_SUCCESS(status) && - !(info.FileAttributes & FILE_ATTRIBUTE_ENCRYPTED)) - { - /* Nothing needs to be done. */ - return 0; + memset(buf, 0, bufsize); + + /* Build the path with the short name. */ + name = &ctx->pathbuf.Buffer[ctx->pathbuf.Length / sizeof(wchar_t)]; + while (name != ctx->pathbuf.Buffer && *(name - 1) != L'\\') + name--; + end = mempcpy(name, dentry->d_short_name, dentry->d_short_name_nbytes); + ctx->pathbuf.Length = ((u8 *)end - (u8 *)ctx->pathbuf.Buffer); + + /* Open the conflicting file (by short name). */ + status = (*func_NtOpenFile)(&h, GENERIC_WRITE | DELETE, + &ctx->attr, &ctx->iosb, + FILE_SHARE_VALID_FLAGS, + FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT); + if (!NT_SUCCESS(status)) { + winnt_warning(status, L"Can't open \"%ls\"", current_path(ctx)); + goto out; } - /* Set the new encryption state */ +#if 0 + WARNING("Overriding conflicting short name; path=\"%ls\"", + current_path(ctx)); +#endif - /* Due to Windows' crappy file encryption APIs, we need to close the - * handle to the file so we don't get ERROR_SHARING_VIOLATION. We also - * hack together a Win32 path, although we will use the \\?\ prefix so - * it will actually be a NT path in disguise... */ - (*func_NtClose)(*h_ret); - *h_ret = NULL; + /* Try to remove the short name on the conflicting file. */ - build_win32_extraction_path(dentry, ctx); +retry: + status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize, + FileShortNameInformation); - bret = DecryptFile(ctx->pathbuf.Buffer, 0); + if (status == STATUS_INVALID_PARAMETER && !retried) { - /* Restore the NT namespace path */ - build_extraction_path(dentry, ctx); + /* Microsoft forgot to make it possible to remove short names + * until Windows 7. Oops. Use a random short name instead. */ - if (!bret) { - DWORD err = GetLastError(); - set_errno_from_win32_error(err); - ERROR_WITH_ERRNO("Can't decrypt file \"%ls\" (err=%"PRIu32")", - current_path(ctx), (u32)err); - return WIMLIB_ERR_SET_ATTRIBUTES; + info->FileNameLength = 12 * sizeof(wchar_t); + for (int i = 0; i < 8; i++) + info->FileName[i] = 'A' + (rand() % 26); + info->FileName[8] = L'.'; + info->FileName[9] = L'W'; + info->FileName[10] = L'L'; + info->FileName[11] = L'B'; + info->FileName[12] = L'\0'; + retried = true; + goto retry; } - return 0; + (*func_NtClose)(h); +out: + build_extraction_path(dentry, ctx); + return status; } /* Set the short name on the open file @h which has been created at the location @@ -752,34 +1232,95 @@ static int set_short_name(HANDLE h, const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) { + + if (!ctx->common.supported_features.short_names) + return 0; + + /* + * Note: The size of the FILE_NAME_INFORMATION buffer must be such that + * FileName contains at least 2 wide characters (4 bytes). Otherwise, + * NtSetInformationFile() will return STATUS_INFO_LENGTH_MISMATCH. This + * is despite the fact that FileNameLength can validly be 0 or 2 bytes, + * with the former case being removing the existing short name if + * present, rather than setting one. + * + * The null terminator is seemingly optional, but to be safe we include + * space for it and zero all unused space. + */ + size_t bufsize = offsetof(FILE_NAME_INFORMATION, FileName) + - dentry->short_name_nbytes; + max(dentry->d_short_name_nbytes, sizeof(wchar_t)) + + sizeof(wchar_t); u8 buf[bufsize] _aligned_attribute(8); FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf; NTSTATUS status; + bool tried_to_remove_existing = false; + + memset(buf, 0, bufsize); - info->FileNameLength = dentry->short_name_nbytes; - memcpy(info->FileName, dentry->short_name, dentry->short_name_nbytes); + info->FileNameLength = dentry->d_short_name_nbytes; + memcpy(info->FileName, dentry->d_short_name, dentry->d_short_name_nbytes); +retry: status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize, FileShortNameInformation); if (NT_SUCCESS(status)) return 0; + if (status == STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME) { + if (dentry->d_short_name_nbytes == 0) + return 0; + if (!ctx->tried_to_enable_short_names) { + wchar_t volume[7]; + int ret; + + ctx->tried_to_enable_short_names = true; + + ret = win32_get_drive_path(ctx->common.target, + volume); + if (ret) + return ret; + if (try_to_enable_short_names(volume)) + goto retry; + } + } + + /* + * Short names can conflict in several cases: + * + * - a file being extracted has a short name conflicting with an + * existing file + * + * - a file being extracted has a short name conflicting with another + * file being extracted (possible, but shouldn't happen) + * + * - a file being extracted has a short name that conflicts with the + * automatically generated short name of a file we previously + * extracted, but failed to set the short name for. Sounds unlikely, + * but this actually does happen fairly often on versions of Windows + * prior to Windows 7 because they do not support removing short names + * from files. + */ + if (unlikely(status == STATUS_OBJECT_NAME_COLLISION) && + dentry->d_short_name_nbytes && !tried_to_remove_existing) + { + tried_to_remove_existing = true; + status = remove_conflicting_short_name(dentry, ctx); + if (NT_SUCCESS(status)) + goto retry; + } + /* By default, failure to set short names is not an error (since short * names aren't too important anymore...). */ - if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES)) + if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES)) { + if (dentry->d_short_name_nbytes) + ctx->num_set_short_name_failures++; + else + ctx->num_remove_short_name_failures++; return 0; - - if (status == STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME) { - if (dentry->short_name_nbytes == 0) - return 0; - ERROR("Can't extract short name when short " - "names are not enabled on the volume!"); - } else { - ERROR("Can't set short name on \"%ls\" (status=0x%08"PRIx32")", - current_path(ctx), (u32)status); } + + winnt_error(status, L"Can't set short name on \"%ls\"", current_path(ctx)); return WIMLIB_ERR_SET_SHORT_NAME; } @@ -836,63 +1377,214 @@ create_file(PHANDLE FileHandle, ctx); } -/* Create empty named data streams. - * - * Since these won't have 'struct wim_lookup_table_entry's, they won't show up - * in the call to extract_stream_list(). Hence the need for the special case. +static int +delete_file_or_stream(struct win32_apply_ctx *ctx) +{ + NTSTATUS status; + HANDLE h; + FILE_DISPOSITION_INFORMATION disposition_info; + FILE_BASIC_INFORMATION basic_info; + bool retried = false; + + status = do_create_file(&h, + DELETE, + NULL, + 0, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, + ctx); + if (unlikely(!NT_SUCCESS(status))) { + winnt_error(status, L"Can't open \"%ls\" for deletion", + current_path(ctx)); + return WIMLIB_ERR_OPEN; + } + +retry: + disposition_info.DoDeleteFile = TRUE; + status = (*func_NtSetInformationFile)(h, &ctx->iosb, + &disposition_info, + sizeof(disposition_info), + FileDispositionInformation); + (*func_NtClose)(h); + if (likely(NT_SUCCESS(status))) + return 0; + + if (status == STATUS_CANNOT_DELETE && !retried) { + /* Clear file attributes and try again. This is necessary for + * FILE_ATTRIBUTE_READONLY files. */ + status = do_create_file(&h, + FILE_WRITE_ATTRIBUTES | DELETE, + NULL, + 0, + FILE_OPEN, + FILE_NON_DIRECTORY_FILE, + ctx); + if (!NT_SUCCESS(status)) { + winnt_error(status, + L"Can't open \"%ls\" to reset attributes", + current_path(ctx)); + return WIMLIB_ERR_OPEN; + } + memset(&basic_info, 0, sizeof(basic_info)); + basic_info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + status = (*func_NtSetInformationFile)(h, &ctx->iosb, + &basic_info, + sizeof(basic_info), + FileBasicInformation); + if (!NT_SUCCESS(status)) { + winnt_error(status, + L"Can't reset file attributes on \"%ls\"", + current_path(ctx)); + (*func_NtClose)(h); + return WIMLIB_ERR_SET_ATTRIBUTES; + } + retried = true; + goto retry; + } + winnt_error(status, L"Can't delete \"%ls\"", current_path(ctx)); + return WIMLIB_ERR_OPEN; +} + +/* + * Create a nondirectory file or named data stream at the current path, + * superseding any that already exists at that path. If successful, return an + * open handle to the file or named data stream. */ static int -create_any_empty_ads(const struct wim_dentry *dentry, +supersede_file_or_stream(struct win32_apply_ctx *ctx, HANDLE *h_ret) +{ + NTSTATUS status; + bool retried = false; + + /* FILE_ATTRIBUTE_SYSTEM is needed to ensure that + * FILE_ATTRIBUTE_ENCRYPTED doesn't get set before we want it to be. */ +retry: + status = do_create_file(h_ret, + GENERIC_READ | GENERIC_WRITE | DELETE, + NULL, + FILE_ATTRIBUTE_SYSTEM, + FILE_CREATE, + FILE_NON_DIRECTORY_FILE, + ctx); + if (likely(NT_SUCCESS(status))) + return 0; + + /* STATUS_OBJECT_NAME_COLLISION means that the file or stream already + * exists. Delete the existing file or stream, then try again. + * + * Note: we don't use FILE_OVERWRITE_IF or FILE_SUPERSEDE because of + * problems with certain file attributes, especially + * FILE_ATTRIBUTE_ENCRYPTED. FILE_SUPERSEDE is also broken in the + * Windows PE ramdisk. */ + if (status == STATUS_OBJECT_NAME_COLLISION && !retried) { + int ret = delete_file_or_stream(ctx); + if (ret) + return ret; + retried = true; + goto retry; + } + winnt_error(status, L"Can't create \"%ls\"", current_path(ctx)); + return WIMLIB_ERR_OPEN; +} + +/* Set the reparse point @rpbuf of length @rpbuflen on the extracted file + * corresponding to the WIM dentry @dentry. */ +static int +do_set_reparse_point(const struct wim_dentry *dentry, + const struct reparse_buffer_disk *rpbuf, u16 rpbuflen, struct win32_apply_ctx *ctx) { - const struct wim_inode *inode = dentry->d_inode; - LARGE_INTEGER allocation_size; - bool path_modified = false; - int ret = 0; + NTSTATUS status; + HANDLE h; + + status = create_file(&h, GENERIC_WRITE, NULL, + 0, FILE_OPEN, 0, dentry, ctx); + if (!NT_SUCCESS(status)) + goto fail; - if (!ctx->common.supported_features.named_data_streams) + status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, + &ctx->iosb, FSCTL_SET_REPARSE_POINT, + (void *)rpbuf, rpbuflen, + NULL, 0); + (*func_NtClose)(h); + + if (NT_SUCCESS(status)) return 0; - for (u16 i = 0; i < inode->i_num_ads; i++) { - const struct wim_ads_entry *entry; - NTSTATUS status; - HANDLE h; + /* On Windows, by default only the Administrator can create symbolic + * links for some reason. By default we just issue a warning if this + * appears to be the problem. Use WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS + * to get a hard error. */ + if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS) + && (status == STATUS_PRIVILEGE_NOT_HELD || + status == STATUS_ACCESS_DENIED) + && (dentry->d_inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || + dentry->d_inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) + { + WARNING("Can't create symbolic link \"%ls\"! \n" + " (Need Administrator rights, or at least " + "the\n" + " SeCreateSymbolicLink privilege.)", + current_path(ctx)); + return 0; + } - entry = &inode->i_ads_entries[i]; +fail: + winnt_error(status, L"Can't set reparse data on \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_SET_REPARSE_DATA; +} - /* Not named? */ - if (!entry->stream_name_nbytes) - continue; +/* + * Create empty named data streams and potentially a reparse point for the + * specified file, if any. + * + * Since these won't have blob descriptors, they won't show up in the call to + * extract_blob_list(). Hence the need for the special case. + */ +static int +create_empty_streams(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) +{ + const struct wim_inode *inode = dentry->d_inode; + int ret; - /* Not empty? */ - if (entry->lte) + for (unsigned i = 0; i < inode->i_num_streams; i++) { + const struct wim_inode_stream *strm = &inode->i_streams[i]; + + if (stream_blob_resolved(strm) != NULL) continue; - /* Probably setting the allocation size to 0 has no effect, but - * we might as well try. */ - allocation_size.QuadPart = 0; + if (strm->stream_type == STREAM_TYPE_REPARSE_POINT && + ctx->common.supported_features.reparse_points) + { + u8 buf[REPARSE_DATA_OFFSET] _aligned_attribute(8); + struct reparse_buffer_disk *rpbuf = + (struct reparse_buffer_disk *)buf; + complete_reparse_point(rpbuf, inode, 0); + ret = do_set_reparse_point(dentry, rpbuf, + REPARSE_DATA_OFFSET, ctx); + if (ret) + return ret; + } else if (stream_is_named_data_stream(strm) && + ctx->common.supported_features.named_data_streams) + { + HANDLE h; - build_extraction_path_with_ads(dentry, ctx, - entry->stream_name, - entry->stream_name_nbytes / - sizeof(wchar_t)); - path_modified = true; - status = do_create_file(&h, FILE_WRITE_DATA, &allocation_size, - 0, FILE_SUPERSEDE, 0, ctx); - if (!NT_SUCCESS(status)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't create \"%ls\" " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)status); - ret = WIMLIB_ERR_OPEN; - break; + build_extraction_path_with_ads(dentry, ctx, + strm->stream_name, + utf16le_len_chars(strm->stream_name)); + ret = supersede_file_or_stream(ctx, &h); + + build_extraction_path(dentry, ctx); + + if (ret) + return ret; + (*func_NtClose)(h); } - (*func_NtClose)(h); } - /* Restore the path to the dentry itself */ - if (path_modified) - build_extraction_path(dentry, ctx); - return ret; + + return 0; } /* @@ -903,48 +1595,52 @@ create_any_empty_ads(const struct wim_dentry *dentry, * Returns 0, WIMLIB_ERR_MKDIR, or WIMLIB_ERR_SET_SHORT_NAME. */ static int -create_directory(const struct wim_dentry *dentry, - struct win32_apply_ctx *ctx) +create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) { - HANDLE h; + DWORD perms; NTSTATUS status; + HANDLE h; int ret; - ULONG attrib; - /* Special attributes: - * - * Use FILE_ATTRIBUTE_ENCRYPTED if the directory needs to have it set. - * This doesn't work for FILE_ATTRIBUTE_COMPRESSED (unfortunately). - * - * Don't specify FILE_ATTRIBUTE_DIRECTORY; it gets set anyway as a - * result of the FILE_DIRECTORY_FILE option. */ - attrib = (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED); - - /* DELETE is needed for set_short_name(). - * GENERIC_READ and GENERIC_WRITE are needed for - * adjust_compression_attribute(). */ - status = create_file(&h, GENERIC_READ | GENERIC_WRITE | DELETE, NULL, - attrib, FILE_OPEN_IF, FILE_DIRECTORY_FILE, - dentry, ctx); + /* DELETE is needed for set_short_name(); GENERIC_READ and GENERIC_WRITE + * are needed for adjust_compression_attribute(). */ + perms = GENERIC_READ | GENERIC_WRITE; + if (!dentry_is_root(dentry)) + perms |= DELETE; + + /* FILE_ATTRIBUTE_SYSTEM is needed to ensure that + * FILE_ATTRIBUTE_ENCRYPTED doesn't get set before we want it to be. */ + status = create_file(&h, perms, NULL, FILE_ATTRIBUTE_SYSTEM, + FILE_OPEN_IF, FILE_DIRECTORY_FILE, dentry, ctx); if (!NT_SUCCESS(status)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't create directory \"%ls\" " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_error(status, L"Can't create directory \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_MKDIR; } - ret = set_short_name(h, dentry, ctx); - - if (!ret) - ret = adjust_compression_attribute(h, dentry, ctx); + if (ctx->iosb.Information == FILE_OPENED) { + /* If we opened an existing directory, try to clear its file + * attributes. As far as I know, this only actually makes a + * difference in the case where a FILE_ATTRIBUTE_READONLY + * directory has a named data stream which needs to be + * extracted. You cannot create a named data stream of such a + * directory, even though this contradicts Microsoft's + * documentation for FILE_ATTRIBUTE_READONLY which states it is + * not honored for directories! */ + FILE_BASIC_INFORMATION basic_info = { .FileAttributes = FILE_ATTRIBUTE_NORMAL }; + (*func_NtSetInformationFile)(h, &ctx->iosb, &basic_info, + sizeof(basic_info), FileBasicInformation); + } - if (!ret) - ret = maybe_clear_encryption_attribute(&h, dentry, ctx); - /* May close the handle!!! */ + if (!dentry_is_root(dentry)) { + ret = set_short_name(h, dentry, ctx); + if (ret) + goto out; + } - if (h) - (*func_NtClose)(h); + ret = adjust_compression_attribute(h, dentry, ctx); +out: + (*func_NtClose)(h); return ret; } @@ -971,16 +1667,16 @@ create_directories(struct list_head *dentry_list, * FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT, but we * wait until later to actually set the reparse data. */ - /* If the root dentry is being extracted, it was already done so - * it prepare_target(). */ - if (dentry_is_root(dentry)) - continue; - ret = create_directory(dentry, ctx); + + if (!ret) + ret = create_empty_streams(dentry, ctx); + + ret = check_apply_error(dentry, ctx, ret); if (ret) return ret; - ret = create_any_empty_ads(dentry, ctx); + ret = report_file_created(&ctx->common); if (ret) return ret; } @@ -991,117 +1687,37 @@ create_directories(struct list_head *dentry_list, * Creates the nondirectory file named by @dentry. * * On success, returns an open handle to the file in @h_ret, with GENERIC_READ, - * GENERIC_WRITE, and DELETE access. Also, the path to the file will be saved - * in ctx->pathbuf. On failure, returns WIMLIB_ERR_OPEN. - */ -static int -create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, - struct win32_apply_ctx *ctx) -{ - const struct wim_inode *inode; - ULONG attrib; - NTSTATUS status; - bool retried = false; - - inode = dentry->d_inode; - - /* If the file already exists and has FILE_ATTRIBUTE_SYSTEM and/or - * FILE_ATTRIBUTE_HIDDEN, these must be specified in order to supersede - * the file. - * - * Normally the user shouldn't be trying to overwrite such files anyway, - * but we at least provide FILE_ATTRIBUTE_SYSTEM and - * FILE_ATTRIBUTE_HIDDEN if the WIM inode has those attributes so that - * we catch the case where the user extracts the same files to the same - * location more than one time. - * - * Also specify FILE_ATTRIBUTE_ENCRYPTED if the file needs to be - * encrypted. - * - * In NO_ATTRIBUTES mode just don't specify any attributes at all. - */ - if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) { - attrib = 0; - } else { - attrib = (inode->i_attributes & (FILE_ATTRIBUTE_SYSTEM | - FILE_ATTRIBUTE_HIDDEN | - FILE_ATTRIBUTE_ENCRYPTED)); - } - build_extraction_path(dentry, ctx); -retry: - status = do_create_file(h_ret, GENERIC_READ | GENERIC_WRITE | DELETE, - NULL, attrib, FILE_SUPERSEDE, - FILE_NON_DIRECTORY_FILE, ctx); - if (NT_SUCCESS(status)) { - int ret; - - ret = adjust_compression_attribute(*h_ret, dentry, ctx); - if (ret) { - (*func_NtClose)(*h_ret); - return ret; - } - - ret = maybe_clear_encryption_attribute(h_ret, dentry, ctx); - /* May close the handle!!! */ - - if (ret) { - if (*h_ret) - (*func_NtClose)(*h_ret); - return ret; - } - - if (!*h_ret) { - /* Re-open the handle so that we can return it on - * success. */ - status = do_create_file(h_ret, - GENERIC_READ | - GENERIC_WRITE | DELETE, - NULL, 0, FILE_OPEN, - FILE_NON_DIRECTORY_FILE, ctx); - if (!NT_SUCCESS(status)) - goto fail; - } + * GENERIC_WRITE, and DELETE access. Also, the path to the file will be saved + * in ctx->pathbuf. On failure, returns an error code. + */ +static int +create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) +{ + int ret; + HANDLE h; - ret = create_any_empty_ads(dentry, ctx); - if (ret) { - (*func_NtClose)(*h_ret); - return ret; - } - return 0; - } + build_extraction_path(dentry, ctx); - if (status == STATUS_ACCESS_DENIED && !retried) { - /* We also can't supersede an existing file that has - * FILE_ATTRIBUTE_READONLY set; doing so causes NtCreateFile() - * to return STATUS_ACCESS_DENIED . The only workaround seems - * to be to explicitly remove FILE_ATTRIBUTE_READONLY on the - * existing file, then try again. */ + ret = supersede_file_or_stream(ctx, &h); + if (ret) + goto out; - FILE_BASIC_INFORMATION info; - HANDLE h; + ret = adjust_compression_attribute(h, dentry, ctx); + if (ret) + goto out_close; - status = do_create_file(&h, FILE_WRITE_ATTRIBUTES, NULL, 0, - FILE_OPEN, FILE_NON_DIRECTORY_FILE, ctx); - if (!NT_SUCCESS(status)) - goto fail; + ret = create_empty_streams(dentry, ctx); + if (ret) + goto out_close; - memset(&info, 0, sizeof(info)); - info.FileAttributes = FILE_ATTRIBUTE_NORMAL; + *h_ret = h; + return 0; - status = (*func_NtSetInformationFile)(h, &ctx->iosb, - &info, sizeof(info), - FileBasicInformation); - (*func_NtClose)(h); - if (!NT_SUCCESS(status)) - goto fail; - retried = true; - goto retry; - } -fail: - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't create file \"%ls\" (status=0x%08"PRIx32")", - current_path(ctx), (u32)status); - return WIMLIB_ERR_OPEN; +out_close: + (*func_NtClose)(h); +out: + return ret; } /* Creates a hard link at the location named by @dentry to the file represented @@ -1137,8 +1753,8 @@ create_link(HANDLE h, const struct wim_dentry *dentry, FileLinkInformation); if (NT_SUCCESS(status)) return 0; - ERROR("Failed to create link \"%ls\" (status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_error(status, L"Failed to create link \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_LINK; } else { HANDLE h2; @@ -1164,31 +1780,25 @@ static int create_links(HANDLE h, const struct wim_dentry *first_dentry, struct win32_apply_ctx *ctx) { - const struct wim_inode *inode; - const struct list_head *next; + const struct wim_inode *inode = first_dentry->d_inode; const struct wim_dentry *dentry; int ret; - inode = first_dentry->d_inode; - next = inode->i_extraction_aliases.next; - do { - dentry = list_entry(next, struct wim_dentry, - d_extraction_alias_node); + inode_for_each_extraction_alias(dentry, inode) { if (dentry != first_dentry) { ret = create_link(h, dentry, ctx); if (ret) return ret; } - next = next->next; - } while (next != &inode->i_extraction_aliases); + } return 0; } /* Create a nondirectory file, including all links. */ static int -create_nondirectory(const struct wim_inode *inode, struct win32_apply_ctx *ctx) +create_nondirectory(struct wim_inode *inode, struct win32_apply_ctx *ctx) { - const struct wim_dentry *first_dentry; + struct wim_dentry *first_dentry; HANDLE h; int ret; @@ -1207,6 +1817,10 @@ create_nondirectory(const struct wim_inode *inode, struct win32_apply_ctx *ctx) if (!ret) ret = create_links(h, first_dentry, ctx); + /* "WIMBoot" extraction: set external backing by the WIM file if needed. */ + if (!ret && unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) + ret = set_backed_from_wim(h, inode, ctx); + (*func_NtClose)(h); return ret; } @@ -1216,8 +1830,8 @@ create_nondirectory(const struct wim_inode *inode, struct win32_apply_ctx *ctx) static int create_nondirectories(struct list_head *dentry_list, struct win32_apply_ctx *ctx) { - const struct wim_dentry *dentry; - const struct wim_inode *inode; + struct wim_dentry *dentry; + struct wim_inode *inode; int ret; list_for_each_entry(dentry, dentry_list, d_extraction_list_node) { @@ -1225,9 +1839,13 @@ create_nondirectories(struct list_head *dentry_list, struct win32_apply_ctx *ctx if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) continue; /* Call create_nondirectory() only once per inode */ - if (dentry != inode_first_extraction_dentry(inode)) - continue; - ret = create_nondirectory(inode, ctx); + if (dentry == inode_first_extraction_dentry(inode)) { + ret = create_nondirectory(inode, ctx); + ret = check_apply_error(dentry, ctx, ret); + if (ret) + return ret; + } + ret = report_file_created(&ctx->common); if (ret) return ret; } @@ -1241,130 +1859,88 @@ close_handles(struct win32_apply_ctx *ctx) (*func_NtClose)(ctx->open_handles[i]); } -/* Prepare to read the next stream, which has size @stream_size, into an - * in-memory buffer. */ -static int -prepare_data_buffer(struct win32_apply_ctx *ctx, u64 stream_size) +/* Prepare to read the next blob, which has size @blob_size, into an in-memory + * buffer. */ +static bool +prepare_data_buffer(struct win32_apply_ctx *ctx, u64 blob_size) { - if (stream_size > ctx->data_buffer_size) { + if (blob_size > ctx->data_buffer_size) { /* Larger buffer needed. */ void *new_buffer; - if ((size_t)stream_size != stream_size) - return WIMLIB_ERR_NOMEM; - new_buffer = REALLOC(ctx->data_buffer, stream_size); + if ((size_t)blob_size != blob_size) + return false; + new_buffer = REALLOC(ctx->data_buffer, blob_size); if (!new_buffer) - return WIMLIB_ERR_NOMEM; + return false; ctx->data_buffer = new_buffer; - ctx->data_buffer_size = stream_size; + ctx->data_buffer_size = blob_size; } /* On the first call this changes data_buffer_ptr from NULL, which tells * extract_chunk() that the data buffer needs to be filled while reading * the stream data. */ ctx->data_buffer_ptr = ctx->data_buffer; - return 0; + return true; } static int -begin_extract_stream_instance(const struct wim_lookup_table_entry *stream, - struct wim_dentry *dentry, - const wchar_t *stream_name, - struct win32_apply_ctx *ctx) +begin_extract_blob_instance(const struct blob_descriptor *blob, + struct wim_dentry *dentry, + const struct wim_inode_stream *strm, + struct win32_apply_ctx *ctx) { - const struct wim_inode *inode = dentry->d_inode; - size_t stream_name_nchars = 0; FILE_ALLOCATION_INFORMATION alloc_info; HANDLE h; NTSTATUS status; - if (unlikely(stream_name)) - stream_name_nchars = wcslen(stream_name); - - if (unlikely(stream_name_nchars)) { - build_extraction_path_with_ads(dentry, ctx, - stream_name, stream_name_nchars); - } else { - build_extraction_path(dentry, ctx); - } - - /* Reparse point? */ - if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) - && (stream_name_nchars == 0)) - { - if (!ctx->common.supported_features.reparse_points) - return 0; - - /* We can't write the reparse stream directly; we must set it - * with FSCTL_SET_REPARSE_POINT, which requires that all the + if (unlikely(strm->stream_type == STREAM_TYPE_REPARSE_POINT)) { + /* We can't write the reparse point stream directly; we must set + * it with FSCTL_SET_REPARSE_POINT, which requires that all the * data be available. So, stage the data in a buffer. */ - - list_add_tail(&dentry->tmp_list, &ctx->reparse_dentries); - return prepare_data_buffer(ctx, stream->size); + if (!prepare_data_buffer(ctx, blob->size)) + return WIMLIB_ERR_NOMEM; + list_add_tail(&dentry->d_tmp_list, &ctx->reparse_dentries); + return 0; } - /* Encrypted file? */ - if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) - && (stream_name_nchars == 0)) - { - if (!ctx->common.supported_features.encrypted_files) - return 0; - - /* We can't write encrypted file streams directly; we must use + if (unlikely(strm->stream_type == STREAM_TYPE_EFSRPC_RAW_DATA)) { + /* We can't write encrypted files directly; we must use * WriteEncryptedFileRaw(), which requires providing the data * through a callback function. This can't easily be combined * with our own callback-based approach. * - * The current workaround is to simply read the stream into - * memory and write the encrypted file from that. + * The current workaround is to simply read the blob into memory + * and write the encrypted file from that. * * TODO: This isn't sufficient for extremely large encrypted * files. Perhaps we should create an extra thread to write * such files... */ - list_add_tail(&dentry->tmp_list, &ctx->encrypted_dentries); - return prepare_data_buffer(ctx, stream->size); + if (!prepare_data_buffer(ctx, blob->size)) + return WIMLIB_ERR_NOMEM; + list_add_tail(&dentry->d_tmp_list, &ctx->encrypted_dentries); + return 0; } - /* Extracting unnamed data stream in WIMBoot mode? */ - if (unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) - && (stream_name_nchars == 0) - && (stream->resource_location == RESOURCE_IN_WIM) - && (stream->rspec->wim == ctx->common.wim) - && (stream->size == stream->rspec->uncompressed_size)) - { - int ret = calculate_dentry_full_path(dentry); - if (ret) - return ret; - if (in_prepopulate_list(dentry, ctx)) { - union wimlib_progress_info info; - - info.wimboot_exclude.path_in_wim = dentry->_full_path; - info.wimboot_exclude.extraction_path = current_path(ctx); - - ret = call_progress(ctx->common.progfunc, - WIMLIB_PROGRESS_MSG_WIMBOOT_EXCLUDE, - &info, ctx->common.progctx); - FREE(dentry->_full_path); - dentry->_full_path = NULL; - if (ret) - return ret; - /* Go on and open the file for normal extraction. */ - } else { - FREE(dentry->_full_path); - dentry->_full_path = NULL; - return wimboot_set_pointer(&ctx->attr, - current_path(ctx), - stream, - ctx->wimboot.data_source_id, - ctx->wimboot.wim_lookup_table_hash, - ctx->wimboot.wof_running); - } - } + /* It's a data stream (may be unnamed or named). */ + wimlib_assert(strm->stream_type == STREAM_TYPE_DATA); - /* Too many open handles? */ - if (ctx->num_open_handles == MAX_OPEN_HANDLES) { + if (ctx->num_open_handles == MAX_OPEN_FILES) { + /* XXX: Fix this. But because of the checks in + * extract_blob_list(), this can now only happen on a filesystem + * that does not support hard links. */ ERROR("Can't extract data: too many open files!"); return WIMLIB_ERR_UNSUPPORTED; } + + if (unlikely(stream_is_named(strm))) { + build_extraction_path_with_ads(dentry, ctx, + strm->stream_name, + utf16le_len_chars(strm->stream_name)); + } else { + build_extraction_path(dentry, ctx); + } + + /* Open a new handle */ status = do_create_file(&h, FILE_WRITE_DATA | SYNCHRONIZE, @@ -1373,73 +1949,21 @@ begin_extract_stream_instance(const struct wim_lookup_table_entry *stream, FILE_SYNCHRONOUS_IO_NONALERT, ctx); if (!NT_SUCCESS(status)) { - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't open \"%ls\" for writing " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)status); + winnt_error(status, L"Can't open \"%ls\" for writing", + current_path(ctx)); return WIMLIB_ERR_OPEN; } ctx->open_handles[ctx->num_open_handles++] = h; /* Allocate space for the data. */ - alloc_info.AllocationSize.QuadPart = stream->size; + alloc_info.AllocationSize.QuadPart = blob->size; (*func_NtSetInformationFile)(h, &ctx->iosb, &alloc_info, sizeof(alloc_info), FileAllocationInformation); return 0; } -/* Set the reparse data @rpbuf of length @rpbuflen on the extracted file - * corresponding to the WIM dentry @dentry. */ -static int -do_set_reparse_data(const struct wim_dentry *dentry, - const void *rpbuf, u16 rpbuflen, - struct win32_apply_ctx *ctx) -{ - NTSTATUS status; - HANDLE h; - - status = create_file(&h, GENERIC_WRITE, NULL, - 0, FILE_OPEN, 0, dentry, ctx); - if (!NT_SUCCESS(status)) - goto fail; - - status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, - &ctx->iosb, FSCTL_SET_REPARSE_POINT, - (void *)rpbuf, rpbuflen, - NULL, 0); - (*func_NtClose)(h); - - if (NT_SUCCESS(status)) - return 0; - - /* On Windows, by default only the Administrator can create symbolic - * links for some reason. By default we just issue a warning if this - * appears to be the problem. Use WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS - * to get a hard error. */ - if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SYMLINKS) - && (status == STATUS_PRIVILEGE_NOT_HELD || - status == STATUS_ACCESS_DENIED) - && (dentry->d_inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - dentry->d_inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) - { - WARNING("Can't create symbolic link \"%ls\"! \n" - " (Need Administrator rights, or at least " - "the\n" - " SeCreateSymbolicLink privilege.)", - current_path(ctx)); - return 0; - } - -fail: - set_errno_from_nt_status(status); - ERROR_WITH_ERRNO("Can't set reparse data on \"%ls\" " - "(status=0x%08"PRIx32")", - current_path(ctx), (u32)status); - return WIMLIB_ERR_SET_REPARSE_DATA; -} - /* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a * pointer to the suffix of the path that begins with the device directly, such * as e:\Windows\System32. */ @@ -1451,27 +1975,27 @@ skip_nt_toplevel_component(const wchar_t *path, size_t path_nchars) L"\\DosDevices\\", L"\\Device\\", }; - size_t first_dir_len = 0; const wchar_t * const end = path + path_nchars; for (size_t i = 0; i < ARRAY_LEN(dirs); i++) { size_t len = wcslen(dirs[i]); - if (len <= (end - path) && !wcsnicmp(path, dirs[i], len)) { - first_dir_len = len; - break; + if (len <= (end - path) && !wmemcmp(path, dirs[i], len)) { + path += len; + while (path != end && *path == L'\\') + path++; + return path; } } - if (first_dir_len == 0) - return path; - path += first_dir_len; - while (path != end && *path == L'\\') - path++; return path; } -/* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a - * pointer to the suffix of the path that is device-relative, such as - * Windows\System32. */ +/* + * Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a + * pointer to the suffix of the path that is device-relative but possibly with + * leading slashes, such as \Windows\System32. + * + * The path has an explicit length and is not necessarily null terminated. + */ static const wchar_t * get_device_relative_path(const wchar_t *path, size_t path_nchars) { @@ -1482,24 +2006,22 @@ get_device_relative_path(const wchar_t *path, size_t path_nchars) if (path == orig_path) return orig_path; - path = wmemchr(path, L'\\', (end - path)); - if (!path) - return orig_path; - do { + while (path != end && *path != L'\\') path++; - } while (path != end && *path == L'\\'); + return path; } /* - * Given a reparse point buffer for a symbolic link or junction, adjust its - * contents so that the target of the link is consistent with the new location - * of the files. + * Given a reparse point buffer for an inode for which the absolute link target + * was relativized when it was archived, de-relative the link target to be + * consistent with the actual extraction location. */ static void -try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx) +try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p, + struct win32_apply_ctx *ctx) { - struct reparse_data rpdata; + struct link_reparse_point link; size_t orig_subst_name_nchars; const wchar_t *relpath; size_t relpath_nchars; @@ -1508,39 +2030,33 @@ try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx) const wchar_t *fixed_print_name; size_t fixed_print_name_nchars; - if (parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata)) { - /* Do nothing if the reparse data is invalid. */ + /* Do nothing if the reparse data is invalid. */ + if (parse_link_reparse_point(rpbuf, *rpbuflen_p, &link)) return; - } - if (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK && - (rpdata.rpflags & SYMBOLIC_LINK_RELATIVE)) - { - /* Do nothing if it's a relative symbolic link. */ + /* Do nothing if the reparse point is a relative symbolic link. */ + if (link_is_relative_symlink(&link)) return; - } /* Build the new substitute name from the NT namespace path to the * target directory, then a path separator, then the "device relative" * part of the old substitute name. */ - orig_subst_name_nchars = rpdata.substitute_name_nbytes / sizeof(wchar_t); + orig_subst_name_nchars = link.substitute_name_nbytes / sizeof(wchar_t); - relpath = get_device_relative_path(rpdata.substitute_name, + relpath = get_device_relative_path(link.substitute_name, orig_subst_name_nchars); relpath_nchars = orig_subst_name_nchars - - (relpath - rpdata.substitute_name); + (relpath - link.substitute_name); target_ntpath_nchars = ctx->target_ntpath.Length / sizeof(wchar_t); - fixed_subst_name_nchars = target_ntpath_nchars + 1 + relpath_nchars; + fixed_subst_name_nchars = target_ntpath_nchars + relpath_nchars; + wchar_t fixed_subst_name[fixed_subst_name_nchars]; - wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer, - target_ntpath_nchars); - fixed_subst_name[target_ntpath_nchars] = L'\\'; - wmemcpy(&fixed_subst_name[target_ntpath_nchars + 1], - relpath, relpath_nchars); + wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer, target_ntpath_nchars); + wmemcpy(&fixed_subst_name[target_ntpath_nchars], relpath, relpath_nchars); /* Doesn't need to be null-terminated. */ /* Print name should be Win32, but not all NT names can even be @@ -1552,33 +2068,29 @@ try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx) fixed_print_name_nchars = fixed_subst_name_nchars - (fixed_print_name - fixed_subst_name); - rpdata.substitute_name = fixed_subst_name; - rpdata.substitute_name_nbytes = fixed_subst_name_nchars * sizeof(wchar_t); - rpdata.print_name = (wchar_t *)fixed_print_name; - rpdata.print_name_nbytes = fixed_print_name_nchars * sizeof(wchar_t); - make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p); + link.substitute_name = fixed_subst_name; + link.substitute_name_nbytes = fixed_subst_name_nchars * sizeof(wchar_t); + link.print_name = (wchar_t *)fixed_print_name; + link.print_name_nbytes = fixed_print_name_nchars * sizeof(wchar_t); + make_link_reparse_point(&link, rpbuf, rpbuflen_p); } -/* Sets reparse data on the specified file. This handles "fixing" the targets - * of absolute symbolic links and junctions if WIMLIB_EXTRACT_FLAG_RPFIX was - * specified. */ +/* Sets the reparse point on the specified file. This handles "fixing" the + * targets of absolute symbolic links and junctions if WIMLIB_EXTRACT_FLAG_RPFIX + * was specified. */ static int -set_reparse_data(const struct wim_dentry *dentry, - const void *_rpbuf, u16 rpbuflen, struct win32_apply_ctx *ctx) +set_reparse_point(const struct wim_dentry *dentry, + const struct reparse_buffer_disk *rpbuf, u16 rpbuflen, + struct win32_apply_ctx *ctx) { - const struct wim_inode *inode = dentry->d_inode; - const void *rpbuf = _rpbuf; - if ((ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) - && !inode->i_not_rpfixed - && (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) + && !(dentry->d_inode->i_rp_flags & WIM_RP_FLAG_NOT_FIXED)) { - memcpy(&ctx->rpfixbuf, _rpbuf, rpbuflen); - try_rpfix((u8 *)&ctx->rpfixbuf, &rpbuflen, ctx); + memcpy(&ctx->rpfixbuf, rpbuf, rpbuflen); + try_rpfix(&ctx->rpfixbuf, &rpbuflen, ctx); rpbuf = &ctx->rpfixbuf; } - return do_set_reparse_data(dentry, rpbuf, rpbuflen, ctx); + return do_set_reparse_point(dentry, rpbuf, rpbuflen, ctx); } @@ -1596,31 +2108,49 @@ import_encrypted_data(PBYTE pbData, PVOID pvCallbackContext, PULONG Length) return ERROR_SUCCESS; } -/* Write the raw encrypted data to the already-created file corresponding to - * @dentry. +/* + * Write the raw encrypted data to the already-created file (or directory) + * corresponding to @dentry. * * The raw encrypted data is provided in ctx->data_buffer, and its size is - * ctx->encrypted_size. */ + * ctx->encrypted_size. + * + * This function may close the target directory, in which case the caller needs + * to re-open it if needed. + */ static int extract_encrypted_file(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) { void *rawctx; DWORD err; + ULONG flags; + bool retried; /* Temporarily build a Win32 path for OpenEncryptedFileRaw() */ build_win32_extraction_path(dentry, ctx); - err = OpenEncryptedFileRaw(ctx->pathbuf.Buffer, - CREATE_FOR_IMPORT, &rawctx); + flags = CREATE_FOR_IMPORT | OVERWRITE_HIDDEN; + if (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) + flags |= CREATE_FOR_DIR; + + retried = false; +retry: + err = OpenEncryptedFileRaw(ctx->pathbuf.Buffer, flags, &rawctx); + if (err == ERROR_SHARING_VIOLATION && !retried) { + /* This can be caused by the handle we have open to the target + * directory. Try closing it temporarily. */ + close_target_directory(ctx); + retried = true; + goto retry; + } /* Restore the NT namespace path */ build_extraction_path(dentry, ctx); if (err != ERROR_SUCCESS) { - set_errno_from_win32_error(err); - ERROR_WITH_ERRNO("Can't open \"%ls\" for encrypted import " - "(err=%"PRIu32")", current_path(ctx), (u32)err); + win32_error(err, L"Can't open \"%ls\" for encrypted import", + current_path(ctx)); return WIMLIB_ERR_OPEN; } @@ -1631,22 +2161,20 @@ extract_encrypted_file(const struct wim_dentry *dentry, CloseEncryptedFileRaw(rawctx); if (err != ERROR_SUCCESS) { - set_errno_from_win32_error(err); - ERROR_WITH_ERRNO("Can't import encrypted file \"%ls\" " - "(err=%"PRIu32")", current_path(ctx), (u32)err); + win32_error(err, L"Can't import encrypted file \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_WRITE; } return 0; } -/* Called when starting to read a stream for extraction on Windows */ +/* Called when starting to read a blob for extraction on Windows */ static int -begin_extract_stream(struct wim_lookup_table_entry *stream, - u32 flags, void *_ctx) +begin_extract_blob(struct blob_descriptor *blob, void *_ctx) { struct win32_apply_ctx *ctx = _ctx; - const struct stream_owner *owners = stream_owners(stream); + const struct blob_extraction_target *targets = blob_extraction_targets(blob); int ret; ctx->num_open_handles = 0; @@ -1654,44 +2182,31 @@ begin_extract_stream(struct wim_lookup_table_entry *stream, INIT_LIST_HEAD(&ctx->reparse_dentries); INIT_LIST_HEAD(&ctx->encrypted_dentries); - for (u32 i = 0; i < stream->out_refcnt; i++) { - const struct wim_inode *inode = owners[i].inode; - const wchar_t *stream_name = owners[i].stream_name; + 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 stream needs to be extracted to @inode. */ + /* 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_stream_instance(stream, dentry, - stream_name, ctx); + 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 stream + /* Hard links not supported. Extract the blob * separately to each alias of the inode. */ - struct list_head *next; - - next = inode->i_extraction_aliases.next; - do { - dentry = list_entry(next, struct wim_dentry, - d_extraction_alias_node); - ret = begin_extract_stream_instance(stream, - dentry, - stream_name, - ctx); + 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; - next = next->next; - } while (next != &inode->i_extraction_aliases); + } } } - if (unlikely(ctx->num_open_handles == 0 && ctx->data_buffer_ptr == NULL)) { - /* The data of this stream isn't actually needed! - * (This can happen in WIMBoot mode.) */ - return BEGIN_STREAM_STATUS_SKIP_STREAM; - } return 0; fail: @@ -1699,8 +2214,8 @@ fail: return ret; } -/* Called when the next chunk of a stream has been read for extraction on - * Windows */ +/* 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) { @@ -1719,10 +2234,7 @@ extract_chunk(const void *chunk, size_t size, void *_ctx) &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); + winnt_error(status, L"Error writing data to target volume"); return WIMLIB_ERR_WRITE; } bufptr += ctx->iosb.Information; @@ -1737,9 +2249,126 @@ extract_chunk(const void *chunk, size_t size, void *_ctx) 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) +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 DWORD +set_system_compression(HANDLE h, int format) +{ + DWORD bytes_returned; + DWORD err; + 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, + }, + }; + + if (DeviceIoControl(h, FSCTL_SET_EXTERNAL_BACKING, &in, sizeof(in), + NULL, 0, &bytes_returned, NULL)) + return 0; + + err = GetLastError(); + + if (err == 344) /* "Compressing this object would not save space." */ + return 0; + + return err; +} + +/* + * 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; + HANDLE h; + NTSTATUS status; + DWORD err; + + if (!stream_is_unnamed_data_stream(strm)) + continue; + + if (will_externally_back_inode(inode, ctx, NULL, false) != 0) + continue; + + status = create_file(&h, GENERIC_READ | GENERIC_WRITE, NULL, + 0, FILE_OPEN, 0, + inode_first_extraction_dentry(inode), ctx); + + if (NT_SUCCESS(status)) { + err = set_system_compression(h, format); + (*func_NtClose)(h); + } else { + err = (*func_RtlNtStatusToDosError)(status); + } + + if (err == ERROR_INVALID_FUNCTION) { + 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; + } + + if (err) { + ctx->num_system_compression_failures++; + if (ctx->num_system_compression_failures < 10) { + win32_warning(err, 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; @@ -1750,39 +2379,51 @@ end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_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 (stream->size > REPARSE_DATA_MAX_SIZE) { + if (blob->size > REPARSE_DATA_MAX_SIZE) { dentry = list_first_entry(&ctx->reparse_dentries, - struct wim_dentry, tmp_list); + 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), stream->size, + current_path(ctx), blob->size, REPARSE_DATA_MAX_SIZE); - return WIMLIB_ERR_INVALID_REPARSE_DATA; + ret = WIMLIB_ERR_INVALID_REPARSE_DATA; + return check_apply_error(dentry, ctx, ret); } - /* 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); + /* 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 = stream->size; - list_for_each_entry(dentry, &ctx->encrypted_dentries, tmp_list) { + 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; } @@ -1802,33 +2443,100 @@ end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx /* 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, +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. + */ - /* 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".) */ + 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 = (*func_NtSetSecurityObject)(h, info, (PSECURITY_DESCRIPTOR)desc); + status = (*func_NtSetSecurityObject)(h, info, desc); if (NT_SUCCESS(status)) - return 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. */ @@ -1837,7 +2545,9 @@ retry: !(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) { if (info & SACL_SECURITY_INFORMATION) { - info &= ~SACL_SECURITY_INFORMATION; + info &= ~(SACL_SECURITY_INFORMATION | + LABEL_SECURITY_INFORMATION | + BACKUP_SECURITY_INFORMATION); ctx->partial_security_descriptors++; goto retry; } @@ -1859,6 +2569,10 @@ retry: 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; } @@ -1871,7 +2585,7 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, NTSTATUS status; /* Set security descriptor if present and not in NO_ACLS mode */ - if (inode->i_security_id >= 0 && + if (inode_has_security_descriptor(inode) && !(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)) { const struct wim_security_data *sd; @@ -1886,10 +2600,9 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, 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); + winnt_error(status, + L"Can't set security descriptor on \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_SET_SECURITY; } } @@ -1899,10 +2612,13 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, 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 + 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 = (*func_NtSetInformationFile)(h, &ctx->iosb, &info, sizeof(info), @@ -1914,10 +2630,8 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, && !(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); + winnt_error(status, L"Can't set basic metadata on \"%ls\"", + current_path(ctx)); return WIMLIB_ERR_SET_ATTRIBUTES; } @@ -1959,10 +2673,8 @@ apply_metadata_to_file(const struct wim_dentry *dentry, 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); + winnt_error(status, L"Can't open \"%ls\" to set metadata", + current_path(ctx)); return WIMLIB_ERR_OPEN; } @@ -1985,6 +2697,10 @@ apply_metadata(struct list_head *dentry_list, struct win32_apply_ctx *ctx) 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; } @@ -1997,24 +2713,54 @@ apply_metadata(struct list_head *dentry_list, struct win32_apply_ctx *ctx) static void do_warnings(const struct win32_apply_ctx *ctx) { - if (ctx->partial_security_descriptors == 0 && - ctx->no_security_descriptors == 0) + 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->partial_security_descriptors != 0) { + 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 != 0) { + if (ctx->no_security_descriptors) { 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."); + 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 */ @@ -2023,17 +2769,24 @@ 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 (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) { - ret = start_wimboot_extraction(ctx); + if (unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) { + ret = start_wimboot_extraction(dentry_list, ctx); if (ret) goto out; } + 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; @@ -2042,15 +2795,21 @@ win32_extract(struct list_head *dentry_list, struct apply_ctx *_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 = 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_stream_list(&ctx->common, &cbs); + ret = extract_blob_list(&ctx->common, &cbs); + if (ret) + goto out; + + ret = start_file_metadata_phase(&ctx->common, dentry_count); if (ret) goto out; @@ -2058,19 +2817,29 @@ win32_extract(struct list_head *dentry_list, struct apply_ctx *_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: - if (ctx->h_target) - (*func_NtClose)(ctx->h_target); + close_target_directory(ctx); 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.wims); + if (ctx->prepopulate_pats) { + FREE(ctx->prepopulate_pats->strings); + FREE(ctx->prepopulate_pats); } - FREE(ctx->wimboot.mem_prepopulate_pats); + FREE(ctx->mem_prepopulate_pats); FREE(ctx->data_buffer); return ret; } @@ -2079,6 +2848,7 @@ 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), };