]> wimlib.net Git - wimlib/blobdiff - src/win32_apply.c
win32_apply.c: set_short_name(): Increase minimum buffer size
[wimlib] / src / win32_apply.c
index f741b447e078322adf12d86f2d213f405a0b82d7..a6b37c4a90cda4375a874fa7d9691ad63a80b82b 100644 (file)
@@ -21,6 +21,8 @@
  * along with wimlib; if not, see http://www.gnu.org/licenses/.
  */
 
+#ifdef __WIN32__
+
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
 #endif
 #include "wimlib/xml.h"
 #include "wimlib/wimboot.h"
 
-/* TODO: Add workaround for when a stream needs to be extracted to more places
- * than this  */
-#define MAX_OPEN_HANDLES 32768
-
 struct win32_apply_ctx {
 
        /* Extract flags, the pointer to the WIMStruct, etc.  */
@@ -98,12 +96,12 @@ struct win32_apply_ctx {
        struct reparse_buffer_disk rpbuf;
 
        /* Temporary buffer for reparse data of "fixed" absolute symbolic links
-        * and junction  */
+        * and junctions  */
        struct reparse_buffer_disk rpfixbuf;
 
        /* Array of open handles to filesystem streams currently being written
         */
-       HANDLE open_handles[MAX_OPEN_HANDLES];
+       HANDLE open_handles[MAX_OPEN_STREAMS];
 
        /* Number of handles in @open_handles currently open (filled in from the
         * beginning of the array)  */
@@ -126,6 +124,13 @@ struct win32_apply_ctx {
        /* Number of files for which we didn't have permission to set any part
         * of the security descriptor.  */
        unsigned long no_security_descriptors;
+
+       /* Number of files for which we couldn't set the short name.  */
+       unsigned long num_short_name_failures;
+
+       /* Have we tried to enable short name support on the target volume yet?
+        */
+       bool tried_to_enable_short_names;
 };
 
 /* Get the drive letter from a Windows path, or return the null character if the
@@ -237,7 +242,7 @@ win32_get_supported_features(const wchar_t *target,
        return 0;
 }
 
-/* Load the patterns from [PrepopulateList] of WimBootCompresse.ini in the WIM
+/* Load the patterns from [PrepopulateList] of WimBootCompress.ini in the WIM
  * image being extracted.  */
 static int
 load_prepopulate_pats(struct win32_apply_ctx *ctx)
@@ -327,8 +332,7 @@ start_wimboot_extraction(struct win32_apply_ctx *ctx)
        if (ret == WIMLIB_ERR_NOMEM)
                return ret;
 
-       if (!wim_info_get_wimboot(wim->wim_info,
-                                 wim->current_image))
+       if (!wim_info_get_wimboot(wim->wim_info, wim->current_image))
                WARNING("Image is not marked as WIMBoot compatible!");
 
        ret = hash_lookup_table(ctx->common.wim,
@@ -344,6 +348,80 @@ start_wimboot_extraction(struct win32_apply_ctx *ctx)
                                            &ctx->wimboot.wof_running);
 }
 
+static void
+build_win32_extraction_path(const struct wim_dentry *dentry,
+                           struct win32_apply_ctx *ctx);
+
+/* Sets WimBoot=1 in the extracted SYSTEM registry hive.
+ *
+ * WIMGAPI does this, and it's possible that it's important.
+ * But I don't know exactly what this value means to Windows.  */
+static int
+end_wimboot_extraction(struct win32_apply_ctx *ctx)
+{
+       struct wim_dentry *dentry;
+       wchar_t subkeyname[32];
+       LONG res;
+       LONG res2;
+       HKEY key;
+       DWORD value;
+
+       dentry = get_dentry(ctx->common.wim, L"\\Windows\\System32\\config\\SYSTEM",
+                           WIMLIB_CASE_INSENSITIVE);
+
+       if (!dentry || !will_extract_dentry(dentry))
+               goto out;
+
+       if (!will_extract_dentry(wim_get_current_root_dentry(ctx->common.wim)))
+               goto out;
+
+       /* Not bothering to use the native routines (e.g. NtLoadKey()) for this.
+        * If this doesn't work, you probably also have many other problems.  */
+
+       build_win32_extraction_path(dentry, ctx);
+
+       randomize_char_array_with_alnum(subkeyname, 20);
+       subkeyname[20] = L'\0';
+
+       res = RegLoadKey(HKEY_LOCAL_MACHINE, subkeyname, ctx->pathbuf.Buffer);
+       if (res)
+               goto out_check_res;
+
+       wcscpy(&subkeyname[20], L"\\Setup");
+
+       res = RegCreateKeyEx(HKEY_LOCAL_MACHINE, subkeyname, 0, NULL,
+                            REG_OPTION_BACKUP_RESTORE, 0, NULL, &key, NULL);
+       if (res)
+               goto out_unload_key;
+
+       value = 1;
+
+       res = RegSetValueEx(key, L"WimBoot", 0, REG_DWORD,
+                           (const BYTE *)&value, sizeof(DWORD));
+       if (res)
+               goto out_close_key;
+
+       res = RegFlushKey(key);
+
+out_close_key:
+       res2 = RegCloseKey(key);
+       if (!res)
+               res = res2;
+out_unload_key:
+       subkeyname[20] = L'\0';
+       RegUnLoadKey(HKEY_LOCAL_MACHINE, subkeyname);
+out_check_res:
+       if (res) {
+               /* Warning only.  */
+               set_errno_from_win32_error(res);
+               WARNING_WITH_ERRNO("Failed to set \\Setup: dword \"WimBoot\"=1 value "
+                                  "in registry hive \"%ls\" (err=%"PRIu32")",
+                                  ctx->pathbuf.Buffer, (u32)res);
+       }
+out:
+       return 0;
+}
+
 /* Returns the number of wide characters needed to represent the path to the
  * specified @dentry, relative to the target directory, when extracted.
  *
@@ -357,7 +435,7 @@ dentry_extraction_path_length(const struct wim_dentry *dentry)
        d = dentry;
        do {
                len += d->d_extraction_name_nchars + 1;
-               d = d->parent;
+               d = d->d_parent;
        } while (!dentry_is_root(d) && will_extract_dentry(d));
 
        return --len;  /* No leading slash  */
@@ -368,7 +446,7 @@ dentry_extraction_path_length(const struct wim_dentry *dentry)
  *
  * If the inode has no named data streams, this will be 0.  Otherwise, this will
  * be 1 plus the length of the longest-named data stream, since the data stream
- * name must be separated form the path by the ':' character.  */
+ * name must be separated from the path by the ':' character.  */
 static size_t
 inode_longest_named_data_stream_spec(const struct wim_inode *inode)
 {
@@ -426,8 +504,8 @@ build_extraction_path(const struct wim_dentry *dentry,
        ctx->pathbuf.Length = len * sizeof(wchar_t);
        p = ctx->pathbuf.Buffer + len;
        for (d = dentry;
-            !dentry_is_root(d->parent) && will_extract_dentry(d->parent);
-            d = d->parent)
+            !dentry_is_root(d->d_parent) && will_extract_dentry(d->d_parent);
+            d = d->d_parent)
        {
                p -= d->d_extraction_name_nchars;
                wmemcpy(p, d->d_extraction_name, d->d_extraction_name_nchars);
@@ -464,7 +542,7 @@ build_extraction_path_with_ads(const struct wim_dentry *dentry,
  * The path is saved in ctx->pathbuf and will be null terminated.
  *
  * XXX: We could get rid of this if it wasn't needed for the file encryption
- * APIs.  */
+ * APIs, and the registry manipulation in WIMBoot mode.  */
 static void
 build_win32_extraction_path(const struct wim_dentry *dentry,
                            struct win32_apply_ctx *ctx)
@@ -482,7 +560,7 @@ build_win32_extraction_path(const struct wim_dentry *dentry,
        ctx->pathbuf.Length += ctx->target_ntpath.Length + sizeof(wchar_t);
        ctx->pathbuf.Buffer[ctx->pathbuf.Length / sizeof(wchar_t)] = L'\0';
 
-       wimlib_assert(ctx->pathbuf.Length >= 8 &&
+       wimlib_assert(ctx->pathbuf.Length >= 4 * sizeof(wchar_t) &&
                      !wmemcmp(ctx->pathbuf.Buffer, L"\\??\\", 4));
 
        ctx->pathbuf.Buffer[1] = L'\\';
@@ -512,24 +590,15 @@ current_path(struct win32_apply_ctx *ctx)
 static int
 prepare_target(struct list_head *dentry_list, struct win32_apply_ctx *ctx)
 {
+       int ret;
        NTSTATUS status;
        size_t path_max;
 
        /* Open handle to the target directory (possibly creating it).  */
 
-       status = (*func_RtlDosPathNameToNtPathName_U_WithStatus)(ctx->common.target,
-                                                                &ctx->target_ntpath,
-                                                                NULL, NULL);
-       if (!NT_SUCCESS(status)) {
-               if (status == STATUS_NO_MEMORY) {
-                       return WIMLIB_ERR_NOMEM;
-               } else {
-                       ERROR("\"%ls\": invalid path name "
-                             "(status=0x%08"PRIx32")",
-                             ctx->common.target, (u32)status);
-                       return WIMLIB_ERR_INVALID_PARAM;
-               }
-       }
+       ret = win32_path_to_nt_path(ctx->common.target, &ctx->target_ntpath);
+       if (ret)
+               return ret;
 
        ctx->attr.Length = sizeof(ctx->attr);
        ctx->attr.ObjectName = &ctx->target_ntpath;
@@ -674,7 +743,7 @@ adjust_compression_attribute(HANDLE h, const struct wim_dentry *dentry,
  * handle to the file.  If it does, it sets it to NULL.
  */
 static int
-maybe_clear_encryption_attribute(HANDLE *h_ret, const struct wim_dentry *dentry,
+maybe_clear_encryption_attribute(HANDLE *h_ptr, const struct wim_dentry *dentry,
                                 struct win32_apply_ctx *ctx)
 {
        if (dentry->d_inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
@@ -691,7 +760,7 @@ maybe_clear_encryption_attribute(HANDLE *h_ret, const struct wim_dentry *dentry,
        BOOL bret;
 
        /* Get current attributes  */
-       status = (*func_NtQueryInformationFile)(*h_ret, &ctx->iosb,
+       status = (*func_NtQueryInformationFile)(*h_ptr, &ctx->iosb,
                                                &info, sizeof(info),
                                                FileBasicInformation);
        if (NT_SUCCESS(status) &&
@@ -707,8 +776,8 @@ maybe_clear_encryption_attribute(HANDLE *h_ret, const struct wim_dentry *dentry,
         * handle to the file so we don't get ERROR_SHARING_VIOLATION.  We also
         * hack together a Win32 path, although we will use the \\?\ prefix so
         * it will actually be a NT path in disguise...  */
-       (*func_NtClose)(*h_ret);
-       *h_ret = NULL;
+       (*func_NtClose)(*h_ptr);
+       *h_ptr = NULL;
 
        build_win32_extraction_path(dentry, ctx);
 
@@ -727,6 +796,43 @@ maybe_clear_encryption_attribute(HANDLE *h_ret, const struct wim_dentry *dentry,
        return 0;
 }
 
+/* Try to enable short name support on the target volume.  If successful, return
+ * true.  If unsuccessful, issue a warning and return false.  */
+static bool
+try_to_enable_short_names(const wchar_t *volume)
+{
+       HANDLE h;
+       FILE_FS_PERSISTENT_VOLUME_INFORMATION info;
+       BOOL bret;
+       DWORD bytesReturned;
+
+       h = CreateFile(volume, GENERIC_WRITE,
+                      FILE_SHARE_VALID_FLAGS, NULL, OPEN_EXISTING,
+                      FILE_FLAG_BACKUP_SEMANTICS, NULL);
+       if (h == INVALID_HANDLE_VALUE)
+               goto fail;
+
+       info.VolumeFlags = 0;
+       info.FlagMask = PERSISTENT_VOLUME_STATE_SHORT_NAME_CREATION_DISABLED;
+       info.Version = 1;
+       info.Reserved = 0;
+
+       bret = DeviceIoControl(h, FSCTL_SET_PERSISTENT_VOLUME_STATE,
+                              &info, sizeof(info), NULL, 0,
+                              &bytesReturned, NULL);
+
+       CloseHandle(h);
+
+       if (!bret)
+               goto fail;
+       return true;
+
+fail:
+       WARNING("Failed to enable short name support on %ls "
+               "(err=%"PRIu32")", volume + 4, (u32)GetLastError());
+       return false;
+}
+
 /* Set the short name on the open file @h which has been created at the location
  * indicated by @dentry.
  *
@@ -741,8 +847,24 @@ static int
 set_short_name(HANDLE h, const struct wim_dentry *dentry,
               struct win32_apply_ctx *ctx)
 {
+
+       if (!ctx->common.supported_features.short_names)
+               return 0;
+
+       /*
+        * Note: The size of the FILE_NAME_INFORMATION buffer must be such that
+        * FileName contains at least 2 wide characters (4 bytes).  Otherwise,
+        * NtSetInformationFile() will return STATUS_INFO_LENGTH_MISMATCH.  This
+        * is despite the fact that FileNameLength can validly be 0 or 2 bytes,
+        * with the former case being removing the existing short name if
+        * present, rather than setting one.
+        *
+        * FileName seemingly does not, however, need to be null-terminated in
+        * any case.
+        */
+
        size_t bufsize = offsetof(FILE_NAME_INFORMATION, FileName) +
-                        dentry->short_name_nbytes;
+                        max(dentry->short_name_nbytes, 2 * sizeof(wchar_t));
        u8 buf[bufsize] _aligned_attribute(8);
        FILE_NAME_INFORMATION *info = (FILE_NAME_INFORMATION *)buf;
        NTSTATUS status;
@@ -750,20 +872,40 @@ set_short_name(HANDLE h, const struct wim_dentry *dentry,
        info->FileNameLength = dentry->short_name_nbytes;
        memcpy(info->FileName, dentry->short_name, dentry->short_name_nbytes);
 
+
+retry:
        status = (*func_NtSetInformationFile)(h, &ctx->iosb, info, bufsize,
                                              FileShortNameInformation);
        if (NT_SUCCESS(status))
                return 0;
 
+       if (status == STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME) {
+               if (dentry->short_name_nbytes == 0)
+                       return 0;
+               if (!ctx->tried_to_enable_short_names) {
+                       wchar_t volume[7];
+                       int ret;
+
+                       ctx->tried_to_enable_short_names = true;
+
+                       ret = win32_get_drive_path(ctx->common.target,
+                                                  volume);
+                       if (ret)
+                               return ret;
+                       if (try_to_enable_short_names(volume))
+                               goto retry;
+               }
+       }
+
        /* By default, failure to set short names is not an error (since short
         * names aren't too important anymore...).  */
-       if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES))
+       if (!(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_SHORT_NAMES)) {
+               ctx->num_short_name_failures++;
                return 0;
+       }
 
        if (status == STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME) {
-               if (dentry->short_name_nbytes == 0)
-                       return 0;
-               ERROR("Can't extract short name when short "
+               ERROR("Can't set short name when short "
                      "names are not enabled on the volume!");
        } else {
                ERROR("Can't set short name on \"%ls\" (status=0x%08"PRIx32")",
@@ -961,7 +1103,7 @@ create_directories(struct list_head *dentry_list,
                 * wait until later to actually set the reparse data.  */
 
                /* If the root dentry is being extracted, it was already done so
-                * it prepare_target().  */
+                * in prepare_target().  */
                if (dentry_is_root(dentry))
                        continue;
 
@@ -972,26 +1114,14 @@ create_directories(struct list_head *dentry_list,
                ret = create_any_empty_ads(dentry, ctx);
                if (ret)
                        return ret;
+
+               ret = report_file_created(&ctx->common);
+               if (ret)
+                       return ret;
        }
        return 0;
 }
 
-/* Gets the number of bytes to allocate for the specified inode.  */
-static void
-inode_get_allocation_size(const struct wim_inode *inode,
-                         LARGE_INTEGER *allocation_size_ret)
-{
-       const struct wim_lookup_table_entry *unnamed_stream;
-
-       /* We just count the unnamed data stream.  */
-
-       unnamed_stream = inode_unnamed_lte_resolved(inode);
-       if (unnamed_stream)
-               allocation_size_ret->QuadPart = unnamed_stream->size;
-       else
-               allocation_size_ret->QuadPart = 0;
-}
-
 /*
  * Creates the nondirectory file named by @dentry.
  *
@@ -1004,17 +1134,12 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry,
                          struct win32_apply_ctx *ctx)
 {
        const struct wim_inode *inode;
-       LARGE_INTEGER allocation_size;
        ULONG attrib;
        NTSTATUS status;
        bool retried = false;
 
        inode = dentry->d_inode;
 
-       /* To increase performance, we will pre-allocate space for the file
-        * data.  */
-       inode_get_allocation_size(inode, &allocation_size);
-
        /* If the file already exists and has FILE_ATTRIBUTE_SYSTEM and/or
         * FILE_ATTRIBUTE_HIDDEN, these must be specified in order to supersede
         * the file.
@@ -1040,7 +1165,7 @@ create_nondirectory_inode(HANDLE *h_ret, const struct wim_dentry *dentry,
        build_extraction_path(dentry, ctx);
 retry:
        status = do_create_file(h_ret, GENERIC_READ | GENERIC_WRITE | DELETE,
-                               &allocation_size, attrib, FILE_SUPERSEDE,
+                               NULL, attrib, FILE_SUPERSEDE,
                                FILE_NON_DIRECTORY_FILE, ctx);
        if (NT_SUCCESS(status)) {
                int ret;
@@ -1240,6 +1365,9 @@ create_nondirectories(struct list_head *dentry_list, struct win32_apply_ctx *ctx
                ret = create_nondirectory(inode, ctx);
                if (ret)
                        return ret;
+               ret = report_file_created(&ctx->common);
+               if (ret)
+                       return ret;
        }
        return 0;
 }
@@ -1282,7 +1410,7 @@ begin_extract_stream_instance(const struct wim_lookup_table_entry *stream,
 {
        const struct wim_inode *inode = dentry->d_inode;
        size_t stream_name_nchars = 0;
-       LARGE_INTEGER allocation_size;
+       FILE_ALLOCATION_INFORMATION alloc_info;
        HANDLE h;
        NTSTATUS status;
 
@@ -1354,7 +1482,9 @@ begin_extract_stream_instance(const struct wim_lookup_table_entry *stream,
                                            &info, ctx->common.progctx);
                        FREE(dentry->_full_path);
                        dentry->_full_path = NULL;
-                       return ret;
+                       if (ret)
+                               return ret;
+                       /* Go on and open the file for normal extraction.  */
                } else {
                        FREE(dentry->_full_path);
                        dentry->_full_path = NULL;
@@ -1367,17 +1497,18 @@ begin_extract_stream_instance(const struct wim_lookup_table_entry *stream,
                }
        }
 
-       /* Too many open handles?  */
-       if (ctx->num_open_handles == MAX_OPEN_HANDLES) {
-               ERROR("Too many open handles!");
+       if (ctx->num_open_handles == MAX_OPEN_STREAMS) {
+               /* XXX: Fix this.  But because of the checks in
+                * extract_stream_list(), this can now only happen on a
+                * filesystem that does not support hard links.  */
+               ERROR("Can't extract data: too many open files!");
                return WIMLIB_ERR_UNSUPPORTED;
        }
 
        /* Open a new handle  */
-       allocation_size.QuadPart = stream->size;
        status = do_create_file(&h,
                                FILE_WRITE_DATA | SYNCHRONIZE,
-                               &allocation_size, 0, FILE_OPEN_IF,
+                               NULL, 0, FILE_OPEN_IF,
                                FILE_SEQUENTIAL_ONLY |
                                        FILE_SYNCHRONOUS_IO_NONALERT,
                                ctx);
@@ -1390,6 +1521,12 @@ begin_extract_stream_instance(const struct wim_lookup_table_entry *stream,
        }
 
        ctx->open_handles[ctx->num_open_handles++] = h;
+
+       /* Allocate space for the data.  */
+       alloc_info.AllocationSize.QuadPart = stream->size;
+       (*func_NtSetInformationFile)(h, &ctx->iosb,
+                                    &alloc_info, sizeof(alloc_info),
+                                    FileAllocationInformation);
        return 0;
 }
 
@@ -1474,7 +1611,13 @@ skip_nt_toplevel_component(const wchar_t *path, size_t path_nchars)
 
 /* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a
  * pointer to the suffix of the path that is device-relative, such as
- * Windows\System32.  */
+ * Windows\System32.
+ *
+ * The path has an explicit length and is not necessarily null terminated.
+ *
+ * If the path just something like \??\e: then the returned pointer will point
+ * just past the colon.  In this case the length of the result will be 0
+ * characters.  */
 static const wchar_t *
 get_device_relative_path(const wchar_t *path, size_t path_nchars)
 {
@@ -1487,7 +1630,7 @@ get_device_relative_path(const wchar_t *path, size_t path_nchars)
 
        path = wmemchr(path, L'\\', (end - path));
        if (!path)
-               return orig_path;
+               return end;
        do {
                path++;
        } while (path != end && *path == L'\\');
@@ -1536,14 +1679,18 @@ try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx)
 
        target_ntpath_nchars = ctx->target_ntpath.Length / sizeof(wchar_t);
 
-       fixed_subst_name_nchars = target_ntpath_nchars + 1 + relpath_nchars;
+       fixed_subst_name_nchars = target_ntpath_nchars;
+       if (relpath_nchars)
+               fixed_subst_name_nchars += 1 + relpath_nchars;
        wchar_t fixed_subst_name[fixed_subst_name_nchars];
 
        wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer,
                target_ntpath_nchars);
-       fixed_subst_name[target_ntpath_nchars] = L'\\';
-       wmemcpy(&fixed_subst_name[target_ntpath_nchars + 1],
-               relpath, relpath_nchars);
+       if (relpath_nchars) {
+               fixed_subst_name[target_ntpath_nchars] = L'\\';
+               wmemcpy(&fixed_subst_name[target_ntpath_nchars + 1],
+                       relpath, relpath_nchars);
+       }
        /* Doesn't need to be null-terminated.  */
 
        /* Print name should be Win32, but not all NT names can even be
@@ -1645,8 +1792,7 @@ extract_encrypted_file(const struct wim_dentry *dentry,
 
 /* Called when starting to read a stream for extraction on Windows  */
 static int
-begin_extract_stream(struct wim_lookup_table_entry *stream,
-                    u32 flags, void *_ctx)
+begin_extract_stream(struct wim_lookup_table_entry *stream, void *_ctx)
 {
        struct win32_apply_ctx *ctx = _ctx;
        const struct stream_owner *owners = stream_owners(stream);
@@ -1690,11 +1836,6 @@ begin_extract_stream(struct wim_lookup_table_entry *stream,
                }
        }
 
-       if (unlikely(ctx->num_open_handles == 0 && ctx->data_buffer_ptr == NULL)) {
-               /* The data of this stream isn't actually needed!
-                * (This can happen in WIMBoot mode.)  */
-               return BEGIN_STREAM_STATUS_SKIP_STREAM;
-       }
        return 0;
 
 fail:
@@ -1761,7 +1902,10 @@ end_extract_stream(struct wim_lookup_table_entry *stream, int status, void *_ctx
                        dentry = list_first_entry(&ctx->reparse_dentries,
                                                  struct wim_dentry, tmp_list);
                        build_extraction_path(dentry, ctx);
-                       ERROR("Invalid reparse point", current_path(ctx));
+                       ERROR("Reparse data of \"%ls\" has size "
+                             "%"PRIu64" bytes (exceeds %u bytes)",
+                             current_path(ctx), stream->size,
+                             REPARSE_DATA_MAX_SIZE);
                        return WIMLIB_ERR_INVALID_REPARSE_DATA;
                }
                /* In the WIM format, reparse streams are just the reparse data
@@ -1987,6 +2131,9 @@ apply_metadata(struct list_head *dentry_list, struct win32_apply_ctx *ctx)
                ret = apply_metadata_to_file(dentry, ctx);
                if (ret)
                        return ret;
+               ret = report_file_metadata_applied(&ctx->common);
+               if (ret)
+                       return ret;
        }
        return 0;
 }
@@ -1998,23 +2145,30 @@ static void
 do_warnings(const struct win32_apply_ctx *ctx)
 {
        if (ctx->partial_security_descriptors == 0 &&
-           ctx->no_security_descriptors == 0)
+           ctx->no_security_descriptors == 0 &&
+           ctx->num_short_name_failures == 0)
                return;
 
        WARNING("Extraction to \"%ls\" complete, but with one or more warnings:",
                ctx->common.target);
-       if (ctx->partial_security_descriptors != 0) {
+       if (ctx->num_short_name_failures) {
+               WARNING("- Could not set short names on %lu files or directories",
+                       ctx->num_short_name_failures);
+       }
+       if (ctx->partial_security_descriptors) {
                WARNING("- Could only partially set the security descriptor\n"
                        "            on %lu files or directories.",
                        ctx->partial_security_descriptors);
        }
-       if (ctx->no_security_descriptors != 0) {
+       if (ctx->no_security_descriptors) {
                WARNING("- Could not set security descriptor at all\n"
                        "            on %lu files or directories.",
                        ctx->no_security_descriptors);
        }
-       WARNING("To fully restore all security descriptors, run the program\n"
-               "          with Administrator rights.");
+       if (ctx->partial_security_descriptors || ctx->no_security_descriptors) {
+               WARNING("To fully restore all security descriptors, run the program\n"
+                       "          with Administrator rights.");
+       }
 }
 
 /* Extract files from a WIM image to a directory on Windows  */
@@ -2028,12 +2182,14 @@ win32_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
        if (ret)
                goto out;
 
-       if (ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT) {
+       if (unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) {
                ret = start_wimboot_extraction(ctx);
                if (ret)
                        goto out;
        }
 
+       reset_file_progress(&ctx->common);
+
        ret = create_directories(dentry_list, ctx);
        if (ret)
                goto out;
@@ -2054,10 +2210,18 @@ win32_extract(struct list_head *dentry_list, struct apply_ctx *_ctx)
        if (ret)
                goto out;
 
+       reset_file_progress(&ctx->common);
+
        ret = apply_metadata(dentry_list, ctx);
        if (ret)
                goto out;
 
+       if (unlikely(ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_WIMBOOT)) {
+               ret = end_wimboot_extraction(ctx);
+               if (ret)
+                       goto out;
+       }
+
        do_warnings(ctx);
 out:
        if (ctx->h_target)
@@ -2081,3 +2245,5 @@ const struct apply_operations win32_apply_ops = {
        .extract                = win32_extract,
        .context_size           = sizeof(struct win32_apply_ctx),
 };
+
+#endif /* __WIN32__ */