From 8d45b4691c81d2d1cde7a1b2f828dedd55315b3f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 11 Oct 2014 14:34:24 -0400 Subject: [PATCH 1/2] p_lstat win32: don't canonicalize volume mounts A reparse point that is an IO_REPARSE_TAG_MOUNT_POINT could be a junction or an actual filesystem mount point. (Who knew?) If it's the latter, its reparse point will report the actual volume information \??\Volume{GUID}\ and we should not attempt to dereference that further, instead readlink should report EINVAL since it's not a symlink / junction and its original path was canonical. Yes, really. --- src/win32/posix_w32.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 0023f95ff..7b4555719 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -131,6 +131,11 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft) return (time_t)winTime; } +static bool path_is_volume(wchar_t *target, size_t target_len) +{ + return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0); +} + /* On success, returns the length, in characters, of the path stored in dest. * On failure, returns a negative value. */ static int readlink_w( @@ -177,7 +182,13 @@ static int readlink_w( goto on_error; } - if (target_len) { + if (path_is_volume(target, target_len)) { + /* This path is a reparse point that represents another volume mounted + * at this location, it is not a symbolic link our input was canonical. + */ + errno = EINVAL; + error = -1; + } else if (target_len) { /* The path may need to have a prefix removed. */ target_len = git_win32__canonicalize_path(target, target_len); From 969b6a4710451b3e905757093dce4977cd596635 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 11 Oct 2014 11:23:34 -0400 Subject: [PATCH 2/2] is_empty_dir (wi32): cope with empty mount points FindFirstFile will fail with INVALID_HANDLE_VALUE if there are no children to the given path, which can happen if the given path is a file (and obviously has no children) or if the given path is an empty mount point. (Most directories have at least directory entries '.' and '..', but ridiculously another volume mounted in another drive letter's path space do not, and thus have nothing to enumerate.) If FindFirstFile fails, check if this is a directory-like thing (a mount point). --- src/path.c | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/path.c b/src/path.c index 21b6e18d6..143c564e8 100644 --- a/src/path.c +++ b/src/path.c @@ -515,23 +515,33 @@ bool git_path_is_empty_dir(const char *path) WIN32_FIND_DATAW findData; HANDLE hFind = FindFirstFileW(filter_w, &findData); + /* FindFirstFile will fail if there are no children to the given + * path, which can happen if the given path is a file (and obviously + * has no children) or if the given path is an empty mount point. + * (Most directories have at least directory entries '.' and '..', + * but ridiculously another volume mounted in another drive letter's + * path space do not, and thus have nothing to enumerate.) If + * FindFirstFile fails, check if this is a directory-like thing + * (a mount point). + */ + if (hFind == INVALID_HANDLE_VALUE) + return git_path_isdir(path); + /* If the find handle was created successfully, then it's a directory */ - if (hFind != INVALID_HANDLE_VALUE) { - empty = true; + empty = true; - do { - /* Allow the enumeration to return . and .. and still be considered - * empty. In the special case of drive roots (i.e. C:\) where . and - * .. do not occur, we can still consider the path to be an empty - * directory if there's nothing there. */ - if (!git_path_is_dot_or_dotdotW(findData.cFileName)) { - empty = false; - break; - } - } while (FindNextFileW(hFind, &findData)); + do { + /* Allow the enumeration to return . and .. and still be considered + * empty. In the special case of drive roots (i.e. C:\) where . and + * .. do not occur, we can still consider the path to be an empty + * directory if there's nothing there. */ + if (!git_path_is_dot_or_dotdotW(findData.cFileName)) { + empty = false; + break; + } + } while (FindNextFileW(hFind, &findData)); - FindClose(hFind); - } + FindClose(hFind); } return empty;