]> wimlib.net Git - wimlib/blobdiff - src/win32_apply.c
Improve random number generation
[wimlib] / src / win32_apply.c
index 57f3a54b31265e55d347f00d9abe5ae2330096a9..96d9df6f578842eea91fc7b5308066b9a0df5715 100644 (file)
@@ -30,7 +30,6 @@
 #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"
@@ -39,6 +38,7 @@
 #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"
@@ -69,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;
 
@@ -125,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.  */
@@ -286,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;
@@ -308,8 +316,28 @@ win32_get_supported_features(const wchar_t *target,
 
        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;
 }
@@ -338,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;
 
@@ -366,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 |
@@ -382,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;
 }
@@ -779,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);
@@ -1129,13 +1157,35 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
        status = winnt_fsctl(h, FSCTL_SET_COMPRESSION,
                             &compression_state, sizeof(USHORT), NULL, 0, NULL);
        if (NT_SUCCESS(status))
-               return status;
+               return 0;
 
        winnt_error(status, L"Can't %s compression attribute on \"%ls\"",
                    (compressed ? "set" : "clear"), current_path(ctx));
        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
@@ -1218,18 +1268,11 @@ retry:
                                      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;
        }
@@ -1471,10 +1514,11 @@ retry:
 /*
  * 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;
@@ -1483,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,
@@ -1595,7 +1639,13 @@ 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);
 
@@ -1624,28 +1674,16 @@ 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(); WRITE_DAC is needed to
-        * remove the directory's DACL if the directory already existed  */
-       perms = GENERIC_READ | GENERIC_WRITE | WRITE_DAC;
+        * are needed for adjust_compression_attribute().  */
+       perms = GENERIC_READ | GENERIC_WRITE;
        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 (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);
 
@@ -1683,25 +1721,6 @@ retry:
                                             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,
-                       };
-                       NtSetSecurityObject(h, DACL_SECURITY_INFORMATION,
-                                           (void *)&desc);
-               }
        }
 
        if (!dentry_is_root(dentry)) {
@@ -1771,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;
 
@@ -1779,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;
@@ -1959,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;
 
@@ -2025,12 +2051,28 @@ begin_extract_blob_instance(const struct blob_descriptor *blob,
                return WIMLIB_ERR_OPEN;
        }
 
+       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;
-
-       /* Allocate space for the data.  */
-       alloc_info.AllocationSize.QuadPart = blob->size;
-       NtSetInformationFile(h, &ctx->iosb, &alloc_info, sizeof(alloc_info),
-                            FileAllocationInformation);
        return 0;
 }
 
@@ -2259,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);
@@ -2269,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);
 
@@ -2304,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 = 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;
                }
        }
 
@@ -2405,7 +2475,7 @@ set_system_compression(HANDLE h, int format)
 
 /* 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[] = {
+static const wchar_t * const bootloader_pattern_strings[] = {
        L"*winload.*",
        L"*winresume.*",
        L"\\Windows\\AppPatch\\drvmain.sdb",
@@ -2436,8 +2506,8 @@ static wchar_t *bootloader_pattern_strings[] = {
        L"\\Windows\\System32\\CodeIntegrity\\driver.stl",
 };
 
-static const struct string_set bootloader_patterns = {
-       .strings = bootloader_pattern_strings,
+static const struct string_list bootloader_patterns = {
+       .strings = (wchar_t **)bootloader_pattern_strings,
        .num_strings = ARRAY_LEN(bootloader_pattern_strings),
 };
 
@@ -2592,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)
@@ -3073,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);