Created
July 17, 2020 00:23
-
-
Save wackoisgod/f5905807ecb2635c1f883bda7369f0a5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define MAX2(a, b) ((a) > (b) ? (a) : (b)) | |
void ResolvePrefixPath(char* buf, wchar_t** prefix, bool* resolveFullPath) { | |
*resolveFullPath = true; | |
if (::isalpha(buf[0]) && !::IsDBCSLeadByte(buf[0]) && buf[1] == ':' && buf[2] == '\\') { | |
*prefix = const_cast<wchar_t*>(L"\\\\?\\"); | |
} | |
else if (buf[0] == '\\' && buf[1] == '\\') { | |
if (buf[2] == '?' && buf[3] == '\\') { | |
*prefix = const_cast<wchar_t*>(L""); | |
*resolveFullPath = false; | |
} | |
} | |
else { | |
*prefix = const_cast<wchar_t*>(L"\\\\?\\"); | |
} | |
} | |
errno_t ConvertToUnicode(char const* path, wchar_t** widePath) { | |
// Get required buffer size to convert to Unicode | |
int wideLen = MultiByteToWideChar(CP_ACP, | |
MB_ERR_INVALID_CHARS, | |
path, -1, | |
NULL, 0); | |
if (wideLen == 0) { | |
return EINVAL; | |
} | |
*widePath = static_cast<wchar_t*>(::malloc(wideLen * sizeof(wchar_t))); | |
int result = MultiByteToWideChar(CP_ACP, | |
MB_ERR_INVALID_CHARS, | |
path, -1, | |
*widePath, wideLen); | |
return ERROR_SUCCESS; | |
} | |
errno_t ResolveFullPath(wchar_t* widePath, wchar_t** resolvedPath) { | |
// Get required buffer size to convert to full path. The return | |
// value INCLUDES the terminating null character. | |
DWORD resolvedLen = GetFullPathNameW(widePath, 0, NULL, NULL); | |
if (resolvedLen == 0) { | |
return EINVAL; | |
} | |
*resolvedPath = static_cast<wchar_t*>(::malloc(resolvedLen * sizeof(wchar_t))); | |
// When the buffer has sufficient size, the return value EXCLUDES the | |
// terminating null character | |
DWORD result = GetFullPathNameW(widePath, resolvedLen, *resolvedPath, NULL); | |
return ERROR_SUCCESS; | |
} | |
// Proper Long Path support is hard :( and requires some extra care | |
// and love. To allow windows to support Long Paths you are required | |
// to provide a prefix to the path "//?/" this prefix lets the underlying | |
// Win32 API calls know that is can skip the validation checks against | |
// MAX_PATH, but this also causes issues where if there is a ".." in the | |
// path (including paths that have the drive letter attached e.g D:\Foo\..\Thing) | |
// windows doesn't properly collapse the path, and the underlying kernel call doesn't | |
// know how to resolve the ".." properly and functions such as CreateDirectoryW | |
// will fail with "ERROR_INVALID_NAME" which in this case will cause tundra to fail | |
// when it tries to copy over files that are relative and long paths. | |
// See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation | |
// Also this code could be way less gross if we could use std::wstring (andrews) | |
wchar_t* ConvertToLongPath(char const* path, errno_t& err) { | |
if ((path == NULL) || (path[0] == '\0')) { | |
err = ENOENT; | |
return NULL; | |
} | |
size_t buffLen = 1 + MAX2((size_t)3, strlen(path)); | |
char* buf = static_cast<char*>(::malloc(buffLen * sizeof(char))); | |
strncpy(buf, path, buffLen); | |
wchar_t* prefix = NULL; | |
bool resolveFullPath = true; | |
ResolvePrefixPath(buf, &prefix, &resolveFullPath); | |
wchar_t* widePath = NULL; | |
err = ConvertToUnicode(buf, &widePath); | |
free(buf); | |
if (err != ERROR_SUCCESS) { | |
return NULL; | |
} | |
wchar_t* fullResolvedPath = NULL; | |
if (resolveFullPath) { | |
err = ResolveFullPath(widePath, &fullResolvedPath); | |
} | |
else { | |
fullResolvedPath = widePath; | |
} | |
wchar_t* result = NULL; | |
if (fullResolvedPath != NULL) { | |
size_t prefixLen = wcslen(prefix); | |
size_t resultLen = prefixLen + wcslen(fullResolvedPath) + 1; | |
result = static_cast<wchar_t*>(::malloc(resultLen * sizeof(wchar_t))); | |
_snwprintf(result, resultLen, L"%s%s", prefix, &fullResolvedPath[0]); | |
// Remove trailing pathsep (not for \\?\<DRIVE>:\, since it would make it relative) | |
resultLen = wcslen(result); | |
if ((result[resultLen - 1] == L'\\') && | |
!(::iswalpha(result[4]) && result[5] == L':' && resultLen == 7)) { | |
result[resultLen - 1] = L'\0'; | |
} | |
} | |
if (fullResolvedPath != widePath) { | |
free(fullResolvedPath); | |
} | |
free(widePath); | |
return static_cast<wchar_t*>(result); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment