Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Add Windows long path support #110

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did you find that information? This page suggests to me that all Unicode functions in the Win32 API can handle long names, and creating directories is mentioned specifically (including the note that the limit for directory names is actually SHRT_MAX - 12 to allow for an 8.3 file name to be appended... (I failed to see any note about length limitations on _wmkdir's documentation on MSDN either...)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. And I wish I could find the documentation on it again. Unfortunately, even if there is no documentation, it doesn't change the fact it won't work. :) Without stripping "\?", you'll find that a number of these functions simply don't work -- you get erratic behavior, and it's clear these functions are to blame. Here's some reading on why "\?" is so ... frustrating sometimes:

http://blogs.msdn.com/b/bclteam/archive/2007/02/13/long-paths-in-net-part-1-of-3-kim-hamilton.aspx
http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx (hey, CreateProcess can have long paths... but apparently will balk if you send it "\?")
http://stackoverflow.com/questions/1880321/why-does-the-260-character-path-length-limit-exist-in-windows
http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/2156195-fix-260-character-file-name-length-limitation (Basically, Visual Studio team has declined to investigate this issue)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's too bad! However, I find those links so informative that I would strongly suggest putting them into the commit message (remember, commit messages are the perfect place to put information related to the change that cannot be directly inferred from the diff).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be that we need to call SetCurrentDirectoryW instead of _wchdir?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! Unfortunately, it seems _wchdir() calls SetCurrentDirectoryW and that does, in fact, have a hard limit of 260 characters. http://msdn.microsoft.com/en-us/library/windows/desktop/aa365530(v=vs.85).aspx

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's too bad. However, let's just do as good as we can (not better)... I am willing to stay with _wchdir()...

return _wchdir(strip_abspath_prefix(wdirname));
}

int mingw_chmod(const char *filename, int mode)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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] == '\\')
Expand Down Expand Up @@ -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);
Expand Down
32 changes: 27 additions & 5 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions compat/win32/dirent.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions path.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down