diff --git a/compat/mingw.c b/compat/mingw.c index 2d4613e0b1..5a412691d2 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -269,6 +269,34 @@ int mingw_core_config(const char *var, const char *value, void *cb) return 0; } +/* Normalizes NT paths as returned by some low-level APIs. */ +static wchar_t *normalize_ntpath(wchar_t *wbuf) +{ + int i; + /* fix absolute path prefixes */ + if (wbuf[0] == '\\') { + /* strip NT namespace prefixes */ + if (!wcsncmp(wbuf, L"\\??\\", 4) || + !wcsncmp(wbuf, L"\\\\?\\", 4)) + wbuf += 4; + else if (!wcsnicmp(wbuf, L"\\DosDevices\\", 12)) + wbuf += 12; + /* replace remaining '...UNC\' with '\\' */ + if (!wcsnicmp(wbuf, L"UNC\\", 4)) { + wbuf += 2; + *wbuf = '\\'; + } + } + /* convert backslashes to slashes */ + for (i = 0; wbuf[i]; i++) + if (wbuf[i] == '\\') + wbuf[i] = '/'; + /* remove potential trailing slashes */ + while (i && wbuf[i - 1] == '/') + wbuf[--i] = 0; + return wbuf; +} + int mingw_unlink(const char *pathname) { int tries = 0; @@ -598,10 +626,29 @@ static int current_directory_len = 0; int mingw_chdir(const char *dirname) { int result; + DECLARE_PROC_ADDR(kernel32.dll, DWORD, GetFinalPathNameByHandleW, + HANDLE, LPWSTR, DWORD, DWORD); wchar_t wdirname[MAX_LONG_PATH]; if (xutftowcs_long_path(wdirname, dirname) < 0) return -1; - result = _wchdir(wdirname); + + if (has_symlinks && INIT_PROC_ADDR(GetFinalPathNameByHandleW)) { + HANDLE hnd = CreateFileW(wdirname, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hnd == INVALID_HANDLE_VALUE) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + if (!GetFinalPathNameByHandleW(hnd, wdirname, ARRAY_SIZE(wdirname), 0)) { + errno = err_win_to_posix(GetLastError()); + CloseHandle(hnd); + return -1; + } + CloseHandle(hnd); + } + + result = _wchdir(normalize_ntpath(wdirname)); current_directory_len = GetCurrentDirectoryW(0, NULL); return result; }