mirror of
https://github.com/git-for-windows/git.git
synced 2026-03-28 15:45:21 -05:00
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:
https://github.com/msysgit/git/pull/122#issuecomment-43604199
[jes: adjusted test number to avoid conflicts]
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>
104 lines
2.7 KiB
C
104 lines
2.7 KiB
C
#include "../../git-compat-util.h"
|
|
|
|
typedef struct dirent_DIR {
|
|
struct DIR base_dir; /* extend base struct DIR */
|
|
struct dirent dd_dir; /* includes d_type */
|
|
HANDLE dd_handle; /* FindFirstFile handle */
|
|
int dd_stat; /* 0-based index */
|
|
char dd_name[MAX_PATH * 3]; /* file name (* 3 for UTF-8 conversion) */
|
|
} dirent_DIR;
|
|
|
|
DIR *(*opendir)(const char *dirname) = dirent_opendir;
|
|
|
|
static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata)
|
|
{
|
|
/* convert UTF-16 name to UTF-8 (d_name points to dirent_DIR.dd_name) */
|
|
xwcstoutf(ent->d_name, fdata->cFileName, MAX_PATH * 3);
|
|
|
|
/* Set file type, based on WIN32_FIND_DATA */
|
|
if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
ent->d_type = DT_DIR;
|
|
else
|
|
ent->d_type = DT_REG;
|
|
}
|
|
|
|
static struct dirent *dirent_readdir(dirent_DIR *dir)
|
|
{
|
|
if (!dir) {
|
|
errno = EBADF; /* No set_errno for mingw */
|
|
return NULL;
|
|
}
|
|
|
|
/* if first entry, dirent has already been set up by opendir */
|
|
if (dir->dd_stat) {
|
|
/* get next entry and convert from WIN32_FIND_DATA to dirent */
|
|
WIN32_FIND_DATAW fdata;
|
|
if (FindNextFileW(dir->dd_handle, &fdata)) {
|
|
finddata2dirent(&dir->dd_dir, &fdata);
|
|
} else {
|
|
DWORD lasterr = GetLastError();
|
|
/* POSIX says you shouldn't set errno when readdir can't
|
|
find any more files; so, if another error we leave it set. */
|
|
if (lasterr != ERROR_NO_MORE_FILES)
|
|
errno = err_win_to_posix(lasterr);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
++dir->dd_stat;
|
|
return &dir->dd_dir;
|
|
}
|
|
|
|
static int dirent_closedir(dirent_DIR *dir)
|
|
{
|
|
if (!dir) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
FindClose(dir->dd_handle);
|
|
free(dir);
|
|
return 0;
|
|
}
|
|
|
|
DIR *dirent_opendir(const char *name)
|
|
{
|
|
wchar_t pattern[MAX_LONG_PATH + 2]; /* + 2 for "\*" */
|
|
WIN32_FIND_DATAW fdata;
|
|
HANDLE h;
|
|
int len;
|
|
dirent_DIR *dir;
|
|
|
|
/* convert name to UTF-16 and check length */
|
|
if ((len = xutftowcs_path_ex(pattern, name, MAX_LONG_PATH, -1,
|
|
MAX_PATH - 2, core_long_paths)) < 0)
|
|
return NULL;
|
|
|
|
/*
|
|
* append optional '\' and wildcard '*'. Note: we need to use '\' as
|
|
* Windows doesn't translate '/' to '\' for "\\?\"-prefixed paths.
|
|
*/
|
|
if (len && !is_dir_sep(pattern[len - 1]))
|
|
pattern[len++] = '\\';
|
|
pattern[len++] = '*';
|
|
pattern[len] = 0;
|
|
|
|
/* open find handle */
|
|
h = FindFirstFileW(pattern, &fdata);
|
|
if (h == INVALID_HANDLE_VALUE) {
|
|
DWORD err = GetLastError();
|
|
errno = (err == ERROR_DIRECTORY) ? ENOTDIR : err_win_to_posix(err);
|
|
return NULL;
|
|
}
|
|
|
|
/* initialize DIR structure and copy first dir entry */
|
|
dir = xmalloc(sizeof(dirent_DIR));
|
|
dir->base_dir.preaddir = (struct dirent *(*)(DIR *dir)) dirent_readdir;
|
|
dir->base_dir.pclosedir = (int (*)(DIR *dir)) dirent_closedir;
|
|
dir->dd_dir.d_name = dir->dd_name;
|
|
dir->dd_handle = h;
|
|
dir->dd_stat = 0;
|
|
finddata2dirent(&dir->dd_dir, &fdata);
|
|
return (DIR*) dir;
|
|
}
|