*/
/*
- * 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
/* 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;
+
+ /* 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?
*/
}
}
+/* 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);
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;
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.");
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 */
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,
FILE_SHARE_VALID_FLAGS,
FILE_OPEN_IF,
FILE_DIRECTORY_FILE |
- FILE_OPEN_REPARSE_POINT |
FILE_OPEN_FOR_BACKUP_INTENT,
NULL,
0);
FILE_BASIC_INFORMATION info;
NTSTATUS status;
USHORT compression_state;
+ DWORD bytes_returned;
/* Get current attributes */
status = (*func_NtQueryInformationFile)(h, &ctx->iosb,
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;
}
{
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;
}
/*
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;
}
* 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)) {
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];
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 wchar_t *bootloader_pattern_strings[] = {
L"*winload.*",
L"*winresume.*",
L"\\Windows\\AppPatch\\drvmain.sdb",
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_set bootloader_patterns = {
+ .strings = bootloader_pattern_strings,
+ .num_strings = ARRAY_LEN(bootloader_pattern_strings),
};
static NTSTATUS
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!");
}
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"
}
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;
}
+
}
}
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);