Win32: Unicode file name support (except dirent)

Replaces Windows "ANSI" APIs dealing with file- or path names with their Unicode equivalent, adding UTF-8/UTF-16LE conversion as necessary. The dirent API (opendir/readdir/closedir) is updated in a separate commit. Adds trivial wrappers for access, chmod and chdir. Adds wrapper for mktemp (needed for both mkstemp and mkdtemp). The simplest way to convert a repository with legacy-encoded (e.g. Cp1252) file names to UTF-8 ist to checkout with an old msysgit version and "git add --all & git commit" with the new version. Includes a fix for bug reported by John Chen: On Windows XP (not Win7), directories cannot be deleted while a find handle is open, causing "Deletion of directory '...' failed. Should I try again?" prompts. Prior to this commit, these failures were silently ignored due to strbuf_free in is_dir_empty resetting GetLastError to ERROR_SUCCESS. Close the find handle in is_dir_empty so that git doesn't block deletion of the directory even after all other applications have released it. Reported-by: John Chen <john0312@gmail.com> Signed-off-by: Karsten Blees <blees@dcon.de> Signed-off-by: Stepan Kasal <kasal@ucw.cz> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karsten Blees committed Mar 15, 2012 at 18:21 UTC 85faec9d3acd8bb20e26e2d3386f7d3708d02fca
2 files changed +160 -56
compat/mingw.c
+146 -52
@@ -1,6 +1,7 @@
1 #include "../git-compat-util.h"
2 #include "win32.h"
3 #include <conio.h>
4 +#include <wchar.h>
5 #include "../strbuf.h"
6 #include "../run-command.h"
7
@@ -198,14 +199,16 @@ static int ask_yes_no_if_possible(const char *format, ...)
199 }
200 }
201
201 -#undef unlink
202 int mingw_unlink(const char *pathname)
203 {
204 int ret, tries = 0;
205 + wchar_t wpathname[MAX_PATH];
206 + if (xutftowcs_path(wpathname, pathname) < 0)
207 + return -1;
208
209 /* read-only files cannot be removed */
207 - chmod(pathname, 0666);
208 - while ((ret = unlink(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
210 + _wchmod(wpathname, 0666);
211 + while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
212 if (!is_file_in_use_error(GetLastError()))
213 break;
214 /*
@@ -221,45 +224,45 @@ int mingw_unlink(const char *pathname)
224 while (ret == -1 && is_file_in_use_error(GetLastError()) &&
225 ask_yes_no_if_possible("Unlink of file '%s' failed. "
226 "Should I try again?", pathname))
224 - ret = unlink(pathname);
227 + ret = _wunlink(wpathname);
228 return ret;
229 }
230
228 -static int is_dir_empty(const char *path)
231 +static int is_dir_empty(const wchar_t *wpath)
232 {
230 - struct strbuf buf = STRBUF_INIT;
231 - WIN32_FIND_DATAA findbuf;
233 + WIN32_FIND_DATAW findbuf;
234 HANDLE handle;
233 -
234 - strbuf_addf(&buf, "%s\\*", path);
235 - handle = FindFirstFileA(buf.buf, &findbuf);
236 - if (handle == INVALID_HANDLE_VALUE) {
237 - strbuf_release(&buf);
235 + wchar_t wbuf[MAX_PATH + 2];
236 + wcscpy(wbuf, wpath);
237 + wcscat(wbuf, L"\\*");
238 + handle = FindFirstFileW(wbuf, &findbuf);
239 + if (handle == INVALID_HANDLE_VALUE)
240 return GetLastError() == ERROR_NO_MORE_FILES;
239 - }
241
241 - while (!strcmp(findbuf.cFileName, ".") ||
242 - !strcmp(findbuf.cFileName, ".."))
243 - if (!FindNextFile(handle, &findbuf)) {
244 - strbuf_release(&buf);
245 - return GetLastError() == ERROR_NO_MORE_FILES;
242 + while (!wcscmp(findbuf.cFileName, L".") ||
243 + !wcscmp(findbuf.cFileName, L".."))
244 + if (!FindNextFileW(handle, &findbuf)) {
245 + DWORD err = GetLastError();
246 + FindClose(handle);
247 + return err == ERROR_NO_MORE_FILES;
248 }
249 FindClose(handle);
248 - strbuf_release(&buf);
250 return 0;
251 }
252
252 -#undef rmdir
253 int mingw_rmdir(const char *pathname)
254 {
255 int ret, tries = 0;
256 + wchar_t wpathname[MAX_PATH];
257 + if (xutftowcs_path(wpathname, pathname) < 0)
258 + return -1;
259
257 - while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) {
260 + while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
261 if (!is_file_in_use_error(GetLastError()))
262 errno = err_win_to_posix(GetLastError());
263 if (errno != EACCES)
264 break;
262 - if (!is_dir_empty(pathname)) {
265 + if (!is_dir_empty(wpathname)) {
266 errno = ENOTEMPTY;
267 break;
268 }
@@ -276,16 +279,26 @@ int mingw_rmdir(const char *pathname)
279 while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) &&
280 ask_yes_no_if_possible("Deletion of directory '%s' failed. "
281 "Should I try again?", pathname))
279 - ret = rmdir(pathname);
282 + ret = _wrmdir(wpathname);
283 + return ret;
284 +}
285 +
286 +int mingw_mkdir(const char *path, int mode)
287 +{
288 + int ret;
289 + wchar_t wpath[MAX_PATH];
290 + if (xutftowcs_path(wpath, path) < 0)
291 + return -1;
292 + ret = _wmkdir(wpath);
293 return ret;
294 }
295
283 -#undef open
296 int mingw_open (const char *filename, int oflags, ...)
297 {
298 va_list args;
299 unsigned mode;
300 int fd;
301 + wchar_t wfilename[MAX_PATH];
302
303 va_start(args, oflags);
304 mode = va_arg(args, int);
@@ -294,10 +307,12 @@ int mingw_open (const char *filename, int oflags, ...)
307 if (filename && !strcmp(filename, "/dev/null"))
308 filename = "nul";
309
297 - fd = open(filename, oflags, mode);
310 + if (xutftowcs_path(wfilename, filename) < 0)
311 + return -1;
312 + fd = _wopen(wfilename, oflags, mode);
313
314 if (fd < 0 && (oflags & O_CREAT) && errno == EACCES) {
300 - DWORD attrs = GetFileAttributes(filename);
315 + DWORD attrs = GetFileAttributesW(wfilename);
316 if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
317 errno = EISDIR;
318 }
@@ -332,17 +347,28 @@ int mingw_fgetc(FILE *stream)
347 #undef fopen
348 FILE *mingw_fopen (const char *filename, const char *otype)
349 {
350 + FILE *file;
351 + wchar_t wfilename[MAX_PATH], wotype[4];
352 if (filename && !strcmp(filename, "/dev/null"))
353 filename = "nul";
337 - return fopen(filename, otype);
354 + if (xutftowcs_path(wfilename, filename) < 0 ||
355 + xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
356 + return NULL;
357 + file = _wfopen(wfilename, wotype);
358 + return file;
359 }
360
340 -#undef freopen
361 FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
362 {
363 + FILE *file;
364 + wchar_t wfilename[MAX_PATH], wotype[4];
365 if (filename && !strcmp(filename, "/dev/null"))
366 filename = "nul";
345 - return freopen(filename, otype, stream);
367 + if (xutftowcs_path(wfilename, filename) < 0 ||
368 + xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
369 + return NULL;
370 + file = _wfreopen(wfilename, wotype, stream);
371 + return file;
372 }
373
374 #undef fflush
@@ -367,6 +393,31 @@ int mingw_fflush(FILE *stream)
393 return ret;
394 }
395
396 +int mingw_access(const char *filename, int mode)
397 +{
398 + wchar_t wfilename[MAX_PATH];
399 + if (xutftowcs_path(wfilename, filename) < 0)
400 + return -1;
401 + /* X_OK is not supported by the MSVCRT version */
402 + return _waccess(wfilename, mode & ~X_OK);
403 +}
404 +
405 +int mingw_chdir(const char *dirname)
406 +{
407 + wchar_t wdirname[MAX_PATH];
408 + if (xutftowcs_path(wdirname, dirname) < 0)
409 + return -1;
410 + return _wchdir(wdirname);
411 +}
412 +
413 +int mingw_chmod(const char *filename, int mode)
414 +{
415 + wchar_t wfilename[MAX_PATH];
416 + if (xutftowcs_path(wfilename, filename) < 0)
417 + return -1;
418 + return _wchmod(wfilename, mode);
419 +}
420 +
421 /*
422 * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC.
423 * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch.
@@ -392,10 +443,12 @@ static inline time_t filetime_to_time_t(const FILETIME *ft)
443 */
444 static int do_lstat(int follow, const char *file_name, struct stat *buf)
445 {
395 - int err;
446 WIN32_FILE_ATTRIBUTE_DATA fdata;
447 + wchar_t wfilename[MAX_PATH];
448 + if (xutftowcs_path(wfilename, file_name) < 0)
449 + return -1;
450
398 - if (!(err = get_file_attr(file_name, &fdata))) {
451 + if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
452 buf->st_ino = 0;
453 buf->st_gid = 0;
454 buf->st_uid = 0;
@@ -408,8 +461,8 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
461 buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
462 buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
463 if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
411 - WIN32_FIND_DATAA findbuf;
412 - HANDLE handle = FindFirstFileA(file_name, &findbuf);
464 + WIN32_FIND_DATAW findbuf;
465 + HANDLE handle = FindFirstFileW(wfilename, &findbuf);
466 if (handle != INVALID_HANDLE_VALUE) {
467 if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
468 (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
@@ -428,7 +481,23 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
481 }
482 return 0;
483 }
431 - errno = err;
484 + switch (GetLastError()) {
485 + case ERROR_ACCESS_DENIED:
486 + case ERROR_SHARING_VIOLATION:
487 + case ERROR_LOCK_VIOLATION:
488 + case ERROR_SHARING_BUFFER_EXCEEDED:
489 + errno = EACCES;
490 + break;
491 + case ERROR_BUFFER_OVERFLOW:
492 + errno = ENAMETOOLONG;
493 + break;
494 + case ERROR_NOT_ENOUGH_MEMORY:
495 + errno = ENOMEM;
496 + break;
497 + default:
498 + errno = ENOENT;
499 + break;
500 + }
501 return -1;
502 }
503
@@ -516,16 +585,20 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
585 {
586 FILETIME mft, aft;
587 int fh, rc;
588 + DWORD attrs;
589 + wchar_t wfilename[MAX_PATH];
590 + if (xutftowcs_path(wfilename, file_name) < 0)
591 + return -1;
592
593 /* must have write permission */
521 - DWORD attrs = GetFileAttributes(file_name);
594 + attrs = GetFileAttributesW(wfilename);
595 if (attrs != INVALID_FILE_ATTRIBUTES &&
596 (attrs & FILE_ATTRIBUTE_READONLY)) {
597 /* ignore errors here; open() will report them */
525 - SetFileAttributes(file_name, attrs & ~FILE_ATTRIBUTE_READONLY);
598 + SetFileAttributesW(wfilename, attrs & ~FILE_ATTRIBUTE_READONLY);
599 }
600
528 - if ((fh = open(file_name, O_RDWR | O_BINARY)) < 0) {
601 + if ((fh = _wopen(wfilename, O_RDWR | O_BINARY)) < 0) {
602 rc = -1;
603 goto revert_attrs;
604 }
@@ -548,7 +621,7 @@ revert_attrs:
621 if (attrs != INVALID_FILE_ATTRIBUTES &&
622 (attrs & FILE_ATTRIBUTE_READONLY)) {
623 /* ignore errors again */
551 - SetFileAttributes(file_name, attrs);
624 + SetFileAttributesW(wfilename, attrs);
625 }
626 return rc;
627 }
@@ -559,6 +632,18 @@ unsigned int sleep (unsigned int seconds)
632 return 0;
633 }
634
635 +char *mingw_mktemp(char *template)
636 +{
637 + wchar_t wtemplate[MAX_PATH];
638 + if (xutftowcs_path(wtemplate, template) < 0)
639 + return NULL;
640 + if (!_wmktemp(wtemplate))
641 + return NULL;
642 + if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0)
643 + return NULL;
644 + return template;
645 +}
646 +
647 int mkstemp(char *template)
648 {
649 char *filename = mktemp(template);
@@ -617,17 +702,18 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
702 return result;
703 }
704
620 -#undef getcwd
705 char *mingw_getcwd(char *pointer, int len)
706 {
707 int i;
624 - char *ret = getcwd(pointer, len);
625 - if (!ret)
626 - return ret;
708 + wchar_t wpointer[MAX_PATH];
709 + if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
710 + return NULL;
711 + if (xwcstoutf(pointer, wpointer, len) < 0)
712 + return NULL;
713 for (i = 0; pointer[i]; i++)
714 if (pointer[i] == '\\')
715 pointer[i] = '/';
630 - return ret;
716 + return pointer;
717 }
718
719 /*
@@ -1485,33 +1571,36 @@ int mingw_rename(const char *pold, const char *pnew)
1571 {
1572 DWORD attrs, gle;
1573 int tries = 0;
1574 + wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1575 + if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1576 + return -1;
1577
1578 /*
1579 * Try native rename() first to get errno right.
1580 * It is based on MoveFile(), which cannot overwrite existing files.
1581 */
1493 - if (!rename(pold, pnew))
1582 + if (!_wrename(wpold, wpnew))
1583 return 0;
1584 if (errno != EEXIST)
1585 return -1;
1586 repeat:
1498 - if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
1587 + if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
1588 return 0;
1589 /* TODO: translate more errors */
1590 gle = GetLastError();
1591 if (gle == ERROR_ACCESS_DENIED &&
1503 - (attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) {
1592 + (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
1593 if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
1594 errno = EISDIR;
1595 return -1;
1596 }
1597 if ((attrs & FILE_ATTRIBUTE_READONLY) &&
1509 - SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
1510 - if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
1598 + SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
1599 + if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
1600 return 0;
1601 gle = GetLastError();
1602 /* revert file attributes on failure */
1514 - SetFileAttributes(pnew, attrs);
1603 + SetFileAttributesW(wpnew, attrs);
1604 }
1605 }
1606 if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
@@ -1757,11 +1846,16 @@ void mingw_open_html(const char *unixpath)
1846
1847 int link(const char *oldpath, const char *newpath)
1848 {
1760 - typedef BOOL (WINAPI *T)(const char*, const char*, LPSECURITY_ATTRIBUTES);
1849 + typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
1850 static T create_hard_link = NULL;
1851 + wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
1852 + if (xutftowcs_path(woldpath, oldpath) < 0 ||
1853 + xutftowcs_path(wnewpath, newpath) < 0)
1854 + return -1;
1855 +
1856 if (!create_hard_link) {
1857 create_hard_link = (T) GetProcAddress(
1764 - GetModuleHandle("kernel32.dll"), "CreateHardLinkA");
1858 + GetModuleHandle("kernel32.dll"), "CreateHardLinkW");
1859 if (!create_hard_link)
1860 create_hard_link = (T)-1;
1861 }
@@ -1769,7 +1863,7 @@ int link(const char *oldpath, const char *newpath)
1863 errno = ENOSYS;
1864 return -1;
1865 }
1772 - if (!create_hard_link(newpath, oldpath, NULL)) {
1866 + if (!create_hard_link(wnewpath, woldpath, NULL)) {
1867 errno = err_win_to_posix(GetLastError());
1868 return -1;
1869 }
compat/mingw.h
+14 -4
@@ -118,10 +118,7 @@ static inline int fcntl(int fd, int cmd, ...)
118 * simple adaptors
119 */
120
121 -static inline int mingw_mkdir(const char *path, int mode)
122 -{
123 - return mkdir(path);
124 -}
121 +int mingw_mkdir(const char *path, int mode);
122 #define mkdir mingw_mkdir
123
124 #define WNOHANG 1
@@ -192,6 +189,19 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream);
189 int mingw_fflush(FILE *stream);
190 #define fflush mingw_fflush
191
192 +int mingw_access(const char *filename, int mode);
193 +#undef access
194 +#define access mingw_access
195 +
196 +int mingw_chdir(const char *dirname);
197 +#define chdir mingw_chdir
198 +
199 +int mingw_chmod(const char *filename, int mode);
200 +#define chmod mingw_chmod
201 +
202 +char *mingw_mktemp(char *template);
203 +#define mktemp mingw_mktemp
204 +
205 char *mingw_getcwd(char *pointer, int len);
206 #define getcwd mingw_getcwd
207