]> wimlib.net Git - wimlib/blobdiff - src/win32_apply.c
mount_image.c: add fallback definitions of RENAME_* constants
[wimlib] / src / win32_apply.c
index 96d9df6f578842eea91fc7b5308066b9a0df5715..e493ecfe298fcd9bb24167586011c5353673159b 100644 (file)
@@ -3,7 +3,7 @@
  */
 
 /*
- * Copyright (C) 2013-2016 Eric Biggers
+ * Copyright 2013-2023 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
  * details.
  *
  * You should have received a copy of the GNU Lesser General Public License
- * along with this file; if not, see http://www.gnu.org/licenses/.
+ * along with this file; if not, see https://www.gnu.org/licenses/.
  */
 
-#ifdef __WIN32__
+#ifdef _WIN32
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #include "wimlib/reparse.h"
 #include "wimlib/scan.h" /* for mangle_pat() and match_pattern_list()  */
 #include "wimlib/textfile.h"
-#include "wimlib/xml.h"
 #include "wimlib/wimboot.h"
 #include "wimlib/wof.h"
+#include "wimlib/xattr.h"
+#include "wimlib/xml.h"
 
 struct win32_apply_ctx {
 
@@ -169,6 +170,9 @@ struct win32_apply_ctx {
        /* Number of files for which we couldn't set the object ID.  */
        unsigned long num_object_id_failures;
 
+       /* Number of files for which we couldn't set extended attributes.  */
+       unsigned long num_xattr_failures;
+
        /* The Windows build number of the image being applied, or 0 if unknown.
         */
        u64 windows_build_number;
@@ -225,9 +229,14 @@ get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret,
        }
 
        if (wcsstr(filesystem_name, L"NTFS")) {
-               /* FILE_SUPPORTS_HARD_LINKS is only supported on Windows 7 and
-                * later.  Force it on anyway if filesystem is NTFS.  */
+               /*
+                * FILE_SUPPORTS_HARD_LINKS and
+                * FILE_SUPPORTS_EXTENDED_ATTRIBUTES are only supported on
+                * Windows 7 and later.  Force them on anyway if the filesystem
+                * is NTFS.
+                */
                *vol_flags_ret |= FILE_SUPPORTS_HARD_LINKS;
+               *vol_flags_ret |= FILE_SUPPORTS_EXTENDED_ATTRIBUTES;
 
                /* There's no volume flag for short names, but according to the
                 * MS documentation they are only user-settable on NTFS.  */
@@ -235,14 +244,6 @@ get_vol_flags(const wchar_t *target, DWORD *vol_flags_ret,
        }
 }
 
-/* Is the image being extracted an OS image for Windows 10 or later?  */
-static bool
-is_image_windows_10_or_later(struct win32_apply_ctx *ctx)
-{
-       /* Note: if no build number is available, this returns false.  */
-       return ctx->windows_build_number >= 10240;
-}
-
 static const wchar_t *
 current_path(struct win32_apply_ctx *ctx);
 
@@ -339,6 +340,9 @@ win32_get_supported_features(const wchar_t *target,
                        supported_features->case_sensitive_filenames = 1;
        }
 
+       if (vol_flags & FILE_SUPPORTS_EXTENDED_ATTRIBUTES)
+               supported_features->xattrs = 1;
+
        return 0;
 }
 
@@ -403,10 +407,10 @@ load_prepopulate_pats(struct win32_apply_ctx *ctx)
        sec.name = T("PrepopulateList");
        sec.strings = strings;
 
