mingw: enable atomic O_APPEND

The Windows CRT implements O_APPEND "manually": on write() calls, the file pointer is set to EOF before the data is written. Clearly, this is not atomic. And in fact, this is the root cause of failures observed in t5552-skipping-fetch-negotiator.sh and t5503-tagfollow.sh, where different processes write to the same trace file simultanously; it also occurred in t5400-send-pack.sh, but there it was worked around in 71406ed4d6 ("t5400: avoid concurrent writes into a trace file", 2017-05-18). Fortunately, Windows does support atomic O_APPEND semantics using the file access mode FILE_APPEND_DATA. Provide an implementation that does. This implementation is minimal in such a way that it only implements the open modes that are actually used in the Git code base. Emulation for other modes can be added as necessary later. To become aware of the necessity early, the unusal error ENOSYS is reported if an unsupported mode is encountered. Diagnosed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Helped-by: Jeff Hostetler <git@jeffhostetler.com> Signed-off-by: Johannes Sixt <j6t@kdbg.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Sixt committed Aug 13, 2018 at 21:02 UTC d641097589160eb795127d8dbcb14c877c217b60
1 file changed +39 -2
compat/mingw.c
+39 -2
@@ -341,12 +341,44 @@ int mingw_mkdir(const char *path, int mode)
341 return ret;
342 }
343
344 +static int mingw_open_append(wchar_t const *wfilename, int oflags, ...)
345 +{
346 + HANDLE handle;
347 + int fd;
348 + DWORD create = (oflags & O_CREAT) ? OPEN_ALWAYS : OPEN_EXISTING;
349 +
350 + /* only these flags are supported */
351 + if ((oflags & ~O_CREAT) != (O_WRONLY | O_APPEND))
352 + return errno = ENOSYS, -1;
353 +
354 + /*
355 + * FILE_SHARE_WRITE is required to permit child processes
356 + * to append to the file.
357 + */
358 + handle = CreateFileW(wfilename, FILE_APPEND_DATA,
359 + FILE_SHARE_WRITE | FILE_SHARE_READ,
360 + NULL, create, FILE_ATTRIBUTE_NORMAL, NULL);
361 + if (handle == INVALID_HANDLE_VALUE)
362 + return errno = err_win_to_posix(GetLastError()), -1;
363 + /*
364 + * No O_APPEND here, because the CRT uses it only to reset the
365 + * file pointer to EOF on write(); but that is not necessary
366 + * for a file created with FILE_APPEND_DATA.
367 + */
368 + fd = _open_osfhandle((intptr_t)handle, O_BINARY);
369 + if (fd < 0)
370 + CloseHandle(handle);
371 + return fd;
372 +}
373 +
374 int mingw_open (const char *filename, int oflags, ...)
375 {
376 + typedef int (*open_fn_t)(wchar_t const *wfilename, int oflags, ...);
377 va_list args;
378 unsigned mode;
379 int fd;
380 wchar_t wfilename[MAX_PATH];
381 + open_fn_t open_fn;
382
383 va_start(args, oflags);
384 mode = va_arg(args, int);
@@ -355,9 +387,14 @@ int mingw_open (const char *filename, int oflags, ...)
387 if (filename && !strcmp(filename, "/dev/null"))
388 filename = "nul";
389
390 + if (oflags & O_APPEND)
391 + open_fn = mingw_open_append;
392 + else
393 + open_fn = _wopen;
394 +
395 if (xutftowcs_path(wfilename, filename) < 0)
396 return -1;
360 - fd = _wopen(wfilename, oflags, mode);
397 + fd = open_fn(wfilename, oflags, mode);
398
399 if (fd < 0 && (oflags & O_ACCMODE) != O_RDONLY && errno == EACCES) {
400 DWORD attrs = GetFileAttributesW(wfilename);
@@ -375,7 +412,7 @@ int mingw_open (const char *filename, int oflags, ...)
412 * CREATE_ALWAYS flag of CreateFile()).
413 */
414 if (fd < 0 && errno == EACCES)
378 - fd = _wopen(wfilename, oflags & ~O_CREAT, mode);
415 + fd = open_fn(wfilename, oflags & ~O_CREAT, mode);
416 if (fd >= 0 && set_hidden_flag(wfilename, 1))
417 warning("could not mark '%s' as hidden.", filename);
418 }