Skip to content

Commit

Permalink
Win32: support long paths
Browse files Browse the repository at this point in the history
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)

Note that the test cannot rely on the presence of short names, as they
are not enabled by default except on the system drive.

[jes: adjusted test number to avoid conflicts, reinstated && chain,
adjusted test to work without short names]

Thanks-to: Martin W. Kirst <maki@bitkings.de>
Thanks-to: Doug Kelly <dougk.ff7@gmail.com>
Signed-off-by: Karsten Blees <blees@dcon.de>
Original-test-by: Andrey Rogozhnikov <rogozhnikov.andrey@gmail.com>
Signed-off-by: Stepan Kasal <kasal@ucw.cz>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
  • Loading branch information
kblees authored and dscho committed Oct 29, 2017
1 parent 3a855b2 commit aa6285a
Show file tree
Hide file tree
Showing 2 changed files with 9 additions and 7 deletions.
5 changes: 2 additions & 3 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,8 @@ static int current_directory_len = 0;
int mingw_chdir(const char *dirname)
{
int result;
wchar_t wdirname[MAX_PATH];
/* SetCurrentDirectoryW doesn't support long paths */
if (xutftowcs_path(wdirname, dirname) < 0)
wchar_t wdirname[MAX_LONG_PATH];
if (xutftowcs_long_path(wdirname, dirname) < 0)
return -1;
result = _wchdir(wdirname);
current_directory_len = GetCurrentDirectoryW(0, NULL);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,17 @@ test_expect_success 'checkout of long paths without core.longpaths fails' '
git config core.longpaths false &&
test_must_fail git checkout -f 2>error &&
grep -q "Filename too long" error &&
test_path_is_missing longpa~1/longtestfile
test ! -d longpa*
'

test_expect_success 'checkout of long paths with core.longpaths works' '
git config core.longpaths true &&
git checkout -f &&
test_path_is_file longpa~1/longtestfile
test_path_is_file longpa*/longtestfile
'

test_expect_success 'update of long paths' '
echo frotz >> longpa~1/longtestfile &&
echo frotz >>$(ls longpa*/longtestfile) &&
echo $path > expect &&
git ls-files -m > actual &&
test_cmp expect actual &&
Expand All @@ -52,7 +52,10 @@ test_expect_success 'update of long paths' '
test_expect_success cleanup '
# bash cannot delete the trash dir if it contains a long path
# lets help cleaning up (unless in debug mode)
test ! -z "$debug" || rm -rf longpa~1
if test -z "$debug"
then
rm -rf longpa~1
fi
'

# check that the template used in the test won't be too long:
Expand Down

0 comments on commit aa6285a

Please sign in to comment.