]> wimlib.net Git - wimlib/blobdiff - src/win32_apply.c
win32_apply.c: don't clear directory attributes in NO_ATTRIBUTES mode
[wimlib] / src / win32_apply.c
index 5bc1e51f9c12b3ba68a8c37b5ac9629a2d986962..4c2f97d34d41ff3ad5e9bbdb37618e4a4ef01d4d 100644 (file)
@@ -151,10 +151,16 @@ 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;
+
+       /* 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 +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);
 
@@ -672,8 +686,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.");
@@ -887,7 +901,9 @@ build_extraction_path(const struct wim_dentry *dentry,
             d = d->d_parent)
        {
                p -= d->d_extraction_name_nchars;
-               wmemcpy(p, d->d_extraction_name, d->d_extraction_name_nchars);
+               if (d->d_extraction_name_nchars)
+                       wmemcpy(p, d->d_extraction_name,
+                               d->d_extraction_name_nchars);
                *--p = '\\';
        }
        /* No leading slash  */
@@ -975,6 +991,9 @@ open_target_directory(struct win32_apply_ctx *ctx)
        ctx->attr.Length = sizeof(ctx->attr);
        ctx->attr.RootDirectory = NULL;
        ctx->attr.ObjectName = &ctx->target_ntpath;
+
+       /* Don't use FILE_OPEN_REPARSE_POINT here; we want the extraction to
+        * happen at the directory "pointed to" by the reparse point. */
        status = (*func_NtCreateFile)(&ctx->h_target,
                                      FILE_TRAVERSE,
                                      &ctx->attr,
@@ -984,7 +1003,6 @@ open_target_directory(struct win32_apply_ctx *ctx)
                                      FILE_SHARE_VALID_FLAGS,
                                      FILE_OPEN_IF,
                                      FILE_DIRECTORY_FILE |
-                                             FILE_OPEN_REPARSE_POINT |
                                              FILE_OPEN_FOR_BACKUP_INTENT,
                                      NULL,
                                      0);
@@ -1083,6 +1101,7 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
        FILE_BASIC_INFORMATION info;
        NTSTATUS status;
        USHORT compression_state;
