Skip to content

Commit a270cb1

Browse files
pks-tttaylorr
authored andcommitted
compat/mingw: allow deletion of most opened files
On Windows, we emulate open(3p) via `mingw_open()`. This function implements handling of some platform-specific quirks that are required to make it behave as closely as possible like open(3p) would, but for most cases we just call the Windows-specific `_wopen()` function. This function has a major downside though: it does not allow us to specify the sharing mode. While there is `_wsopen()` that allows us to pass sharing flags, those sharing flags are not the same `FILE_SHARE_*` flags as `CreateFileW()` accepts. Instead, `_wsopen()` only allows concurrent read- and write-access, but does not allow for concurrent deletions. Unfortunately though, we have to allow concurrent deletions if we want to have POSIX-style atomic renames on top of an existing file that has open file handles. Implement a new function that emulates open(3p) for existing files via `CreateFileW()` such that we can set the required sharing flags. While we have the same issue when calling open(3p) with `O_CREAT`, implementing that mode would be more complex due to the required permission handling. Furthermore, atomic updates via renames typically write to exclusive lockfile and then perform the rename, and thus we don't have to handle the case where the locked path has been created with `O_CREATE`. So while it would be nice to have proper POSIX semantics in all paths, we instead aim for a minimum viable fix here. Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Taylor Blau <[email protected]>
1 parent b0b65ec commit a270cb1

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

compat/mingw.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)