X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_apply.c;h=a1301d646290f4d8671428119698e3a41871e619;hp=d3534643115e86b93c63f0460cb6e4ed7eb0b6dc;hb=01ce2d43d6ba9721bf46c3e132c4be394ef3f0f9;hpb=51829aecdac415b417ab5b8ac897014bb780de10 diff --git a/src/win32_apply.c b/src/win32_apply.c index d3534643..a1301d64 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2013-2016 Eric Biggers + * Copyright (C) 2013-2021 Eric Biggers * * 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 @@ -30,7 +30,6 @@ #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" @@ -39,10 +38,12 @@ #include "wimlib/paths.h" #include "wimlib/pattern.h" #include "wimlib/reparse.h" +#include "wimlib/scan.h" /* for mangle_pat() and match_pattern_list() */ #include "wimlib/textfile.h" -#include "wimlib/xml.h" #include "wimlib/wimboot.h" #include "wimlib/wof.h" +#include "wimlib/xattr.h" +#include "wimlib/xml.h" struct win32_apply_ctx { @@ -69,7 +70,7 @@ struct win32_apply_ctx { } wimboot; /* External backing information */ - struct string_set *prepopulate_pats; + struct string_list *prepopulate_pats; void *mem_prepopulate_pats; bool tried_to_load_prepopulate_list; @@ -125,6 +126,13 @@ struct win32_apply_ctx { * beginning of the array) */ unsigned num_open_handles; + /* For each currently open stream, whether we're writing to it in + * "sparse" mode or not. */ + bool is_sparse_stream[MAX_OPEN_FILES]; + + /* Whether is_sparse_stream[] is true for any currently open stream */ + bool any_sparse_streams; + /* 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. */ @@ -162,6 +170,9 @@ struct win32_apply_ctx { /* Number of files for which we couldn't set the object ID. */ unsigned long num_object_id_failures; + /* Number of files for which we couldn't set extended attributes. */ + unsigned long num_xattr_failures; + /* The Windows build number of the image being applied, or 0 if unknown. */ u64 windows_build_number; @@ -218,9 +229,14 @@ get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret, } if (wcsstr(filesystem_name, L"NTFS")) { - /* FILE_SUPPORTS_HARD_LINKS is only supported on Windows 7 and - * later. Force it on anyway if filesystem is NTFS. */ + /* + * FILE_SUPPORTS_HARD_LINKS and + * FILE_SUPPORTS_EXTENDED_ATTRIBUTES are only supported on + * Windows 7 and later. Force them on anyway if the filesystem + * is NTFS. + */ *vol_flags_ret |= FILE_SUPPORTS_HARD_LINKS; + *vol_flags_ret |= FILE_SUPPORTS_EXTENDED_ATTRIBUTES; /* There's no volume flag for short names, but according to the * MS documentation they are only user-settable on NTFS. */ @@ -228,14 +244,6 @@ 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); @@ -286,7 +294,8 @@ win32_get_supported_features(const wchar_t *target, supported_features->not_context_indexed_files = 1; - /* Don't do anything with FILE_SUPPORTS_SPARSE_FILES. */ + if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) + supported_features->sparse_files = 1; if (vol_flags & FILE_NAMED_STREAMS) supported_features->named_data_streams = 1; @@ -308,8 +317,31 @@ win32_get_supported_features(const wchar_t *target, supported_features->timestamps = 1; - /* Note: Windows does not support case sensitive filenames! At least - * not without changing the registry and rebooting... */ + if (vol_flags & FILE_CASE_SENSITIVE_SEARCH) { + /* + * The filesystem supports case-sensitive filenames. But does + * the operating system as well? This normally requires the + * registry setting ObCaseInsensitive=0. We can test it + * indirectly by attempting to open the "\SystemRoot" symbolic + * link using a name with the wrong case. If we get + * STATUS_OBJECT_NAME_NOT_FOUND instead of STATUS_ACCESS_DENIED, + * then case-sensitive names must be enabled. + */ + UNICODE_STRING path; + OBJECT_ATTRIBUTES attr; + HANDLE h; + NTSTATUS status; + + RtlInitUnicodeString(&path, L"\\systemroot"); + InitializeObjectAttributes(&attr, &path, 0, NULL, NULL); + + status = NtOpenSymbolicLinkObject(&h, 0, &attr); + if (status == STATUS_OBJECT_NAME_NOT_FOUND) + supported_features->case_sensitive_filenames = 1; + } + + if (vol_flags & FILE_SUPPORTS_EXTENDED_ATTRIBUTES) + supported_features->xattrs = 1; return 0; } @@ -338,7 +370,7 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) const struct blob_descriptor *blob; int ret; void *buf; - struct string_set *s; + struct string_list *strings; void *mem; struct text_file_section sec; @@ -366,26 +398,26 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) if (ret) return ret; - s = CALLOC(1, sizeof(struct string_set)); - if (!s) { + strings = CALLOC(1, sizeof(struct string_list)); + if (!strings) { FREE(buf); return WIMLIB_ERR_NOMEM; } sec.name = T("PrepopulateList"); - sec.strings = s; + sec.strings = strings; - ret = do_load_text_file(path, buf, blob->size, &mem, &sec, 1, - LOAD_TEXT_FILE_REMOVE_QUOTES | - LOAD_TEXT_FILE_NO_WARNINGS, - mangle_pat); + ret = load_text_file(path, buf, blob->size, &mem, &sec, 1, + LOAD_TEXT_FILE_REMOVE_QUOTES | + LOAD_TEXT_FILE_NO_WARNINGS, + mangle_pat); STATIC_ASSERT(OS_PREFERRED_PATH_SEPARATOR == WIM_PATH_SEPARATOR); FREE(buf); if (ret) { - FREE(s); + FREE(strings); return ret; } - ctx->prepopulate_pats = s; + ctx->prepopulate_pats = strings; ctx->mem_prepopulate_pats = mem; return 0; } @@ -397,7 +429,8 @@ can_externally_back_path(const wchar_t *path, const struct win32_apply_ctx *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)) + if (ctx->prepopulate_pats && match_pattern_list(path, ctx->prepopulate_pats, + MATCH_RECURSIVELY)) return false; /* Since we attempt to modify the SYSTEM registry after it's extracted @@ -409,7 +442,7 @@ can_externally_back_path(const wchar_t *path, const struct win32_apply_ctx *ctx) * 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)) + if (match_path(path, L"\\Windows\\System32\\config\\SYSTEM*", 0)) return false; return true; @@ -779,7 +812,7 @@ end_wimboot_extraction(struct win32_apply_ctx *ctx) build_win32_extraction_path(dentry, ctx); - randomize_char_array_with_alnum(subkeyname, 20); + get_random_alnum_chars(subkeyname, 20); subkeyname[20] = L'\0'; res = RegLoadKey(HKEY_LOCAL_MACHINE, subkeyname, ctx->pathbuf.Buffer); @@ -1010,8 +1043,7 @@ open_target_directory(struct win32_apply_ctx *ctx) 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN_IF, - FILE_DIRECTORY_FILE | - FILE_OPEN_FOR_BACKUP_INTENT, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT, NULL, 0); if (!NT_SUCCESS(status)) { @@ -1054,12 +1086,18 @@ prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx) 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 + * and ensure we have at least enough to potentially use an 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); + if (ctx->pathbuf.MaximumLength != path_max * sizeof(wchar_t)) { + /* Paths are too long for a UNICODE_STRING! */ + ERROR("Some paths are too long to extract (> 32768 characters)!"); + return WIMLIB_ERR_UNSUPPORTED; + } + ctx->pathbuf.Buffer = MALLOC(ctx->pathbuf.MaximumLength); if (!ctx->pathbuf.Buffer) return WIMLIB_ERR_NOMEM; @@ -1130,13 +1168,35 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry, status = winnt_fsctl(h, FSCTL_SET_COMPRESSION, &compression_state, sizeof(USHORT), NULL, 0, NULL); if (NT_SUCCESS(status)) - return status; + return 0; winnt_error(status, L"Can't %s compression attribute on \"%ls\"", (compressed ? "set" : "clear"), current_path(ctx)); return WIMLIB_ERR_SET_ATTRIBUTES; } +static bool +need_sparse_flag(const struct wim_inode *inode, + const struct win32_apply_ctx *ctx) +{ + return (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) && + ctx->common.supported_features.sparse_files; +} + +static int +set_sparse_flag(HANDLE h, struct win32_apply_ctx *ctx) +{ + NTSTATUS status; + + status = winnt_fsctl(h, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, NULL); + if (NT_SUCCESS(status)) + return 0; + + winnt_error(status, L"Can't set sparse flag on \"%ls\"", + current_path(ctx)); + return WIMLIB_ERR_SET_ATTRIBUTES; +} + /* Try to enable short name support on the target volume. If successful, return * true. If unsuccessful, issue a warning and return false. */ static bool @@ -1219,18 +1279,11 @@ retry: FileShortNameInformation); if (status == STATUS_INVALID_PARAMETER && !retried) { - /* Microsoft forgot to make it possible to remove short names * until Windows 7. Oops. Use a random short name instead. */ - + get_random_alnum_chars(info->FileName, 8); + wcscpy(&info->FileName[8], L".WLB"); 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; } @@ -1472,10 +1525,11 @@ retry: /* * 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. + * open handle to the file or named data stream with the requested permissions. */ static int -supersede_file_or_stream(struct win32_apply_ctx *ctx, HANDLE *h_ret) +supersede_file_or_stream(struct win32_apply_ctx *ctx, DWORD perms, + HANDLE *h_ret) { NTSTATUS status; bool retried = false; @@ -1484,7 +1538,7 @@ supersede_file_or_stream(struct win32_apply_ctx *ctx, HANDLE *h_ret) * 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, + perms, NULL, FILE_ATTRIBUTE_SYSTEM, FILE_CREATE, @@ -1596,7 +1650,13 @@ create_empty_streams(const struct wim_dentry *dentry, build_extraction_path_with_ads(dentry, ctx, strm->stream_name, utf16le_len_chars(strm->stream_name)); - ret = supersede_file_or_stream(ctx, &h); + /* + * Note: do not request any permissions on the handle. + * Otherwise, we may encounter a Windows bug where the + * parent directory DACL denies read access to the new + * named data stream, even when using backup semantics! + */ + ret = supersede_file_or_stream(ctx, 0, &h); build_extraction_path(dentry, ctx); @@ -1625,28 +1685,16 @@ 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(); WRITE_DAC is needed to - * remove the directory's DACL if the directory already existed */ - perms = GENERIC_READ | GENERIC_WRITE | WRITE_DAC; + * 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. */ -retry: status = create_file(&h, perms, NULL, FILE_ATTRIBUTE_SYSTEM, FILE_OPEN_IF, FILE_DIRECTORY_FILE, dentry, ctx); 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; - } - } const wchar_t *path = current_path(ctx); winnt_error(status, L"Can't create directory \"%ls\"", path); @@ -1684,25 +1732,6 @@ retry: 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. */ - if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)) { - 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, - }; - NtSetSecurityObject(h, DACL_SECURITY_INFORMATION, - (void *)&desc); - } } if (!dentry_is_root(dentry)) { @@ -1772,7 +1801,9 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, build_extraction_path(dentry, ctx); - ret = supersede_file_or_stream(ctx, &h); + ret = supersede_file_or_stream(ctx, + GENERIC_READ | GENERIC_WRITE | DELETE, + &h); if (ret) goto out; @@ -1780,6 +1811,12 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, if (ret) goto out_close; + if (need_sparse_flag(dentry->d_inode, ctx)) { + ret = set_sparse_flag(h, ctx); + if (ret) + goto out_close; + } + ret = create_empty_streams(dentry, ctx); if (ret) goto out_close; @@ -1960,7 +1997,6 @@ begin_extract_blob_instance(const struct blob_descriptor *blob, const struct wim_inode_stream *strm, struct win32_apply_ctx *ctx) { - FILE_ALLOCATION_INFORMATION alloc_info; HANDLE h; NTSTATUS status; @@ -2026,12 +2062,28 @@ begin_extract_blob_instance(const struct blob_descriptor *blob, return WIMLIB_ERR_OPEN; } + ctx->is_sparse_stream[ctx->num_open_handles] = false; + if (need_sparse_flag(dentry->d_inode, ctx)) { + /* If the stream is unnamed, then the sparse flag was already + * set when the file was created. But if the stream is named, + * then we need to set the sparse flag here. */ + if (unlikely(stream_is_named(strm))) { + int ret = set_sparse_flag(h, ctx); + if (ret) { + NtClose(h); + return ret; + } + } + ctx->is_sparse_stream[ctx->num_open_handles] = true; + ctx->any_sparse_streams = true; + } else { + /* Allocate space for the data. */ + FILE_ALLOCATION_INFORMATION info = + { .AllocationSize = { .QuadPart = blob->size }}; + NtSetInformationFile(h, &ctx->iosb, &info, sizeof(info), + FileAllocationInformation); + } ctx->open_handles[ctx->num_open_handles++] = h; - - /* Allocate space for the data. */ - alloc_info.AllocationSize.QuadPart = blob->size; - NtSetInformationFile(h, &ctx->iosb, &alloc_info, sizeof(alloc_info), - FileAllocationInformation); return 0; } @@ -2260,9 +2312,9 @@ retry: return 0; } -/* Called when starting to read a blob for extraction on Windows */ +/* Called when starting to read a blob for extraction */ static int -begin_extract_blob(struct blob_descriptor *blob, void *_ctx) +win32_begin_extract_blob(struct blob_descriptor *blob, void *_ctx) { struct win32_apply_ctx *ctx = _ctx; const struct blob_extraction_target *targets = blob_extraction_targets(blob); @@ -2270,6 +2322,7 @@ begin_extract_blob(struct blob_descriptor *blob, void *_ctx) ctx->num_open_handles = 0; ctx->data_buffer_ptr = NULL; + ctx->any_sparse_streams = false; INIT_LIST_HEAD(&ctx->reparse_dentries); INIT_LIST_HEAD(&ctx->encrypted_dentries); @@ -2305,31 +2358,58 @@ fail: return ret; } -/* 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) +pwrite_to_handle(HANDLE h, const void *data, size_t size, u64 offset) +{ + const void * const end = data + size; + const void *p; + IO_STATUS_BLOCK iosb; + NTSTATUS status; + + for (p = data; p != end; p += iosb.Information, + offset += iosb.Information) + { + LARGE_INTEGER offs = { .QuadPart = offset }; + + status = NtWriteFile(h, NULL, NULL, NULL, &iosb, + (void *)p, min(INT32_MAX, end - p), + &offs, NULL); + if (!NT_SUCCESS(status)) { + winnt_error(status, + L"Error writing data to target volume"); + return WIMLIB_ERR_WRITE; + } + } + return 0; +} + +/* Called when the next chunk of a blob has been read for extraction */ +static int +win32_extract_chunk(const struct blob_descriptor *blob, u64 offset, + const void *chunk, size_t size, void *_ctx) { struct win32_apply_ctx *ctx = _ctx; + const void * const end = chunk + size; + const void *p; + bool zeroes; + size_t len; + unsigned i; + int ret; - /* Write the data chunk to each open handle */ - for (unsigned i = 0; i < ctx->num_open_handles; i++) { - u8 *bufptr = (u8 *)chunk; - size_t bytes_remaining = size; - NTSTATUS status; - while (bytes_remaining) { - ULONG count = min(0xFFFFFFFF, bytes_remaining); - - status = NtWriteFile(ctx->open_handles[i], - NULL, NULL, NULL, - &ctx->iosb, bufptr, count, - NULL, NULL); - if (!NT_SUCCESS(status)) { - winnt_error(status, L"Error writing data to target volume"); - return WIMLIB_ERR_WRITE; + /* + * For sparse streams, only write nonzero regions. This lets the + * filesystem use holes to represent zero regions. + */ + for (p = chunk; p != end; p += len, offset += len) { + zeroes = maybe_detect_sparse_region(p, end - p, &len, + ctx->any_sparse_streams); + for (i = 0; i < ctx->num_open_handles; i++) { + if (!zeroes || !ctx->is_sparse_stream[i]) { + ret = pwrite_to_handle(ctx->open_handles[i], + p, len, offset); + if (ret) + return ret; } - bufptr += ctx->iosb.Information; - bytes_remaining -= ctx->iosb.Information; } } @@ -2344,15 +2424,15 @@ static int get_system_compression_format(int extract_flags) { if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K) - return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K; + return FILE_PROVIDER_COMPRESSION_XPRESS4K; if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K) - return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS8K; + return FILE_PROVIDER_COMPRESSION_XPRESS8K; if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K) - return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS16K; + return FILE_PROVIDER_COMPRESSION_XPRESS16K; - return FILE_PROVIDER_COMPRESSION_FORMAT_LZX; + return FILE_PROVIDER_COMPRESSION_LZX; } @@ -2360,11 +2440,11 @@ static const wchar_t * get_system_compression_format_string(int format) { switch (format) { - case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K: + case FILE_PROVIDER_COMPRESSION_XPRESS4K: return L"XPRESS4K"; - case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS8K: + case FILE_PROVIDER_COMPRESSION_XPRESS8K: return L"XPRESS8K"; - case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS16K: + case FILE_PROVIDER_COMPRESSION_XPRESS16K: return L"XPRESS16K"; default: return L"LZX"; @@ -2376,16 +2456,16 @@ set_system_compression(HANDLE h, int format) { NTSTATUS status; struct { - struct wof_external_info wof_info; - struct file_provider_external_info file_info; + WOF_EXTERNAL_INFO wof_info; + FILE_PROVIDER_EXTERNAL_INFO_V1 file_info; } in = { .wof_info = { - .version = WOF_CURRENT_VERSION, - .provider = WOF_PROVIDER_FILE, + .Version = WOF_CURRENT_VERSION, + .Provider = WOF_PROVIDER_FILE, }, .file_info = { - .version = FILE_PROVIDER_CURRENT_VERSION, - .compression_format = format, + .Version = FILE_PROVIDER_CURRENT_VERSION, + .Algorithm = format, }, }; @@ -2406,7 +2486,7 @@ set_system_compression(HANDLE h, int format) /* 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[] = { +static const wchar_t * const bootloader_pattern_strings[] = { L"*winload.*", L"*winresume.*", L"\\Windows\\AppPatch\\drvmain.sdb", @@ -2437,11 +2517,27 @@ static wchar_t *bootloader_pattern_strings[] = { L"\\Windows\\System32\\CodeIntegrity\\driver.stl", }; -static const struct string_set bootloader_patterns = { - .strings = bootloader_pattern_strings, +static const struct string_list bootloader_patterns = { + .strings = (wchar_t **)bootloader_pattern_strings, .num_strings = ARRAY_LEN(bootloader_pattern_strings), }; +/* Returns true if the specified system compression format is supported by the + * bootloader of the image being applied. */ +static bool +bootloader_supports_compression_format(struct win32_apply_ctx *ctx, int format) +{ + /* Windows 10 and later support XPRESS4K */ + if (format == FILE_PROVIDER_COMPRESSION_XPRESS4K) + return ctx->windows_build_number >= 10240; + + /* + * Windows 10 version 1903 and later support the other formats; + * see https://wimlib.net/forums/viewtopic.php?f=1&t=444 + */ + return ctx->windows_build_number >= 18362; +} + static NTSTATUS set_system_compression_on_inode(struct wim_inode *inode, int format, struct win32_apply_ctx *ctx) @@ -2451,12 +2547,8 @@ set_system_compression_on_inode(struct wim_inode *inode, int format, 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) - { + * force this file to XPRESS4K or uncompressed format. */ + if (!bootloader_supports_compression_format(ctx, format)) { /* We need to check the patterns against every name of the * inode, in case any of them match. */ struct wim_dentry *dentry; @@ -2470,7 +2562,8 @@ set_system_compression_on_inode(struct wim_inode *inode, int format, } incompatible = match_pattern_list(dentry->d_full_path, - &bootloader_patterns); + &bootloader_patterns, + MATCH_RECURSIVELY); FREE(dentry->d_full_path); dentry->d_full_path = NULL; @@ -2479,7 +2572,9 @@ set_system_compression_on_inode(struct wim_inode *inode, int format, warned = (ctx->num_system_compression_exclusions++ > 0); - if (is_image_windows_10_or_later(ctx)) { + if (bootloader_supports_compression_format(ctx, + FILE_PROVIDER_COMPRESSION_XPRESS4K)) + { /* Force to XPRESS4K */ if (!warned) { WARNING("For compatibility with the " @@ -2491,7 +2586,7 @@ set_system_compression_on_inode(struct wim_inode *inode, int format, " you requested.", get_system_compression_format_string(format)); } - format = FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K; + format = FILE_PROVIDER_COMPRESSION_XPRESS4K; break; } else { /* Force to uncompressed */ @@ -2593,14 +2688,37 @@ handle_system_compression(struct blob_descriptor *blob, struct win32_apply_ctx * } } -/* Called when a blob has been fully read for extraction on Windows */ +/* Called when a blob has been fully read for extraction */ static int -end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) +win32_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) { struct win32_apply_ctx *ctx = _ctx; int ret; const struct wim_dentry *dentry; + /* Extend sparse streams to their final size. */ + if (ctx->any_sparse_streams && !status) { + for (unsigned i = 0; i < ctx->num_open_handles; i++) { + FILE_END_OF_FILE_INFORMATION info = + { .EndOfFile = { .QuadPart = blob->size } }; + NTSTATUS ntstatus; + + if (!ctx->is_sparse_stream[i]) + continue; + + ntstatus = NtSetInformationFile(ctx->open_handles[i], + &ctx->iosb, + &info, sizeof(info), + FileEndOfFileInformation); + if (!NT_SUCCESS(ntstatus)) { + winnt_error(ntstatus, L"Error writing data to " + "target volume (while extending)"); + status = WIMLIB_ERR_WRITE; + break; + } + } + } + close_handles(ctx); if (status) @@ -2707,6 +2825,105 @@ set_object_id(HANDLE h, const struct wim_inode *inode, } } +static int +set_xattrs(HANDLE h, const struct wim_inode *inode, struct win32_apply_ctx *ctx) +{ + const void *entries, *entries_end; + u32 len; + const struct wim_xattr_entry *entry; + size_t bufsize = 0; + u8 _buf[1024] _aligned_attribute(4); + u8 *buf = _buf; + FILE_FULL_EA_INFORMATION *ea, *ea_prev; + NTSTATUS status; + int ret; + + if (!ctx->common.supported_features.xattrs) + return 0; + + entries = inode_get_xattrs(inode, &len); + if (likely(entries == NULL || len == 0)) /* No extended attributes? */ + return 0; + entries_end = entries + len; + + entry = entries; + for (entry = entries; (void *)entry < entries_end; + entry = xattr_entry_next(entry)) { + if (!valid_xattr_entry(entry, entries_end - (void *)entry)) { + ERROR("\"%"TS"\": extended attribute is corrupt or unsupported", + inode_any_full_path(inode)); + return WIMLIB_ERR_INVALID_XATTR; + } + + bufsize += ALIGN(offsetof(FILE_FULL_EA_INFORMATION, EaName) + + entry->name_len + 1 + + le16_to_cpu(entry->value_len), 4); + } + + if (unlikely(bufsize != (u32)bufsize)) { + ERROR("\"%"TS"\": too many extended attributes to extract!", + inode_any_full_path(inode)); + return WIMLIB_ERR_INVALID_XATTR; + } + + if (unlikely(bufsize > sizeof(_buf))) { + buf = MALLOC(bufsize); + if (!buf) + return WIMLIB_ERR_NOMEM; + } + + ea_prev = NULL; + ea = (FILE_FULL_EA_INFORMATION *)buf; + for (entry = entries; (void *)entry < entries_end; + entry = xattr_entry_next(entry)) { + u8 *p; + + if (ea_prev) + ea_prev->NextEntryOffset = (u8 *)ea - (u8 *)ea_prev; + ea->Flags = entry->flags; + ea->EaNameLength = entry->name_len; + ea->EaValueLength = le16_to_cpu(entry->value_len); + p = mempcpy(ea->EaName, entry->name, + ea->EaNameLength + 1 + ea->EaValueLength); + while ((uintptr_t)p & 3) + *p++ = 0; + ea_prev = ea; + ea = (FILE_FULL_EA_INFORMATION *)p; + } + ea_prev->NextEntryOffset = 0; + wimlib_assert((u8 *)ea - buf == bufsize); + + status = NtSetEaFile(h, &ctx->iosb, buf, bufsize); + if (unlikely(!NT_SUCCESS(status))) { + if (status == STATUS_EAS_NOT_SUPPORTED) { + /* This happens with Samba. */ + WARNING("Filesystem advertised extended attribute (EA) support, but it doesn't\n" + " work. EAs will not be extracted."); + ctx->common.supported_features.xattrs = 0; + } else if (status == STATUS_INVALID_EA_NAME) { + ctx->num_xattr_failures++; + if (ctx->num_xattr_failures < 5) { + winnt_warning(status, + L"Can't set extended attributes on \"%ls\"", + current_path(ctx)); + } else if (ctx->num_xattr_failures == 5) { + WARNING("Suppressing further warnings about " + "failure to set extended attributes."); + } + } else { + winnt_error(status, L"Can't set extended attributes on \"%ls\"", + current_path(ctx)); + ret = WIMLIB_ERR_SET_XATTR; + goto out; + } + } + ret = 0; +out: + if (buf != _buf) + FREE(buf); + return ret; +} + /* Set the security descriptor @desc, of @desc_size bytes, on the file with open * handle @h. */ static NTSTATUS @@ -2850,11 +3067,18 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, { FILE_BASIC_INFORMATION info; NTSTATUS status; + int ret; /* Set the file's object ID if present and object IDs are supported by * the filesystem. */ set_object_id(h, inode, ctx); + /* Set the file's extended attributes (EAs) if present and EAs are + * supported by the filesystem. */ + ret = set_xattrs(h, inode, ctx); + if (ret) + return ret; + /* Set the file's security descriptor if present and we're not in * NO_ACLS mode */ if (inode_has_security_descriptor(inode) && @@ -2919,7 +3143,7 @@ apply_metadata_to_file(const struct wim_dentry *dentry, NTSTATUS status; int ret; - perms = FILE_WRITE_ATTRIBUTES | WRITE_DAC | + perms = FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY; build_extraction_path(dentry, ctx); @@ -3074,9 +3298,9 @@ win32_extract(struct list_head *dentry_list, struct apply_ctx *_ctx) goto out; struct read_blob_callbacks cbs = { - .begin_blob = begin_extract_blob, - .consume_chunk = extract_chunk, - .end_blob = end_extract_blob, + .begin_blob = win32_begin_extract_blob, + .continue_blob = win32_extract_chunk, + .end_blob = win32_end_extract_blob, .ctx = ctx, }; ret = extract_blob_list(&ctx->common, &cbs);