X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_apply.c;h=395e69841820a08da85f4645be4c47cf5f544abe;hp=0367719558a6d9a747b1b284f71672b5d5f41d77;hb=44dfae76fad55a93caba52bcf5a591d8ceef766c;hpb=8b709192cd2811b83c248fbe61ca4f11ee9de797 diff --git a/src/win32_apply.c b/src/win32_apply.c index 03677195..395e6984 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 @@ -151,6 +151,17 @@ 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 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; @@ -213,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); @@ -248,9 +267,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; @@ -352,7 +372,7 @@ 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); @@ -667,8 +687,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."); @@ -882,7 +902,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 */ @@ -970,6 +992,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, @@ -979,7 +1004,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); @@ -1078,6 +1102,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, @@ -1097,20 +1122,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; } @@ -1382,67 +1401,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; } /* @@ -1603,18 +1626,46 @@ 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)) { - winnt_error(status, L"Can't create directory \"%ls\"", - current_path(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); + + /* 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; } @@ -1627,9 +1678,32 @@ 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 }; + (*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. */ + 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, + }; + (*func_NtSetSecurityObject)(h, DACL_SECURITY_INFORMATION, + (void *)&desc); + } } if (!dentry_is_root(dentry)) { @@ -2051,6 +2125,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]; @@ -2264,11 +2358,27 @@ get_system_compression_format(int extract_flags) return FILE_PROVIDER_COMPRESSION_FORMAT_LZX; } -static DWORD + +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) { - DWORD bytes_returned; - DWORD err; + NTSTATUS status; + IO_STATUS_BLOCK iosb; struct { struct wof_external_info wof_info; struct file_provider_external_info file_info; @@ -2283,16 +2393,149 @@ set_system_compression(HANDLE h, int format) }, }; - if (DeviceIoControl(h, FSCTL_SET_EXTERNAL_BACKING, &in, sizeof(in), - NULL, 0, &bytes_returned, NULL)) - return 0; + /* 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); - err = GetLastError(); + if (status == 0xC000046F) /* "Compressing this object would not save space." */ + return STATUS_SUCCESS; - if (err == 344) /* "Compressing this object would not save space." */ - return 0; + 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; - return err; + /* 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; } /* @@ -2321,9 +2564,7 @@ handle_system_compression(struct blob_descriptor *blob, struct win32_apply_ctx * for (u32 i = 0; i < blob->out_refcnt; i++) { struct wim_inode *inode = targets[i].inode; struct wim_inode_stream *strm = targets[i].stream; - HANDLE h; NTSTATUS status; - DWORD err; if (!stream_is_unnamed_data_stream(strm)) continue; @@ -2331,18 +2572,11 @@ handle_system_compression(struct blob_descriptor *blob, struct win32_apply_ctx * if (will_externally_back_inode(inode, ctx, NULL, false) != 0) continue; - status = create_file(&h, GENERIC_READ | GENERIC_WRITE, NULL, - 0, FILE_OPEN, 0, - inode_first_extraction_dentry(inode), ctx); - - if (NT_SUCCESS(status)) { - err = set_system_compression(h, format); - (*func_NtClose)(h); - } else { - err = (*func_RtlNtStatusToDosError)(status); - } + status = set_system_compression_on_inode(inode, format, ctx); + if (likely(NT_SUCCESS(status))) + continue; - if (err == ERROR_INVALID_FUNCTION) { + 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" @@ -2352,16 +2586,14 @@ handle_system_compression(struct blob_descriptor *blob, struct win32_apply_ctx * return; } - if (err) { - ctx->num_system_compression_failures++; - if (ctx->num_system_compression_failures < 10) { - win32_warning(err, L"\"%ls\": Failed to compress " - "extracted file using System Compression", - current_path(ctx)); - } else if (ctx->num_system_compression_failures == 10) { - WARNING("Suppressing further warnings about " - "System Compression failures."); - } + 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."); } } } @@ -2781,6 +3013,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);