Win32: Fix drive root detection with \\?\-style paths
authorEric Biggers <ebiggers3@gmail.com>
Tue, 21 May 2013 16:13:37 +0000 (11:13 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Tue, 21 May 2013 16:15:05 +0000 (11:15 -0500)
include/wimlib/win32_common.h
src/win32_apply.c
src/win32_capture.c
src/win32_common.c

index ebd0564..d84b0ff 100644 (file)
@@ -23,6 +23,9 @@ win32_error(DWORD err_code)
 extern void
 set_errno_from_GetLastError(void);
 
+extern bool
+win32_path_is_root_of_drive(const wchar_t *path);
+
 extern int
 win32_error_to_errno(DWORD err_code);
 
index f022e1d..1737fa1 100644 (file)
@@ -479,26 +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 == L'\0')
-               return false;
-
-       if (!wcsncmp(path, L"\\\\?\\", 4))
-               path += 4;
-
-       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)
 {
@@ -602,7 +582,7 @@ win32_begin_extract_unnamed_stream(const struct wim_inode *inode,
         * to CreateFileW() will merely open the directory that was already
         * created rather than creating a new file. */
        if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
-               if (!path_is_root_of_drive(path)) {
+               if (!win32_path_is_root_of_drive(path)) {
                        if (!CreateDirectoryW(path, NULL)) {
                                err = GetLastError();
                                if (err != ERROR_ALREADY_EXISTS) {
@@ -655,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)))
@@ -813,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;
@@ -827,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"";
@@ -871,7 +849,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;
@@ -1285,7 +1263,7 @@ win32_do_apply_dentry_timestamps(const wchar_t *path,
        /* 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);
index 43c09e9..afb38e3 100644 (file)
@@ -757,10 +757,7 @@ win32_capture_stream(const wchar_t *path,
        if (is_named_stream) {
                spath_nchars += 1 + stream_name_nchars;
                colonchar = L":";
-               if (path_num_chars == 1 &&
-                   path[0] != L'/' &&
-                   path[0] != L'\\')
-               {
+               if (path_num_chars == 1 && !is_any_path_separator(path[0])) {
                        spath_nchars += 2;
                        static const wchar_t _relpath_prefix[] =
                                {L'.', OS_PREFERRED_PATH_SEPARATOR, L'\0'};
index e0d39ac..e5d6a38 100644 (file)
@@ -33,6 +33,7 @@
 
 #include "wimlib/assert.h"
 #include "wimlib/error.h"
+#include "wimlib/util.h"
 
 #ifdef ENABLE_ERROR_MESSAGES
 void
@@ -329,6 +330,58 @@ set_errno_from_GetLastError(void)
        errno = win32_error_to_errno(GetLastError());
 }
 
+/* Given a Windows-style path, return the number of characters of the prefix
+ * that specify the path to the root directory of a drive, or return 0 if the
+ * drive is relative (or at least on the current drive, in the case of
+ * absolute-but-not-really-absolute paths like \Windows\System32) */
+static size_t
+win32_path_drive_spec_len(const wchar_t *path)
+{
+       size_t n = 0;
+
+       if (!wcsncmp(path, L"\\\\?\\", 4)) {
+               /* \\?\-prefixed path.  Check for following drive letter and
+                * path separator. */
+               if (path[4] != L'\0' && path[5] == L':' &&
+                   is_any_path_separator(path[6]))
+                       n = 7;
+       } else {
+               /* Not a \\?\-prefixed path.  Check for an initial drive letter
+                * and path separator. */
+               if (path[0] != L'\0' && path[1] == L':' &&
+                   is_any_path_separator(path[2]))
+                       n = 3;
+       }
+       /* Include any additional path separators.*/
+       if (n > 0)
+               while (is_any_path_separator(path[n]))
+                       n++;
+       return n;
+}
+
+bool
+win32_path_is_root_of_drive(const wchar_t *path)
+{
+       size_t drive_spec_len;
+
+       /* Explicit drive letter and path separator? */
+       drive_spec_len = win32_path_drive_spec_len(path);
+       if (drive_spec_len > 0 && path[drive_spec_len] == L'\0')
+               return true;
+
+       /* All path separators? */
+       for (const wchar_t *p = path; *p != L'\0'; p++)
+               if (!is_any_path_separator(*p))
+                       return false;
+       return true;
+
+       /* XXX This function does not handle paths like "c:" where the working
+        * directory on "c:" is actually "c:\", or weird paths like "\.".  But
+        * currently the capture and apply code always prefixes the paths with
+        * \\?\ anyway so this is irrelevant... */
+}
+
+
 /* Given a path, which may not yet exist, get a set of flags that describe the
  * features of the volume the path is on. */
 int
@@ -337,20 +390,27 @@ win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret)
        wchar_t *volume;
        BOOL bret;
        DWORD vol_flags;
+       size_t drive_spec_len;
 
-       if (path[0] != L'\0' && path[0] != L'\\' &&
-           path[0] != L'/' && path[1] == L':')
-       {
-               /* Path starts with a drive letter; use it. */
-               volume = alloca(4 * sizeof(wchar_t));
-               volume[0] = path[0];
-               volume[1] = path[1];
-               volume[2] = L'\\';
-               volume[3] = L'\0';
-       } else {
+       drive_spec_len = win32_path_drive_spec_len(path);
+
+       if (drive_spec_len == 0)
+               if (path[0] != L'\0' && path[1] == L':') /* Drive-relative path? */
+                       drive_spec_len = 2;
+
+       if (drive_spec_len == 0) {
                /* Path does not start with a drive letter; use the volume of
                 * the current working directory. */
                volume = NULL;
+       } else {
+               /* Path starts with a drive letter (or \\?\ followed by a drive
+                * letter); use it. */
+               volume = alloca((drive_spec_len + 2) * sizeof(wchar_t));
+               wmemcpy(volume, path, drive_spec_len);
+               /* Add trailing backslash in case this was a drive-relative
+                * path. */
+               volume[drive_spec_len] = L'\\';
+               volume[drive_spec_len + 1] = L'\0';
        }
        bret = GetVolumeInformationW(volume, /* lpRootPathName */
                                     NULL,  /* lpVolumeNameBuffer */