summaryrefslogtreecommitdiff
path: root/src/win32/path_w32.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/win32/path_w32.c')
-rw-r--r--src/win32/path_w32.c511
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);
-}