-       ret = do_load_text_file(path, buf, blob->size, &mem, &sec, 1,
-                               LOAD_TEXT_FILE_REMOVE_QUOTES |
-                                       LOAD_TEXT_FILE_NO_WARNINGS,
-                               mangle_pat);
+       ret = load_text_file(path, buf, blob->size, &mem, &sec, 1,
+                            LOAD_TEXT_FILE_REMOVE_QUOTES |
+                            LOAD_TEXT_FILE_NO_WARNINGS,
+                            mangle_pat);
        STATIC_ASSERT(OS_PREFERRED_PATH_SEPARATOR == WIM_PATH_SEPARATOR);
        FREE(buf);
        if (ret) {
@@ -425,7 +429,8 @@ can_externally_back_path(const wchar_t *path, const struct win32_apply_ctx *ctx)
 {
        /* Does the path match a pattern given in the [PrepopulateList] section
         * of WimBootCompress.ini?  */
-       if (ctx->prepopulate_pats && match_pattern_list(path, ctx->prepopulate_pats))
+       if (ctx->prepopulate_pats && match_pattern_list(path, ctx->prepopulate_pats,
+                                                       MATCH_RECURSIVELY))
                return false;
 
        /* Since we attempt to modify the SYSTEM registry after it's extracted
@@ -437,7 +442,7 @@ can_externally_back_path(const wchar_t *path, const struct win32_apply_ctx *ctx)
         * However, a WIM that wasn't specifically captured in "WIMBoot mode"
         * may contain SYSTEM.* files.  So to make things "just work", hard-code
         * the pattern.  */
-       if (match_path(path, L"\\Windows\\System32\\config\\SYSTEM*", false))
+       if (match_path(path, L"\\Windows\\System32\\config\\SYSTEM*", 0))
                return false;
 
        return true;
@@ -1081,12 +1086,18 @@ prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx)
 
        path_max = compute_path_max(dentry_list);
        /* Add some extra for building Win32 paths for the file encryption APIs,
-        * and ensure we have at least enough to potentially use a 8.3 name for
+        * and ensure we have at least enough to potentially use an 8.3 name for
         * the last component.  */
        path_max += max(2 + (ctx->target_ntpath.Length / sizeof(wchar_t)),
                        8 + 1 + 3);
 
        ctx->pathbuf.MaximumLength = path_max * sizeof(wchar_t);
+       if (ctx->pathbuf.MaximumLength != path_max * sizeof(wchar_t)) {
+               /* Paths are too long for a UNICODE_STRING! */
+               ERROR("Some paths are too long to extract (> 32768 characters)!");
+               return WIMLIB_ERR_UNSUPPORTED;
+       }
+
        ctx->pathbuf.Buffer = MALLOC(ctx->pathbuf.MaximumLength);
        if (!ctx->pathbuf.Buffer)
                return WIMLIB_ERR_NOMEM;
@@ -1233,7 +1244,7 @@ remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_appl
        HANDLE h;
        size_t bufsize = offsetof(FILE_NAME_INFORMATION, FileName) +
                         (13 * sizeof(wchar_t));
-       u8 buf[bufsize] _aligned_attribute(8);
+       u8 buf[bufsize] __attribute__((aligned(8)));
        bool retried = false;
        FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf;
 
@@ -1315,7 +1326,7 @@ set_short_name(HANDLE h, const struct wim_dentry *dentry,
        size_t bufsize = offsetof(FILE_NAME_INFORMATION, FileName) +
                         max(dentry->d_short_name_nbytes, sizeof(wchar_t)) +
                         sizeof(wchar_t);
-       u8 buf[bufsize] _aligned_attribute(8);
+       u8 buf[bufsize] __attribute__((aligned(8)));
        FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf;
        NTSTATUS status;
        bool tried_to_remove_existing = false;
@@ -1469,7 +1480,7 @@ retry:
        if (unlikely(!NT_SUCCESS(status))) {
                winnt_error(status, L"Can't open \"%ls\" for deletion "
                            "(perms=%x, flags=%x)",
-                           current_path(ctx), perms, flags);
+                           current_path(ctx), (u32)perms, (u32)flags);
                return WIMLIB_ERR_OPEN;
        }
 
@@ -1623,7 +1634,7 @@ create_empty_streams(const struct wim_dentry *dentry,
                if (strm->stream_type == STREAM_TYPE_REPARSE_POINT &&
                    ctx->common.supported_features.reparse_points)
                {
-                       u8 buf[REPARSE_DATA_OFFSET] _aligned_attribute(8);
+                       u8 buf[REPARSE_DATA_OFFSET] __attribute__((aligned(8)));
                        struct reparse_buffer_disk *rpbuf =
                                (struct reparse_buffer_disk *)buf;
                        complete_reparse_point(rpbuf, inode, 0);
@@ -1832,7 +1843,7 @@ create_link(HANDLE h, const struct wim_dentry *dentry,
 
                size_t bufsize = offsetof(FILE_LINK_INFORMATION, FileName) +
                                 ctx->pathbuf.Length + sizeof(wchar_t);
-               u8 buf[bufsize] _aligned_attribute(8);
+               u8 buf[bufsize] __attribute__((aligned(8)));
                FILE_LINK_INFORMATION *info = (FILE_LINK_INFORMATION *)buf;
                NTSTATUS status;
 
@@ -1841,16 +1852,25 @@ create_link(HANDLE h, const struct wim_dentry *dentry,
                info->FileNameLength = ctx->pathbuf.Length;
                memcpy(info->FileName, ctx->pathbuf.Buffer, ctx->pathbuf.Length);
                info->FileName[info->FileNameLength / 2] = L'\0';
+               /*
+                * Note: the null terminator isn't actually necessary, but if
+                * you don't add the extra character, you get
+                * STATUS_INFO_LENGTH_MISMATCH when FileNameLength is 2.
+                */
 
-               /* Note: the null terminator isn't actually necessary,
-                * but if you don't add the extra character, you get
-                * STATUS_INFO_LENGTH_MISMATCH when FileNameLength
-                * happens to be 2  */
-
-               status = NtSetInformationFile(h, &ctx->iosb, info, bufsize,
-                                             FileLinkInformation);
-               if (NT_SUCCESS(status))
-                       return 0;
+               /*
+                * When fuzzing with wlfuzz.exe, creating a hard link sometimes
+                * fails with STATUS_ACCESS_DENIED.  However, it eventually
+                * succeeds when re-attempted...
+                */
+               int i = 0;
+               do {
+                       status = NtSetInformationFile(h, &ctx->iosb, info,
+                                                     bufsize,
+                                                     FileLinkInformation);
+                       if (NT_SUCCESS(status))
+                               return 0;
+               } while (++i < 32);
                winnt_error(status, L"Failed to create link \"%ls\"",
                            current_path(ctx));
                return WIMLIB_ERR_LINK;
@@ -2413,15 +2433,15 @@ static int
 get_system_compression_format(int extract_flags)
 {
        if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS4K)
-               return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K;
+               return FILE_PROVIDER_COMPRESSION_XPRESS4K;
 
        if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS8K)
-               return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS8K;
+               return FILE_PROVIDER_COMPRESSION_XPRESS8K;
 
        if (extract_flags & WIMLIB_EXTRACT_FLAG_COMPACT_XPRESS16K)
-               return FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS16K;
+               return FILE_PROVIDER_COMPRESSION_XPRESS16K;
 
-       return FILE_PROVIDER_COMPRESSION_FORMAT_LZX;
+       return FILE_PROVIDER_COMPRESSION_LZX;
 }
 
 
@@ -2429,11 +2449,11 @@ static const wchar_t *
 get_system_compression_format_string(int format)
 {
        switch (format) {
-       case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K:
+       case FILE_PROVIDER_COMPRESSION_XPRESS4K:
                return L"XPRESS4K";
-       case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS8K:
+       case FILE_PROVIDER_COMPRESSION_XPRESS8K:
                return L"XPRESS8K";
-       case FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS16K:
+       case FILE_PROVIDER_COMPRESSION_XPRESS16K:
                return L"XPRESS16K";
        default:
                return L"LZX";
@@ -2445,16 +2465,16 @@ set_system_compression(HANDLE h, int format)
 {
        NTSTATUS status;
        struct {
-               struct wof_external_info wof_info;
-               struct file_provider_external_info file_info;
+               WOF_EXTERNAL_INFO wof_info;
+               FILE_PROVIDER_EXTERNAL_INFO_V1 file_info;
        } in = {
                .wof_info = {
-                       .version = WOF_CURRENT_VERSION,
-                       .provider = WOF_PROVIDER_FILE,
+                       .Version = WOF_CURRENT_VERSION,
+                       .Provider = WOF_PROVIDER_FILE,
                },
                .file_info = {
-                       .version = FILE_PROVIDER_CURRENT_VERSION,
-                       .compression_format = format,
+                       .Version = FILE_PROVIDER_CURRENT_VERSION,
+                       .Algorithm = format,
                },
        };
 
@@ -2511,6 +2531,22 @@ static const struct string_list bootloader_patterns = {
        .num_strings = ARRAY_LEN(bootloader_pattern_strings),
 };
 
+/* Returns true if the specified system compression format is supported by the
+ * bootloader of the image being applied.  */
+static bool
+bootloader_supports_compression_format(struct win32_apply_ctx *ctx, int format)
+{
+       /* Windows 10 and later support XPRESS4K */
+       if (format == FILE_PROVIDER_COMPRESSION_XPRESS4K)
+               return ctx->windows_build_number >= 10240;
+
+       /*
+        * Windows 10 version 1903 and later support the other formats;
+        * see https://wimlib.net/forums/viewtopic.php?f=1&t=444
+        */
+       return ctx->windows_build_number >= 18362;
+}
+
 static NTSTATUS
 set_system_compression_on_inode(struct wim_inode *inode, int format,
                                struct win32_apply_ctx *ctx)
@@ -2520,12 +2556,8 @@ set_system_compression_on_inode(struct wim_inode *inode, int format,
        HANDLE h;
 
        /* If it may be needed for compatibility with the Windows bootloader,
-        * force this file to XPRESS4K or uncompressed format.  The bootloader
-        * of Windows 10 supports XPRESS4K only; older versions don't support
-        * system compression at all.  */
-       if (!is_image_windows_10_or_later(ctx) ||
-           format != FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K)
-       {
+        * force this file to XPRESS4K or uncompressed format.  */
+       if (!bootloader_supports_compression_format(ctx, format)) {
                /* We need to check the patterns against every name of the
                 * inode, in case any of them match.  */
                struct wim_dentry *dentry;
@@ -2539,7 +2571,8 @@ set_system_compression_on_inode(struct wim_inode *inode, int format,
                        }
 
                        incompatible = match_pattern_list(dentry->d_full_path,
-                                                         &bootloader_patterns);
+                                                         &bootloader_patterns,
+                                                         MATCH_RECURSIVELY);
                        FREE(dentry->d_full_path);
                        dentry->d_full_path = NULL;
 
@@ -2548,7 +2581,9 @@ set_system_compression_on_inode(struct wim_inode *inode, int format,
 
                        warned = (ctx->num_system_compression_exclusions++ > 0);
 
-                       if (is_image_windows_10_or_later(ctx)) {
+                       if (bootloader_supports_compression_format(ctx,
+                                  FILE_PROVIDER_COMPRESSION_XPRESS4K))
+                       {
                                /* Force to XPRESS4K  */
                                if (!warned) {
                                        WARNING("For compatibility with the "
@@ -2560,7 +2595,7 @@ set_system_compression_on_inode(struct wim_inode *inode, int format,
                                                "          you requested.",
                                                get_system_compression_format_string(format));
                                }
-                               format = FILE_PROVIDER_COMPRESSION_FORMAT_XPRESS4K;
+                               format = FILE_PROVIDER_COMPRESSION_XPRESS4K;
                                break;
                        } else {
                                /* Force to uncompressed  */
@@ -2799,6 +2834,105 @@ set_object_id(HANDLE h, const struct wim_inode *inode,
        }
 }
 
+static int
+set_xattrs(HANDLE h, const struct wim_inode *inode, struct win32_apply_ctx *ctx)
+{
+       const void *entries, *entries_end;
+       u32 len;
+       const struct wim_xattr_entry *entry;
+       size_t bufsize = 0;
+       u8 _buf[1024] __attribute__((aligned(4)));
+       u8 *buf = _buf;
+       FILE_FULL_EA_INFORMATION *ea, *ea_prev;
+       NTSTATUS status;
+       int ret;
+
+       if (!ctx->common.supported_features.xattrs)
+               return 0;
+
+       entries = inode_get_xattrs(inode, &len);
+       if (likely(entries == NULL || len == 0))  /* No extended attributes? */
+               return 0;
+       entries_end = entries + len;
+
+       entry = entries;
+       for (entry = entries; (void *)entry < entries_end;
+            entry = xattr_entry_next(entry)) {
+               if (!valid_xattr_entry(entry, entries_end - (void *)entry)) {
+                       ERROR("\"%"TS"\": extended attribute is corrupt or unsupported",
+                             inode_any_full_path(inode));
+                       return WIMLIB_ERR_INVALID_XATTR;
+               }
+
+               bufsize += ALIGN(offsetof(FILE_FULL_EA_INFORMATION, EaName) +
+                                entry->name_len + 1 +
+                                le16_to_cpu(entry->value_len), 4);
+       }
+
+       if (unlikely(bufsize != (u32)bufsize)) {
+               ERROR("\"%"TS"\": too many extended attributes to extract!",
+                     inode_any_full_path(inode));
+               return WIMLIB_ERR_INVALID_XATTR;
+       }
+
+       if (unlikely(bufsize > sizeof(_buf))) {
+               buf = MALLOC(bufsize);
+               if (!buf)
+                       return WIMLIB_ERR_NOMEM;
+       }
+
+       ea_prev = NULL;
+       ea = (FILE_FULL_EA_INFORMATION *)buf;
+       for (entry = entries; (void *)entry < entries_end;
+            entry = xattr_entry_next(entry)) {
+               u8 *p;
+
+               if (ea_prev)
+                       ea_prev->NextEntryOffset = (u8 *)ea - (u8 *)ea_prev;
+               ea->Flags = entry->flags;
+               ea->EaNameLength = entry->name_len;
+               ea->EaValueLength = le16_to_cpu(entry->value_len);
+               p = mempcpy(ea->EaName, entry->name,
+                           ea->EaNameLength + 1 + ea->EaValueLength);
+               while ((uintptr_t)p & 3)
+                       *p++ = 0;
+               ea_prev = ea;
+               ea = (FILE_FULL_EA_INFORMATION *)p;
+       }
+       ea_prev->NextEntryOffset = 0;
+       wimlib_assert((u8 *)ea - buf == bufsize);
+
+       status = NtSetEaFile(h, &ctx->iosb, buf, bufsize);
+       if (unlikely(!NT_SUCCESS(status))) {
+               if (status == STATUS_EAS_NOT_SUPPORTED) {
+                       /* This happens with Samba. */
+                       WARNING("Filesystem advertised extended attribute (EA) support, but it doesn't\n"
+                               "          work.  EAs will not be extracted.");
+                       ctx->common.supported_features.xattrs = 0;
+               } else if (status == STATUS_INVALID_EA_NAME) {
+                       ctx->num_xattr_failures++;
+                       if (ctx->num_xattr_failures < 5) {
+                               winnt_warning(status,
+                                             L"Can't set extended attributes on \"%ls\"",
+                                             current_path(ctx));
+                       } else if (ctx->num_xattr_failures == 5) {
+                               WARNING("Suppressing further warnings about "
+                                       "failure to set extended attributes.");
+                       }
+               } else {
+                       winnt_error(status, L"Can't set extended attributes on \"%ls\"",
+                                   current_path(ctx));
+                       ret = WIMLIB_ERR_SET_XATTR;
+                       goto out;
+               }
+       }
+       ret = 0;
+out:
+       if (buf != _buf)
+               FREE(buf);
+       return ret;
+}
+
 /* Set the security descriptor @desc, of @desc_size bytes, on the file with open
  * handle @h.  */
 static NTSTATUS
@@ -2942,11 +3076,18 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode,
 {
        FILE_BASIC_INFORMATION info;
        NTSTATUS status;
+       int ret;
 
        /* Set the file's object ID if present and object IDs are supported by
         * the filesystem.  */
        set_object_id(h, inode, ctx);
 
+       /* Set the file's extended attributes (EAs) if present and EAs are
+        * supported by the filesystem.  */
+       ret = set_xattrs(h, inode, ctx);
+       if (ret)
+               return ret;
+
        /* Set the file's security descriptor if present and we're not in
         * NO_ACLS mode  */
        if (inode_has_security_descriptor(inode) &&
@@ -3011,7 +3152,7 @@ apply_metadata_to_file(const struct wim_dentry *dentry,
        NTSTATUS status;
        int ret;
 
-       perms = FILE_WRITE_ATTRIBUTES | WRITE_DAC |
+       perms = FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | WRITE_DAC |
                WRITE_OWNER | ACCESS_SYSTEM_SECURITY;
 
        build_extraction_path(dentry, ctx);
@@ -3218,4 +3359,4 @@ const struct apply_operations win32_apply_ops = {
        .context_size           = sizeof(struct win32_apply_ctx),
 };
 
-#endif /* __WIN32__ */
+#endif /* _WIN32 */