diff --git a/libc/calls/sig.c b/libc/calls/sig.c index 3347e4114..48137ec34 100644 --- a/libc/calls/sig.c +++ b/libc/calls/sig.c @@ -596,6 +596,9 @@ static textwindows int __sig_console_sig(uint32_t dwCtrlType) { } __msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) { + // win32 launches a thread to deliver ctrl-c and ctrl-break when typed + // it only happens when kNtEnableProcessedInput is in play on console. + // otherwise we need to wait until read-nt.c discovers that keystroke. struct CosmoTib tls; __bootstrap_tls(&tls, __builtin_frame_address(0)); __sig_generate(__sig_console_sig(dwCtrlType), SI_KERNEL); @@ -616,7 +619,12 @@ textwindows int __sig_check(void) { } } -// delivers signals from other processes asynchronously +// background thread for delivering inter-process signals asynchronously +// this checks for undelivered process-wide signals, once per scheduling +// quantum, which on windows should be every ~15ms or so, unless somehow +// the process was tuned to have more fine-grained event timing. we want +// signals to happen faster when possible; that happens when cancelation +// points, e.g. read need to wait on i/o; they too check for new signals textwindows dontinstrument static uint32_t __sig_worker(void *arg) { struct CosmoTib tls; __bootstrap_tls(&tls, __builtin_frame_address(0)); @@ -624,9 +632,16 @@ textwindows dontinstrument static uint32_t __sig_worker(void *arg) { __maps_track((char *)(((uintptr_t)sp + __pagesize - 1) & -__pagesize) - STKSZ, STKSZ); for (;;) { - int sig; - if ((sig = __sig_getter(__sig.process, 0))) + // dequeue all pending signals and fire them off. if there's no + // thread that can handle them then __sig_generate will requeue + // those signals back to __sig.process; hence the need for xchg + unsigned long sigs = + atomic_exchange_explicit(__sig.process, 0, memory_order_acq_rel); + while (sigs) { + int sig = bsfl(sigs) + 1; + sigs &= ~(1ull << (sig - 1)); __sig_generate(sig, SI_KERNEL); + } Sleep(1); } return 0; diff --git a/libc/calls/sig.internal.h b/libc/calls/sig.internal.h index 81ea4464c..34b2f1d7c 100644 --- a/libc/calls/sig.internal.h +++ b/libc/calls/sig.internal.h @@ -2,6 +2,7 @@ #define COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_ #include "libc/atomic.h" #include "libc/calls/struct/sigset.h" +#include "libc/nt/thunk/msabi.h" #include "libc/thread/posixthread.internal.h" #define SIG_HANDLED_NO_RESTART 1 @@ -28,8 +29,8 @@ void __sig_delete(int); void __sig_generate(int, int); void __sig_init(void); -char16_t *__sig_process_path(char16_t *, uint32_t); -atomic_ulong *__sig_map_process(int, int); +__msabi char16_t *__sig_process_path(char16_t *, uint32_t, int); +__msabi atomic_ulong *__sig_map_process(int, int); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_SIGNALS_INTERNAL_H_ */ diff --git a/libc/fmt/internal.h b/libc/fmt/internal.h index 135acfbdb..f2161c615 100644 --- a/libc/fmt/internal.h +++ b/libc/fmt/internal.h @@ -2,6 +2,7 @@ #define COSMOPOLITAN_LIBC_FMT_STRTOL_H_ #include "libc/ctype.h" #include "libc/errno.h" +#include "libc/nt/thunk/msabi.h" #include "libc/str/str.h" #define CONSUME_SPACES(t, s, c) \ @@ -48,6 +49,6 @@ int __vcscanf(int (*)(void *), int (*)(int, void *), void *, const char *, va_list); int __fmt(void *, void *, const char *, va_list, int *); -char16_t *__itoa16(char16_t[21], uint64_t); +__msabi char16_t *__itoa16(char16_t[21], uint64_t); #endif /* COSMOPOLITAN_LIBC_FMT_STRTOL_H_ */ diff --git a/libc/intrin/itoa16.c b/libc/intrin/itoa16.c index f89116e5d..2d8055887 100644 --- a/libc/intrin/itoa16.c +++ b/libc/intrin/itoa16.c @@ -18,7 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/fmt/internal.h" -textwindows char16_t *__itoa16(char16_t p[21], uint64_t x) { +__msabi textwindows char16_t *__itoa16(char16_t p[21], uint64_t x) { char t; size_t a, b, i = 0; do { diff --git a/libc/intrin/sigproc.c b/libc/intrin/sigproc.c index 58303fd81..644d77263 100644 --- a/libc/intrin/sigproc.c +++ b/libc/intrin/sigproc.c @@ -21,6 +21,7 @@ #include "libc/fmt/internal.h" #include "libc/nt/createfile.h" #include "libc/nt/enum/accessmask.h" +#include "libc/nt/enum/creationdisposition.h" #include "libc/nt/enum/fileflagandattributes.h" #include "libc/nt/enum/filemapflags.h" #include "libc/nt/enum/filemovemethod.h" @@ -29,28 +30,60 @@ #include "libc/nt/files.h" #include "libc/nt/memory.h" #include "libc/nt/runtime.h" +#include "libc/nt/thunk/msabi.h" #ifdef __x86_64__ -textwindows char16_t *__sig_process_path(char16_t *path, uint32_t pid) { +// cut back on code size and avoid setting errno +// this code is a mandatory dependency of winmain +__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; +__msabi extern typeof(CreateDirectory) *const __imp_CreateDirectoryW; +__msabi extern typeof(CreateFile) *const __imp_CreateFileW; +__msabi extern typeof(CreateFileMapping) *const __imp_CreateFileMappingW; +__msabi extern typeof(MapViewOfFileEx) *const __imp_MapViewOfFileEx; +__msabi extern typeof(SetEndOfFile) *const __imp_SetEndOfFile; +__msabi extern typeof(SetFilePointer) *const __imp_SetFilePointer; + +__msabi textwindows char16_t *__sig_process_path(char16_t *path, uint32_t pid, + int create_directories) { char16_t *p = path; - *p++ = 'C'; + *p++ = 'C'; // C:\ProgramData\cosmo\sig\x\y.pid *p++ = ':'; *p++ = '\\'; - *p++ = 'v'; - *p++ = 'a'; + *p++ = 'P'; *p++ = 'r'; + *p++ = 'o'; + *p++ = 'g'; + *p++ = 'r'; + *p++ = 'a'; + *p++ = 'm'; + *p++ = 'D'; + *p++ = 'a'; + *p++ = 't'; + *p++ = 'a'; *p = 0; - CreateDirectory(path, 0); + if (create_directories) + __imp_CreateDirectoryW(path, 0); + *p++ = '\\'; + *p++ = 'c'; + *p++ = 'o'; + *p++ = 's'; + *p++ = 'm'; + *p++ = 'o'; + *p = 0; + if (create_directories) + __imp_CreateDirectoryW(path, 0); *p++ = '\\'; *p++ = 's'; *p++ = 'i'; *p++ = 'g'; *p = 0; - CreateDirectory(path, 0); + if (create_directories) + __imp_CreateDirectoryW(path, 0); *p++ = '\\'; - p = __itoa16(p, (pid & 0x000fff00) >> 8); + p = __itoa16(p, (pid & 0x000ff800) >> 11); *p = 0; - CreateDirectory(path, 0); + if (create_directories) + __imp_CreateDirectoryW(path, 0); *p++ = '\\'; p = __itoa16(p, pid); *p++ = '.'; @@ -61,33 +94,25 @@ textwindows char16_t *__sig_process_path(char16_t *path, uint32_t pid) { return path; } -textwindows static atomic_ulong *__sig_map_process_impl(int pid, - int disposition) { +__msabi textwindows atomic_ulong *__sig_map_process(int pid, int disposition) { char16_t path[128]; - intptr_t hand = CreateFile(__sig_process_path(path, pid), - kNtGenericRead | kNtGenericWrite, - kNtFileShareRead | kNtFileShareWrite, 0, - disposition, kNtFileAttributeNormal, 0); + __sig_process_path(path, pid, disposition == kNtOpenAlways); + intptr_t hand = __imp_CreateFileW(path, kNtGenericRead | kNtGenericWrite, + kNtFileShareRead | kNtFileShareWrite, 0, + disposition, kNtFileAttributeNormal, 0); if (hand == -1) return 0; - SetFilePointer(hand, 8, 0, kNtFileBegin); - SetEndOfFile(hand); - intptr_t map = CreateFileMapping(hand, 0, kNtPageReadwrite, 0, 8, 0); + __imp_SetFilePointer(hand, 8, 0, kNtFileBegin); + __imp_SetEndOfFile(hand); + intptr_t map = __imp_CreateFileMappingW(hand, 0, kNtPageReadwrite, 0, 8, 0); if (!map) { - CloseHandle(hand); + __imp_CloseHandle(hand); return 0; } - atomic_ulong *sigs = MapViewOfFileEx(map, kNtFileMapWrite, 0, 0, 8, 0); - CloseHandle(map); - CloseHandle(hand); + atomic_ulong *sigs = __imp_MapViewOfFileEx(map, kNtFileMapWrite, 0, 0, 8, 0); + __imp_CloseHandle(map); + __imp_CloseHandle(hand); return sigs; } -textwindows atomic_ulong *__sig_map_process(int pid, int disposition) { - int e = errno; - atomic_ulong *res = __sig_map_process_impl(pid, disposition); - errno = e; - return res; -} - #endif /* __x86_64__ */ diff --git a/libc/intrin/terminatethisprocess.c b/libc/intrin/terminatethisprocess.c index b8d81b441..d036c4f31 100644 --- a/libc/intrin/terminatethisprocess.c +++ b/libc/intrin/terminatethisprocess.c @@ -27,7 +27,9 @@ #include "libc/runtime/internal.h" #ifdef __x86_64__ +__msabi extern typeof(DeleteFile) *const __imp_DeleteFileW; __msabi extern typeof(TerminateProcess) *const __imp_TerminateProcess; +__msabi extern typeof(UnmapViewOfFile) *const __imp_UnmapViewOfFile; /** * Terminates the calling process and all of its threads. @@ -40,8 +42,8 @@ textwindows dontinstrument void TerminateThisProcess(uint32_t dwWaitStatus) { atomic_ulong fake = 0; real = __sig.process; __sig.process = &fake; - UnmapViewOfFile(real); - DeleteFile(__sig_process_path(path, __pid)); + __imp_UnmapViewOfFile(real); + __imp_DeleteFileW(__sig_process_path(path, __pid, false)); // "When a process terminates itself, TerminateProcess stops execution // of the calling thread and does not return." -Quoth MSDN diff --git a/libc/proc/fork-nt.c b/libc/proc/fork-nt.c index 721b2cb63..f889d3e8b 100644 --- a/libc/proc/fork-nt.c +++ b/libc/proc/fork-nt.c @@ -465,7 +465,6 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) { __morph_tls(); __tls_enabled = true; // the child's pending signals is initially empty - atomic_store_explicit(__sig.process, 0, memory_order_relaxed); atomic_store_explicit(&tib->tib_sigpending, 0, memory_order_relaxed); // re-apply code morphing for function tracing if (ftrace_stackdigs) diff --git a/libc/proc/kill-nt.c b/libc/proc/kill-nt.c index d561388cb..c2726fa81 100644 --- a/libc/proc/kill-nt.c +++ b/libc/proc/kill-nt.c @@ -59,11 +59,22 @@ textwindows int sys_kill_nt(int pid, int sig) { if (pid <= 0 || pid == getpid()) { if (sig) { if (pid <= 0) { + // if pid is 0 or -1 then kill the processes beneath us too. + // this isn't entirely right but it's closer to being right. + // having this behavior is helpful for servers like redbean. struct Dll *e; BLOCK_SIGNALS; __proc_lock(); - for (e = dll_first(__proc.list); e; e = dll_next(__proc.list, e)) - TerminateProcess(PROC_CONTAINER(e)->handle, sig); + for (e = dll_first(__proc.list); e; e = dll_next(__proc.list, e)) { + atomic_ulong *sigproc; + struct Proc *pr = PROC_CONTAINER(e); + if (sig != 9 && (sigproc = __sig_map_process(pid, kNtOpenExisting))) { + atomic_fetch_or_explicit(sigproc, 1ull << (sig - 1), + memory_order_release); + } else { + TerminateProcess(pr->handle, sig); + } + } __proc_unlock(); ALLOW_SIGNALS; } @@ -73,7 +84,25 @@ textwindows int sys_kill_nt(int pid, int sig) { } } - // attempt to signal via /var/sig shared memory file + // find existing handle we own for process + // + // this step should come first to verify process existence. this is + // because there's no guarantee that just because the shared memory + // file exists, the process actually exists. + int64_t handle, closeme = 0; + if (!(handle = __proc_handle(pid))) { + if ((handle = OpenProcess(kNtProcessTerminate, false, pid))) { + closeme = handle; + } else { + goto OnError; + } + } + + // attempt to signal via shared memory file + // + // now that we know the process exists, if it has a shared memory file + // then we can be reasonably certain it's a cosmo process which should + // be trusted to deliver its signal, unless it's a nine exterminations if (pid > 0 && sig != 9) { atomic_ulong *sigproc; if ((sigproc = __sig_map_process(pid, kNtOpenExisting))) { @@ -81,20 +110,12 @@ textwindows int sys_kill_nt(int pid, int sig) { atomic_fetch_or_explicit(sigproc, 1ull << (sig - 1), memory_order_release); UnmapViewOfFile(sigproc); + if (closeme) + CloseHandle(closeme); return 0; } } - // find existing handle we own for process - int64_t handle, closeme = 0; - if (!(handle = __proc_handle(pid))) { - if ((handle = OpenProcess(kNtProcessTerminate, false, pid))) { - closeme = handle; - } else { - goto OnError; - } - } - // perform actual kill // process will report WIFSIGNALED with WTERMSIG(sig) bool32 ok = TerminateProcess(handle, sig); diff --git a/libc/proc/kill.c b/libc/proc/kill.c index 9c0e99a6e..ec913d5c8 100644 --- a/libc/proc/kill.c +++ b/libc/proc/kill.c @@ -29,6 +29,17 @@ * The impact of this action can be terminating the process, or * interrupting it to request something happen. * + * On Windows, signals are delivered between processes using shared + * memory files stored in C:\ProgramData\cosmo\sig\x\y.pid which hold + * the process signal mask. Any process that can access these files can + * signal a cosmo process. The targeting process will then notice that a + * signal has been added and delivers to any thread as soon as possible. + * + * On Windows, the concept of a process group isn't fully implemented. + * Saying `kill(0, sig)` will deliver `sig` to all direct descendent + * processes. Saying `kill(-pid, sig)` will be the same as saying + * `kill(pid, sig)`. + * * @param pid can be: * >0 signals one process by id * =0 signals all processes in current process group diff --git a/libc/proc/proc.c b/libc/proc/proc.c index bdada4c46..cfb1e5f30 100644 --- a/libc/proc/proc.c +++ b/libc/proc/proc.c @@ -323,6 +323,7 @@ textwindows int64_t __proc_search(int pid) { int64_t handle = 0; BLOCK_SIGNALS; __proc_lock(); + // TODO(jart): we should increment a reference count when returning for (e = dll_first(__proc.list); e; e = dll_next(__proc.list, e)) { if (pid == PROC_CONTAINER(e)->pid) { handle = PROC_CONTAINER(e)->handle;