@@ -502,7 +502,7 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
502
502
* to append to the file.
503
503
*/
504
504
handle = CreateFileW (wfilename , FILE_APPEND_DATA ,
505
- FILE_SHARE_WRITE | FILE_SHARE_READ ,
505
+ FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE ,
506
506
NULL , create , FILE_ATTRIBUTE_NORMAL , NULL );
507
507
if (handle == INVALID_HANDLE_VALUE ) {
508
508
DWORD err = GetLastError ();
@@ -532,6 +532,70 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
532
532
return fd ;
533
533
}
534
534
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
+
535
599
/*
536
600
* Does the pathname map to the local named pipe filesystem?
537
601
* That is, does it have a "//./pipe/" prefix?
@@ -567,6 +631,8 @@ int mingw_open (const char *filename, int oflags, ...)
567
631
568
632
if ((oflags & O_APPEND ) && !is_local_named_pipe_path (filename ))
569
633
open_fn = mingw_open_append ;
634
+ else if (!(oflags & ~(O_ACCMODE | O_NOINHERIT )))
635
+ open_fn = mingw_open_existing ;
570
636
else
571
637
open_fn = _wopen ;
572
638
@@ -1006,7 +1072,7 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
1006
1072
1007
1073
osfilehandle = CreateFileW (wfilename ,
1008
1074
FILE_WRITE_ATTRIBUTES ,
1009
- 0 /*FileShare.None*/ ,
1075
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE ,
1010
1076
NULL ,
1011
1077
OPEN_EXISTING ,
1012
1078
(attrs != INVALID_FILE_ATTRIBUTES &&
@@ -2155,10 +2221,16 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
2155
2221
#undef rename
2156
2222
int mingw_rename (const char * pold , const char * pnew )
2157
2223
{
2224
+ static int supports_file_rename_info_ex = 1 ;
2158
2225
DWORD attrs , gle ;
2159
2226
int tries = 0 ;
2160
2227
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 )
2162
2234
return -1 ;
2163
2235
2164
2236
/*
@@ -2169,11 +2241,84 @@ int mingw_rename(const char *pold, const char *pnew)
2169
2241
return 0 ;
2170
2242
if (errno != EEXIST )
2171
2243
return -1 ;
2244
+
2172
2245
repeat :
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
+
2175
2321
/* TODO: translate more errors */
2176
- gle = GetLastError ();
2177
2322
if (gle == ERROR_ACCESS_DENIED &&
2178
2323
(attrs = GetFileAttributesW (wpnew )) != INVALID_FILE_ATTRIBUTES ) {
2179
2324
if (attrs & FILE_ATTRIBUTE_DIRECTORY ) {
0 commit comments