diff --git a/HISTORY b/HISTORY index 94006c67..8c8325c7 100644 --- a/HISTORY +++ b/HISTORY @@ -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 ========== diff --git a/TODO b/TODO index d9538489..2f3aaf4f 100644 --- a/TODO +++ b/TODO @@ -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. diff --git a/mingw.c b/mingw.c index 0a6e629a..04ea492d 100644 --- a/mingw.c +++ b/mingw.c @@ -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; diff --git a/mingw.h b/mingw.h index ee62a1b3..2d7b2420 100644 --- a/mingw.h +++ b/mingw.h @@ -23,10 +23,10 @@ #include /** - * 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 diff --git a/portable.h b/portable.h index 27fb8d5a..4358c426 100644 --- a/portable.h +++ b/portable.h @@ -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"; }