]> wimlib.net Git - wimlib/blobdiff - src/win32_apply.c
Improve random number generation
[wimlib] / src / win32_apply.c
index c31d266841700f029a674dc1ea4b06577ebca503..96d9df6f578842eea91fc7b5308066b9a0df5715 100644 (file)
@@ -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
 #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"
 #include "wimlib/metadata.h"
+#include "wimlib/object_id.h"
 #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"
@@ -68,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;
 
@@ -124,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.  */
@@ -158,6 +166,9 @@ struct win32_apply_ctx {
         * [PrepopulateList].  */
        unsigned long num_system_compression_exclusions;
 
+       /* Number of files for which we couldn't set the object ID.  */
+       unsigned long num_object_id_failures;
+
        /* The Windows build number of the image being applied, or 0 if unknown.
         */
        u64 windows_build_number;
@@ -267,9 +278,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;
@@ -281,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;
@@ -298,10 +311,33 @@ win32_get_supported_features(const wchar_t *target,
        if (short_names_supported)
                supported_features->short_names = 1;
 
+       if (vol_flags & FILE_SUPPORTS_OBJECT_IDS)
+               supported_features->object_ids = 1;
+
        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;
 }
@@ -330,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;
 
@@ -358,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 |
@@ -374,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;
 }
@@ -771,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);
@@ -901,7 +937,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  */
@@ -992,18 +1030,17 @@ open_target_directory(struct win32_apply_ctx *ctx)
 
        /* 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,
-                                     &ctx->iosb,
-                                     NULL,
-                                     0,
-                                     FILE_SHARE_VALID_FLAGS,
-                                     FILE_OPEN_IF,
-                                     FILE_DIRECTORY_FILE |
-                                             FILE_OPEN_FOR_BACKUP_INTENT,
-                                     NULL,
-                                     0);
+       status = NtCreateFile(&ctx->h_target,
+                             FILE_TRAVERSE,
+                             &ctx->attr,
+                             &ctx->iosb,
+                             NULL,
+                             0,
+                             FILE_SHARE_VALID_FLAGS,
+                             FILE_OPEN_IF,
+                             FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT,
+                             NULL,
+                             0);
        if (!NT_SUCCESS(status)) {
                winnt_error(status, L"Can't open or create directory \"%ls\"",
                            ctx->common.target);
@@ -1018,7 +1055,7 @@ static void
 close_target_directory(struct win32_apply_ctx *ctx)
 {
        if (ctx->h_target) {
-               (*func_NtClose)(ctx->h_target);
+               NtClose(ctx->h_target);
                ctx->h_target = NULL;
                ctx->attr.RootDirectory = NULL;
        }
@@ -1089,6 +1126,9 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
 {
        const bool compressed = (dentry->d_inode->i_attributes &
                                 FILE_ATTRIBUTE_COMPRESSED);
+       FILE_BASIC_INFORMATION info;
+       USHORT compression_state;
+       NTSTATUS status;
 
        if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ATTRIBUTES)
                return 0;
@@ -1096,15 +1136,10 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
        if (!ctx->common.supported_features.compressed_files)
                return 0;
 
-       FILE_BASIC_INFORMATION info;
-       NTSTATUS status;
-       USHORT compression_state;
-       DWORD bytes_returned;
 
        /* Get current attributes  */
