Skip to content

Commit 7a9d069

Browse files
committed
Win32: mingw_chdir: change to symlink-resolved directory
If symlinks are enabled, resolve all symlinks when changing directories, as required by POSIX. Note: Git's real_path() function bases its link resolution algorithm on this property of chdir(). Unfortunately, the current directory on Windows is limited to only MAX_PATH (260) characters. Therefore using symlinks and long paths in combination may be problematic. Signed-off-by: Karsten Blees <[email protected]>
1 parent fb95cda commit 7a9d069

File tree

1 file changed

+48
-1
lines changed

1 file changed

+48
-1
lines changed

compat/mingw.c

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,34 @@ static int retry_ask_yes_no(int *tries, const char *format, ...)
230230
return result;
231231
}
232232

233+
/* Normalizes NT paths as returned by some low-level APIs. */
234+
static wchar_t *normalize_ntpath(wchar_t *wbuf)
235+
{
236+
int i;
237+
/* fix absolute path prefixes */
238+
if (wbuf[0] == '\\') {
239+
/* strip NT namespace prefixes */
240+
if (!wcsncmp(wbuf, L"\\??\\", 4) ||
241+
!wcsncmp(wbuf, L"\\\\?\\", 4))
242+
wbuf += 4;
243+
else if (!wcsnicmp(wbuf, L"\\DosDevices\\", 12))
244+
wbuf += 12;
245+
/* replace remaining '...UNC\' with '\\' */
246+
if (!wcsnicmp(wbuf, L"UNC\\", 4)) {
247+
wbuf += 2;
248+
*wbuf = '\\';
249+
}
250+
}
251+
/* convert backslashes to slashes */
252+
for (i = 0; wbuf[i]; i++)
253+
if (wbuf[i] == '\\')
254+
wbuf[i] = '/';
255+
/* remove potential trailing slashes */
256+
while (i && wbuf[i - 1] == '/')
257+
wbuf[--i] = 0;
258+
return wbuf;
259+
}
260+
233261
int mingw_unlink(const char *pathname)
234262
{
235263
int tries = 0;
@@ -480,11 +508,30 @@ static int current_directory_len = 0;
480508
int mingw_chdir(const char *dirname)
481509
{
482510
int result;
511+
DECLARE_PROC_ADDR(kernel32.dll, DWORD, GetFinalPathNameByHandleW,
512+
HANDLE, LPWSTR, DWORD, DWORD);
483513
wchar_t wdirname[MAX_PATH];
484514
/* SetCurrentDirectoryW doesn't support long paths */
485515
if (xutftowcs_path(wdirname, dirname) < 0)
486516
return -1;
487-
result = _wchdir(wdirname);
517+
518+
if (has_symlinks && INIT_PROC_ADDR(GetFinalPathNameByHandleW)) {
519+
HANDLE hnd = CreateFileW(wdirname, 0,
520+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
521+
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
522+
if (hnd == INVALID_HANDLE_VALUE) {
523+
errno = err_win_to_posix(GetLastError());
524+
return -1;
525+
}
526+
if (!GetFinalPathNameByHandleW(hnd, wdirname, ARRAY_SIZE(wdirname), 0)) {
527+
errno = err_win_to_posix(GetLastError());
528+
CloseHandle(hnd);
529+
return -1;
530+
}
531+
CloseHandle(hnd);
532+
}
533+
534+
result = _wchdir(normalize_ntpath(wdirname));
488535
current_directory_len = GetCurrentDirectoryW(0, NULL);
489536
return result;
490537
}

0 commit comments

Comments
 (0)