]> wimlib.net Git - wimlib/blobdiff - src/win32_apply.c
win32_apply.c: Do not request DELETE access on extraction root
[wimlib] / src / win32_apply.c
index 90051f425041cb85e43c04075d3a2de92f9131f3..7148ca814151c1106ee425b1a57b9ce2b4651984 100644 (file)
@@ -1,12 +1,44 @@
+/*
+ * win32_apply.c - Windows-specific code for applying files from a WIM image.
+ */
+
+/*
+ * Copyright (C) 2013 Eric Biggers
+ *
+ * This file is part of wimlib, a library for working with WIM files.
+ *
+ * wimlib is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wimlib; if not, see http://www.gnu.org/licenses/.
+ */
+
 #ifdef __WIN32__
 
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
 #include <aclapi.h> /* for SetSecurityInfo() */
 
-#include "win32_common.h"
-#include "wimlib_internal.h"
-#include "dentry.h"
-#include "lookup_table.h"
-#include "endianness.h"
+#include "wimlib/win32_common.h"
+
+#include "wimlib/apply.h"
+#include "wimlib/dentry.h"
+#include "wimlib/endianness.h"
+#include "wimlib/error.h"
+#include "wimlib/lookup_table.h"
+#include "wimlib/metadata.h"
+#include "wimlib/reparse.h"
+#include "wimlib/security.h"
 
 #define MAX_CREATE_HARD_LINK_WARNINGS 5
 #define MAX_CREATE_SOFT_LINK_WARNINGS 5
@@ -25,6 +57,7 @@ L"If you are not running this program as the administrator, you may\n"
 
 static int
 win32_extract_try_rpfix(u8 *rpbuf,
+                       u16 *rpbuflen_p,
                        const wchar_t *extract_root_realpath,
                        unsigned extract_root_realpath_nchars)
 {
@@ -42,8 +75,7 @@ win32_extract_try_rpfix(u8 *rpbuf,
        size_t new_print_name_nchars;
        utf16lechar *p;
 
-       ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
-                                &rpdata);
+       ret = parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata);
        if (ret)
                return ret;
 
@@ -65,7 +97,7 @@ win32_extract_try_rpfix(u8 *rpbuf,
        stripped_nchars = ret;
        target = rpdata.substitute_name;
        target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar);
-       stripped_target = target + 6;
+       stripped_target = target + stripped_nchars;
        stripped_target_nchars = target_nchars - stripped_nchars;
 
        new_target = alloca((6 + extract_root_realpath_nchars +
@@ -74,8 +106,7 @@ win32_extract_try_rpfix(u8 *rpbuf,
        p = new_target;
        if (stripped_nchars == 6) {
                /* Include \??\ prefix if it was present before */
-               wmemcpy(p, L"\\??\\", 4);
-               p += 4;
+               p = wmempcpy(p, L"\\??\\", 4);
        }
 
        /* Print name excludes the \??\ if present. */
@@ -87,12 +118,10 @@ win32_extract_try_rpfix(u8 *rpbuf,
                *p++ = extract_root_realpath[1];
        }
        /* Copy the rest of the extract root */
-       wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
-       p += extract_root_realpath_nchars - 2;
+       p = wmempcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2);
 
        /* Append the stripped target */
-       wmemcpy(p, stripped_target, stripped_target_nchars);
-       p += stripped_target_nchars;
+       p = wmempcpy(p, stripped_target, stripped_target_nchars);
        new_target_nchars = p - new_target;
        new_print_name_nchars = p - new_print_name;
 
@@ -106,7 +135,7 @@ win32_extract_try_rpfix(u8 *rpbuf,
        rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar);
        rpdata.print_name = new_print_name;
        rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar);
-       return make_reparse_buffer(&rpdata, rpbuf);
+       return make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p);
 }
 
 /* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on
@@ -119,12 +148,13 @@ win32_set_reparse_data(HANDLE h,
                       struct apply_args *args)
 {
        int ret;
-       u8 rpbuf[REPARSE_POINT_MAX_SIZE];
+       u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8);
        DWORD bytesReturned;
+       u16 rpbuflen;
 
        DEBUG("Setting reparse data on \"%ls\"", path);
 
-       ret = wim_inode_get_reparse_data(inode, rpbuf);
+       ret = wim_inode_get_reparse_data(inode, rpbuf, &rpbuflen);
        if (ret)
                return ret;
 
@@ -134,6 +164,7 @@ win32_set_reparse_data(HANDLE h,
            !inode->i_not_rpfixed)
        {
                ret = win32_extract_try_rpfix(rpbuf,
+                                             &rpbuflen,
                                              args->target_realpath,
                                              args->target_realpath_len);
                if (ret)
@@ -162,7 +193,7 @@ win32_set_reparse_data(HANDLE h,
         *  "Not used with this operation; set to NULL."
         */
        if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf,
