cosmopolitan/libc/calls/readwrite-nt.c
Justine Tunney 3f26dfbb31
Share file offset across execve() on Windows
This is a breaking change. It defines the new environment variable named
_COSMO_FDS_V2 which is used for inheriting non-stdio file descriptors on
execve() or posix_spawn(). No effort has been spent thus far integrating
with the older variable. If a new binary launches the older ones or vice
versa they'll only be able to pass stdin / stdout / stderr to each other
therefore it's important that you upgrade all your cosmo binaries if you
depend on this functionality. You'll be glad you did because inheritance
of file descriptors is more aligned with the POSIX standard than before.
2024-08-03 17:48:00 -07:00

165 lines
6.8 KiB
C

/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2023 Justine Alexandra Roberts Tunney │
│ │
│ Permission to use, copy, modify, and/or distribute this software for │
│ any purpose with or without fee is hereby granted, provided that the │
│ above copyright notice and this permission notice appear in all copies. │
│ │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
│ PERFORMANCE OF THIS SOFTWARE. │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/calls/createfileflags.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/intrin/fds.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/enum/filetype.h"
#include "libc/nt/errors.h"
#include "libc/nt/events.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/overlapped.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/stdio/sysparam.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
/**
* Runs code that's common to read/write/pread/pwrite/etc on Windows.
*
* @return bytes exchanged, or -1 w/ errno, or -2 if operation failed
* and caller needs to do more work, examining the GetLastError()
*/
textwindows ssize_t
sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset,
int64_t handle, sigset_t waitmask,
bool32 ReadOrWriteFile(int64_t, void *, uint32_t, uint32_t *,
struct NtOverlapped *)) {
int sig;
uint32_t exchanged;
struct Fd *f = g_fds.p + fd;
// pread() and pwrite() perform an implicit lseek() operation, so
// similar to the lseek() system call, they too raise ESPIPE when
// operating on a non-seekable file.
bool pwriting = offset != -1;
bool isdisk = f->kind == kFdFile && GetFileType(handle) == kNtFileTypeDisk;
bool seekable = isdisk || f->kind == kFdDevNull || f->kind == kFdDevRandom;
if (pwriting && !seekable)
return espipe();
// determine if we need to lock a file descriptor across processes
bool locked = isdisk && !pwriting && f->cursor;
if (locked)
__cursor_lock(f->cursor);
RestartOperation:
// when a file is opened in overlapped mode win32 requires that we
// take over full responsibility for managing our own file pointer
// which is fine, because the one win32 has was never very good in
// the sense that it behaves so differently from linux, that using
// win32 i/o required more compatibilty toil than doing it by hand
if (!pwriting) {
if (seekable && f->cursor) {
offset = f->cursor->shared->pointer;
} else {
offset = 0;
}
}
bool eagained = false;
// check for signals and cancelation
if (_check_cancel() == -1) {
if (locked)
__cursor_unlock(f->cursor);
return -1; // ECANCELED
}
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
goto HandleInterrupt;
}
// signals have already been fully blocked by caller
// perform i/o operation with atomic signal/cancel checking
struct NtOverlapped overlap = {.hEvent = CreateEvent(0, 1, 0, 0),
.Pointer = offset};
bool32 ok = ReadOrWriteFile(handle, data, size, 0, &overlap);
if (!ok && GetLastError() == kNtErrorIoPending) {
// win32 says this i/o operation needs to block
if (f->flags & _O_NONBLOCK) {
// abort the i/o operation if file descriptor is in non-blocking mode
CancelIoEx(handle, &overlap);
eagained = true;
} else {
// wait until i/o either completes or is canceled by another thread
// we avoid a race condition by having a second mask for unblocking
struct PosixThread *pt;
pt = _pthread_self();
pt->pt_blkmask = waitmask;
pt->pt_iohandle = handle;
pt->pt_ioverlap = &overlap;
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_IO,
memory_order_release);
WaitForSingleObject(overlap.hEvent, -1u);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
}
ok = true;
}
if (ok)
ok = GetOverlappedResult(handle, &overlap, &exchanged, true);
CloseHandle(overlap.hEvent);
// if i/o succeeded then return its result
if (ok) {
if (!pwriting && seekable && f->cursor)
f->cursor->shared->pointer = offset + exchanged;
if (locked)
__cursor_unlock(f->cursor);
return exchanged;
}
// only raise EINTR or EAGAIN if I/O got canceled
if (GetLastError() == kNtErrorOperationAborted) {
// raise EAGAIN if it's due to O_NONBLOCK mmode
if (eagained) {
if (locked)
__cursor_unlock(f->cursor);
return eagain();
}
// otherwise it must be due to a kill() via __sig_cancel()
if (_weaken(__sig_relay) && (sig = _weaken(__sig_get)(waitmask))) {
HandleInterrupt:
if (locked)
__cursor_unlock(f->cursor);
int handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1)
return -1; // possible if we SIGTHR'd
if (locked)
__cursor_lock(f->cursor);
// read() is @restartable unless non-SA_RESTART hands were called
if (!(handler_was_called & SIG_HANDLED_NO_RESTART))
goto RestartOperation;
}
if (locked)
__cursor_unlock(f->cursor);
return eintr();
}
// read() and write() have generally different error-handling paths
if (locked)
__cursor_unlock(f->cursor);
return -2;
}
#endif /* __x86_64__ */