From 2200ddb2ab85b390daa140de5338ac9f023d3683 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 21 May 2013 11:13:37 -0500 Subject: [PATCH] Win32: Fix drive root detection with \\?\-style paths --- include/wimlib/win32_common.h | 3 ++ src/win32_apply.c | 46 ++++++-------------- src/win32_capture.c | 5 +-- src/win32_common.c | 80 ++++++++++++++++++++++++++++++----- 4 files changed, 86 insertions(+), 48 deletions(-) diff --git a/include/wimlib/win32_common.h b/include/wimlib/win32_common.h index ebd0564d..d84b0ffd 100644 --- a/include/wimlib/win32_common.h +++ b/include/wimlib/win32_common.h @@ -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); diff --git a/src/win32_apply.c b/src/win32_apply.c index f022e1d0..1737fa1b 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -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); diff --git a/src/win32_capture.c b/src/win32_capture.c index 43c09e9e..afb38e30 100644 --- a/src/win32_capture.c +++ b/src/win32_capture.c @@ -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'}; diff --git a/src/win32_common.c b/src/win32_common.c index e0d39ac3..e5d6a386 100644 --- a/src/win32_common.c +++ b/src/win32_common.c @@ -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 */ -- 2.43.0