cosmopolitan/libc/proc/fork-nt.c
2025-01-03 19:01:58 -08:00

299 lines
11 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/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/errno.h"
#include "libc/intrin/directmap.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/maps.h"
#include "libc/intrin/strace.h"
#include "libc/intrin/weaken.h"
#include "libc/limits.h"
#include "libc/macros.h"
#include "libc/nt/enum/creationdisposition.h"
#include "libc/nt/enum/filemapflags.h"
#include "libc/nt/enum/memflags.h"
#include "libc/nt/enum/pageflags.h"
#include "libc/nt/enum/processcreationflags.h"
#include "libc/nt/enum/startf.h"
#include "libc/nt/errors.h"
#include "libc/nt/memory.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/struct/processinformation.h"
#include "libc/nt/struct/startupinfo.h"
#include "libc/nt/thread.h"
#include "libc/nt/thunk/msabi.h"
#include "libc/nt/winsock.h"
#include "libc/proc/proc.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/symbols.internal.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/tls.h"
#ifdef __x86_64__
extern long __klog_handle;
extern bool __winmain_isfork;
extern intptr_t __winmain_jmpbuf[5];
extern struct CosmoTib *__winmain_tib;
__msabi extern typeof(TlsAlloc) *const __imp_TlsAlloc;
__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx;
__msabi extern typeof(VirtualProtectEx) *const __imp_VirtualProtectEx;
textwindows wontreturn static void AbortFork(const char *func, void *addr) {
#if SYSDEBUG
kprintf("fork() %!s(%lx) failed with win32 error %u\n", func, addr,
GetLastError());
#endif
TerminateThisProcess(SIGSTKFLT);
}
textwindows static void ViewOrDie(int64_t h, uint32_t access, size_t pos,
size_t size, void *base) {
TryAgain:
if (!__imp_MapViewOfFileEx(h, access, pos >> 32, pos, size, base)) {
if ((access & kNtFileMapExecute) &&
GetLastError() == kNtErrorAccessDenied) {
access &= ~kNtFileMapExecute;
goto TryAgain;
}
AbortFork("ViewOrDie", base);
}
}
textwindows static void sys_fork_nt_child(void) {
// setup runtime
__klog_handle = 0;
__tls_index = __imp_TlsAlloc();
__set_tls_win32(__winmain_tib);
__tls_enabled = true;
// resurrect shared memory mappings
struct Map *next;
for (struct Map *map = __maps_first(); map; map = next) {
next = __maps_next(map);
// cleanup nofork mappings
if (map->flags & MAP_NOFORK) {
if ((map->flags & MAP_TYPE) != MAP_FILE) {
tree_remove(&__maps.maps, &map->tree);
__maps.pages -= (map->size + __pagesize - 1) / __pagesize;
__maps.count -= 1;
__maps_free(map);
}
continue;
}
// private maps already copied/protected to child by parent
if ((map->flags & MAP_TYPE) != MAP_SHARED) {
// it's not copy-on-write anymore
map->iscow = false;
// but it used VirtualAlloc() so munmap() must VirtualFree()
if (map->hand > 0) {
CloseHandle(map->hand);
map->hand = MAPS_VIRTUAL;
}
continue;
}
// handle granularity aligned shared mapping
if (__maps_isalloc(map)) {
// get true size of win32 allocation
size_t allocsize = map->size;
for (struct Map *map2 = next; map2; map2 = __maps_next(map2)) {
if (!__maps_isalloc(map2) && map->addr + allocsize == map2->addr) {
allocsize += map2->size;
} else {
break;
}
}
// create allocation with most permissive access possible
// if we don't create as rwx then we can't mprotect(rwx) later
unsigned access;
if (map->readonlyfile) {
access = kNtFileMapRead | kNtFileMapExecute;
} else {
access = kNtFileMapWrite | kNtFileMapExecute;
}
// resurrect copyless memory via inherited win32 handle
ViewOrDie(map->hand, access, map->off, allocsize, map->addr);
}
// restore memory protection status on pages
unsigned old_protect;
if (!__imp_VirtualProtectEx(GetCurrentProcess(), map->addr, map->size,
__prot2nt(map->prot, false), &old_protect))
AbortFork("VirtualProtectEx", map->addr);
}
// function tracing is now safe
ftrace_enabled(+1);
// initialize winsock
void WinSockFork(void);
if (_weaken(WinSockFork))
_weaken(WinSockFork)();
// rewrap the stdin named pipe hack
// since the handles closed on fork
g_fds.p[0].handle = GetStdHandle(kNtStdInputHandle);
g_fds.p[1].handle = GetStdHandle(kNtStdOutputHandle);
g_fds.p[2].handle = GetStdHandle(kNtStdErrorHandle);
}
textwindows static int sys_fork_nt_parent(uint32_t dwCreationFlags) {
// allocate process object
struct Proc *proc;
if (!(proc = __proc_new()))
return -1;
// get path of this executable
char16_t prog[PATH_MAX];
unsigned got = GetModuleFileName(0, prog, ARRAYLEN(prog));
if (!got || got >= ARRAYLEN(prog)) {
dll_make_first(&__proc.free, &proc->elem);
enomem();
return -1;
}
// spawn new process in suspended state
struct NtProcessInformation procinfo;
struct NtStartupInfo startinfo = {
.cb = sizeof(struct NtStartupInfo),
.dwFlags = kNtStartfUsestdhandles,
.hStdInput = g_fds.p[0].handle,
.hStdOutput = g_fds.p[1].handle,
.hStdError = g_fds.p[2].handle,
};
if (!CreateProcess(prog, 0, 0, 0, true,
dwCreationFlags | kNtCreateSuspended |
kNtInheritParentAffinity |
kNtCreateUnicodeEnvironment |
GetPriorityClass(GetCurrentProcess()),
0, 0, &startinfo, &procinfo)) {
STRACE("fork() %s() failed w/ %m %d", "CreateProcess", GetLastError());
dll_make_first(&__proc.free, &proc->elem);
if (errno != ENOMEM)
eagain();
return -1;
}
// ensure process can be signaled before returning
UnmapViewOfFile(__sig_map_process(procinfo.dwProcessId, kNtOpenAlways));
// let's go
bool ok = true;
uint32_t child_old_protect;
uint32_t parent_old_protect;
// copy memory manager maps
for (struct MapSlab *slab =
atomic_load_explicit(&__maps.slabs, memory_order_acquire);
slab; slab = slab->next) {
ok = ok && !!VirtualAllocEx(procinfo.hProcess, slab, MAPS_SIZE,
kNtMemReserve | kNtMemCommit, kNtPageReadwrite);
ok =
ok && !!WriteProcessMemory(procinfo.hProcess, slab, slab, MAPS_SIZE, 0);
}
// copy private memory maps
for (struct Map *map = __maps_first(); map; map = __maps_next(map)) {
if ((map->flags & MAP_TYPE) == MAP_SHARED)
continue;
if ((map->flags & MAP_NOFORK) && (map->flags & MAP_TYPE) != MAP_FILE)
continue;
if (__maps_isalloc(map)) {
size_t allocsize = map->size;
for (struct Map *m2 = __maps_next(map); m2; m2 = __maps_next(m2)) {
if (!__maps_isalloc(m2) && map->addr + allocsize == m2->addr) {
allocsize += m2->size;
} else {
break;
}
}
if ((map->flags & MAP_NOFORK) && (map->flags & MAP_TYPE) == MAP_FILE) {
ok = ok && !!VirtualProtectEx(procinfo.hProcess, map->addr, allocsize,
kNtPageReadwrite, &child_old_protect);
} else {
ok = ok && !!VirtualAllocEx(procinfo.hProcess, map->addr, allocsize,
kNtMemReserve | kNtMemCommit,
kNtPageExecuteReadwrite);
}
}
if (!(map->prot & PROT_READ))
ok = ok && !!VirtualProtect(map->addr, map->size, kNtPageReadwrite,
&parent_old_protect);
ok = ok && !!WriteProcessMemory(procinfo.hProcess, map->addr, map->addr,
map->size, 0);
ok = ok &&
!!VirtualProtectEx(procinfo.hProcess, map->addr, map->size,
__prot2nt(map->prot, false), &child_old_protect);
if (!(map->prot & PROT_READ))
ok = ok && !!VirtualProtect(map->addr, map->size, parent_old_protect,
&parent_old_protect);
}
// set process loose
ok = ok && ResumeThread(procinfo.hThread) != -1u;
ok &= !!CloseHandle(procinfo.hThread);
// return pid of new process
if (ok) {
proc->wasforked = true;
proc->handle = procinfo.hProcess;
proc->pid = procinfo.dwProcessId;
__proc_add(proc);
return procinfo.dwProcessId;
} else {
if (errno != ENOMEM)
eagain(); // posix fork() only specifies two errors
TerminateProcess(procinfo.hProcess, SIGKILL);
CloseHandle(procinfo.hProcess);
dll_make_first(&__proc.free, &proc->elem);
return -1;
}
}
textwindows int sys_fork_nt(uint32_t dwCreationFlags) {
int rc;
__winmain_isfork = true;
__winmain_tib = __get_tls();
if (!__builtin_setjmp(__winmain_jmpbuf)) {
rc = sys_fork_nt_parent(dwCreationFlags);
} else {
sys_fork_nt_child();
rc = 0;
}
__winmain_isfork = false;
return rc;
}
#endif /* __x86_64__ */