-       status = (*func_NtQueryInformationFile)(h, &ctx->iosb,
-                                               &info, sizeof(info),
-                                               FileBasicInformation);
+       status = NtQueryInformationFile(h, &ctx->iosb, &info, sizeof(info),
+                                       FileBasicInformation);
        if (NT_SUCCESS(status) &&
            compressed == !!(info.FileAttributes & FILE_ATTRIBUTE_COMPRESSED))
        {
@@ -1119,18 +1154,38 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
        else
                compression_state = COMPRESSION_FORMAT_NONE;
 
-       /* 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))
+       status = winnt_fsctl(h, FSCTL_SET_COMPRESSION,
+                            &compression_state, sizeof(USHORT), NULL, 0, NULL);
+       if (NT_SUCCESS(status))
                return 0;
 
-       win32_error(GetLastError(), L"Can't %s compression attribute on \"%ls\"",
+       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
@@ -1192,10 +1247,10 @@ remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_appl
        ctx->pathbuf.Length = ((u8 *)end - (u8 *)ctx->pathbuf.Buffer);
 
        /* Open the conflicting file (by short name).  */
-       status = (*func_NtOpenFile)(&h, GENERIC_WRITE | DELETE,
-                                   &ctx->attr, &ctx->iosb,
-                                   FILE_SHARE_VALID_FLAGS,
-                                   FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT);
+       status = NtOpenFile(&h, GENERIC_WRITE | DELETE,
+                           &ctx->attr, &ctx->iosb,
+                           FILE_SHARE_VALID_FLAGS,
+                           FILE_OPEN_REPARSE_POINT | FILE_OPEN_FOR_BACKUP_INTENT);
        if (!NT_SUCCESS(status)) {
                winnt_warning(status, L"Can't open \"%ls\"", current_path(ctx));
                goto out;
@@ -1209,26 +1264,19 @@ remove_conflicting_short_name(const struct wim_dentry *dentry, struct win32_appl
        /* Try to remove the short name on the conflicting file.  */
 
 retry:
-       status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize,
-                                             FileShortNameInformation);
+       status = NtSetInformationFile(h, &ctx->iosb, info, bufsize,
+                                     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;
        }
-       (*func_NtClose)(h);
+       NtClose(h);
 out:
        build_extraction_path(dentry, ctx);
        return status;
@@ -1278,8 +1326,8 @@ set_short_name(HANDLE h, const struct wim_dentry *dentry,
        memcpy(info->FileName, dentry->d_short_name, dentry->d_short_name_nbytes);
 
 retry:
-       status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize,
-                                             FileShortNameInformation);
+       status = NtSetInformationFile(h, &ctx->iosb, info, bufsize,
+                                     FileShortNameInformation);
        if (NT_SUCCESS(status))
                return 0;
 
@@ -1344,7 +1392,7 @@ retry:
  * A wrapper around NtCreateFile() to make it slightly more usable...
  * This uses the path currently constructed in ctx->pathbuf.
  *
- * Also, we always specify FILE_OPEN_FOR_BACKUP_INTENT and
+ * Also, we always specify SYNCHRONIZE access, FILE_OPEN_FOR_BACKUP_INTENT, and
  * FILE_OPEN_REPARSE_POINT.
  */
 static NTSTATUS
@@ -1356,19 +1404,19 @@ do_create_file(PHANDLE FileHandle,
               ULONG CreateOptions,
               struct win32_apply_ctx *ctx)
 {
-       return (*func_NtCreateFile)(FileHandle,
-                                   DesiredAccess,
-                                   &ctx->attr,
-                                   &ctx->iosb,
-                                   AllocationSize,
-                                   FileAttributes,
-                                   FILE_SHARE_VALID_FLAGS,
-                                   CreateDisposition,
-                                   CreateOptions |
-                                       FILE_OPEN_FOR_BACKUP_INTENT |
-                                       FILE_OPEN_REPARSE_POINT,
-                                   NULL,
-                                   0);
+       return NtCreateFile(FileHandle,
+                           DesiredAccess | SYNCHRONIZE,
+                           &ctx->attr,
+                           &ctx->iosb,
+                           AllocationSize,
+                           FileAttributes,
+                           FILE_SHARE_VALID_FLAGS,
+                           CreateDisposition,
+                           CreateOptions |
+                               FILE_OPEN_FOR_BACKUP_INTENT |
+                               FILE_OPEN_REPARSE_POINT,
+                           NULL,
+                           0);
 }
 
 /* Like do_create_file(), but builds the extraction path of the @dentry first.
@@ -1398,76 +1446,79 @@ 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))) {
+
+               FILE_BASIC_INFORMATION basic_info =
+                       { .FileAttributes = FILE_ATTRIBUTE_NORMAL };
+               status = NtSetInformationFile(h, &ctx->iosb, &basic_info,
+                                             sizeof(basic_info),
+                                             FileBasicInformation);
 
-       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;
+                       winnt_error(status, L"Can't reset attributes of \"%ls\" "
+                                   "to prepare for deletion", current_path(ctx));
+                       NtClose(h);
+                       return WIMLIB_ERR_SET_ATTRIBUTES;
                }
-               memset(&basic_info, 0, sizeof(basic_info));
-               basic_info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
-               status = (*func_NtSetInformationFile)(h, &ctx->iosb,
-                                                     &basic_info,
-                                                     sizeof(basic_info),
-                                                     FileBasicInformation);
+
+               FILE_DISPOSITION_INFORMATION disp_info =
+                       { .DoDeleteFile = TRUE };
+               status = NtSetInformationFile(h, &ctx->iosb, &disp_info,
+                                             sizeof(disp_info),
+                                             FileDispositionInformation);
                if (!NT_SUCCESS(status)) {
-                       winnt_error(status,
-                                   L"Can't reset file attributes on \"%ls\"",
-                                   current_path(ctx));
-                       (*func_NtClose)(h);
+                       winnt_error(status, L"Can't set delete-on-close "
+                                   "disposition on \"%ls\"", current_path(ctx));
+                       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 = 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;
 }
 
 /*
  * 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;
@@ -1476,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,
@@ -1518,11 +1569,9 @@ do_set_reparse_point(const struct wim_dentry *dentry,
        if (!NT_SUCCESS(status))
                goto fail;
 
-       status = (*func_NtFsControlFile)(h, NULL, NULL, NULL,
-                                        &ctx->iosb, FSCTL_SET_REPARSE_POINT,
-                                        (void *)rpbuf, rpbuflen,
-                                        NULL, 0);
-       (*func_NtClose)(h);
+       status = winnt_fsctl(h, FSCTL_SET_REPARSE_POINT,
+                            rpbuf, rpbuflen, NULL, 0, NULL);
+       NtClose(h);
 
        if (NT_SUCCESS(status))
                return 0;
@@ -1590,13 +1639,19 @@ 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);
 
                        if (ret)
                                return ret;
-                       (*func_NtClose)(h);
+                       NtClose(h);
                }
        }
 
@@ -1628,9 +1683,25 @@ create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx)
         * FILE_ATTRIBUTE_ENCRYPTED doesn't get set before we want it to be.  */
        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))) {
+               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;
        }
 