-                            8 + le16_to_cpu(*(u16*)(rpbuf + 4)),
+                            rpbuflen,
                             NULL, 0,
                             &bytesReturned /* lpBytesReturned */,
                             NULL /* lpOverlapped */))
@@ -448,23 +479,6 @@ do_win32_extract_encrypted_stream(const wchar_t *path,
        return ret;
 }
 
-static bool
-path_is_root_of_drive(const wchar_t *path)
-{
-       if (!*path)
-               return false;
-
-       if (*path != L'/' && *path != L'\\') {
-               if (*(path + 1) == L':')
-                       path += 2;
-               else
-                       return false;
-       }
-       while (*path == L'/' || *path == L'\\')
-               path++;
-       return (*path == L'\0');
-}
-
 static inline DWORD
 win32_mask_attributes(DWORD i_attributes)
 {
@@ -567,18 +581,19 @@ win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
        /* Directories must be created with CreateDirectoryW().  Then the call
         * to CreateFileW() will merely open the directory that was already
         * created rather than creating a new file. */
-       if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY &&
-           !path_is_root_of_drive(path)) {
-               if (!CreateDirectoryW(path, NULL)) {
-                       err = GetLastError();
-                       if (err != ERROR_ALREADY_EXISTS) {
-                               ERROR("Failed to create directory \"%ls\"",
-                                     path);
-                               win32_error(err);
-                               return WIMLIB_ERR_MKDIR;
+       if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
+               if (!win32_path_is_root_of_drive(path)) {
+                       if (!CreateDirectoryW(path, NULL)) {
+                               err = GetLastError();
+                               if (err != ERROR_ALREADY_EXISTS) {
+                                       ERROR("Failed to create directory \"%ls\"",
+                                             path);
+                                       win32_error(err);
+                                       return WIMLIB_ERR_MKDIR;
+                               }
                        }
+                       DEBUG("Created directory \"%ls\"", path);
                }
-               DEBUG("Created directory \"%ls\"", path);
                *creationDisposition_ret = OPEN_EXISTING;
        }
        if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED &&
@@ -620,7 +635,7 @@ win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
         * directory, so treat that as a special case and do not set attributes.
         * */
        if (*creationDisposition_ret == OPEN_EXISTING &&
-           !path_is_root_of_drive(path))
+           !win32_path_is_root_of_drive(path))
        {
                if (!SetFileAttributesW(path,
                                        win32_mask_attributes(inode->i_attributes)))
@@ -645,7 +660,6 @@ win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry,
 {
        int ret = 0;
        const struct wim_inode *inode = dentry->d_inode;
-       const wchar_t *short_name;
        if (stream_name_utf16 == NULL) {
                /* Unnamed stream. */
 
@@ -700,18 +714,10 @@ win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry,
                                return ret;
                }
 
-               if (dentry_has_short_name(dentry))
-                       short_name = dentry->short_name;
-               else
-                       short_name = L"";
-               /* Set short name */
-               if (!SetFileShortNameW(h, short_name)) {
-               #if 0
-                       DWORD err = GetLastError();
-                       ERROR("Could not set short name on \"%ls\"", stream_path);
-                       win32_error(err);
-               #endif
-               }
+               if (dentry_has_short_name(dentry) && !dentry->dos_name_invalid)
+                       SetFileShortNameW(h, dentry->short_name);
+               else if (running_on_windows_7_or_later())
+                       SetFileShortNameW(h, L"");
        } else {
                /* Extract the data for a named data stream. */
                if (lte != NULL) {
@@ -787,12 +793,12 @@ win32_extract_stream(const struct wim_dentry *dentry,
 
        if (stream_name_utf16) {
                /* Named stream.  Create a buffer that contains the UTF-16LE
-                * string [./]path:stream_name_utf16.  This is needed to
+                * string [.\]path:stream_name_utf16.  This is needed to
                 * create and open the stream using CreateFileW().  I'm not
                 * aware of any other APIs to do this.  Note: the '$DATA' suffix
-                * seems to be unneeded.  Additional note: a "./" prefix needs
-                * to be added when the path is not absolute to avoid ambiguity
-                * with drive letters. */
+                * seems to be unneeded.  Additional note: a ".\" prefix needs
+                * to be added when the path is a 1-character long relative path
+                * to avoid ambiguity with drive letters. */
                size_t stream_path_nchars;
                size_t path_nchars;
                size_t stream_name_nchars;
@@ -801,12 +807,10 @@ win32_extract_stream(const struct wim_dentry *dentry,
                path_nchars = wcslen(path);
                stream_name_nchars = wcslen(stream_name_utf16);
                stream_path_nchars = path_nchars + 1 + stream_name_nchars;
-               if (path[0] != cpu_to_le16(L'\0') &&
-                   path[0] != cpu_to_le16(L'/') &&
-                   path[0] != cpu_to_le16(L'\\') &&
-                   path[1] != cpu_to_le16(L':'))
-               {
-                       prefix = L"./";
+               if (path_nchars == 1 && !is_any_path_separator(path[0])) {
+                       static const wchar_t _prefix[] =
+                               {L'.', OS_PREFERRED_PATH_SEPARATOR, L'\0'};
+                       prefix = _prefix;
                        stream_path_nchars += 2;
                } else {
                        prefix = L"";
@@ -827,9 +831,24 @@ win32_extract_stream(const struct wim_dentry *dentry,
        }
 
        DEBUG("Opening \"%ls\"", stream_path);
-       /* DELETE access is needed for SetFileShortNameW(), for some reason. */
-       requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE |
+       requestedAccess = GENERIC_READ | GENERIC_WRITE |
                          ACCESS_SYSTEM_SECURITY;
+       /* DELETE access is needed for SetFileShortNameW(), for some reason.
+        * But don't request it for the extraction root, for the following
+        * reasons:
+        *
+        * - Requesting DELETE access on the extraction root will cause a
+        *   sharing violation if the extraction root is the current working
+        *   directory (".").
+        * - The extraction root may be extracted to a different name than given
+        *   in the WIM file, in which case the DOS name, if given, would not be
+        *   meaningful.
+        * - For full-image extractions, the root dentry is supposed to be
+        *   unnamed anyway.
+        * - Microsoft's ImageX does not extract the root directory.
+        */
+       if (dentry != args->extract_root)
+               requestedAccess |= DELETE;
 try_open_again:
        /* Open the stream to be extracted.  Depending on what we have set
         * creationDisposition to, we may be creating this for the first time,
@@ -845,7 +864,7 @@ try_open_again:
        if (h == INVALID_HANDLE_VALUE) {
                err = GetLastError();
                if (err == ERROR_ACCESS_DENIED &&
-                   path_is_root_of_drive(stream_path))
+                   win32_path_is_root_of_drive(stream_path))
                {
                        ret = 0;
                        goto out;
@@ -859,7 +878,11 @@ try_open_again:
                        requestedAccess &= ~ACCESS_SYSTEM_SECURITY;
                        goto try_open_again;
                }
-               if (err == ERROR_SHARING_VIOLATION) {
+               if (err == ERROR_SHARING_VIOLATION &&
+                   (inode->i_attributes & (FILE_ATTRIBUTE_ENCRYPTED |
+                                           FILE_ATTRIBUTE_DIRECTORY)) ==
+                       (FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_DIRECTORY))
+               {
                        if (remaining_sharing_violations) {
                                --remaining_sharing_violations;
                                /* This can happen when restoring encrypted directories
@@ -871,13 +894,12 @@ try_open_again:
                        } else {
                                ERROR("Too many sharing violations; giving up...");
                        }
-               } else {
-                       if (creationDisposition == OPEN_EXISTING)
-                               ERROR("Failed to open \"%ls\"", stream_path);
-                       else
-                               ERROR("Failed to create \"%ls\"", stream_path);
-                       win32_error(err);
                }
+               if (creationDisposition == OPEN_EXISTING)
+                       ERROR("Failed to open \"%ls\"", stream_path);
+               else
+                       ERROR("Failed to create \"%ls\"", stream_path);
+               win32_error(err);
                ret = WIMLIB_ERR_OPEN;
                goto fail;
        }
@@ -1019,37 +1041,83 @@ out:
        return ret;
 }
 
+static int
+dentry_clear_inode_visited(struct wim_dentry *dentry, void *_ignore)
+{
+       dentry->d_inode->i_visited = 0;
+       return 0;
+}
+
+static int
+dentry_get_features(struct wim_dentry *dentry, void *_features_p)
+{
+       DWORD features = 0;
+       DWORD *features_p = _features_p;
+       struct wim_inode *inode = dentry->d_inode;
+
+       if (inode->i_visited) {
+               features |= FILE_SUPPORTS_HARD_LINKS;
+       } else {
+               inode->i_visited = 1;
+               if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE)
+                       features |= FILE_SUPPORTS_SPARSE_FILES;
+               if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)
+                       features |= FILE_SUPPORTS_REPARSE_POINTS;
+               for (unsigned i = 0; i < inode->i_num_ads; i++)
+                       if (inode->i_ads_entries[i].stream_name_nbytes)
+                               features |= FILE_NAMED_STREAMS;
+               if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)
+                       features |= FILE_SUPPORTS_ENCRYPTION;
+               if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)
+                       features |= FILE_FILE_COMPRESSION;
+               if (inode->i_security_id != -1)
+                       features |= FILE_PERSISTENT_ACLS;
+       }
+       *features_p |= features;
+       return 0;
+}
+
 /* If not done already, load the supported feature flags for the volume onto
  * which the image is being extracted, and warn the user about any missing
  * features that could be important. */
 static int
-win32_check_vol_flags(const wchar_t *output_path, struct apply_args *args)
+win32_check_vol_flags(const wchar_t *output_path,
+                     struct wim_dentry *root, struct apply_args *args)
 {
+       DWORD dentry_features = 0;
+       DWORD missing_features;
+
        if (args->have_vol_flags)
                return 0;
 
+       for_dentry_in_tree(root, dentry_clear_inode_visited, NULL);
+       for_dentry_in_tree(root, dentry_get_features, &dentry_features);
+
        win32_get_vol_flags(output_path, &args->vol_flags);
        args->have_vol_flags = true;
+
+       missing_features = dentry_features & ~args->vol_flags;
+
        /* Warn the user about data that may not be extracted. */
-       if (!(args->vol_flags & FILE_SUPPORTS_SPARSE_FILES))
+       if (missing_features & FILE_SUPPORTS_SPARSE_FILES)
                WARNING("Volume does not support sparse files!\n"
                        "          Sparse files will be extracted as non-sparse.");
-       if (!(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
+       if (missing_features & FILE_SUPPORTS_REPARSE_POINTS)
                WARNING("Volume does not support reparse points!\n"
                        "          Reparse point data will not be extracted.");
-       if (!(args->vol_flags & FILE_NAMED_STREAMS)) {
+       if (missing_features & FILE_NAMED_STREAMS) {
                WARNING("Volume does not support named data streams!\n"
                        "          Named data streams will not be extracted.");
        }
-       if (!(args->vol_flags & FILE_SUPPORTS_ENCRYPTION)) {
+       if (missing_features & FILE_SUPPORTS_ENCRYPTION) {
                WARNING("Volume does not support encryption!\n"
                        "          Encrypted files will be extracted as raw data.");
        }
-       if (!(args->vol_flags & FILE_FILE_COMPRESSION)) {
+       if (missing_features & FILE_FILE_COMPRESSION) {
                WARNING("Volume does not support transparent compression!\n"
                        "          Compressed files will be extracted as non-compressed.");
        }
-       if (!(args->vol_flags & FILE_PERSISTENT_ACLS)) {
+       if (missing_features & FILE_PERSISTENT_ACLS) {
                if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) {
                        ERROR("Strict ACLs requested, but the volume does not "
                              "support ACLs!");
@@ -1059,6 +1127,12 @@ win32_check_vol_flags(const wchar_t *output_path, struct apply_args *args)
                                "          File permissions will not be extracted.");
                }
        }
+       if (running_on_windows_7_or_later() &&
+           (missing_features & FILE_SUPPORTS_HARD_LINKS))
+       {
+               WARNING("Volume does not support hard links!\n"
+                       "          Hard links will be extracted as duplicate files.");
+       }
        return 0;
 }
 
@@ -1089,12 +1163,18 @@ win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
        DWORD err;
 
        /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS),
-        * but it's only available on Windows 7 and later.  So no use
-        * even checking it, really.  Instead, CreateHardLinkW() will
-        * apparently return ERROR_INVALID_FUNCTION if the volume does
-        * not support hard links. */
+        * but it's only available on Windows 7 and later.
+        *
+        * Otherwise, CreateHardLinkW() will apparently return
+        * ERROR_INVALID_FUNCTION if the volume does not support hard links. */
+
        DEBUG("Creating hard link \"%ls => %ls\"",
              output_path, inode->i_extracted_file);
+
+       if (running_on_windows_7_or_later() &&
+           !(args->vol_flags & FILE_SUPPORTS_HARD_LINKS))
+               goto hard_links_unsupported;
+
        if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL))
                return 0;
 
@@ -1104,19 +1184,24 @@ win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode,
                      output_path, inode->i_extracted_file);
                win32_error(err);
                return WIMLIB_ERR_LINK;
-       } else {
-               args->num_hard_links_failed++;
-               if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
-                       WARNING("Can't create hard link \"%ls => %ls\":\n"
+       }
+hard_links_unsupported:
+       args->num_hard_links_failed++;
+       if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) {
+               if (running_on_windows_7_or_later())
+               {
+                       WARNING("Extracting duplicate copy of \"%ls\" "
+                               "rather than hard link", output_path);
+               } else {
+                       WARNING("Can't create hard link \"%ls\" => \"%ls\":\n"
                                "          Volume does not support hard links!\n"
                                "          Falling back to extracting a copy of the file.",
                                output_path, inode->i_extracted_file);
                }
-               if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) {
-                       WARNING("Suppressing further hard linking warnings...");
-               }
-               return -1;
        }
+       if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS)
+               WARNING("Suppressing further hard linking warnings...");
+       return -1;
 }
 
 /* Extract a file, directory, reparse point, or hard link to an
@@ -1130,7 +1215,7 @@ win32_do_apply_dentry(const wchar_t *output_path,
        int ret;
        struct wim_inode *inode = dentry->d_inode;
 
-       ret = win32_check_vol_flags(output_path, args);
+       ret = win32_check_vol_flags(output_path, dentry, args);
        if (ret)
                return ret;
        if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
@@ -1149,9 +1234,8 @@ win32_do_apply_dentry(const wchar_t *output_path,
        if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
            !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
        {
-               WARNING("Skipping extraction of reparse point \"%ls\":\n"
-                       "          Not supported by destination filesystem",
-                       output_path);
+               WARNING("Not extracting reparse point \"%ls\"", output_path);
+               dentry->not_extracted = 1;
        } else {
                /* Create the file, directory, or reparse point, and extract the
                 * data streams. */
@@ -1175,7 +1259,7 @@ win32_do_apply_dentry(const wchar_t *output_path,
                        /* Save extracted path for a later call to
                         * CreateHardLinkW() if this inode has multiple links.
                         * */
-                       inode->i_extracted_file = WSTRDUP(output_path);
+                       inode->i_extracted_file = WCSDUP(output_path);
                        if (!inode->i_extracted_file)
                                return WIMLIB_ERR_NOMEM;
                }
@@ -1194,17 +1278,10 @@ win32_do_apply_dentry_timestamps(const wchar_t *path,
        HANDLE h;
        const struct wim_inode *inode = dentry->d_inode;
 
-       if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
-           !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS))
-       {
-               /* Skip reparse points not extracted */
-               return 0;
-       }
-
        /* Windows doesn't let you change the timestamps of the root directory
         * (at least on FAT, which is dumb but expected since FAT doesn't store
         * any metadata about the root directory...) */
-       if (path_is_root_of_drive(path))
+       if (win32_path_is_root_of_drive(path))
                return 0;
 
        DEBUG("Opening \"%ls\" to set timestamps", path);