diff options
Diffstat (limited to 'src/win32/path_w32.c')
-rw-r--r-- | src/win32/path_w32.c | 511 |
1 files changed, 0 insertions, 511 deletions
diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c deleted file mode 100644 index e6640c85e..000000000 --- a/src/win32/path_w32.c +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#include "path_w32.h" - -#include "path.h" -#include "utf-conv.h" -#include "posix.h" -#include "reparse.h" -#include "dir.h" - -#define PATH__NT_NAMESPACE L"\\\\?\\" -#define PATH__NT_NAMESPACE_LEN 4 - -#define PATH__ABSOLUTE_LEN 3 - -#define path__is_nt_namespace(p) \ - (((p)[0] == '\\' && (p)[1] == '\\' && (p)[2] == '?' && (p)[3] == '\\') || \ - ((p)[0] == '/' && (p)[1] == '/' && (p)[2] == '?' && (p)[3] == '/')) - -#define path__is_unc(p) \ - (((p)[0] == '\\' && (p)[1] == '\\') || ((p)[0] == '/' && (p)[1] == '/')) - -#define path__startswith_slash(p) \ - ((p)[0] == '\\' || (p)[0] == '/') - -GIT_INLINE(int) path__cwd(wchar_t *path, int size) -{ - int len; - - if ((len = GetCurrentDirectoryW(size, path)) == 0) { - errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : ENOENT; - return -1; - } else if (len > size) { - errno = ENAMETOOLONG; - return -1; - } - - /* The Win32 APIs may return "\\?\" once you've used it first. - * But it may not. What a gloriously predictible API! - */ - if (wcsncmp(path, PATH__NT_NAMESPACE, PATH__NT_NAMESPACE_LEN)) - return len; - - len -= PATH__NT_NAMESPACE_LEN; - - memmove(path, path + PATH__NT_NAMESPACE_LEN, sizeof(wchar_t) * len); - return len; -} - -static wchar_t *path__skip_server(wchar_t *path) -{ - wchar_t *c; - - for (c = path; *c; c++) { - if (git_path_is_dirsep(*c)) - return c + 1; - } - - return c; -} - -static wchar_t *path__skip_prefix(wchar_t *path) -{ - if (path__is_nt_namespace(path)) { - path += PATH__NT_NAMESPACE_LEN; - - if (wcsncmp(path, L"UNC\\", 4) == 0) - path = path__skip_server(path + 4); - else if (git_path_is_absolute(path)) - path += PATH__ABSOLUTE_LEN; - } else if (git_path_is_absolute(path)) { - path += PATH__ABSOLUTE_LEN; - } else if (path__is_unc(path)) { - path = path__skip_server(path + 2); - } - - return path; -} - -int git_win32_path_canonicalize(git_win32_path path) -{ - wchar_t *base, *from, *to, *next; - size_t len; - - base = to = path__skip_prefix(path); - - /* Unposixify if the prefix */ - for (from = path; from < to; from++) { - if (*from == L'/') - *from = L'\\'; - } - - while (*from) { - for (next = from; *next; ++next) { - if (*next == L'/') { - *next = L'\\'; - break; - } - - if (*next == L'\\') - break; - } - - len = next - from; - - if (len == 1 && from[0] == L'.') - /* do nothing with singleton dot */; - - else if (len == 2 && from[0] == L'.' && from[1] == L'.') { - if (to == base) { - /* no more path segments to strip, eat the "../" */ - if (*next == L'\\') - len++; - - base = to; - } else { - /* back up a path segment */ - while (to > base && to[-1] == L'\\') to--; - while (to > base && to[-1] != L'\\') to--; - } - } else { - if (*next == L'\\' && *from != L'\\') - len++; - - if (to != from) - memmove(to, from, sizeof(wchar_t) * len); - - to += len; - } - - from += len; - - while (*from == L'\\') from++; - } - - /* Strip trailing backslashes */ - while (to > base && to[-1] == L'\\') to--; - - *to = L'\0'; - - if ((to - path) > INT_MAX) { - SetLastError(ERROR_FILENAME_EXCED_RANGE); - return -1; - } - - return (int)(to - path); -} - -static int win32_path_cwd(wchar_t *out, size_t len) -{ - int cwd_len; - - if (len > INT_MAX) { - errno = ENAMETOOLONG; - return -1; - } - - if ((cwd_len = path__cwd(out, (int)len)) < 0) - return -1; - - /* UNC paths */ - if (wcsncmp(L"\\\\", out, 2) == 0) { - /* Our buffer must be at least 5 characters larger than the - * current working directory: we swallow one of the leading - * '\'s, but we we add a 'UNC' specifier to the path, plus - * a trailing directory separator, plus a NUL. - */ - if (cwd_len > GIT_WIN_PATH_MAX - 4) { - errno = ENAMETOOLONG; - return -1; - } - - memmove(out+2, out, sizeof(wchar_t) * cwd_len); - out[0] = L'U'; - out[1] = L'N'; - out[2] = L'C'; - - cwd_len += 2; - } - - /* Our buffer must be at least 2 characters larger than the current - * working directory. (One character for the directory separator, - * one for the null. - */ - else if (cwd_len > GIT_WIN_PATH_MAX - 2) { - errno = ENAMETOOLONG; - return -1; - } - - return cwd_len; -} - -int git_win32_path_from_utf8(git_win32_path out, const char *src) -{ - wchar_t *dest = out; - - /* All win32 paths are in NT-prefixed format, beginning with "\\?\". */ - memcpy(dest, PATH__NT_NAMESPACE, sizeof(wchar_t) * PATH__NT_NAMESPACE_LEN); - dest += PATH__NT_NAMESPACE_LEN; - - /* See if this is an absolute path (beginning with a drive letter) */ - if (git_path_is_absolute(src)) { - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) - goto on_error; - } - /* File-prefixed NT-style paths beginning with \\?\ */ - else if (path__is_nt_namespace(src)) { - /* Skip the NT prefix, the destination already contains it */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) - goto on_error; - } - /* UNC paths */ - else if (path__is_unc(src)) { - memcpy(dest, L"UNC\\", sizeof(wchar_t) * 4); - dest += 4; - - /* Skip the leading "\\" */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) - goto on_error; - } - /* Absolute paths omitting the drive letter */ - else if (path__startswith_slash(src)) { - if (path__cwd(dest, GIT_WIN_PATH_MAX) < 0) - goto on_error; - - if (!git_path_is_absolute(dest)) { - errno = ENOENT; - goto on_error; - } - - /* Skip the drive letter specification ("C:") */ - if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) - goto on_error; - } - /* Relative paths */ - else { - int cwd_len; - - if ((cwd_len = win32_path_cwd(dest, GIT_WIN_PATH_MAX)) < 0) - goto on_error; - - dest[cwd_len++] = L'\\'; - - if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) - goto on_error; - } - - return git_win32_path_canonicalize(out); - -on_error: - /* set windows error code so we can use its error message */ - if (errno == ENAMETOOLONG) - SetLastError(ERROR_FILENAME_EXCED_RANGE); - - return -1; -} - -int git_win32_path_relative_from_utf8(git_win32_path out, const char *src) -{ - wchar_t *dest = out, *p; - int len; - - /* Handle absolute paths */ - if (git_path_is_absolute(src) || - path__is_nt_namespace(src) || - path__is_unc(src) || - path__startswith_slash(src)) { - return git_win32_path_from_utf8(out, src); - } - - if ((len = git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0) - return -1; - - for (p = dest; p < (dest + len); p++) { - if (*p == L'/') - *p = L'\\'; - } - - return len; -} - -int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) -{ - char *out = dest; - int len; - - /* Strip NT namespacing "\\?\" */ - if (path__is_nt_namespace(src)) { - src += 4; - - /* "\\?\UNC\server\share" -> "\\server\share" */ - if (wcsncmp(src, L"UNC\\", 4) == 0) { - src += 4; - - memcpy(dest, "\\\\", 2); - out = dest + 2; - } - } - - if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) - return len; - - git_path_mkposix(dest); - - return len; -} - -char *git_win32_path_8dot3_name(const char *path) -{ - git_win32_path longpath, shortpath; - wchar_t *start; - char *shortname; - int len, namelen = 1; - - if (git_win32_path_from_utf8(longpath, path) < 0) - return NULL; - - len = GetShortPathNameW(longpath, shortpath, GIT_WIN_PATH_UTF16); - - while (len && shortpath[len-1] == L'\\') - shortpath[--len] = L'\0'; - - if (len == 0 || len >= GIT_WIN_PATH_UTF16) - return NULL; - - for (start = shortpath + (len - 1); - start > shortpath && *(start-1) != '/' && *(start-1) != '\\'; - start--) - namelen++; - - /* We may not have actually been given a short name. But if we have, - * it will be in the ASCII byte range, so we don't need to worry about - * multi-byte sequences and can allocate naively. - */ - if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) - return NULL; - - if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) - return NULL; - - return shortname; -} - -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. */ -int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) -{ - BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - GIT_REPARSE_DATA_BUFFER *reparse_buf = (GIT_REPARSE_DATA_BUFFER *)buf; - HANDLE handle = NULL; - DWORD ioctl_ret; - wchar_t *target; - size_t target_len; - - int error = -1; - - handle = CreateFileW(path, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); - - if (handle == INVALID_HANDLE_VALUE) { - errno = ENOENT; - return -1; - } - - if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, - reparse_buf, sizeof(buf), &ioctl_ret, NULL)) { - errno = EINVAL; - goto on_error; - } - - switch (reparse_buf->ReparseTag) { - case IO_REPARSE_TAG_SYMLINK: - target = reparse_buf->ReparseBuffer.SymbolicLink.PathBuffer + - (reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameOffset / sizeof(WCHAR)); - target_len = reparse_buf->ReparseBuffer.SymbolicLink.SubstituteNameLength / sizeof(WCHAR); - break; - case IO_REPARSE_TAG_MOUNT_POINT: - target = reparse_buf->ReparseBuffer.MountPoint.PathBuffer + - (reparse_buf->ReparseBuffer.MountPoint.SubstituteNameOffset / sizeof(WCHAR)); - target_len = reparse_buf->ReparseBuffer.MountPoint.SubstituteNameLength / sizeof(WCHAR); - break; - default: - errno = EINVAL; - goto on_error; - } - - 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 namespace prefix removed. */ - target_len = git_win32_path_remove_namespace(target, target_len); - - /* Need one additional character in the target buffer - * for the terminating NULL. */ - if (GIT_WIN_PATH_UTF16 > target_len) { - wcscpy(dest, target); - error = (int)target_len; - } - } - -on_error: - CloseHandle(handle); - return error; -} - -/** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_trim_end(wchar_t *str, size_t len) -{ - while (1) { - if (!len || str[len - 1] != L'\\') - break; - - /* - * Don't trim backslashes from drive letter paths, which - * are 3 characters long and of the form C:\, D:\, etc. - */ - if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') - break; - - len--; - } - - str[len] = L'\0'; - - return len; -} - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32_path_remove_namespace(wchar_t *str, size_t len) -{ - static const wchar_t dosdevices_namespace[] = L"\\\?\?\\"; - static const wchar_t nt_namespace[] = L"\\\\?\\"; - static const wchar_t unc_namespace_remainder[] = L"UNC\\"; - static const wchar_t unc_prefix[] = L"\\\\"; - - const wchar_t *prefix = NULL, *remainder = NULL; - size_t prefix_len = 0, remainder_len = 0; - - /* "\??\" -- DOS Devices prefix */ - if (len >= CONST_STRLEN(dosdevices_namespace) && - !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) { - remainder = str + CONST_STRLEN(dosdevices_namespace); - remainder_len = len - CONST_STRLEN(dosdevices_namespace); - } - /* "\\?\" -- NT namespace prefix */ - else if (len >= CONST_STRLEN(nt_namespace) && - !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) { - remainder = str + CONST_STRLEN(nt_namespace); - remainder_len = len - CONST_STRLEN(nt_namespace); - } - - /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ - if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) && - !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) { - - /* - * The proper Win32 path for a UNC share has "\\" at beginning of it - * and looks like "\\server\share\<folderStructure>". So remove the - * UNC namespace and add a prefix of "\\" in its place. - */ - remainder += CONST_STRLEN(unc_namespace_remainder); - remainder_len -= CONST_STRLEN(unc_namespace_remainder); - - prefix = unc_prefix; - prefix_len = CONST_STRLEN(unc_prefix); - } - - /* - * Sanity check that the new string isn't longer than the old one. - * (This could only happen due to programmer error introducing a - * prefix longer than the namespace it replaces.) - */ - if (remainder && len >= remainder_len + prefix_len) { - if (prefix) - memmove(str, prefix, prefix_len * sizeof(wchar_t)); - - memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t)); - - len = remainder_len + prefix_len; - str[len] = L'\0'; - } - - return git_win32_path_trim_end(str, len); -} |