@@ -1643,9 +1714,13 @@ 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 };
+                       NtSetInformationFile(h, &ctx->iosb, &basic_info,
+                                            sizeof(basic_info),
+                                            FileBasicInformation);
+               }
        }
 
        if (!dentry_is_root(dentry)) {
@@ -1656,7 +1731,7 @@ create_directory(const struct wim_dentry *dentry, struct win32_apply_ctx *ctx)
 
        ret = adjust_compression_attribute(h, dentry, ctx);
 out:
-       (*func_NtClose)(h);
+       NtClose(h);
        return ret;
 }
 
@@ -1715,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;
 
@@ -1723,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;
@@ -1731,7 +1814,7 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry,
        return 0;
 
 out_close:
-       (*func_NtClose)(h);
+       NtClose(h);
 out:
        return ret;
 }
@@ -1764,9 +1847,8 @@ create_link(HANDLE h, const struct wim_dentry *dentry,
                 * STATUS_INFO_LENGTH_MISMATCH when FileNameLength
                 * happens to be 2  */
 
-               status = (*func_NtSetInformationFile)(h, &ctx->iosb,
-                                                     info, bufsize,
-                                                     FileLinkInformation);
+               status = NtSetInformationFile(h, &ctx->iosb, info, bufsize,
+                                             FileLinkInformation);
                if (NT_SUCCESS(status))
                        return 0;
                winnt_error(status, L"Failed to create link \"%ls\"",
@@ -1780,7 +1862,7 @@ create_link(HANDLE h, const struct wim_dentry *dentry,
                if (ret)
                        return ret;
 
-               (*func_NtClose)(h2);
+               NtClose(h2);
                return 0;
        }
 }
@@ -1837,7 +1919,7 @@ create_nondirectory(struct wim_inode *inode, struct win32_apply_ctx *ctx)
        if (!ret && unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT))
                ret = set_backed_from_wim(h, inode, ctx);
 
-       (*func_NtClose)(h);
+       NtClose(h);
        return ret;
 }
 
@@ -1872,7 +1954,7 @@ static void
 close_handles(struct win32_apply_ctx *ctx)
 {
        for (unsigned i = 0; i < ctx->num_open_handles; i++)
-               (*func_NtClose)(ctx->open_handles[i]);
+               NtClose(ctx->open_handles[i]);
 }
 
 /* Prepare to read the next blob, which has size @blob_size, into an in-memory
@@ -1904,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;
 
@@ -1970,13 +2051,28 @@ begin_extract_blob_instance(const struct blob_descriptor *blob,
                return WIMLIB_ERR_OPEN;
        }
 
-       ctx->open_handles[ctx->num_open_handles++] = h;
-
-       /* Allocate space for the data.  */
-       alloc_info.AllocationSize.QuadPart = blob->size;
-       (*func_NtSetInformationFile)(h, &ctx->iosb,
-                                    &alloc_info, sizeof(alloc_info),
+       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;
        return 0;
 }
 
