X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_apply.c;h=96d9df6f578842eea91fc7b5308066b9a0df5715;hp=5bc1e51f9c12b3ba68a8c37b5ac9629a2d986962;hb=90f1e04a2a143876a4413577b25db60b5ba0fe97;hpb=658ee1d652613948b29c412d75b3732801c2a235 diff --git a/src/win32_apply.c b/src/win32_apply.c index 5bc1e51f..96d9df6f 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -3,7 +3,7 @@ */ /* - * Copyright (C) 2013, 2014, 2015 Eric Biggers + * Copyright (C) 2013-2016 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,14 +30,15 @@ #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/metadata.h" +#include "wimlib/object_id.h" #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" @@ -68,7 +69,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; @@ -124,6 +125,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. */ @@ -151,10 +159,19 @@ struct win32_apply_ctx { /* Number of files on which we couldn't set System Compression. */ unsigned long num_system_compression_failures; - /* The number of files on which we used XPRESS4K System Compression - * rather than a stronger variant, to be compatible with the Windows - * bootloader. */ - unsigned long num_xpress4k_forced_files; + /* 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; + + /* Number of files for which we couldn't set the object ID. */ + unsigned long num_object_id_failures; + + /* 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? */ @@ -218,6 +235,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); @@ -253,9 +278,10 @@ win32_get_supported_features(const wchar_t *target, get_vol_flags(target, &vol_flags, &short_names_supported); - supported_features->archive_files = 1; + supported_features->readonly_files = 1; supported_features->hidden_files = 1; supported_features->system_files = 1; + supported_features->archive_files = 1; if (vol_flags & FILE_FILE_COMPRESSION) supported_features->compressed_files = 1; @@ -267,7 +293,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; @@ -284,10 +311,33 @@ win32_get_supported_features(const wchar_t *target, if (short_names_supported) supported_features->short_names = 1; + if (vol_flags & FILE_SUPPORTS_OBJECT_IDS) + supported_features->object_ids = 1; + 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; + } return 0; } @@ -316,7 +366,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; @@ -344,14 +394,14 @@ 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 | @@ -360,10 +410,10 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx) 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; } @@ -672,8 +722,8 @@ start_wimboot_extraction(struct list_head *dentry_list, struct win32_apply_ctx * int ret; struct wim_dentry *dentry; - 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."); @@ -757,7 +807,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); @@ -887,7 +937,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 */ @@ -975,19 +1027,20 @@ open_target_directory(struct win32_apply_ctx *ctx) 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, - &ctx->iosb, - NULL, - 0, - FILE_SHARE_VALID_FLAGS, - FILE_OPEN_IF, - FILE_DIRECTORY_FILE | - FILE_OPEN_REPARSE_POINT | - FILE_OPEN_FOR_BACKUP_INTENT, - NULL, - 0); + + /* Don't use FILE_OPEN_REPARSE_POINT here; we want the extraction to + * happen at the directory "pointed to" by the reparse point. */ + status = NtCreateFile(&ctx->h_target, + FILE_TRAVERSE, + &ctx->attr, + &ctx->iosb, + NULL, + 0, + FILE_SHARE_VALID_FLAGS, + FILE_OPEN_IF, + FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT, + NULL, + 0); if (!NT_SUCCESS(status)) { winnt_error(status, L"Can't open or create directory \"%ls\"", ctx->common.target); @@ -1002,7 +1055,7 @@ static void close_target_directory(struct win32_apply_ctx *ctx) { if (ctx->h_target) { - (*func_NtClose)(ctx->h_target); + NtClose(ctx->h_target); ctx->h_target = NULL; ctx->attr.RootDirectory = NULL; } @@ -1073,6 +1126,9 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry, { const bool compressed = (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED); + FILE_BASIC_INFORMATION info; + USHORT compression_state; + NTSTATUS status; if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES) return 0; @@ -1080,14 +1136,10 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry, if (!ctx->common.supported_features.compressed_files) return 0; - FILE_BASIC_INFORMATION info; - NTSTATUS status; - USHORT compression_state; /* Get current attributes */ - status = (*func_NtQueryInformationFile)(h, &ctx->iosb, - &info, sizeof(info), - FileBasicInformation); + status = NtQueryInformationFile(h, &ctx->iosb, &info, sizeof(info), + FileBasicInformation); if (NT_SUCCESS(status) && compressed == !!(info.FileAttributes & FILE_ATTRIBUTE_COMPRESSED)) { @@ -1102,16 +1154,8 @@ 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); + status = winnt_fsctl(h, FSCTL_SET_COMPRESSION, + &compression_state, sizeof(USHORT), NULL, 0, NULL); if (NT_SUCCESS(status)) return 0; @@ -1120,6 +1164,28 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry, 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 @@ -1181,10 +1247,10 @@ remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_appl 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); + status = 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; @@ -1198,26 +1264,19 @@ remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_appl /* Try to remove the short name on the conflicting file. */ retry: - status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize, - FileShortNameInformation); + status = NtSetInformationFile(h, &ctx->iosb, info, bufsize, + 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; } - (*func_NtClose)(h); + NtClose(h); out: build_extraction_path(dentry, ctx); return status; @@ -1267,8 +1326,8 @@ set_short_name(HANDLE h, const struct wim_dentry *dentry, memcpy(info->FileName, dentry->d_short_name, dentry->d_short_name_nbytes); retry: - status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize, - FileShortNameInformation); + status = NtSetInformationFile(h, &ctx->iosb, info, bufsize, + FileShortNameInformation); if (NT_SUCCESS(status)) return 0; @@ -1333,7 +1392,7 @@ retry: * A wrapper around NtCreateFile() to make it slightly more usable... * This uses the path currently constructed in ctx->pathbuf. * - * Also, we always specify FILE_OPEN_FOR_BACKUP_INTENT and + * Also, we always specify SYNCHRONIZE access, FILE_OPEN_FOR_BACKUP_INTENT, and * FILE_OPEN_REPARSE_POINT. */ static NTSTATUS @@ -1345,19 +1404,19 @@ do_create_file(PHANDLE FileHandle, ULONG CreateOptions, struct win32_apply_ctx *ctx) { - return (*func_NtCreateFile)(FileHandle, - DesiredAccess, - &ctx->attr, - &ctx->iosb, - AllocationSize, - FileAttributes, - FILE_SHARE_VALID_FLAGS, - CreateDisposition, - CreateOptions | - FILE_OPEN_FOR_BACKUP_INTENT | - FILE_OPEN_REPARSE_POINT, - NULL, - 0); + return NtCreateFile(FileHandle, + DesiredAccess | SYNCHRONIZE, + &ctx->attr, + &ctx->iosb, + AllocationSize, + FileAttributes, + FILE_SHARE_VALID_FLAGS, + CreateDisposition, + CreateOptions | + FILE_OPEN_FOR_BACKUP_INTENT | + FILE_OPEN_REPARSE_POINT, + NULL, + 0); } /* Like do_create_file(), but builds the extraction path of the @dentry first. @@ -1387,76 +1446,79 @@ 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))) { + + FILE_BASIC_INFORMATION basic_info = + { .FileAttributes = FILE_ATTRIBUTE_NORMAL }; + status = NtSetInformationFile(h, &ctx->iosb, &basic_info, + sizeof(basic_info), + FileBasicInformation); - 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; + winnt_error(status, L"Can't reset attributes of \"%ls\" " + "to prepare for deletion", current_path(ctx)); + NtClose(h); + return WIMLIB_ERR_SET_ATTRIBUTES; } - 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); + + FILE_DISPOSITION_INFORMATION disp_info = + { .DoDeleteFile = TRUE }; + status = NtSetInformationFile(h, &ctx->iosb, &disp_info, + sizeof(disp_info), + FileDispositionInformation); if (!NT_SUCCESS(status)) { - winnt_error(status, - L"Can't reset file attributes on \"%ls\"", - current_path(ctx)); - (*func_NtClose)(h); + winnt_error(status, L"Can't set delete-on-close " + "disposition on \"%ls\"", current_path(ctx)); + 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 = 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; } /* * 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; @@ -1465,7 +1527,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, @@ -1507,11 +1569,9 @@ do_set_reparse_point(const struct wim_dentry *dentry, 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); + status = winnt_fsctl(h, FSCTL_SET_REPARSE_POINT, + rpbuf, rpbuflen, NULL, 0, NULL); + NtClose(h); if (NT_SUCCESS(status)) return 0; @@ -1579,13 +1639,19 @@ 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); if (ret) return ret; - (*func_NtClose)(h); + NtClose(h); } } @@ -1617,9 +1683,25 @@ create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) * 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)) { - winnt_error(status, L"Can't create directory \"%ls\"", - current_path(ctx)); + if (unlikely(!NT_SUCCESS(status))) { + const wchar_t *path = current_path(ctx); + winnt_error(status, L"Can't create directory \"%ls\"", path); + + /* Check for known issue with WindowsApps directory. */ + if (status == STATUS_ACCESS_DENIED && + (wcsstr(path, L"\\WindowsApps\\") || + wcsstr(path, L"\\InfusedApps\\"))) { + ERROR( +"You seem to be trying to extract files to the WindowsApps directory.\n" +" Windows 8.1 and later use new file permissions in this directory that\n" +" cannot be overridden, even by backup/restore programs. To extract your\n" +" files anyway, you need to choose a different target directory, delete\n" +" the WindowsApps directory entirely, reformat the volume, do the\n" +" extraction from a non-broken operating system such as Windows 7 or\n" +" Linux, or wait for Microsoft to fix the design flaw in their operating\n" +" system. This is *not* a bug in wimlib. See this thread for more\n" +" information: https://wimlib.net/forums/viewtopic.php?f=1&t=261"); + } return WIMLIB_ERR_MKDIR; } @@ -1632,9 +1714,13 @@ create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) * 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 (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)) { + FILE_BASIC_INFORMATION basic_info = + { .FileAttributes = FILE_ATTRIBUTE_NORMAL }; + NtSetInformationFile(h, &ctx->iosb, &basic_info, + sizeof(basic_info), + FileBasicInformation); + } } if (!dentry_is_root(dentry)) { @@ -1645,7 +1731,7 @@ create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx) ret = adjust_compression_attribute(h, dentry, ctx); out: - (*func_NtClose)(h); + NtClose(h); return ret; } @@ -1704,7 +1790,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; @@ -1712,6 +1800,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; @@ -1720,7 +1814,7 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry, return 0; out_close: - (*func_NtClose)(h); + NtClose(h); out: return ret; } @@ -1753,9 +1847,8 @@ create_link(HANDLE h, const struct wim_dentry *dentry, * STATUS_INFO_LENGTH_MISMATCH when FileNameLength * happens to be 2 */ - status = (*func_NtSetInformationFile)(h, &ctx->iosb, - info, bufsize, - FileLinkInformation); + status = NtSetInformationFile(h, &ctx->iosb, info, bufsize, + FileLinkInformation); if (NT_SUCCESS(status)) return 0; winnt_error(status, L"Failed to create link \"%ls\"", @@ -1769,7 +1862,7 @@ create_link(HANDLE h, const struct wim_dentry *dentry, if (ret) return ret; - (*func_NtClose)(h2); + NtClose(h2); return 0; } } @@ -1826,7 +1919,7 @@ create_nondirectory(struct wim_inode *inode, struct win32_apply_ctx *ctx) if (!ret && unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) ret = set_backed_from_wim(h, inode, ctx); - (*func_NtClose)(h); + NtClose(h); return ret; } @@ -1861,7 +1954,7 @@ static void close_handles(struct win32_apply_ctx *ctx) { for (unsigned i = 0; i < ctx->num_open_handles; i++) - (*func_NtClose)(ctx->open_handles[i]); + NtClose(ctx->open_handles[i]); } /* Prepare to read the next blob, which has size @blob_size, into an in-memory @@ -1893,7 +1986,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; @@ -1959,13 +2051,28 @@ begin_extract_blob_instance(const struct blob_descriptor *blob, return WIMLIB_ERR_OPEN; } - ctx->open_handles[ctx->num_open_handles++] = h; - - /* Allocate space for the data. */ - alloc_info.AllocationSize.QuadPart = blob->size; - (*func_NtSetInformationFile)(h, &ctx->iosb, - &alloc_info, sizeof(alloc_info), + 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; return 0; } @@ -2056,6 +2163,26 @@ try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p, target_ntpath_nchars = ctx->target_ntpath.Length / sizeof(wchar_t); + /* If the target directory is a filesystem root, such as \??\C:\, then + * it already will have a trailing slash. Don't include this slash if + * we are already adding slashes via 'relpath'. This prevents an extra + * slash from being generated each time the link is extracted. And + * unlike on UNIX, the number of slashes in paths on Windows can be + * significant; Windows won't understand the link target if it contains + * too many slashes. */ + if (target_ntpath_nchars > 0 && relpath_nchars > 0 && + ctx->target_ntpath.Buffer[target_ntpath_nchars - 1] == L'\\') + target_ntpath_nchars--; + + /* Also remove extra slashes from the beginning of 'relpath'. Normally + * this isn't needed, but this is here to make the extra slash(es) added + * by wimlib pre-v1.9.1 get removed automatically. */ + while (relpath_nchars >= 2 && + relpath[0] == L'\\' && relpath[1] == L'\\') { + relpath++; + relpath_nchars--; + } + fixed_subst_name_nchars = target_ntpath_nchars + relpath_nchars; wchar_t fixed_subst_name[fixed_subst_name_nchars]; @@ -2174,9 +2301,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); @@ -2184,6 +2311,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); @@ -2219,31 +2347,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 = (*func_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; } } @@ -2289,7 +2444,6 @@ 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; @@ -2310,9 +2464,8 @@ set_system_compression(HANDLE h, int format) * 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); + status = winnt_fsctl(h, FSCTL_SET_EXTERNAL_BACKING, + &in, sizeof(in), NULL, 0, NULL); if (status == 0xC000046F) /* "Compressing this object would not save space." */ return STATUS_SUCCESS; @@ -2320,14 +2473,15 @@ set_system_compression(HANDLE h, int format) return status; } -/* Hard-coded list of files which the Windows bootloader needs to access before - * the WOF driver has been loaded. Since the Windows bootloader only supports - * the XPRESS4K variant of System Compression, such files should not be - * compressed using other variants. */ -static wchar_t *xpress4k_only_pattern_strings[] = { +/* Hard-coded list of files which the Windows bootloader may need to access + * before the WOF driver has been loaded. */ +static const wchar_t * const 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", @@ -2352,9 +2506,9 @@ static wchar_t *xpress4k_only_pattern_strings[] = { L"\\Windows\\System32\\CodeIntegrity\\driver.stl", }; -static const struct string_set xpress4k_only_patterns = { - .strings = xpress4k_only_pattern_strings, - .num_strings = ARRAY_LEN(xpress4k_only_pattern_strings), +static const struct string_list bootloader_patterns = { + .strings = (wchar_t **)bootloader_pattern_strings, + .num_strings = ARRAY_LEN(bootloader_pattern_strings), }; static NTSTATUS @@ -2365,13 +2519,19 @@ set_system_compression_on_inode(struct wim_inode *inode, int format, NTSTATUS status; HANDLE h; - /* If needed, force the XPRESS4K format for this file. */ - if (format != FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K) { + /* 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!"); @@ -2379,12 +2539,18 @@ set_system_compression_on_inode(struct wim_inode *inode, int format, } incompatible = match_pattern_list(dentry->d_full_path, - &xpress4k_only_patterns); + &bootloader_patterns); FREE(dentry->d_full_path); dentry->d_full_path = NULL; - if (incompatible) { - if (ctx->num_xpress4k_forced_files++ == 0) { + 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" @@ -2396,7 +2562,19 @@ set_system_compression_on_inode(struct wim_inode *inode, int 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; } + } } @@ -2420,7 +2598,7 @@ retry: } } - (*func_NtClose)(h); + NtClose(h); return status; } @@ -2484,14 +2662,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) @@ -2558,6 +2759,46 @@ end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) FILE_ATTRIBUTE_SPARSE_FILE | \ FILE_ATTRIBUTE_COMPRESSED) +static void +set_object_id(HANDLE h, const struct wim_inode *inode, + struct win32_apply_ctx *ctx) +{ + const void *object_id; + u32 len; + NTSTATUS status; + + if (!ctx->common.supported_features.object_ids) + return; + + object_id = inode_get_object_id(inode, &len); + if (likely(object_id == NULL)) /* No object ID? */ + return; + + status = winnt_fsctl(h, FSCTL_SET_OBJECT_ID, + object_id, len, NULL, 0, NULL); + if (NT_SUCCESS(status)) + return; + + /* Object IDs must be unique within the filesystem. A duplicate might + * occur if an image containing object IDs is applied twice to the same + * filesystem. Arguably, the user should be warned in this case; but + * the reality seems to be that nothing important cares about object IDs + * except the Distributed Link Tracking Service... so for now these + * failures are just ignored. */ + if (status == STATUS_DUPLICATE_NAME || + status == STATUS_OBJECT_NAME_COLLISION) + return; + + ctx->num_object_id_failures++; + if (ctx->num_object_id_failures < 10) { + winnt_warning(status, L"Can't set object ID on \"%ls\"", + current_path(ctx)); + } else if (ctx->num_object_id_failures == 10) { + WARNING("Suppressing further warnings about failure to set " + "object IDs."); + } +} + /* Set the security descriptor @desc, of @desc_size bytes, on the file with open * handle @h. */ static NTSTATUS @@ -2651,7 +2892,7 @@ set_security_descriptor(HANDLE h, const void *_desc, */ retry: - status = (*func_NtSetSecurityObject)(h, info, desc); + status = NtSetSecurityObject(h, info, desc); if (NT_SUCCESS(status)) goto out_maybe_free_desc; @@ -2702,7 +2943,12 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, FILE_BASIC_INFORMATION info; NTSTATUS status; - /* Set security descriptor if present and not in NO_ACLS mode */ + /* 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 security descriptor if present and we're not in + * NO_ACLS mode */ if (inode_has_security_descriptor(inode) && !(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)) { @@ -2738,9 +2984,8 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode, info.FileAttributes = FILE_ATTRIBUTE_NORMAL; } - status = (*func_NtSetInformationFile)(h, &ctx->iosb, - &info, sizeof(info), - FileBasicInformation); + status = NtSetInformationFile(h, &ctx->iosb, &info, sizeof(info), + FileBasicInformation); /* On FAT volumes we get STATUS_INVALID_PARAMETER if we try to set * attributes on the root directory. (Apparently because FAT doesn't * actually have a place to store those attributes!) */ @@ -2798,7 +3043,7 @@ apply_metadata_to_file(const struct wim_dentry *dentry, ret = do_apply_metadata_to_file(h, inode, ctx); - (*func_NtClose)(h); + NtClose(h); return ret; } @@ -2899,6 +3144,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); @@ -2918,9 +3166,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);