Skip to content

Commit 6b75bb3

Browse files
kbleesdscho
authored andcommitted
Win32: support long paths
Windows paths are typically limited to MAX_PATH = 260 characters, even though the underlying NTFS file system supports paths up to 32,767 chars. This limitation is also evident in Windows Explorer, cmd.exe and many other applications (including IDEs). Particularly annoying is that most Windows APIs return bogus error codes if a relative path only barely exceeds MAX_PATH in conjunction with the current directory, e.g. ERROR_PATH_NOT_FOUND / ENOENT instead of the infinitely more helpful ERROR_FILENAME_EXCED_RANGE / ENAMETOOLONG. Many Windows wide char APIs support longer than MAX_PATH paths through the file namespace prefix ('\\?\' or '\\?\UNC\') followed by an absolute path. Notable exceptions include functions dealing with executables and the current directory (CreateProcess, LoadLibrary, Get/SetCurrentDirectory) as well as the entire shell API (ShellExecute, SHGetSpecialFolderPath...). Introduce a handle_long_path function to check the length of a specified path properly (and fail with ENAMETOOLONG), and to optionally expand long paths using the '\\?\' file namespace prefix. Short paths will not be modified, so we don't need to worry about device names (NUL, CON, AUX). Contrary to MSDN docs, the GetFullPathNameW function doesn't seem to be limited to MAX_PATH (at least not on Win7), so we can use it to do the heavy lifting of the conversion (translate '/' to '\', eliminate '.' and '..', and make an absolute path). Add long path error checking to xutftowcs_path for APIs with hard MAX_PATH limit. Add a new MAX_LONG_PATH constant and xutftowcs_long_path function for APIs that support long paths. While improved error checking is always active, long paths support must be explicitly enabled via 'core.longpaths' option. This is to prevent end users to shoot themselves in the foot by checking out files that Windows Explorer, cmd/bash or their favorite IDE cannot handle. Test suite: Test the case is when the full pathname length of a dir is close to 260 (MAX_PATH). Bug report and an original reproducer by Andrey Rogozhnikov: msysgit#122 (comment) [jes: adjusted test number to avoid conflicts, added support for chdir(), etc] Thanks-to: Martin W. Kirst <[email protected]> Thanks-to: Doug Kelly <[email protected]> Signed-off-by: Karsten Blees <[email protected]> Original-test-by: Andrey Rogozhnikov <[email protected]> Signed-off-by: Stepan Kasal <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 3252c74 commit 6b75bb3

File tree

7 files changed

+322
-56
lines changed

7 files changed

+322
-56
lines changed

Documentation/config/core.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,13 @@ core.fscache::
557557
Git for Windows uses this to bulk-read and cache lstat data of entire
558558
directories (instead of doing lstat file by file).
559559

560+
core.longpaths::
561+
Enable long path (> 260) support for builtin commands in Git for
562+
Windows. This is disabled by default, as long paths are not supported
563+
by Windows Explorer, cmd.exe and the Git for Windows tool chain
564+
(msys, bash, tcl, perl...). Only enable this if you know what you're
565+
doing and are prepared to live with a few quirks.
566+
560567
core.unsetenvvars::
561568
Windows-only: comma-separated list of environment variables'
562569
names that need to be unset before spawning any other process.

compat/mingw.c

Lines changed: 116 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ enum hide_dotfiles_type {
231231
static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
232232
static char *unset_environment_variables;
233233
int core_fscache;
234+
int core_long_paths;
234235

235236
int mingw_core_config(const char *var, const char *value, void *cb)
236237
{
@@ -247,6 +248,11 @@ int mingw_core_config(const char *var, const char *value, void *cb)
247248
return 0;
248249
}
249250

251+
if (!strcmp(var, "core.longpaths")) {
252+
core_long_paths = git_config_bool(var, value);
253+
return 0;
254+
}
255+
250256
if (!strcmp(var, "core.unsetenvvars")) {
251257
free(unset_environment_variables);
252258
unset_environment_variables = xstrdup(value);
@@ -284,8 +290,8 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
284290
int mingw_unlink(const char *pathname)
285291
{
286292
int ret, tries = 0;
287-
wchar_t wpathname[MAX_PATH];
288-
if (xutftowcs_path(wpathname, pathname) < 0)
293+
wchar_t wpathname[MAX_LONG_PATH];
294+
if (xutftowcs_long_path(wpathname, pathname) < 0)
289295
return -1;
290296

291297
/* read-only files cannot be removed */
@@ -314,7 +320,7 @@ static int is_dir_empty(const wchar_t *wpath)
314320
{
315321
WIN32_FIND_DATAW findbuf;
316322
HANDLE handle;
317-
wchar_t wbuf[MAX_PATH + 2];
323+
wchar_t wbuf[MAX_LONG_PATH + 2];
318324
wcscpy(wbuf, wpath);
319325
wcscat(wbuf, L"\\*");
320326
handle = FindFirstFileW(wbuf, &findbuf);
@@ -335,8 +341,8 @@ static int is_dir_empty(const wchar_t *wpath)
335341
int mingw_rmdir(const char *pathname)
336342
{
337343
int ret, tries = 0;
338-
wchar_t wpathname[MAX_PATH];
339-
if (xutftowcs_path(wpathname, pathname) < 0)
344+
wchar_t wpathname[MAX_LONG_PATH];
345+
if (xutftowcs_long_path(wpathname, pathname) < 0)
340346
return -1;
341347

342348
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
@@ -411,9 +417,12 @@ static int set_hidden_flag(const wchar_t *path, int set)
411417
int mingw_mkdir(const char *path, int mode)
412418
{
413419
int ret;
414-
wchar_t wpath[MAX_PATH];
415-
if (xutftowcs_path(wpath, path) < 0)
420+
wchar_t wpath[MAX_LONG_PATH];
421+
/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
422+
if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
423+
core_long_paths) < 0)
416424
return -1;
425+
417426
ret = _wmkdir(wpath);
418427
if (!ret && needs_hiding(path))
419428
return set_hidden_flag(wpath, 1);
@@ -486,7 +495,7 @@ int mingw_open (const char *filename, int oflags, ...)
486495
va_list args;
487496
unsigned mode;
488497
int fd;
489-
wchar_t wfilename[MAX_PATH];
498+
wchar_t wfilename[MAX_LONG_PATH];
490499
open_fn_t open_fn;
491500

492501
va_start(args, oflags);
@@ -501,7 +510,7 @@ int mingw_open (const char *filename, int oflags, ...)
501510
else
502511
open_fn = _wopen;
503512

504-
if (xutftowcs_path(wfilename, filename) < 0)
513+
if (xutftowcs_long_path(wfilename, filename) < 0)
505514
return -1;
506515
fd = open_fn(wfilename, oflags, mode);
507516

@@ -558,10 +567,10 @@ FILE *mingw_fopen (const char *filename, const char *otype)
558567
{
559568
int hide = needs_hiding(filename);
560569
FILE *file;
561-
wchar_t wfilename[MAX_PATH], wotype[4];
570+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
562571
if (filename && !strcmp(filename, "/dev/null"))
563572
filename = "nul";
564-
if (xutftowcs_path(wfilename, filename) < 0 ||
573+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
565574
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
566575
return NULL;
567576
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -580,10 +589,10 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
580589
{
581590
int hide = needs_hiding(filename);
582591
FILE *file;
583-
wchar_t wfilename[MAX_PATH], wotype[4];
592+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
584593
if (filename && !strcmp(filename, "/dev/null"))
585594
filename = "nul";
586-
if (xutftowcs_path(wfilename, filename) < 0 ||
595+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
587596
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
588597
return NULL;
589598
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -637,25 +646,31 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
637646

638647
int mingw_access(const char *filename, int mode)
639648
{
640-
wchar_t wfilename[MAX_PATH];
641-
if (xutftowcs_path(wfilename, filename) < 0)
649+
wchar_t wfilename[MAX_LONG_PATH];
650+
if (xutftowcs_long_path(wfilename, filename) < 0)
642651
return -1;
643652
/* X_OK is not supported by the MSVCRT version */
644653
return _waccess(wfilename, mode & ~X_OK);
645654
}
646655

656+
/* cached length of current directory for handle_long_path */
657+
static int current_directory_len = 0;
658+
647659
int mingw_chdir(const char *dirname)
648660
{
649-
wchar_t wdirname[MAX_PATH];
650-
if (xutftowcs_path(wdirname, dirname) < 0)
661+
int result;
662+
wchar_t wdirname[MAX_LONG_PATH];
663+
if (xutftowcs_long_path(wdirname, dirname) < 0)
651664
return -1;
652-
return _wchdir(wdirname);
665+
result = _wchdir(wdirname);
666+
current_directory_len = GetCurrentDirectoryW(0, NULL);
667+
return result;
653668
}
654669

655670
int mingw_chmod(const char *filename, int mode)
656671
{
657-
wchar_t wfilename[MAX_PATH];
658-
if (xutftowcs_path(wfilename, filename) < 0)
672+
wchar_t wfilename[MAX_LONG_PATH];
673+
if (xutftowcs_long_path(wfilename, filename) < 0)
659674
return -1;
660675
return _wchmod(wfilename, mode);
661676
}
@@ -703,8 +718,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
703718
static int do_lstat(int follow, const char *file_name, struct stat *buf)
704719
{
705720
WIN32_FILE_ATTRIBUTE_DATA fdata;
706-
wchar_t wfilename[MAX_PATH];
707-
if (xutftowcs_path(wfilename, file_name) < 0)
721+
wchar_t wfilename[MAX_LONG_PATH];
722+
if (xutftowcs_long_path(wfilename, file_name) < 0)
708723
return -1;
709724

710725
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -875,8 +890,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
875890
FILETIME mft, aft;
876891
int fh, rc;
877892
DWORD attrs;
878-
wchar_t wfilename[MAX_PATH];
879-
if (xutftowcs_path(wfilename, file_name) < 0)
893+
wchar_t wfilename[MAX_LONG_PATH];
894+
if (xutftowcs_long_path(wfilename, file_name) < 0)
880895
return -1;
881896

882897
/* must have write permission */
@@ -937,6 +952,7 @@ char *mingw_mktemp(char *template)
937952
wchar_t wtemplate[MAX_PATH];
938953
int offset = 0;
939954

955+
/* we need to return the path, thus no long paths here! */
940956
if (xutftowcs_path(wtemplate, template) < 0)
941957
return NULL;
942958

@@ -1461,6 +1477,10 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
14611477

14621478
if (*argv && !strcmp(cmd, *argv))
14631479
wcmd[0] = L'\0';
1480+
/*
1481+
* Paths to executables and to the current directory do not support
1482+
* long paths, therefore we cannot use xutftowcs_long_path() here.
1483+
*/
14641484
else if (xutftowcs_path(wcmd, cmd) < 0)
14651485
return -1;
14661486
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1893,8 +1913,9 @@ int mingw_rename(const char *pold, const char *pnew)
18931913
{
18941914
DWORD attrs, gle;
18951915
int tries = 0;
1896-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1897-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1916+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1917+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1918+
xutftowcs_long_path(wpnew, pnew) < 0)
18981919
return -1;
18991920

19001921
/*
@@ -2208,9 +2229,9 @@ int mingw_raise(int sig)
22082229

22092230
int link(const char *oldpath, const char *newpath)
22102231
{
2211-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
2212-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
2213-
xutftowcs_path(wnewpath, newpath) < 0)
2232+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
2233+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
2234+
xutftowcs_long_path(wnewpath, newpath) < 0)
22142235
return -1;
22152236

22162237
if (!CreateHardLinkW(wnewpath, woldpath, NULL)) {
@@ -2410,6 +2431,68 @@ static void setup_windows_environment(void)
24102431
}
24112432
}
24122433

2434+
int handle_long_path(wchar_t *path, int len, int max_path, int expand)
2435+
{
2436+
int result;
2437+
wchar_t buf[MAX_LONG_PATH];
2438+
2439+
/*
2440+
* we don't need special handling if path is relative to the current
2441+
* directory, and current directory + path don't exceed the desired
2442+
* max_path limit. This should cover > 99 % of cases with minimal
2443+
* performance impact (git almost always uses relative paths).
2444+
*/
2445+
if ((len < 2 || (!is_dir_sep(path[0]) && path[1] != ':')) &&
2446+
(current_directory_len + len < max_path))
2447+
return len;
2448+
2449+
/*
2450+
* handle everything else:
2451+
* - absolute paths: "C:\dir\file"
2452+
* - absolute UNC paths: "\\server\share\dir\file"
2453+
* - absolute paths on current drive: "\dir\file"
2454+
* - relative paths on other drive: "X:file"
2455+
* - prefixed paths: "\\?\...", "\\.\..."
2456+
*/
2457+
2458+
/* convert to absolute path using GetFullPathNameW */
2459+
result = GetFullPathNameW(path, MAX_LONG_PATH, buf, NULL);
2460+
if (!result) {
2461+
errno = err_win_to_posix(GetLastError());
2462+
return -1;
2463+
}
2464+
2465+
/*
2466+
* return absolute path if it fits within max_path (even if
2467+
* "cwd + path" doesn't due to '..' components)
2468+
*/
2469+
if (result < max_path) {
2470+
wcscpy(path, buf);
2471+
return result;
2472+
}
2473+
2474+
/* error out if we shouldn't expand the path or buf is too small */
2475+
if (!expand || result >= MAX_LONG_PATH - 6) {
2476+
errno = ENAMETOOLONG;
2477+
return -1;
2478+
}
2479+
2480+
/* prefix full path with "\\?\" or "\\?\UNC\" */
2481+
if (buf[0] == '\\') {
2482+
/* ...unless already prefixed */
2483+
if (buf[1] == '\\' && (buf[2] == '?' || buf[2] == '.'))
2484+
return len;
2485+
2486+
wcscpy(path, L"\\\\?\\UNC\\");
2487+
wcscpy(path + 8, buf + 2);
2488+
return result + 6;
2489+
} else {
2490+
wcscpy(path, L"\\\\?\\");
2491+
wcscpy(path + 4, buf);
2492+
return result + 4;
2493+
}
2494+
}
2495+
24132496
#if !defined(_MSC_VER)
24142497
/*
24152498
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
@@ -2571,6 +2654,9 @@ int wmain(int argc, const wchar_t **wargv)
25712654
/* initialize Unicode console */
25722655
winansi_init();
25732656

2657+
/* init length of current directory for handle_long_path */
2658+
current_directory_len = GetCurrentDirectoryW(0, NULL);
2659+
25742660
/* invoke the real main() using our utf8 version of argv. */
25752661
exit_status = main(argc, argv);
25762662

@@ -2638,7 +2724,7 @@ static int is_valid_system_file_owner(PSID sid, TOKEN_USER **info)
26382724
*/
26392725
static int validate_system_file_ownership(const char *path)
26402726
{
2641-
WCHAR wpath[MAX_PATH];
2727+
WCHAR wpath[MAX_LONG_PATH];
26422728
PSID owner_sid = NULL, problem_sid = NULL;
26432729
PSECURITY_DESCRIPTOR descriptor = NULL;
26442730
TOKEN_USER* info = NULL;

0 commit comments

Comments
 (0)