cosmopolitan/libc/proc/wait4-nt.c
Justine Tunney 29eb7e67bb
Fix fork() regression on Windows
Recent optimizations to fork() introduced a regression, that could cause
the subprocess to fail unexpectedly, when TlsAlloc() returns a different
index. This is because we were burning the indexes into the displacement
of x86 opcodes. So when fork() happened and the executable memory copied
it would use the old index. Right now the way this is being solved is to
not copy the executable on fork() and then re-apply code changes. If you
need to be able to preserve self-modified code on fork, reach out and we
can implement a better solution for you. This gets us unblocked quickly.
2025-01-05 09:25:23 -08:00

227 lines
7.9 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 2020 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/assert.h"
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/enum/wait.h"
#include "libc/nt/events.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/proc/proc.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/w.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
textwindows static int __proc_reap(struct Proc *pr, int *wstatus,
struct rusage *opt_out_rusage) {
if (wstatus)
*wstatus = pr->wstatus;
if (opt_out_rusage)
*opt_out_rusage = pr->ru;
dll_remove(&__proc.zombies, &pr->elem);
if (dll_is_empty(__proc.zombies))
ResetEvent(__proc.haszombies);
if (pr->waiters) {
pr->status = PROC_UNDEAD;
dll_make_first(&__proc.undead, &pr->elem);
} else {
dll_make_first(&__proc.free, &pr->elem);
CloseHandle(pr->handle);
}
return pr->pid;
}
textwindows static int __proc_check(int pid, int *wstatus,
struct rusage *opt_out_rusage) {
struct Dll *e;
for (e = dll_first(__proc.zombies); e; e = dll_next(__proc.zombies, e)) {
struct Proc *pr = PROC_CONTAINER(e);
if (pid == -1 || pid == pr->pid)
return __proc_reap(pr, wstatus, opt_out_rusage);
}
return 0;
}
textwindows static int __proc_wait(int pid, int *wstatus, int options,
struct rusage *rusage, sigset_t waitmask) {
for (;;) {
// check for signals and cancelation
int sig, handler_was_called;
if (_check_cancel() == -1)
return -1;
if (_weaken(__sig_get) && (sig = _weaken(__sig_get)(waitmask))) {
handler_was_called = _weaken(__sig_relay)(sig, SI_KERNEL, waitmask);
if (_check_cancel() == -1)
return -1; // ECANCELED because SIGTHR was just handled
if (handler_was_called & SIG_HANDLED_NO_RESTART)
return eintr(); // a non-SA_RESTART handler was called
}
// check for zombie to harvest
__proc_lock();
CheckForZombies:
int rc = __proc_check(pid, wstatus, rusage);
// if there's no zombies left
// check if there's any living processes
if (!rc && dll_is_empty(__proc.list)) {
__proc_unlock();
return echild();
}
// otherwise return zombie or zero
if (rc || (options & WNOHANG)) {
__proc_unlock();
return rc;
}
// get appropriate wait object
// register ourself as waiting
struct Proc *pr = 0;
uintptr_t hWaitObject;
if (pid == -1) {
// wait for any status change
hWaitObject = __proc.haszombies;
++__proc.waiters;
} else {
// wait on specific child
for (struct Dll *e = dll_first(__proc.list); e;
e = dll_next(__proc.list, e)) {
pr = PROC_CONTAINER(e);
if (pid == pr->pid)
break;
}
if (pr) {
// by making the waiter count non-zero, the proc daemon stops
// being obligated to monitor this process. this means we may
// need to assume responsibility later on for zombifying this
++pr->waiters;
hWaitObject = pr->handle;
} else {
__proc_unlock();
return echild();
}
}
__proc_unlock();
// perform blocking operation
uint32_t wi;
uintptr_t event;
if ((event = CreateEvent(0, 0, 0, 0))) {
struct PosixThread *pt = _pthread_self();
pt->pt_event = event;
pt->pt_blkmask = waitmask;
atomic_store_explicit(&pt->pt_blocker, PT_BLOCKER_EVENT,
memory_order_release);
wi = WaitForMultipleObjects(2, (intptr_t[2]){hWaitObject, event}, 0, -1u);
atomic_store_explicit(&pt->pt_blocker, 0, memory_order_release);
CloseHandle(event);
} else {
wi = -1u;
}
// log warning if handle unexpectedly closed
if (wi & kNtWaitAbandoned) {
wi &= ~kNtWaitAbandoned;
STRACE("wait4 abandoned %u", wi);
}
// check for wait() style wakeup
__proc_lock();
if (!wi && !pr) {
--__proc.waiters;
goto CheckForZombies;
}
// check if killed or win32 error
if (wi) {
if (pr) {
if (!--pr->waiters && pr->status == PROC_UNDEAD)
__proc_free(pr);
} else {
--__proc.waiters;
}
__proc_unlock();
if (wi == 1) {
// __sig_wake() woke our semaphore
continue;
} else {
// neither posix or win32 define i/o error conditions for
// generic wait. failure should only be due to api misuse
return einval();
}
}
// handle process exit notification
--pr->waiters;
if (pr->status == PROC_ALIVE)
__proc_harvest(pr, true);
switch (pr->status) {
case PROC_ALIVE:
// exit caused by execve() reparenting
if (!pr->waiters)
// avoid deadlock that could theoretically happen
SetEvent(__proc.onbirth);
__proc_unlock();
break;
case PROC_ZOMBIE:
// exit happened and we're the first to know
rc = __proc_reap(pr, wstatus, rusage);
__proc_unlock();
return rc;
case PROC_UNDEAD:
// exit happened but another thread waited first
if (!pr->waiters)
__proc_free(pr);
__proc_unlock();
return echild();
default:
__builtin_unreachable();
}
}
}
textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
struct rusage *opt_out_rusage) {
// no support for WCONTINUED and WUNTRACED yet
if (options & ~WNOHANG)
return einval();
// XXX: NT doesn't really have process groups. For instance the
// CreateProcess() flag for starting a process group actually
// just does an "ignore ctrl-c" internally.
if (pid == 0)
pid = -1;
if (pid < -1)
pid = -pid;
sigset_t m = __sig_block();
int rc = __proc_wait(pid, opt_out_wstatus, options, opt_out_rusage,
m | 1ull << (SIGCHLD - 1));
__sig_unblock(m);
return rc;
}
#endif /* __x86_64__ */