X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_apply.c;h=347407326f914d3c358c0ade3c9928541f640169;hp=1155b2a58e1584e93ea751082173c2141fb1d48e;hb=da43effd1d1d4b8a502ad3fccc0b579131cc8c50;hpb=ee547cc83f231d727e4d9984c23e86d96d3da769 diff --git a/src/win32_apply.c b/src/win32_apply.c index 1155b2a5..34740732 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -36,11 +36,12 @@ #include "wimlib/error.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/wildcard.h" #include "wimlib/wimboot.h" +#include "wimlib/wof.h" struct win32_apply_ctx { @@ -59,17 +60,18 @@ struct win32_apply_ctx { u8 blob_table_hash[SHA1_HASH_SIZE]; } *wims; size_t num_wims; - struct string_set *prepopulate_pats; - void *mem_prepopulate_pats; bool wof_running; - bool tried_to_load_prepopulate_list; - 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; @@ -146,6 +148,20 @@ struct win32_apply_ctx { /* 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; + + /* The number of files which, for compatibility with the Windows + * bootloader, were not compressed using the requested system + * compression format. This includes matches with the hardcoded pattern + * list only; it does not include matches with patterns in + * [PrepopulateList]. */ + unsigned long num_system_compression_exclusions; + + /* The Windows build number of the image being applied, or 0 if unknown. + */ + u64 windows_build_number; + /* Have we tried to enable short name support on the target volume yet? */ bool tried_to_enable_short_names; @@ -208,6 +224,14 @@ get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret, } } +/* Is the image being extracted an OS image for Windows 10 or later? */ +static bool +is_image_windows_10_or_later(struct win32_apply_ctx *ctx) +{ + /* Note: if no build number is available, this returns false. */ + return ctx->windows_build_number >= 10240; +} + static const wchar_t * current_path(struct win32_apply_ctx *ctx); @@ -282,8 +306,22 @@ win32_get_supported_features(const wchar_t *target, return 0; } -/* Load the patterns from [PrepopulateList] of WimBootCompress.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) { @@ -296,7 +334,10 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) void *mem; struct text_file_section sec; - ctx->wimboot.tried_to_load_prepopulate_list = true; + 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 || @@ -330,39 +371,25 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) LOAD_TEXT_FILE_REMOVE_QUOTES | LOAD_TEXT_FILE_NO_WARNINGS, mangle_pat); - BUILD_BUG_ON(OS_PREFERRED_PATH_SEPARATOR != WIM_PATH_SEPARATOR); + STATIC_ASSERT(OS_PREFERRED_PATH_SEPARATOR == WIM_PATH_SEPARATOR); FREE(buf); if (ret) { 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 specified absolute path to a file in the WIM image - * matches a pattern in [PrepopulateList] of WimBootCompress.ini. Otherwise - * returns %false. */ -static bool -in_prepopulate_list(const wchar_t *path, size_t path_nchars, - const struct win32_apply_ctx *ctx) -{ - const struct string_set *pats = ctx->wimboot.prepopulate_pats; - - if (!pats || !pats->num_strings) - return false; - - return match_pattern_list(path, path_nchars, pats); -} - /* 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 -can_externally_back_path(const wchar_t *path, size_t path_nchars, - const struct win32_apply_ctx *ctx) +can_externally_back_path(const wchar_t *path, const struct win32_apply_ctx *ctx) { - if (in_prepopulate_list(path, path_nchars, ctx)) + /* 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 @@ -374,13 +401,14 @@ can_externally_back_path(const wchar_t *path, size_t path_nchars, * 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, path_nchars, L"\\Windows\\System32\\config\\SYSTEM*", - OS_PREFERRED_PATH_SEPARATOR, false)) + 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) @@ -450,18 +478,38 @@ is_resource_valid_for_external_backing(const struct wim_resource_descriptor *rde return true; } -#define WIM_BACKING_NOT_ENABLED -1 -#define WIM_BACKING_NOT_POSSIBLE -2 -#define WIM_BACKING_EXCLUDED -3 +#define EXTERNAL_BACKING_NOT_ENABLED -1 +#define EXTERNAL_BACKING_NOT_POSSIBLE -2 +#define EXTERNAL_BACKING_EXCLUDED -3 +/* + * 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) + 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; @@ -473,13 +521,17 @@ will_externally_back_inode(struct wim_inode *inode, struct win32_apply_ctx *ctx, if (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_ENCRYPTED)) - return WIM_BACKING_NOT_POSSIBLE; + return EXTERNAL_BACKING_NOT_POSSIBLE; blob = inode_get_blob_for_unnamed_data_stream_resolved(inode); - if (!blob || blob->blob_location != BLOB_IN_WIM || - !is_resource_valid_for_external_backing(blob->rdesc, ctx)) - return WIM_BACKING_NOT_POSSIBLE; + 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 @@ -492,12 +544,10 @@ will_externally_back_inode(struct wim_inode *inode, struct win32_apply_ctx *ctx, if (ret) return ret; - if (!can_externally_back_path(dentry->d_full_path, - wcslen(dentry->d_full_path), ctx)) - { + if (!can_externally_back_path(dentry->d_full_path, ctx)) { if (excluded_dentry_ret) *excluded_dentry_ret = dentry; - return WIM_BACKING_EXCLUDED; + return EXTERNAL_BACKING_EXCLUDED; } } @@ -506,22 +556,19 @@ will_externally_back_inode(struct wim_inode *inode, struct win32_apply_ctx *ctx, } /* - * Determines if the unnamed data stream of a file will be created as an - * external backing, as opposed to a standard extraction. + * 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 -win32_will_externally_back(struct wim_dentry *dentry, struct apply_ctx *_ctx) +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 WIM_BACKING_NOT_ENABLED; - - if (!ctx->wimboot.tried_to_load_prepopulate_list) - if (load_prepopulate_pats(ctx) == WIMLIB_ERR_NOMEM) - return WIMLIB_ERR_NOMEM; + return EXTERNAL_BACKING_NOT_ENABLED; - return will_externally_back_inode(dentry->d_inode, ctx, NULL); + return will_externally_back_inode(dentry->d_inode, ctx, NULL, true); } /* Find the WOF registration information for the specified WIM file. */ @@ -537,21 +584,21 @@ find_wimboot_wim(WIMStruct *wim_to_find, struct win32_apply_ctx *ctx) } static int -set_external_backing(HANDLE h, struct wim_inode *inode, struct win32_apply_ctx *ctx) +set_backed_from_wim(HANDLE h, struct wim_inode *inode, struct win32_apply_ctx *ctx) { int ret; const struct wim_dentry *excluded_dentry; const struct blob_descriptor *blob; const struct wimboot_wim *wimboot_wim; - ret = will_externally_back_inode(inode, ctx, &excluded_dentry); + ret = will_externally_back_inode(inode, ctx, &excluded_dentry, true); if (ret > 0) /* Error. */ return ret; - if (ret < 0 && ret != WIM_BACKING_EXCLUDED) + if (ret < 0 && ret != EXTERNAL_BACKING_EXCLUDED) return 0; /* Not externally backing, other than due to exclusion. */ - if (unlikely(ret == WIM_BACKING_EXCLUDED)) { + if (unlikely(ret == EXTERNAL_BACKING_EXCLUDED)) { /* Not externally backing due to exclusion. */ union wimlib_progress_info info; @@ -631,8 +678,7 @@ register_wim_with_wof(WIMStruct *wim, struct win32_apply_ctx *ctx) return 0; } -/* Prepare for doing a "WIMBoot" extraction by loading patterns from - * [PrepopulateList] of WimBootCompress.ini and registering each source WIM file +/* 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) @@ -640,12 +686,8 @@ start_wimboot_extraction(struct list_head *dentry_list, struct win32_apply_ctx * int ret; struct wim_dentry *dentry; - if (!ctx->wimboot.tried_to_load_prepopulate_list) - if (load_prepopulate_pats(ctx) == WIMLIB_ERR_NOMEM) - return WIMLIB_ERR_NOMEM; - - if (!wim_info_get_wimboot(ctx->common.wim->wim_info, - ctx->common.wim->current_image)) + if (!xml_get_wimboot(ctx->common.wim->xml_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."); @@ -653,7 +695,7 @@ start_wimboot_extraction(struct list_head *dentry_list, struct win32_apply_ctx * list_for_each_entry(dentry, dentry_list, d_extraction_list_node) { struct blob_descriptor *blob; - ret = win32_will_externally_back(dentry, &ctx->common); + ret = win32_will_back_from_wim(dentry, &ctx->common); if (ret > 0) /* Error */ return ret; if (ret < 0) /* Won't externally back */ @@ -859,7 +901,9 @@ build_extraction_path(const struct wim_dentry *dentry, d = d->d_parent) { p -= d->d_extraction_name_nchars; - wmemcpy(p, d->d_extraction_name, d->d_extraction_name_nchars); + if (d->d_extraction_name_nchars) + wmemcpy(p, d->d_extraction_name, + d->d_extraction_name_nchars); *--p = '\\'; } /* No leading slash */ @@ -947,6 +991,9 @@ open_target_directory(struct win32_apply_ctx *ctx) ctx->attr.Length = sizeof(ctx->attr); ctx->attr.RootDirectory = NULL; ctx->attr.ObjectName = &ctx->target_ntpath; + + /* Don't use FILE_OPEN_REPARSE_POINT here; we want the extraction to + * happen at the directory "pointed to" by the reparse point. */ status = (*func_NtCreateFile)(&ctx->h_target, FILE_TRAVERSE, &ctx->attr, @@ -956,7 +1003,6 @@ open_target_directory(struct win32_apply_ctx *ctx) FILE_SHARE_VALID_FLAGS, FILE_OPEN_IF, FILE_DIRECTORY_FILE | - FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT, NULL, 0); @@ -1055,6 +1101,7 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry, FILE_BASIC_INFORMATION info; NTSTATUS status; USHORT compression_state; + DWORD bytes_returned; /* Get current attributes */ status = (*func_NtQueryInformationFile)(h, &ctx->iosb, @@ -1074,20 +1121,14 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry, else compression_state = COMPRESSION_FORMAT_NONE; - status = (*func_NtFsControlFile)(h, - NULL, - NULL, - NULL, - &ctx->iosb, - FSCTL_SET_COMPRESSION, - &compression_state, - sizeof(USHORT), - NULL, - 0); - if (NT_SUCCESS(status)) + /* Note: don't use NtFsControlFile() here unless prepared to handle + * STATUS_PENDING. */ + if (DeviceIoControl(h, FSCTL_SET_COMPRESSION, + &compression_state, sizeof(USHORT), NULL, 0, + &bytes_returned, NULL)) return 0; - winnt_error(status, L"Can't %s compression attribute on \"%ls\"", + win32_error(GetLastError(), L"Can't %s compression attribute on \"%ls\"", (compressed ? "set" : "clear"), current_path(ctx)); return WIMLIB_ERR_SET_ATTRIBUTES; } @@ -1359,67 +1400,71 @@ 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; + ULONG perms = DELETE; + ULONG flags = FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE; + + /* First try opening the file with FILE_DELETE_ON_CLOSE. In most cases, + * all we have to do is that plus close the file handle. */ +retry: + status = do_create_file(&h, perms, NULL, 0, FILE_OPEN, flags, ctx); + + if (unlikely(status == STATUS_CANNOT_DELETE)) { + /* This error occurs for files with FILE_ATTRIBUTE_READONLY set. + * Try an alternate approach: first open the file without + * FILE_DELETE_ON_CLOSE, then reset the file attributes, then + * set the "delete" disposition on the handle. */ + if (flags & FILE_DELETE_ON_CLOSE) { + flags &= ~FILE_DELETE_ON_CLOSE; + perms |= FILE_WRITE_ATTRIBUTES; + goto retry; + } + } - 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)); + winnt_error(status, L"Can't open \"%ls\" for deletion " + "(perms=%x, flags=%x)", + current_path(ctx), perms, flags); 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 (unlikely(!(flags & FILE_DELETE_ON_CLOSE))) { - 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; + FILE_BASIC_INFORMATION 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)); + winnt_error(status, L"Can't reset attributes of \"%ls\" " + "to prepare for deletion", current_path(ctx)); + (*func_NtClose)(h); + return WIMLIB_ERR_SET_ATTRIBUTES; + } + + FILE_DISPOSITION_INFORMATION disp_info = + { .DoDeleteFile = TRUE }; + status = (*func_NtSetInformationFile)(h, &ctx->iosb, + &disp_info, + sizeof(disp_info), + FileDispositionInformation); + if (!NT_SUCCESS(status)) { + winnt_error(status, L"Can't set delete-on-close " + "disposition 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; + + status = (*func_NtClose)(h); + if (unlikely(!NT_SUCCESS(status))) { + winnt_error(status, L"Error closing \"%ls\" after setting " + "delete-on-close disposition", current_path(ctx)); + return WIMLIB_ERR_OPEN; + } + + return 0; } /* @@ -1464,44 +1509,104 @@ retry: 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) +{ + 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: + winnt_error(status, L"Can't set reparse data on \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_SET_REPARSE_DATA; +} + /* - * Create empty named data streams for the specified file, if there are any. + * 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_named_data_streams(const struct wim_dentry *dentry, - struct win32_apply_ctx *ctx) +create_empty_streams(const struct wim_dentry *dentry, + struct win32_apply_ctx *ctx) { const struct wim_inode *inode = dentry->d_inode; - bool path_modified = false; - int ret = 0; - - if (!ctx->common.supported_features.named_data_streams) - return 0; + int ret; for (unsigned i = 0; i < inode->i_num_streams; i++) { const struct wim_inode_stream *strm = &inode->i_streams[i]; - HANDLE h; - if (!stream_is_named_data_stream(strm) || - stream_blob_resolved(strm) != NULL) + if (stream_blob_resolved(strm) != NULL) continue; - build_extraction_path_with_ads(dentry, ctx, - strm->stream_name, - utf16le_len_chars(strm->stream_name)); - path_modified = true; - ret = supersede_file_or_stream(ctx, &h); - if (ret) - break; - (*func_NtClose)(h); + 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, + 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); + } } - /* Restore the path to the dentry itself */ - if (path_modified) - build_extraction_path(dentry, ctx); - return ret; + + return 0; } /* @@ -1520,16 +1625,28 @@ create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) int ret; /* DELETE is needed for set_short_name(); GENERIC_READ and GENERIC_WRITE - * are needed for adjust_compression_attribute(). */ - perms = GENERIC_READ | GENERIC_WRITE; + * are needed for adjust_compression_attribute(); WRITE_DAC is needed to + * remove the directory's DACL if the directory already existed */ + perms = GENERIC_READ | GENERIC_WRITE | WRITE_DAC; 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. */ +retry: status = create_file(&h, perms, NULL, FILE_ATTRIBUTE_SYSTEM, FILE_OPEN_IF, FILE_DIRECTORY_FILE, dentry, ctx); - if (!NT_SUCCESS(status)) { + if (unlikely(!NT_SUCCESS(status))) { + if (status == STATUS_ACCESS_DENIED) { + if (perms & WRITE_DAC) { + perms &= ~WRITE_DAC; + goto retry; + } + if (perms & DELETE) { + perms &= ~DELETE; + goto retry; + } + } winnt_error(status, L"Can't create directory \"%ls\"", current_path(ctx)); return WIMLIB_ERR_MKDIR; @@ -1547,6 +1664,22 @@ create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) FILE_BASIC_INFORMATION basic_info = { .FileAttributes = FILE_ATTRIBUTE_NORMAL }; (*func_NtSetInformationFile)(h, &ctx->iosb, &basic_info, sizeof(basic_info), FileBasicInformation); + + /* Also try to remove the directory's DACL. This isn't supposed + * to be necessary because we *always* use backup semantics. + * However, there is a case where NtCreateFile() fails with + * STATUS_ACCESS_DENIED when creating a named data stream that + * was just deleted, using a directory-relative open. I have no + * idea why Windows is broken in this case. */ + static const SECURITY_DESCRIPTOR_RELATIVE desc = { + .Revision = SECURITY_DESCRIPTOR_REVISION1, + .Control = SE_SELF_RELATIVE | SE_DACL_PRESENT, + .Owner = 0, + .Group = 0, + .Sacl = 0, + .Dacl = 0, + }; + (*func_NtSetSecurityObject)(h, DACL_SECURITY_INFORMATION, (void *)&desc); } if (!dentry_is_root(dentry)) { @@ -1587,7 +1720,7 @@ create_directories(struct list_head *dentry_list, ret = create_directory(dentry, ctx); if (!ret) - ret = create_empty_named_data_streams(dentry, ctx); + ret = create_empty_streams(dentry, ctx); ret = check_apply_error(dentry, ctx, ret); if (ret) @@ -1624,7 +1757,7 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, if (ret) goto out_close; - ret = create_empty_named_data_streams(dentry, ctx); + ret = create_empty_streams(dentry, ctx); if (ret) goto out_close; @@ -1736,7 +1869,7 @@ create_nondirectory(struct wim_inode *inode, struct win32_apply_ctx *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_external_backing(h, inode, ctx); + ret = set_backed_from_wim(h, inode, ctx); (*func_NtClose)(h); return ret; @@ -1881,54 +2014,6 @@ begin_extract_blob_instance(const struct blob_descriptor *blob, return 0; } -/* 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) -{ - 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: - winnt_error(status, L"Can't set reparse data on \"%ls\"", - current_path(ctx)); - 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. */ @@ -2214,6 +2299,261 @@ extract_chunk(const void *chunk, size_t size, void *_ctx) return 0; } +static int +get_system_compression_format(int extract_flags) +{ + if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K) + return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K; + + if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K) + return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS8K; + + if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K) + return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS16K; + + return FILE_PROVIDER_COMPRESSION_FORMAT_LZX; +} + + +static const wchar_t * +get_system_compression_format_string(int format) +{ + switch (format) { + case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K: + return L"XPRESS4K"; + case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS8K: + return L"XPRESS8K"; + case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS16K: + return L"XPRESS16K"; + default: + return L"LZX"; + } +} + +static NTSTATUS +set_system_compression(HANDLE h, int format) +{ + NTSTATUS status; + IO_STATUS_BLOCK iosb; + struct { + struct wof_external_info wof_info; + struct file_provider_external_info file_info; + } in = { + .wof_info = { + .version = WOF_CURRENT_VERSION, + .provider = WOF_PROVIDER_FILE, + }, + .file_info = { + .version = FILE_PROVIDER_CURRENT_VERSION, + .compression_format = format, + }, + }; + + /* We intentionally use NtFsControlFile() rather than DeviceIoControl() + * here because the "compressing this object would not save space" + * status code does not map to a valid Win32 error code on older + * versions of Windows (before Windows 10?). This can be a problem if + * the WOFADK driver is being used rather than the regular WOF, since + * WOFADK can be used on older versions of Windows. */ + status = (*func_NtFsControlFile)(h, NULL, NULL, NULL, &iosb, + FSCTL_SET_EXTERNAL_BACKING, + &in, sizeof(in), NULL, 0); + + if (status == 0xC000046F) /* "Compressing this object would not save space." */ + return STATUS_SUCCESS; + + return status; +} + +/* Hard-coded list of files which the Windows bootloader may need to access + * before the WOF driver has been loaded. */ +static wchar_t *bootloader_pattern_strings[] = { + L"*winload.*", + L"*winresume.*", + L"\\Windows\\AppPatch\\drvmain.sdb", + L"\\Windows\\Boot\\DVD\\*", + L"\\Windows\\Boot\\EFI\\*", + L"\\Windows\\bootstat.dat", + L"\\Windows\\Fonts\\vgaoem.fon", + L"\\Windows\\Fonts\\vgasys.fon", + L"\\Windows\\INF\\errata.inf", + L"\\Windows\\System32\\config\\*", + L"\\Windows\\System32\\ntkrnlpa.exe", + L"\\Windows\\System32\\ntoskrnl.exe", + L"\\Windows\\System32\\bootvid.dll", + L"\\Windows\\System32\\ci.dll", + L"\\Windows\\System32\\hal*.dll", + L"\\Windows\\System32\\mcupdate_AuthenticAMD.dll", + L"\\Windows\\System32\\mcupdate_GenuineIntel.dll", + L"\\Windows\\System32\\pshed.dll", + L"\\Windows\\System32\\apisetschema.dll", + L"\\Windows\\System32\\api-ms-win*.dll", + L"\\Windows\\System32\\ext-ms-win*.dll", + L"\\Windows\\System32\\KernelBase.dll", + L"\\Windows\\System32\\drivers\\*.sys", + L"\\Windows\\System32\\*.nls", + L"\\Windows\\System32\\kbd*.dll", + L"\\Windows\\System32\\kd*.dll", + L"\\Windows\\System32\\clfs.sys", + L"\\Windows\\System32\\CodeIntegrity\\driver.stl", +}; + +static const struct string_set bootloader_patterns = { + .strings = bootloader_pattern_strings, + .num_strings = ARRAY_LEN(bootloader_pattern_strings), +}; + +static NTSTATUS +set_system_compression_on_inode(struct wim_inode *inode, int format, + struct win32_apply_ctx *ctx) +{ + bool retried = false; + NTSTATUS status; + HANDLE h; + + /* If it may be needed for compatibility with the Windows bootloader, + * force this file to XPRESS4K or uncompressed format. The bootloader + * of Windows 10 supports XPRESS4K only; older versions don't support + * system compression at all. */ + if (!is_image_windows_10_or_later(ctx) || + format != FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K) + { + /* We need to check the patterns against every name of the + * inode, in case any of them match. */ + struct wim_dentry *dentry; + inode_for_each_extraction_alias(dentry, inode) { + bool incompatible; + bool warned; + + if (calculate_dentry_full_path(dentry)) { + ERROR("Unable to compute file path!"); + return STATUS_NO_MEMORY; + } + + incompatible = match_pattern_list(dentry->d_full_path, + &bootloader_patterns); + FREE(dentry->d_full_path); + dentry->d_full_path = NULL; + + if (!incompatible) + continue; + + warned = (ctx->num_system_compression_exclusions++ > 0); + + if (is_image_windows_10_or_later(ctx)) { + /* Force to XPRESS4K */ + if (!warned) { + WARNING("For compatibility with the " + "Windows bootloader, some " + "files are being\n" + " compacted " + "using the XPRESS4K format " + "instead of the %"TS" format\n" + " you requested.", + get_system_compression_format_string(format)); + } + format = FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K; + break; + } else { + /* Force to uncompressed */ + if (!warned) { + WARNING("For compatibility with the " + "Windows bootloader, some " + "files will not\n" + " be compressed with" + " system compression " + "(\"compacted\")."); + } + return STATUS_SUCCESS; + } + + } + } + + /* Open the extracted file. */ + status = create_file(&h, GENERIC_READ | GENERIC_WRITE, NULL, + 0, FILE_OPEN, 0, + inode_first_extraction_dentry(inode), ctx); + + if (!NT_SUCCESS(status)) + return status; +retry: + /* Compress the file. If the attempt fails with "invalid device + * request", then attach wof.sys (or wofadk.sys) and retry. */ + status = set_system_compression(h, format); + if (unlikely(status == STATUS_INVALID_DEVICE_REQUEST && !retried)) { + wchar_t drive_path[7]; + if (!win32_get_drive_path(ctx->common.target, drive_path) && + win32_try_to_attach_wof(drive_path + 4)) { + retried = true; + goto retry; + } + } + + (*func_NtClose)(h); + return status; +} + +/* + * This function is called when doing a "compact-mode" extraction and we just + * finished extracting a blob to one or more locations. For each location that + * was the unnamed data stream of a file, this function compresses the + * corresponding file using System Compression, if allowed. + * + * Note: we're doing the compression immediately after extracting the data + * rather than during a separate compression pass. This way should be faster + * since the operating system should still have the file's data cached. + * + * Note: we're having the operating system do the compression, which is not + * ideal because wimlib could create the compressed data faster and more + * efficiently (the compressed data format is identical to a WIM resource). But + * we seemingly don't have a choice because WOF prevents applications from + * creating its reparse points. + */ +static void +handle_system_compression(struct blob_descriptor *blob, struct win32_apply_ctx *ctx) +{ + const struct blob_extraction_target *targets = blob_extraction_targets(blob); + + const int format = get_system_compression_format(ctx->common.extract_flags); + + for (u32 i = 0; i < blob->out_refcnt; i++) { + struct wim_inode *inode = targets[i].inode; + struct wim_inode_stream *strm = targets[i].stream; + NTSTATUS status; + + if (!stream_is_unnamed_data_stream(strm)) + continue; + + if (will_externally_back_inode(inode, ctx, NULL, false) != 0) + continue; + + status = set_system_compression_on_inode(inode, format, ctx); + if (likely(NT_SUCCESS(status))) + continue; + + if (status == STATUS_INVALID_DEVICE_REQUEST) { + WARNING( + "The request to compress the extracted files using System Compression\n" +" will not be honored because the operating system or target volume\n" +" does not support it. System Compression is only supported on\n" +" Windows 10 and later, and only on NTFS volumes."); + ctx->common.extract_flags &= ~COMPACT_FLAGS; + return; + } + + ctx->num_system_compression_failures++; + if (ctx->num_system_compression_failures < 10) { + winnt_warning(status, L"\"%ls\": Failed to compress " + "extracted file using System Compression", + current_path(ctx)); + } else if (ctx->num_system_compression_failures == 10) { + WARNING("Suppressing further warnings about " + "System Compression failures."); + } + } +} + /* Called when a blob has been fully read for extraction on Windows */ static int end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) @@ -2227,6 +2567,9 @@ end_extract_blob(struct blob_descriptor *blob, 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; @@ -2626,6 +2969,9 @@ win32_extract(struct list_head *dentry_list, struct apply_ctx *_ctx) goto out; } + ctx->windows_build_number = xml_get_windows_build_number(ctx->common.wim->xml_info, + ctx->common.wim->current_image); + dentry_count = count_dentries(dentry_list); ret = start_file_structure_phase(&ctx->common, dentry_count); @@ -2680,11 +3026,11 @@ out: FREE(ctx->pathbuf.Buffer); FREE(ctx->print_buffer); FREE(ctx->wimboot.wims); - if (ctx->wimboot.prepopulate_pats) { - FREE(ctx->wimboot.prepopulate_pats->strings); - FREE(ctx->wimboot.prepopulate_pats); + 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; } @@ -2693,7 +3039,7 @@ const struct apply_operations win32_apply_ops = { .name = "Windows", .get_supported_features = win32_get_supported_features, .extract = win32_extract, - .will_externally_back = win32_will_externally_back, + .will_back_from_wim = win32_will_back_from_wim, .context_size = sizeof(struct win32_apply_ctx), };