@@ -2067,6 +2163,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];
@@ -2185,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);
@@ -2195,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);
 
@@ -2230,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 = (*func_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;
                }
        }
 
@@ -2300,7 +2444,6 @@ static NTSTATUS
 set_system_compression(HANDLE h, int format)
 {
        NTSTATUS status;
-       IO_STATUS_BLOCK iosb;
        struct {
                struct wof_external_info wof_info;
                struct file_provider_external_info file_info;
@@ -2321,9 +2464,8 @@ set_system_compression(HANDLE h, int format)
         * 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);
+       status = winnt_fsctl(h, FSCTL_SET_EXTERNAL_BACKING,
+                            &in, sizeof(in), NULL, 0, NULL);
 
        if (status == 0xC000046F) /* "Compressing this object would not save space."  */
                return STATUS_SUCCESS;
@@ -2333,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",
@@ -2364,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),
 };
 
@@ -2456,7 +2598,7 @@ retry:
                }
        }
 
-       (*func_NtClose)(h);
+       NtClose(h);
        return status;
 }
 
@@ -2520,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)
@@ -2594,6 +2759,46 @@ end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx)
         FILE_ATTRIBUTE_SPARSE_FILE     |       \
         FILE_ATTRIBUTE_COMPRESSED)
 
+static void
+set_object_id(HANDLE h, const struct wim_inode *inode,
+             struct win32_apply_ctx *ctx)
+{
+       const void *object_id;
+       u32 len;
+       NTSTATUS status;
+
+       if (!ctx->common.supported_features.object_ids)
+               return;
+
+       object_id = inode_get_object_id(inode, &len);
+       if (likely(object_id == NULL))  /* No object ID?  */
+               return;
+
+       status = winnt_fsctl(h, FSCTL_SET_OBJECT_ID,
+                            object_id, len, NULL, 0, NULL);
+       if (NT_SUCCESS(status))
+               return;
+
+       /* Object IDs must be unique within the filesystem.  A duplicate might
+        * occur if an image containing object IDs is applied twice to the same
+        * filesystem.  Arguably, the user should be warned in this case; but
+        * the reality seems to be that nothing important cares about object IDs
+        * except the Distributed Link Tracking Service... so for now these
+        * failures are just ignored.  */
+       if (status == STATUS_DUPLICATE_NAME ||
+           status == STATUS_OBJECT_NAME_COLLISION)
+               return;
+
+       ctx->num_object_id_failures++;
+       if (ctx->num_object_id_failures < 10) {
+               winnt_warning(status, L"Can't set object ID on \"%ls\"",
+                             current_path(ctx));
+       } else if (ctx->num_object_id_failures == 10) {
+               WARNING("Suppressing further warnings about failure to set "
+                       "object IDs.");
+       }
+}
+
 /* Set the security descriptor @desc, of @desc_size bytes, on the file with open
  * handle @h.  */
 static NTSTATUS
@@ -2687,7 +2892,7 @@ set_security_descriptor(HANDLE h, const void *_desc,
         */
 
 retry:
-       status = (*func_NtSetSecurityObject)(h, info, desc);
+       status = NtSetSecurityObject(h, info, desc);
        if (NT_SUCCESS(status))
                goto out_maybe_free_desc;
 
@@ -2738,7 +2943,12 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode,
        FILE_BASIC_INFORMATION info;
        NTSTATUS status;
 
-       /* Set security descriptor if present and not in NO_ACLS mode  */
+       /* 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 security descriptor if present and we're not in
+        * NO_ACLS mode  */
        if (inode_has_security_descriptor(inode) &&
            !(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS))
        {
@@ -2774,9 +2984,8 @@ do_apply_metadata_to_file(HANDLE h, const struct wim_inode *inode,
                        info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
        }
 
-       status = (*func_NtSetInformationFile)(h, &ctx->iosb,
-                                             &info, sizeof(info),
-                                             FileBasicInformation);
+       status = NtSetInformationFile(h, &ctx->iosb, &info, sizeof(info),
+                                     FileBasicInformation);
        /* On FAT volumes we get STATUS_INVALID_PARAMETER if we try to set
         * attributes on the root directory.  (Apparently because FAT doesn't
         * actually have a place to store those attributes!)  */
@@ -2834,7 +3043,7 @@ apply_metadata_to_file(const struct wim_dentry *dentry,
 
        ret = do_apply_metadata_to_file(h, inode, ctx);
 
-       (*func_NtClose)(h);
+       NtClose(h);
 
        return ret;
 }
@@ -2957,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);