+       DWORD bytes_returned;
 
        /* Get current attributes  */
        status = (*func_NtQueryInformationFile)(h, &ctx->iosb,
@@ -1102,20 +1121,14 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
        else
                compression_state = COMPRESSION_FORMAT_NONE;
 
-       status = (*func_NtFsControlFile)(h,
-                                        NULL,
-                                        NULL,
-                                        NULL,
-                                        &ctx->iosb,
-                                        FSCTL_SET_COMPRESSION,
-                                        &compression_state,
-                                        sizeof(USHORT),
-                                        NULL,
-                                        0);
-       if (NT_SUCCESS(status))
+       /* Note: don't use NtFsControlFile() here unless prepared to handle
+        * STATUS_PENDING.  */
+       if (DeviceIoControl(h, FSCTL_SET_COMPRESSION,
+                           &compression_state, sizeof(USHORT), NULL, 0,
+                           &bytes_returned, NULL))
                return 0;
 
-       winnt_error(status, L"Can't %s compression attribute on \"%ls\"",
+       win32_error(GetLastError(), L"Can't %s compression attribute on \"%ls\"",
                    (compressed ? "set" : "clear"), current_path(ctx));
        return WIMLIB_ERR_SET_ATTRIBUTES;
 }
@@ -1387,67 +1400,71 @@ delete_file_or_stream(struct win32_apply_ctx *ctx)
 {
        NTSTATUS status;
        HANDLE h;
-       FILE_DISPOSITION_INFORMATION disposition_info;
-       FILE_BASIC_INFORMATION basic_info;
-       bool retried = false;
+       ULONG perms = DELETE;
+       ULONG flags = FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE;
+
+       /* First try opening the file with FILE_DELETE_ON_CLOSE.  In most cases,
+        * all we have to do is that plus close the file handle.  */
+retry:
+       status = do_create_file(&h, perms, NULL, 0, FILE_OPEN, flags, ctx);
+
+       if (unlikely(status == STATUS_CANNOT_DELETE)) {
+               /* This error occurs for files with FILE_ATTRIBUTE_READONLY set.
+                * Try an alternate approach: first open the file without
+                * FILE_DELETE_ON_CLOSE, then reset the file attributes, then
+                * set the "delete" disposition on the handle.  */
+               if (flags & FILE_DELETE_ON_CLOSE) {
+                       flags &= ~FILE_DELETE_ON_CLOSE;
+                       perms |= FILE_WRITE_ATTRIBUTES;
+                       goto retry;
+               }
+       }
 
-       status = do_create_file(&h,
-                               DELETE,
-                               NULL,
-                               0,
-                               FILE_OPEN,
-                               FILE_NON_DIRECTORY_FILE,
-                               ctx);
        if (unlikely(!NT_SUCCESS(status))) {
-               winnt_error(status, L"Can't open \"%ls\" for deletion",
-                           current_path(ctx));
+               winnt_error(status, L"Can't open \"%ls\" for deletion "
+                           "(perms=%x, flags=%x)",
+                           current_path(ctx), perms, flags);
                return WIMLIB_ERR_OPEN;
        }
 
-retry:
-       disposition_info.DoDeleteFile = TRUE;
-       status = (*func_NtSetInformationFile)(h, &ctx->iosb,
-                                             &disposition_info,
-                                             sizeof(disposition_info),
-                                             FileDispositionInformation);
-       (*func_NtClose)(h);
-       if (likely(NT_SUCCESS(status)))
-               return 0;
+       if (unlikely(!(flags & FILE_DELETE_ON_CLOSE))) {
 
-       if (status == STATUS_CANNOT_DELETE && !retried) {
-               /* Clear file attributes and try again.  This is necessary for
-                * FILE_ATTRIBUTE_READONLY files.  */
-               status = do_create_file(&h,
-                                       FILE_WRITE_ATTRIBUTES | DELETE,
-                                       NULL,
-                                       0,
-                                       FILE_OPEN,
-                                       FILE_NON_DIRECTORY_FILE,
-                                       ctx);
-               if (!NT_SUCCESS(status)) {
-                       winnt_error(status,
-                                   L"Can't open \"%ls\" to reset attributes",
-                                   current_path(ctx));
-                       return WIMLIB_ERR_OPEN;
-               }
-               memset(&basic_info, 0, sizeof(basic_info));
-               basic_info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
+               FILE_BASIC_INFORMATION basic_info =
+                       { .FileAttributes = FILE_ATTRIBUTE_NORMAL };
                status = (*func_NtSetInformationFile)(h, &ctx->iosb,
                                                      &basic_info,
                                                      sizeof(basic_info),
                                                      FileBasicInformation);
+
                if (!NT_SUCCESS(status)) {
-                       winnt_error(status,
-                                   L"Can't reset file attributes on \"%ls\"",
-                                   current_path(ctx));
+                       winnt_error(status, L"Can't reset attributes of \"%ls\" "
+                                   "to prepare for deletion", current_path(ctx));
+                       (*func_NtClose)(h);
+                       return WIMLIB_ERR_SET_ATTRIBUTES;
+               }
+
+               FILE_DISPOSITION_INFORMATION disp_info =
+                       { .DoDeleteFile = TRUE };
+               status = (*func_NtSetInformationFile)(h, &ctx->iosb,
+                                                     &disp_info,
+                                                     sizeof(disp_info),
+                                                     FileDispositionInformation);
+               if (!NT_SUCCESS(status)) {
+                       winnt_error(status, L"Can't set delete-on-close "
+                                   "disposition on \"%ls\"", current_path(ctx));
                        (*func_NtClose)(h);
                        return WIMLIB_ERR_SET_ATTRIBUTES;
                }
-               retried = true;
-               goto retry;
        }
-       winnt_error(status, L"Can't delete \"%ls\"", current_path(ctx));
-       return WIMLIB_ERR_OPEN;
+
+       status = (*func_NtClose)(h);
+       if (unlikely(!NT_SUCCESS(status))) {
+               winnt_error(status, L"Error closing \"%ls\" after setting "
+                           "delete-on-close disposition", current_path(ctx));
+               return WIMLIB_ERR_OPEN;
+       }
+
+       return 0;
 }
 
 /*
@@ -1608,16 +1625,28 @@ create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx)
        int ret;
 
        /* DELETE is needed for set_short_name(); GENERIC_READ and GENERIC_WRITE
-        * are needed for adjust_compression_attribute().  */
-       perms = GENERIC_READ | GENERIC_WRITE;
+        * are needed for adjust_compression_attribute(); WRITE_DAC is needed to
+        * remove the directory's DACL if the directory already existed  */
+       perms = GENERIC_READ | GENERIC_WRITE | WRITE_DAC;
        if (!dentry_is_root(dentry))
                perms |= DELETE;
 
        /* FILE_ATTRIBUTE_SYSTEM is needed to ensure that
         * FILE_ATTRIBUTE_ENCRYPTED doesn't get set before we want it to be.  */
+retry:
        status = create_file(&h, perms, NULL, FILE_ATTRIBUTE_SYSTEM,
                             FILE_OPEN_IF, FILE_DIRECTORY_FILE, dentry, ctx);
-       if (!NT_SUCCESS(status)) {
+       if (unlikely(!NT_SUCCESS(status))) {
+               if (status == STATUS_ACCESS_DENIED) {
+                       if (perms & WRITE_DAC) {
+                               perms &= ~WRITE_DAC;
+                               goto retry;
+                       }
+                       if (perms & DELETE) {
+                               perms &= ~DELETE;
+                               goto retry;
+                       }
+               }
                winnt_error(status, L"Can't create directory \"%ls\"",
                            current_path(ctx));
                return WIMLIB_ERR_MKDIR;
@@ -1632,9 +1661,29 @@ 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.  */
+               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)) {
@@ -2320,14 +2369,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 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",
@@ -2352,9 +2402,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_set bootloader_patterns = {
+       .strings = bootloader_pattern_strings,
+       .num_strings = ARRAY_LEN(bootloader_pattern_strings),
 };
 
 static NTSTATUS
@@ -2365,13 +2415,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 +2435,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 +2458,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;
                        }
+
                }
        }
 
@@ -2899,6 +2973,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);