@@ -502,7 +502,7 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
502502 * to append to the file.
503503 */
504504 handle = CreateFileW (wfilename , FILE_APPEND_DATA ,
505- FILE_SHARE_WRITE | FILE_SHARE_READ ,
505+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE ,
506506 NULL , create , FILE_ATTRIBUTE_NORMAL , NULL );
507507 if (handle == INVALID_HANDLE_VALUE ) {
508508 DWORD err = GetLastError ();
@@ -532,6 +532,70 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
532532 return fd ;
533533}
534534
535+ /*
536+ * Ideally, we'd use `_wopen()` to implement this functionality so that we
537+ * don't have to reimplement it, but unfortunately we do not have tight control
538+ * over the share mode there. And while `_wsopen()` and friends exist that give
539+ * us _some_ control over the share mode, this family of functions doesn't give
540+ * us the ability to enable FILE_SHARE_DELETE, either. But this is a strict
541+ * requirement for us though so that we can unlink or rename over files that
542+ * are held open by another process.
543+ *
544+ * We are thus forced to implement our own emulation of `open()`. To make our
545+ * life simpler we only implement basic support for this, namely opening
546+ * existing files for reading and/or writing. This means that newly created
547+ * files won't have their sharing mode set up correctly, but for now I couldn't
548+ * find any case where this matters. We may have to revisit that in the future
549+ * though based on our needs.
550+ */
551+ static int mingw_open_existing (const wchar_t * filename , int oflags , ...)
552+ {
553+ SECURITY_ATTRIBUTES security_attributes = {
554+ .nLength = sizeof (security_attributes ),
555+ .bInheritHandle = !(oflags & O_NOINHERIT ),
556+ };
557+ HANDLE handle ;
558+ DWORD access ;
559+ int fd ;
560+
561+ /* We only support basic flags. */
562+ if (oflags & ~(O_ACCMODE | O_NOINHERIT )) {
563+ errno = ENOSYS ;
564+ return -1 ;
565+ }
566+
567+ switch (oflags & O_ACCMODE ) {
568+ case O_RDWR :
569+ access = GENERIC_READ | GENERIC_WRITE ;
570+ break ;
571+ case O_WRONLY :
572+ access = GENERIC_WRITE ;
573+ break ;
574+ default :
575+ access = GENERIC_READ ;
576+ break ;
577+ }
578+
579+ handle = CreateFileW (filename , access ,
580+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE ,
581+ & security_attributes , OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL );
582+ if (handle == INVALID_HANDLE_VALUE ) {
583+ DWORD err = GetLastError ();
584+
585+ /* See `mingw_open_append()` for why we have this conversion. */
586+ if (err == ERROR_INVALID_PARAMETER )
587+ err = ERROR_PATH_NOT_FOUND ;
588+
589+ errno = err_win_to_posix (err );
590+ return -1 ;
591+ }
592+
593+ fd = _open_osfhandle ((intptr_t )handle , oflags | O_BINARY );
594+ if (fd < 0 )
595+ CloseHandle (handle );
596+ return fd ;
597+ }
598+
535599/*
536600 * Does the pathname map to the local named pipe filesystem?
537601 * That is, does it have a "//./pipe/" prefix?
@@ -567,6 +631,8 @@ int mingw_open (const char *filename, int oflags, ...)
567631
568632 if ((oflags & O_APPEND ) && !is_local_named_pipe_path (filename ))
569633 open_fn = mingw_open_append ;
634+ else if (!(oflags & ~(O_ACCMODE | O_NOINHERIT )))
635+ open_fn = mingw_open_existing ;
570636 else
571637 open_fn = _wopen ;
572638
@@ -1006,7 +1072,7 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
10061072
10071073 osfilehandle = CreateFileW (wfilename ,
10081074 FILE_WRITE_ATTRIBUTES ,
1009- 0 /*FileShare.None*/ ,
1075+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ,
10101076 NULL ,
10111077 OPEN_EXISTING ,
10121078 (attrs != INVALID_FILE_ATTRIBUTES &&
@@ -2155,10 +2221,16 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
21552221#undef rename
21562222int mingw_rename (const char * pold , const char * pnew )
21572223{
2224+ static int supports_file_rename_info_ex = 1 ;
21582225 DWORD attrs , gle ;
21592226 int tries = 0 ;
21602227 wchar_t wpold [MAX_PATH ], wpnew [MAX_PATH ];
2161- if (xutftowcs_path (wpold , pold ) < 0 || xutftowcs_path (wpnew , pnew ) < 0 )
2228+ int wpnew_len ;
2229+
2230+ if (xutftowcs_path (wpold , pold ) < 0 )
2231+ return -1 ;
2232+ wpnew_len = xutftowcs_path (wpnew , pnew );
2233+ if (wpnew_len < 0 )
21622234 return -1 ;
21632235
21642236 /*
@@ -2169,11 +2241,84 @@ int mingw_rename(const char *pold, const char *pnew)
21692241 return 0 ;
21702242 if (errno != EEXIST )
21712243 return -1 ;
2244+
21722245repeat :
2173- if (MoveFileExW (wpold , wpnew , MOVEFILE_REPLACE_EXISTING ))
2174- return 0 ;
2246+ if (supports_file_rename_info_ex ) {
2247+ /*
2248+ * Our minimum required Windows version is still set to Windows
2249+ * Vista. We thus have to declare required infrastructure for
2250+ * FileRenameInfoEx ourselves until we bump _WIN32_WINNT to
2251+ * 0x0A00. Furthermore, we have to handle cases where the
2252+ * FileRenameInfoEx call isn't supported yet.
2253+ */
2254+ #define FILE_RENAME_FLAG_REPLACE_IF_EXISTS 0x00000001
2255+ #define FILE_RENAME_FLAG_POSIX_SEMANTICS 0x00000002
2256+ FILE_INFO_BY_HANDLE_CLASS FileRenameInfoEx = 22 ;
2257+ struct {
2258+ /*
2259+ * This is usually an unnamed union, but that is not
2260+ * part of ISO C99. We thus inline the field, as we
2261+ * really only care for the Flags field anyway.
2262+ */
2263+ DWORD Flags ;
2264+ HANDLE RootDirectory ;
2265+ DWORD FileNameLength ;
2266+ /*
2267+ * The actual structure is defined with a single-character
2268+ * flex array so that the structure has to be allocated on
2269+ * the heap. As we declare this structure ourselves though
2270+ * we can avoid the allocation and define FileName to have
2271+ * MAX_PATH bytes.
2272+ */
2273+ WCHAR FileName [MAX_PATH ];
2274+ } rename_info = { 0 };
2275+ HANDLE old_handle = INVALID_HANDLE_VALUE ;
2276+ BOOL success ;
2277+
2278+ old_handle = CreateFileW (wpold , DELETE ,
2279+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE ,
2280+ NULL , OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL );
2281+ if (old_handle == INVALID_HANDLE_VALUE ) {
2282+ errno = err_win_to_posix (GetLastError ());
2283+ return -1 ;
2284+ }
2285+
2286+ rename_info .Flags = FILE_RENAME_FLAG_REPLACE_IF_EXISTS |
2287+ FILE_RENAME_FLAG_POSIX_SEMANTICS ;
2288+ rename_info .FileNameLength = wpnew_len * sizeof (WCHAR );
2289+ memcpy (rename_info .FileName , wpnew , wpnew_len * sizeof (WCHAR ));
2290+
2291+ success = SetFileInformationByHandle (old_handle , FileRenameInfoEx ,
2292+ & rename_info , sizeof (rename_info ));
2293+ gle = GetLastError ();
2294+ CloseHandle (old_handle );
2295+ if (success )
2296+ return 0 ;
2297+
2298+ /*
2299+ * When we see ERROR_INVALID_PARAMETER we can assume that the
2300+ * current system doesn't support FileRenameInfoEx. Keep us
2301+ * from using it in future calls and retry.
2302+ */
2303+ if (gle == ERROR_INVALID_PARAMETER ) {
2304+ supports_file_rename_info_ex = 0 ;
2305+ goto repeat ;
2306+ }
2307+
2308+ /*
2309+ * In theory, we shouldn't get ERROR_ACCESS_DENIED because we
2310+ * always open files with FILE_SHARE_DELETE But in practice we
2311+ * cannot assume that Git is the only one accessing files, and
2312+ * other applications may not set FILE_SHARE_DELETE. So we have
2313+ * to retry.
2314+ */
2315+ } else {
2316+ if (MoveFileExW (wpold , wpnew , MOVEFILE_REPLACE_EXISTING ))
2317+ return 0 ;
2318+ gle = GetLastError ();
2319+ }
2320+
21752321 /* TODO: translate more errors */
2176- gle = GetLastError ();
21772322 if (gle == ERROR_ACCESS_DENIED &&
21782323 (attrs = GetFileAttributesW (wpnew )) != INVALID_FILE_ATTRIBUTES ) {
21792324 if (attrs & FILE_ATTRIBUTE_DIRECTORY ) {
0 commit comments