]> wimlib.net Git - wimlib/blobdiff - src/win32_apply.c
win32_apply.c: better workaround for access denied bug when creating ADS
[wimlib] / src / win32_apply.c
index a19d138f9002ac80a56d0d49d3312067abc13307..d9bf63c2fd305b93cf932b1bbff9d0dc996f8c83 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;
 
@@ -158,6 +159,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 +271,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;
@@ -298,6 +303,9 @@ 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
@@ -330,7 +338,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 +366,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 +382,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;
 }
@@ -901,7 +909,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  */
@@ -989,19 +999,20 @@ open_target_directory(struct win32_apply_ctx *ctx)
        ctx->attr.Length = sizeof(ctx->attr);
        ctx->attr.RootDirectory = NULL;
        ctx->attr.ObjectName = &ctx->target_ntpath;
-       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_REPARSE_POINT |
-                                             FILE_OPEN_FOR_BACKUP_INTENT,
-                                     NULL,
-                                     0);
+
+       /* Don't use FILE_OPEN_REPARSE_POINT here; we want the extraction to
+        * happen at the directory "pointed to" by the reparse point. */
+       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);
@@ -1016,7 +1027,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;
        }
@@ -1087,6 +1098,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;
@@ -1094,14 +1108,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;
 
        /* 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))
        {
@@ -1116,16 +1126,8 @@ 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);
+       status = winnt_fsctl(h, FSCTL_SET_COMPRESSION,
+                            &compression_state, sizeof(USHORT), NULL, 0, NULL);
        if (NT_SUCCESS(status))
                return 0;
 
@@ -1195,10 +1197,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;
@@ -1212,8 +1214,8 @@ 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) {
 
@@ -1231,7 +1233,7 @@ retry:
                retried = true;
                goto retry;
        }
-       (*func_NtClose)(h);
+       NtClose(h);
 out:
        build_extraction_path(dentry, ctx);
        return status;
@@ -1281,8 +1283,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;
 
@@ -1347,7 +1349,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
@@ -1359,19 +1361,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.
@@ -1401,76 +1403,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;
@@ -1479,7 +1484,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,
@@ -1521,11 +1526,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;
@@ -1593,13 +1596,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);
                }
        }
 
@@ -1622,18 +1631,46 @@ 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)) {
-               winnt_error(status, L"Can't create directory \"%ls\"",
-                           current_path(ctx));
+       if (unlikely(!NT_SUCCESS(status))) {
+               if (status == STATUS_ACCESS_DENIED) {
+                       if (perms & WRITE_DAC) {
+                               perms &= ~WRITE_DAC;
+                               goto retry;
+                       }
+                       if (perms & DELETE) {
+                               perms &= ~DELETE;
+                               goto retry;
+                       }
+               }
+               const wchar_t *path = current_path(ctx);
+               winnt_error(status, L"Can't create directory \"%ls\"", path);
+
+               /* Check for known issue with WindowsApps directory.  */
+               if (status == STATUS_ACCESS_DENIED &&
+                   (wcsstr(path, L"\\WindowsApps\\") ||
+                    wcsstr(path, L"\\InfusedApps\\"))) {
+                       ERROR(
+"You seem to be trying to extract files to the WindowsApps directory.\n"
+"        Windows 8.1 and later use new file permissions in this directory that\n"
+"        cannot be overridden, even by backup/restore programs.  To extract your\n"
+"        files anyway, you need to choose a different target directory, delete\n"
+"        the WindowsApps directory entirely, reformat the volume, do the\n"
+"        extraction from a non-broken operating system such as Windows 7 or\n"
+"        Linux, or wait for Microsoft to fix the design flaw in their operating\n"
+"        system.  This is *not* a bug in wimlib.  See this thread for more\n"
+"        information: https://wimlib.net/forums/viewtopic.php?f=1&t=261");
+               }
                return WIMLIB_ERR_MKDIR;
        }
 
@@ -1646,9 +1683,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)) {
@@ -1659,7 +1700,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;
 }
 
@@ -1718,7 +1759,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;
 
@@ -1734,7 +1777,7 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry,
        return 0;
 
 out_close:
-       (*func_NtClose)(h);
+       NtClose(h);
 out:
        return ret;
 }
@@ -1767,9 +1810,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\"",
@@ -1783,7 +1825,7 @@ create_link(HANDLE h, const struct wim_dentry *dentry,
                if (ret)
                        return ret;
 
-               (*func_NtClose)(h2);
+               NtClose(h2);
                return 0;
        }
 }
@@ -1840,7 +1882,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;
 }
 
@@ -1875,7 +1917,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
@@ -1977,9 +2019,8 @@ begin_extract_blob_instance(const struct blob_descriptor *blob,
 
        /* Allocate space for the data.  */
        alloc_info.AllocationSize.QuadPart = blob->size;
-       (*func_NtSetInformationFile)(h, &ctx->iosb,
-                                    &alloc_info, sizeof(alloc_info),
-                                    FileAllocationInformation);
+       NtSetInformationFile(h, &ctx->iosb, &alloc_info, sizeof(alloc_info),
+                            FileAllocationInformation);
        return 0;
 }
 
@@ -2070,6 +2111,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];
@@ -2248,10 +2309,10 @@ extract_chunk(const void *chunk, size_t size, void *_ctx)
                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);
+                       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;
@@ -2303,7 +2364,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;
@@ -2324,9 +2384,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;
@@ -2367,7 +2426,7 @@ static wchar_t *bootloader_pattern_strings[] = {
        L"\\Windows\\System32\\CodeIntegrity\\driver.stl",
 };
 
-static const struct string_set bootloader_patterns = {
+static const struct string_list bootloader_patterns = {
        .strings = bootloader_pattern_strings,
        .num_strings = ARRAY_LEN(bootloader_pattern_strings),
 };
@@ -2459,7 +2518,7 @@ retry:
                }
        }
 
-       (*func_NtClose)(h);
+       NtClose(h);
        return status;
 }
 
@@ -2597,6 +2656,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
@@ -2690,7 +2789,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;
 
@@ -2741,7 +2840,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))
        {
@@ -2777,9 +2881,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!)  */
@@ -2837,7 +2940,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;
 }