Fixes the long path name problem in Windows.

This commit is contained in:
Andrea Mazzoleni 2012-04-12 21:35:13 +02:00
parent c692e83ae5
commit 0f80a7fb98
5 changed files with 105 additions and 35 deletions

View file

@ -3,8 +3,10 @@ SnapRAID HISTORY
1.10 2012/4
===========
* If a file is excluded prints explicitely which file attribute caused the
* If a file is excluded, it prints explicitely which attribute caused the
exclusion.
* Fixed very long paths in Windows. We now always use the special '\\?' prefix
to remove the 260 chars limitation.
1.9 2012/3
==========

9
TODO
View file

@ -5,15 +5,6 @@ This is the list of TODO items for SnapRAID.
- v1.10
* In Windows it's possible to workaround the 260 char path length using
wide char functions and prepending "\\?\".
Like "\\?\D:\very long path"
See: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#maxpath
The problem is that not all the APIs support "\\?\". Check for all of them.
access -> ? FindFirstFile ?
mkdir -> CreateDirectoryW
remove -> DeleteFileW
* In Autosave, do not save if we are near the termination.
* Allow to filter the files to check/fix based on the drive where they reside.

107
mingw.c
View file

@ -39,7 +39,7 @@ static unsigned conv_utf8 = 0;
static char conv_utf8_buffer[CONV_ROLL][PATH_MAX];
/**
* Converts from UTF8 to UTF16.
* Converts a generic string from UTF8 to UTF16.
*/
static wchar_t* u8tou16(const char* src)
{
@ -59,7 +59,7 @@ static wchar_t* u8tou16(const char* src)
}
/**
* Converts from UTF16 to UTF8.
* Converts a generic string from UTF16 to UTF8.
*/
static char* u16tou8(const wchar_t* src)
{
@ -77,6 +77,83 @@ static char* u16tou8(const wchar_t* src)
return conv_utf8_buffer[conv_utf8];
}
/**
* Converts a path to the Windows format.
*
* The exact operation done is:
* - If it's a '\\?\' path, convert any '/' to '\'.
* - If it's a disk designator path, like 'D:\' or 'D:/', it prepends '\\?\' to the path and convert any '/' to '\'.
* - If it's a UNC path, like ''\\server'', it prepends '\\?\UNC\' to the path and convert any '/' to '\'.
* - Otherwise, only the UTF conversion is done. In this case Windows imposes a limit of 260 chars, and automatically convert any '/' to '\'.
*
* For more details see:
* Naming Files, Paths, and Namespaces
* http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#maxpath
*/
static wchar_t* convert(const char* src)
{
int ret;
wchar_t* dst;
int convert_slash;
int count;
if (++conv_utf16 == CONV_ROLL)
conv_utf16 = 0;
dst = conv_utf16_buffer[conv_utf16];
if (src[0] == '\\' && src[1] == '\\' && src[2] == '?' && src[3] == '\\') {
/* if it's already a '\\?\' path */
/* do nothing */
} else if (src[0] == '\\' && src[1] == '\\') {
/* if it is a UNC path, like '\\server' */
/* prefix with '\\?\UNC\' */
*dst++ = L'\\';
*dst++ = L'\\';
*dst++ = L'?';
*dst++ = L'\\';
*dst++ = L'U';
*dst++ = L'N';
*dst++ = L'C';
*dst++ = L'\\';
/* skip initial '\\' */
src += 2;
} else if (src[0] != 0 && src[1] == ':' && (src[2] == '\\' || src[2] == '/')) {
/* if it is a disk designator path, like 'D:\' or 'D:/' */
/* prefix with '\\?\' */
*dst++ = L'\\';
*dst++ = L'\\';
*dst++ = L'?';
*dst++ = L'\\';
}
/* chars already used */
count = dst - conv_utf16_buffer[conv_utf16];
ret = MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, sizeof(conv_utf16_buffer[0]) / sizeof(wchar_t) - count);
if (ret <= 0) {
fprintf(stderr, "Error converting name '%s' from UTF-8 to UTF-16\n", src);
exit(EXIT_FAILURE);
}
/* convert any / to \ */
/* note that in UTF-16, it's not possible to have '/' used as part */
/* of a pair of codes representing a single UNICODE char */
/* See: http://en.wikipedia.org/wiki/UTF-16 */
while (*dst) {
if (*dst == L'/')
*dst = L'\\';
++dst;
}
return conv_utf16_buffer[conv_utf16];
}
/**
* Converts Windows info to the Unix stat format.
*/
@ -263,7 +340,7 @@ int windows_lstat(const char* file, struct windows_stat* st)
HANDLE h;
WIN32_FIND_DATAW data;
h = FindFirstFileW(u8tou16(file), &data);
h = FindFirstFileW(convert(file), &data);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
@ -281,12 +358,12 @@ int windows_lstat(const char* file, struct windows_stat* st)
int windows_access(const char* file, int mode)
{
return _waccess(u8tou16(file), mode);
return _waccess(convert(file), mode);
}
int windows_mkdir(const char* file)
{
return _wmkdir(u8tou16(file));
return _wmkdir(convert(file));
}
int lstat_ex(const char* file, struct windows_stat* st)
@ -300,7 +377,7 @@ int lstat_ex(const char* file, struct windows_stat* st)
* Use FILE_FLAG_BACKUP_SEMANTICS to open directories and to override the file security checks.
* Use FILE_FLAG_OPEN_REPARSE_POINT to open symbolic links and not the their target.
*/
h = CreateFileW(u8tou16(file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
h = CreateFileW(convert(file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
@ -333,7 +410,7 @@ int windows_stat(const char* file, struct windows_stat* st)
*
* Use FILE_FLAG_BACKUP_SEMANTICS to open directories and to override the file security checks.
*/
h = CreateFileW(u8tou16(file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
h = CreateFileW(convert(file), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (h == INVALID_HANDLE_VALUE) {
windows_errno(GetLastError());
return -1;
@ -434,7 +511,7 @@ int windows_rename(const char* a, const char* b)
* Is an atomic file rename (with overwrite) possible on Windows?
* http://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows
*/
if (!MoveFileExW(u8tou16(a), u8tou16(b), MOVEFILE_REPLACE_EXISTING)) {
if (!MoveFileExW(convert(a), u8tou16(b), MOVEFILE_REPLACE_EXISTING)) {
windows_errno(GetLastError());
return -1;
}
@ -444,7 +521,7 @@ int windows_rename(const char* a, const char* b)
int windows_remove(const char* a)
{
if (_wremove(u8tou16(a)) != 0) {
if (_wremove(convert(a)) != 0) {
windows_errno(GetLastError());
return -1;
}
@ -454,7 +531,7 @@ int windows_remove(const char* a)
FILE* windows_fopen(const char* file, const char* mode)
{
return _wfopen(u8tou16(file), u8tou16(mode));
return _wfopen(convert(file), u8tou16(mode));
}
int windows_open(const char* file, int flags, ...)
@ -464,9 +541,9 @@ int windows_open(const char* file, int flags, ...)
va_start(args, flags);
if ((flags & O_CREAT) != 0)
ret = _wopen(u8tou16(file), flags, va_arg(args, int));
ret = _wopen(convert(file), flags, va_arg(args, int));
else
ret = _wopen(u8tou16(file), flags);
ret = _wopen(convert(file), flags);
va_end(args);
return ret;
@ -484,12 +561,12 @@ windows_dir* windows_opendir(const char* dir)
exit(EXIT_FAILURE);
}
wdir = u8tou16(dir);
wdir = convert(dir);
/* add final / and * */
len = wcslen(wdir);
if (len!= 0 && wdir[len-1] != '/')
wdir[len++] = L'/';
if (len!= 0 && wdir[len-1] != '\\')
wdir[len++] = L'\\';
wdir[len++] = L'*';
wdir[len++] = 0;

View file

@ -23,10 +23,10 @@
#include <wchar.h>
/**
* Redefines PATH_MAX to allow long UTF8 names.
* Redefines PATH_MAX to allow very long paths.
*/
#undef PATH_MAX
#define PATH_MAX (1024+64)
#define PATH_MAX 1024
/* Remap functions and types */
#define fopen windows_fopen

View file

@ -130,21 +130,21 @@ static inline int stat_hidden(struct dirent* dd, struct stat* st)
*/
static inline const char* stat_desc(struct stat* st)
{
if (S_ISREG(st.st_mode))
if (S_ISREG(st->st_mode))
return "regular";
if (S_ISDIR(st.st_mode))
if (S_ISDIR(st->st_mode))
return "directory";
if (S_ISCHR(st.st_mode))
if (S_ISCHR(st->st_mode))
return "character";
if (S_ISBLK(st.st_mode))
if (S_ISBLK(st->st_mode))
return "block-device";
if (S_ISFIFO(st.st_mode))
if (S_ISFIFO(st->st_mode))
return "fifo";
if (S_ISLNK(st.st_mode))
if (S_ISLNK(st->st_mode))
return "link";
if (S_ISLNK(st.st_mode))
if (S_ISLNK(st->st_mode))
return "symbolic-link";
if (S_ISSOCK(st.st_mode))
if (S_ISSOCK(st->st_mode))
return "socket";
return "unknown";
}