diff --git a/compat/mingw.c b/compat/mingw.c index 40908883a0e81f..7529f4ed65e343 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -480,7 +480,8 @@ int mingw_chdir(const char *dirname) wchar_t wdirname[MAX_PATH]; if (xutftowcs_canonical_path(wdirname, dirname) < 0) return -1; - return _wchdir(wdirname); + /* TODO: _wchdir() does not support long paths */ + return _wchdir(strip_abspath_prefix(wdirname)); } int mingw_chmod(const char *filename, int mode) @@ -696,7 +697,8 @@ char *mingw_mktemp(char *template) wchar_t wtemplate[MAX_PATH]; if (xutftowcs_path(wtemplate, template) < 0) return NULL; - if (!_wmktemp(wtemplate)) + /* TODO: _wmktemp() does not support long paths. */ + if (!_wmktemp(strip_abspath_prefix(wtemplate))) return NULL; if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0) return NULL; @@ -767,7 +769,7 @@ char *mingw_getcwd(char *pointer, int len) wchar_t wpointer[MAX_PATH]; if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer))) return NULL; - if (xwcstoutf(pointer, wpointer, len) < 0) + if (xwcstoutf(pointer, strip_abspath_prefix(wpointer), len) < 0) return NULL; for (i = 0; pointer[i]; i++) if (pointer[i] == '\\') @@ -1165,8 +1167,8 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen wenvblk = make_environment_block(deltaenv); memset(&pi, 0, sizeof(pi)); - ret = CreateProcessW(wcmd, wargs, NULL, NULL, TRUE, flags, - wenvblk, dir ? wdir : NULL, &si, &pi); + ret = CreateProcessW(strip_abspath_prefix(wcmd), wargs, NULL, NULL, TRUE, flags, + wenvblk, dir ? strip_abspath_prefix(wdir) : NULL, &si, &pi); free(wenvblk); free(wargs); diff --git a/compat/mingw.h b/compat/mingw.h index 4b3df96d619fd5..89bc7e4f0ae0c7 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -80,6 +80,13 @@ struct itimerval { */ #undef HELP_COMMAND /* from winuser.h */ +/* + * redefine MAX_PATH to SHRT_MAX; the actual maximum when using + * long Windows paths with "\\?\" + */ +#undef MAX_PATH +#define MAX_PATH SHRT_MAX + /* * trivial stubs */ @@ -357,6 +364,16 @@ HANDLE winansi_get_osfhandle(int fd); */ #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':') +#define has_win_abspath_prefix(path) ((path)[0] == '\\' && (path)[1] == '\\' \ + && (path)[2] == '?' && (path)[3] == '\\') +#define has_win_abspath_prefix_w(path) ((path)[0] == L'\\' && (path)[1] == L'\\' \ + && (path)[2] == L'?' && (path)[3] == L'\\') +static inline wchar_t * strip_abspath_prefix(wchar_t *path) +{ + if (path && has_win_abspath_prefix_w(path)) + return &path[4]; + return path; +} #define is_dir_sep(c) ((c) == '/' || (c) == '\\') static inline char *mingw_find_last_dir_sep(const char *path) { @@ -457,18 +474,23 @@ static inline int xutftowcs_path(wchar_t *wcs, const char *utf) */ static inline int xutftowcs_canonical_path(wchar_t *wcs, const char *utf) { - wchar_t tmp[SHRT_MAX]; + wchar_t tmp[MAX_PATH]; int result; - result = xutftowcsn(tmp, utf, SHRT_MAX, -1); + result = xutftowcsn(tmp, utf, MAX_PATH, -1); if (result < 0 && errno == ERANGE) errno = ENAMETOOLONG; else if (wcsncmp(tmp, L"nul", 4) == 0 ) wcsncpy(wcs, tmp, 4); else { - wchar_t tmp2[SHRT_MAX]; - GetFullPathNameW(tmp, SHRT_MAX, tmp2, NULL); - if (wcslen(tmp2) < MAX_PATH) + wchar_t tmp2[MAX_PATH]; + GetFullPathNameW(tmp, MAX_PATH, tmp2, NULL); + if (wcslen(tmp2) < MAX_PATH && has_win_abspath_prefix_w(tmp2)) wcsncpy(wcs, tmp2, MAX_PATH - 1); + /* magic: 5 = \\?\ plus null */ + else if (wcslen(tmp2) < MAX_PATH - 5 && !has_win_abspath_prefix_w(tmp2)) { + wcscpy(wcs, L"\\\\?\\"); + wcsncat(wcs, tmp2, MAX_PATH - 5); + } else { result = -1; errno = ENAMETOOLONG; diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c index d3f68774c12c7e..f71a1f7ba80f93 100644 --- a/compat/win32/dirent.c +++ b/compat/win32/dirent.c @@ -73,9 +73,13 @@ DIR *dirent_opendir(const char *name) if ((len = xutftowcs_canonical_path(pattern, name)) < 0) return NULL; - /* append optional '/' and wildcard '*' */ + /* + * append optional '\' and wildcard '*' + * note: when using "\\?\" as a prefix for long paths, + * we cannot expect Windows to remap '/' to '\' for us. + */ if (len && !is_dir_sep(pattern[len - 1])) - pattern[len++] = '/'; + pattern[len++] = '\\'; pattern[len++] = '*'; pattern[len] = 0; diff --git a/git-compat-util.h b/git-compat-util.h index 2dfab4b7b70d28..633e6367f013f4 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -272,6 +272,10 @@ extern char *gitbasename(char *); #define has_dos_drive_prefix(path) 0 #endif +#ifndef has_win_abspath_prefix +#define has_win_abspath_prefix(path) 0 +#endif + #ifndef offset_1st_component #define offset_1st_component(path) (is_dir_sep((path)[0])) #endif diff --git a/path.c b/path.c index 7f59206f8e40db..2ffbf1edfa47cb 100644 --- a/path.c +++ b/path.c @@ -619,6 +619,12 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) { char *dst0; + /* + * If path contains "\\?\", we can skip this while copying. + * We will re-add this later if necessary. + */ + if (has_win_abspath_prefix(src)) + src += 4; if (has_dos_drive_prefix(src)) { *dst++ = *src++; *dst++ = *src++;