From 3f0bcdc3ef00c2c79b1be85b1fa207d508509ac7 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 5 Nov 2022 19:49:41 -0700 Subject: [PATCH] Improve cancellations, randomness, and time - Exhaustively document cancellation points - Rename SIGCANCEL to SIGTHR just like BSDs - Further improve POSIX thread cancellations - Ensure asynchronous cancellations work correctly - Elevate the quality of getrandom() and getentropy() - Make futexes cancel correctly on OpenBSD 6.x and 7.x - Add reboot.com and shutdown.com to examples directory - Remove underscore prefix from awesome timespec_*() APIs - Create assertions that help verify our cancellation points - Remove bad timespec APIs (cmp generalizes eq/ne/gt/gte/lt/lte) --- examples/clock.c | 14 +- examples/clock_getres.c | 2 +- examples/reboot.c | 39 +++ examples/shutdown.c | 39 +++ libc/calls/_timespec_eq.c | 26 -- libc/calls/_timespec_gt.c | 34 --- libc/calls/_timespec_gte.c | 28 --- libc/calls/_timeval_eq.c | 26 -- libc/calls/_timeval_gte.c | 28 --- libc/calls/blockcancel.internal.h | 11 +- libc/calls/calls.mk | 12 +- libc/calls/clock.c | 2 +- libc/calls/clock_gettime-mono.c | 6 +- libc/calls/clock_nanosleep-nt.c | 12 +- libc/calls/clock_nanosleep-openbsd.c | 4 +- libc/calls/clock_nanosleep-xnu.c | 6 +- libc/calls/clock_nanosleep.c | 9 +- libc/calls/closefrom.c | 1 + libc/calls/copy_file_range.c | 10 + libc/calls/{_timeval_gt.c => cp.c} | 49 +++- libc/calls/cp.internal.h | 25 ++ libc/calls/creat.c | 1 + libc/calls/dup-nt.c | 4 +- libc/calls/execve-sysv.c | 4 + libc/calls/fcntl.c | 5 +- libc/calls/fdatasync-nt.c | 1 + libc/calls/fdatasync.c | 5 + libc/calls/flock.c | 5 + libc/calls/fstatfs.c | 5 + libc/calls/fsync.c | 5 + libc/calls/ftruncate.c | 6 + libc/calls/getcwd-xnu.greg.c | 2 +- libc/calls/nanosleep-xnu.c | 8 +- libc/calls/nanosleep.c | 1 + libc/calls/openat.c | 6 + libc/calls/pause.c | 3 + libc/calls/pipe2.c | 4 +- libc/calls/poll.c | 5 + libc/calls/ppoll.c | 5 + libc/calls/pread.c | 6 + libc/calls/preadv.c | 3 + libc/calls/pwrite.c | 5 + libc/calls/pwritev.c | 3 + libc/calls/read.c | 4 + libc/calls/readv.c | 3 + libc/calls/{addrusage.c => rusage_add.c} | 6 +- libc/calls/sigsuspend.c | 5 + libc/calls/sigtimedwait.c | 4 + libc/calls/sigwaitinfo.c | 1 + libc/calls/sleep.c | 10 +- libc/calls/statfs.c | 7 + libc/calls/struct/rusage.h | 2 +- libc/calls/struct/timespec.h | 33 ++- libc/calls/struct/timeval.h | 13 +- libc/calls/syscall-sysv.internal.h | 1 + libc/calls/syscall_support-sysv.internal.h | 8 +- libc/calls/sysinfo.c | 2 +- libc/calls/tcdrain.c | 6 + .../calls/{_timespec_add.c => timespec_add.c} | 2 +- .../calls/{_timespec_cmp.c => timespec_cmp.c} | 4 +- ...pec_frommicros.c => timespec_frommicros.c} | 2 +- ...pec_frommillis.c => timespec_frommillis.c} | 2 +- ...espec_fromnanos.c => timespec_fromnanos.c} | 2 +- .../calls/{_timespec_get.c => timespec_get.c} | 2 +- .../{_timespec_getres.c => timespec_getres.c} | 0 .../{_timespec_mono.c => timespec_mono.c} | 4 +- .../{_timespec_real.c => timespec_real.c} | 4 +- .../{_timespec_sleep.c => timespec_sleep.c} | 17 +- ...c_sleep_until.c => timespec_sleep_until.c} | 7 +- .../calls/{_timespec_sub.c => timespec_sub.c} | 2 +- ...imespec_tomicros.c => timespec_tomicros.c} | 4 +- ...imespec_tomillis.c => timespec_tomillis.c} | 2 +- ..._timespec_tonanos.c => timespec_tonanos.c} | 2 +- ...espec_totimeval.c => timespec_totimeval.c} | 4 +- libc/calls/{_timeval_add.c => timeval_add.c} | 2 +- libc/calls/{_timeval_cmp.c => timeval_cmp.c} | 6 +- libc/calls/{_timeval_sub.c => timeval_sub.c} | 2 +- ...eval_totimespec.c => timeval_totimespec.c} | 2 +- libc/calls/tkill.c | 26 +- libc/calls/tmpfd.c | 3 + libc/calls/touch.c | 6 +- libc/calls/truncate.c | 6 + libc/calls/unveil.c | 7 +- libc/calls/usleep.c | 3 +- libc/calls/utimensat-sysv.c | 4 +- libc/calls/utimensat-xnu.c | 8 +- libc/calls/wait4.c | 5 + libc/calls/write.c | 6 + libc/calls/writev.c | 3 + libc/errno.h | 3 +- libc/intrin/getenv.c | 8 +- libc/intrin/ksignalnames.S | 8 +- libc/intrin/pthread_key_create.c | 2 + libc/intrin/pthread_setcancelstate.c | 11 + libc/intrin/sigisprecious.inc | 2 +- libc/intrin/winerr.greg.c | 2 + libc/log/backtrace2.c | 14 +- libc/log/showcrashreports.c | 24 +- libc/log/vflogf.c | 5 +- libc/runtime/cocmd.c | 4 +- libc/runtime/cosmo.S | 2 + libc/runtime/ezmap.c | 4 +- libc/runtime/fenv.c | 41 ---- libc/runtime/isdynamicexecutable.c | 5 +- libc/runtime/msync.c | 7 + libc/runtime/opensymboltable.greg.c | 23 +- libc/sock/accept4.c | 5 + libc/sock/connect.c | 5 + libc/sock/epoll.c | 72 ++++-- libc/sock/pselect.c | 7 + libc/sock/recv.c | 8 +- libc/sock/recvfrom.c | 5 + libc/sock/recvmsg.c | 3 + libc/sock/select.c | 7 + libc/sock/send.c | 8 +- libc/sock/sendmsg.c | 3 + libc/sock/sendto.c | 5 + libc/stdio/dirstream.c | 2 + libc/stdio/getentropy.c | 35 ++- libc/stdio/getrandom.c | 228 ++++++++++++++---- libc/stdio/pclose.c | 40 ++- libc/stdio/popen.c | 14 +- libc/stdio/posix_spawn.c | 4 - libc/stdio/system.c | 12 +- libc/stdio/tmpfile.c | 3 + libc/str/update.sh | 2 +- libc/sysv/calls/__sys_openat_nc.s | 2 + libc/sysv/calls/sys_copy_file_range.s | 2 +- libc/sysv/calls/sys_getrandom.s | 2 +- libc/sysv/calls/sys_tgkill.s | 2 +- libc/sysv/consts.sh | 6 +- libc/sysv/consts/SIGEMT.s | 2 +- libc/sysv/consts/SIGINFO.s | 2 +- libc/sysv/consts/{SIGCANCEL.s => SIGTHR.s} | 2 +- libc/sysv/consts/sig.h | 4 +- libc/sysv/syscalls.sh | 9 +- libc/sysv/systemfive.S | 42 ++-- libc/testlib/testrunner.c | 4 +- libc/thread/posixthread.internal.h | 12 +- libc/thread/pthread_atfork.c | 7 + libc/thread/pthread_cancel.c | 204 +++++++++++++--- libc/thread/pthread_cond_timedwait.c | 2 +- libc/thread/pthread_kill.c | 3 +- libc/thread/pthread_setcanceltype.c | 10 +- libc/thread/sem_timedwait.c | 11 +- libc/thread/wait0.c | 11 +- libc/time/localtime.c | 16 +- libc/vga/rlinit-vesa.S | 2 +- libc/zipos/open.c | 1 - net/turfwar/blackholed.c | 3 +- net/turfwar/turfwar.c | 37 ++- test/libc/calls/_timespec_test.c | 92 ++++--- test/libc/calls/clock_gettime_test.c | 2 +- test/libc/calls/lock_ofd_test.c | 3 + test/libc/intrin/describesigset_test.c | 18 +- test/libc/intrin/lock_test.c | 12 +- test/libc/intrin/lockscale_test.c | 6 +- test/libc/intrin/strsignal_r_test.c | 1 + test/libc/mem/malloc_test.c | 6 +- test/libc/stdio/getentropy_test.c | 95 ++++++++ test/libc/stdio/getrandom_test.c | 30 +++ test/libc/thread/pthread_cancel_test.c | 105 ++++++-- test/libc/thread/sem_open_test.c | 2 +- test/net/http/tokenbucket_test.c | 6 +- third_party/lua/lunix.c | 11 +- third_party/nsync/README.cosmo | 1 + third_party/nsync/compat.S | 16 +- third_party/nsync/cv.h | 14 +- third_party/nsync/futex.c | 69 +++--- third_party/nsync/mem/nsync_cv.c | 17 +- third_party/nsync/time.h | 22 +- tool/build/lib/syscall.c | 20 +- tool/net/redbean.c | 73 +++--- 173 files changed, 1599 insertions(+), 782 deletions(-) create mode 100644 examples/reboot.c create mode 100644 examples/shutdown.c delete mode 100644 libc/calls/_timespec_eq.c delete mode 100644 libc/calls/_timespec_gt.c delete mode 100644 libc/calls/_timespec_gte.c delete mode 100644 libc/calls/_timeval_eq.c delete mode 100644 libc/calls/_timeval_gte.c rename libc/calls/{_timeval_gt.c => cp.c} (65%) create mode 100644 libc/calls/cp.internal.h rename libc/calls/{addrusage.c => rusage_add.c} (93%) rename libc/calls/{_timespec_add.c => timespec_add.c} (96%) rename libc/calls/{_timespec_cmp.c => timespec_cmp.c} (94%) rename libc/calls/{_timespec_frommicros.c => timespec_frommicros.c} (97%) rename libc/calls/{_timespec_frommillis.c => timespec_frommillis.c} (97%) rename libc/calls/{_timespec_fromnanos.c => timespec_fromnanos.c} (97%) rename libc/calls/{_timespec_get.c => timespec_get.c} (98%) rename libc/calls/{_timespec_getres.c => timespec_getres.c} (100%) rename libc/calls/{_timespec_mono.c => timespec_mono.c} (97%) rename libc/calls/{_timespec_real.c => timespec_real.c} (97%) rename libc/calls/{_timespec_sleep.c => timespec_sleep.c} (86%) rename libc/calls/{_timespec_sleep_until.c => timespec_sleep_until.c} (91%) rename libc/calls/{_timespec_sub.c => timespec_sub.c} (96%) rename libc/calls/{_timespec_tomicros.c => timespec_tomicros.c} (97%) rename libc/calls/{_timespec_tomillis.c => timespec_tomillis.c} (98%) rename libc/calls/{_timespec_tonanos.c => timespec_tonanos.c} (98%) rename libc/calls/{_timespec_totimeval.c => timespec_totimeval.c} (96%) rename libc/calls/{_timeval_add.c => timeval_add.c} (96%) rename libc/calls/{_timeval_cmp.c => timeval_cmp.c} (93%) rename libc/calls/{_timeval_sub.c => timeval_sub.c} (96%) rename libc/calls/{_timeval_totimespec.c => timeval_totimespec.c} (97%) delete mode 100644 libc/runtime/fenv.c create mode 100644 libc/sysv/calls/__sys_openat_nc.s rename libc/sysv/consts/{SIGCANCEL.s => SIGTHR.s} (56%) create mode 100644 test/libc/stdio/getentropy_test.c diff --git a/examples/clock.c b/examples/clock.c index a2eed57c9..886d57d7f 100644 --- a/examples/clock.c +++ b/examples/clock.c @@ -20,22 +20,22 @@ int main(int argc, char *argv[]) { volatile unsigned long x; struct timespec now, start, next, interval; printf("hammering the cpu...\n"); - next = start = _timespec_mono(); - interval = _timespec_frommillis(500); - next = _timespec_add(next, interval); + next = start = timespec_mono(); + interval = timespec_frommillis(500); + next = timespec_add(next, interval); for (;;) { for (i = 0;; ++i) { x *= 7; if (!(i % 256)) { - now = _timespec_mono(); - if (_timespec_gte(now, next)) { + now = timespec_mono(); + if (timespec_cmp(now, next) >= 0) { break; } } } - next = _timespec_add(next, interval); + next = timespec_add(next, interval); printf("consumed %10g seconds monotonic time and %10g seconds cpu time\n", - _timespec_tonanos(_timespec_sub(now, start)) / 1000000000., + timespec_tonanos(timespec_sub(now, start)) / 1000000000., (double)clock() / CLOCKS_PER_SEC); } } diff --git a/examples/clock_getres.c b/examples/clock_getres.c index e40a47d54..e45e97f44 100644 --- a/examples/clock_getres.c +++ b/examples/clock_getres.c @@ -28,7 +28,7 @@ void show(int clock) { } shown[n++] = clock; if (clock_getres(clock, &ts) != -1) { - kprintf("%s %'ld ns\n", DescribeClockName(clock), _timespec_tonanos(ts)); + kprintf("%s %'ld ns\n", DescribeClockName(clock), timespec_tonanos(ts)); } else { kprintf("%s %s\n", DescribeClockName(clock), _strerrno(errno)); } diff --git a/examples/reboot.c b/examples/reboot.c new file mode 100644 index 000000000..d6d686bed --- /dev/null +++ b/examples/reboot.c @@ -0,0 +1,39 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/calls/calls.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/reboot.h" + +int main(int argc, char *argv[]) { + char line[8] = {0}; + if (argc > 1 && !strcmp(argv[1], "-y")) { + line[0] = 'y'; + } else { + printf("reboot your computer? yes or no [no] "); + fflush(stdout); + fgets(line, sizeof(line), stdin); + } + if (line[0] == 'y' || line[0] == 'Y') { + if (reboot(RB_AUTOBOOT)) { + printf("system is rebooting...\n"); + exit(0); + } else { + perror("reboot"); + exit(1); + } + } else if (line[0] == 'n' || line[0] == 'N') { + exit(0); + } else { + printf("error: unrecognized response\n"); + exit(2); + } +} diff --git a/examples/shutdown.c b/examples/shutdown.c new file mode 100644 index 000000000..43ca6102a --- /dev/null +++ b/examples/shutdown.c @@ -0,0 +1,39 @@ +#if 0 +/*─────────────────────────────────────────────────────────────────╗ +│ To the extent possible under law, Justine Tunney has waived │ +│ all copyright and related or neighboring rights to this file, │ +│ as it is written in the following disclaimers: │ +│ • http://unlicense.org/ │ +│ • http://creativecommons.org/publicdomain/zero/1.0/ │ +╚─────────────────────────────────────────────────────────────────*/ +#endif +#include "libc/calls/calls.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/reboot.h" + +int main(int argc, char *argv[]) { + char line[8] = {0}; + if (argc > 1 && !strcmp(argv[1], "-y")) { + line[0] = 'y'; + } else { + printf("shutdown your computer? yes or no [no] "); + fflush(stdout); + fgets(line, sizeof(line), stdin); + } + if (line[0] == 'y' || line[0] == 'Y') { + if (reboot(RB_POWER_OFF)) { + printf("system is shutting down...\n"); + exit(0); + } else { + perror("reboot"); + exit(1); + } + } else if (line[0] == 'n' || line[0] == 'N') { + exit(0); + } else { + printf("error: unrecognized response\n"); + exit(2); + } +} diff --git a/libc/calls/_timespec_eq.c b/libc/calls/_timespec_eq.c deleted file mode 100644 index e0a1b9ccb..000000000 --- a/libc/calls/_timespec_eq.c +++ /dev/null @@ -1,26 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2022 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/struct/timespec.h" - -/** - * Checks if 𝑥 = 𝑦. - */ -bool _timespec_eq(struct timespec x, struct timespec y) { - return x.tv_sec == y.tv_sec && x.tv_nsec == y.tv_nsec; -} diff --git a/libc/calls/_timespec_gt.c b/libc/calls/_timespec_gt.c deleted file mode 100644 index 9cd111da2..000000000 --- a/libc/calls/_timespec_gt.c +++ /dev/null @@ -1,34 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2022 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/struct/timespec.h" - -/** - * Returns true if timespec `x` is greater than `y`. - */ -bool _timespec_gt(struct timespec x, struct timespec y) { - if (x.tv_sec > y.tv_sec) { - return true; - } - if (x.tv_sec == y.tv_sec) { - if (x.tv_nsec > y.tv_nsec) { - return true; - } - } - return false; -} diff --git a/libc/calls/_timespec_gte.c b/libc/calls/_timespec_gte.c deleted file mode 100644 index 3b197a6fa..000000000 --- a/libc/calls/_timespec_gte.c +++ /dev/null @@ -1,28 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2022 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/struct/timespec.h" - -/** - * Checks if 𝑥 ≥ 𝑦. - */ -bool _timespec_gte(struct timespec x, struct timespec y) { - if (x.tv_sec > y.tv_sec) return true; - if (x.tv_sec < y.tv_sec) return false; - return x.tv_nsec >= y.tv_nsec; -} diff --git a/libc/calls/_timeval_eq.c b/libc/calls/_timeval_eq.c deleted file mode 100644 index 7039451a2..000000000 --- a/libc/calls/_timeval_eq.c +++ /dev/null @@ -1,26 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2022 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/struct/timeval.h" - -/** - * Checks if 𝑥 = 𝑦. - */ -bool _timeval_eq(struct timeval x, struct timeval y) { - return x.tv_sec == y.tv_sec && x.tv_usec == y.tv_usec; -} diff --git a/libc/calls/_timeval_gte.c b/libc/calls/_timeval_gte.c deleted file mode 100644 index 3dc063d69..000000000 --- a/libc/calls/_timeval_gte.c +++ /dev/null @@ -1,28 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2022 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/struct/timeval.h" - -/** - * Checks if 𝑥 ≥ 𝑦. - */ -bool _timeval_gte(struct timeval x, struct timeval y) { - if (x.tv_sec > y.tv_sec) return true; - if (x.tv_sec < y.tv_sec) return false; - return x.tv_usec >= y.tv_usec; -} diff --git a/libc/calls/blockcancel.internal.h b/libc/calls/blockcancel.internal.h index e3baf981e..1ecd3ed91 100644 --- a/libc/calls/blockcancel.internal.h +++ b/libc/calls/blockcancel.internal.h @@ -7,13 +7,16 @@ COSMOPOLITAN_C_START_ #define BLOCK_CANCELLATIONS \ do { \ int _CancelState; \ - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &_CancelState) + _CancelState = _pthread_block_cancellations() -#define ALLOW_CANCELLATIONS \ - pthread_setcancelstate(_CancelState, 0); \ - } \ +#define ALLOW_CANCELLATIONS \ + _pthread_allow_cancellations(_CancelState); \ + } \ while (0) +int _pthread_block_cancellations(void); +void _pthread_allow_cancellations(int); + COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_CALLS_BLOCKCANCEL_H_ */ diff --git a/libc/calls/calls.mk b/libc/calls/calls.mk index a2c242722..5d638cde8 100644 --- a/libc/calls/calls.mk +++ b/libc/calls/calls.mk @@ -179,12 +179,12 @@ o//libc/calls/statfs2cosmo.o: private \ # we always want -O2 because: # division is expensive if not optimized o/$(MODE)/libc/calls/clock.o \ -o/$(MODE)/libc/calls/_timespec_tomillis.o \ -o/$(MODE)/libc/calls/_timespec_tomicros.o \ -o/$(MODE)/libc/calls/_timespec_totimeval.o \ -o/$(MODE)/libc/calls/_timespec_fromnanos.o \ -o/$(MODE)/libc/calls/_timespec_frommillis.o \ -o/$(MODE)/libc/calls/_timespec_frommicros.o: private \ +o/$(MODE)/libc/calls/timespec_tomillis.o \ +o/$(MODE)/libc/calls/timespec_tomicros.o \ +o/$(MODE)/libc/calls/timespec_totimeval.o \ +o/$(MODE)/libc/calls/timespec_fromnanos.o \ +o/$(MODE)/libc/calls/timespec_frommillis.o \ +o/$(MODE)/libc/calls/timespec_frommicros.o: private \ OVERRIDE_CFLAGS += \ -O2 diff --git a/libc/calls/clock.c b/libc/calls/clock.c index 5ac525e0c..9b63e1fd8 100644 --- a/libc/calls/clock.c +++ b/libc/calls/clock.c @@ -55,7 +55,7 @@ int64_t clock(void) { if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) == -1) { errno = e; if (getrusage(RUSAGE_SELF, &ru) != -1) { - ts = _timeval_totimespec(_timeval_add(ru.ru_utime, ru.ru_stime)); + ts = timeval_totimespec(timeval_add(ru.ru_utime, ru.ru_stime)); } else { return -1; } diff --git a/libc/calls/clock_gettime-mono.c b/libc/calls/clock_gettime-mono.c index 550af8dcd..072bae513 100644 --- a/libc/calls/clock_gettime-mono.c +++ b/libc/calls/clock_gettime-mono.c @@ -17,11 +17,11 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/timespec.h" -#include "libc/thread/thread.h" #include "libc/nexgen32e/rdtsc.h" #include "libc/nexgen32e/x86feature.h" #include "libc/sysv/consts/clock.h" #include "libc/sysv/errfuns.h" +#include "libc/thread/thread.h" static struct { pthread_once_t once; @@ -30,7 +30,7 @@ static struct { } g_mono; static void sys_clock_gettime_mono_init(void) { - g_mono.base_wall = _timespec_real(); + g_mono.base_wall = timespec_real(); g_mono.base_tick = rdtsc(); } @@ -42,7 +42,7 @@ int sys_clock_gettime_mono(struct timespec *time) { pthread_once(&g_mono.once, sys_clock_gettime_mono_init); cycles = rdtsc() - g_mono.base_tick; nanos = cycles / 3; - *time = _timespec_add(g_mono.base_wall, _timespec_fromnanos(nanos)); + *time = timespec_add(g_mono.base_wall, timespec_fromnanos(nanos)); return 0; } else { return einval(); diff --git a/libc/calls/clock_nanosleep-nt.c b/libc/calls/clock_nanosleep-nt.c index 8aa89a1a9..5bdac12ae 100644 --- a/libc/calls/clock_nanosleep-nt.c +++ b/libc/calls/clock_nanosleep-nt.c @@ -33,24 +33,24 @@ textwindows int sys_clock_nanosleep_nt(int clock, int flags, abs = *req; for (;;) { if (sys_clock_gettime_nt(clock, &now)) return -1; - if (_timespec_gte(now, abs)) return 0; + if (timespec_cmp(now, abs) >= 0) return 0; if (_check_interrupts(false, g_fds.p)) return -1; SleepEx(MIN(__SIG_POLLING_INTERVAL_MS, - _timespec_tomillis(_timespec_sub(abs, now))), + timespec_tomillis(timespec_sub(abs, now))), false); } } else { if (sys_clock_gettime_nt(clock, &now)) return -1; - abs = _timespec_add(now, *req); + abs = timespec_add(now, *req); for (;;) { sys_clock_gettime_nt(clock, &now); - if (_timespec_gte(now, abs)) return 0; + if (timespec_cmp(now, abs) >= 0) return 0; if (_check_interrupts(false, g_fds.p)) { - if (rem) *rem = _timespec_sub(abs, now); + if (rem) *rem = timespec_sub(abs, now); return -1; } SleepEx(MIN(__SIG_POLLING_INTERVAL_MS, - _timespec_tomillis(_timespec_sub(abs, now))), + timespec_tomillis(timespec_sub(abs, now))), false); } } diff --git a/libc/calls/clock_nanosleep-openbsd.c b/libc/calls/clock_nanosleep-openbsd.c index 656c682e0..8c77a71a6 100644 --- a/libc/calls/clock_nanosleep-openbsd.c +++ b/libc/calls/clock_nanosleep-openbsd.c @@ -31,8 +31,8 @@ int sys_clock_nanosleep_openbsd(int clock, int flags, res = sys_nanosleep(req, rem); } else { sys_clock_gettime(clock, &now); - if (_timespec_gt(*req, now)) { - rel = _timespec_sub(*req, now); + if (timespec_cmp(*req, now) > 0) { + rel = timespec_sub(*req, now); res = sys_nanosleep(&rel, 0); } else { res = 0; diff --git a/libc/calls/clock_nanosleep-xnu.c b/libc/calls/clock_nanosleep-xnu.c index fd3a7ddf7..788446da5 100644 --- a/libc/calls/clock_nanosleep-xnu.c +++ b/libc/calls/clock_nanosleep-xnu.c @@ -31,10 +31,10 @@ int sys_clock_nanosleep_xnu(int clock, int flags, const struct timespec *req, struct timeval now, abs, rel; if (clock == CLOCK_REALTIME) { if (flags & TIMER_ABSTIME) { - abs = _timespec_totimeval(*req); + abs = timespec_totimeval(*req); sys_gettimeofday_xnu(&now, 0, 0); - if (_timeval_gt(abs, now)) { - rel = _timeval_sub(abs, now); + if (timeval_cmp(abs, now) > 0) { + rel = timeval_sub(abs, now); res = sys_select(0, 0, 0, 0, &rel); } else { res = 0; diff --git a/libc/calls/clock_nanosleep.c b/libc/calls/clock_nanosleep.c index faa004dff..892db647f 100644 --- a/libc/calls/clock_nanosleep.c +++ b/libc/calls/clock_nanosleep.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/asan.internal.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.internal.h" @@ -55,8 +56,8 @@ * * struct timespec rel, now, abs; * clock_gettime(CLOCK_REALTIME, &now); - * rel = _timespec_frommillis(100); - * abs = _timespec_add(now, rel); + * rel = timespec_frommillis(100); + * abs = timespec_add(now, rel); * while (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &abs, 0)); * * will accurately spin on `EINTR` errors. That way you're not impeding @@ -74,6 +75,7 @@ * if flags is `TIMER_ABSTIME` then `rem` is ignored * @return 0 on success, or errno on error * @raise EINTR when a signal got delivered while we were waiting + * @raise ECANCELED if thread was cancelled in masked mode * @raise ENOTSUP if `clock` is known but we can't use it here * @raise EFAULT if `req` or null or bad memory was passed * @raise EINVAL if `clock` is unknown to current platform @@ -87,6 +89,7 @@ errno_t clock_nanosleep(int clock, int flags, const struct timespec *req, struct timespec *rem) { int rc, e = errno; + BEGIN_CANCELLATION_POINT; if (!req || (IsAsan() && (!__asan_is_valid_timespec(req) || (rem && !__asan_is_valid_timespec(rem))))) { @@ -113,6 +116,8 @@ errno_t clock_nanosleep(int clock, int flags, const struct timespec *req, errno = e; } + END_CANCELLATION_POINT; + #if SYSDEBUG if (__tls_enabled && !(__get_tls()->tib_flags & TIB_FLAG_TIME_CRITICAL)) { STRACE("clock_nanosleep(%s, %s, %s, [%s]) → %s", DescribeClockName(clock), diff --git a/libc/calls/closefrom.c b/libc/calls/closefrom.c index 759a26351..e854ef55e 100644 --- a/libc/calls/closefrom.c +++ b/libc/calls/closefrom.c @@ -37,6 +37,7 @@ * @raise EBADF on OpenBSD if `first` is greater than highest fd * @raise EINVAL if flags are bad or first is greater than last * @raise EMFILE if a weird race condition happens on Linux + * @raise ECANCELED if thread was cancelled in masked mode * @raise EINTR possibly on OpenBSD * @raise ENOMEM on Linux maybe */ diff --git a/libc/calls/copy_file_range.c b/libc/calls/copy_file_range.c index b5ce0ff76..be6cb8164 100644 --- a/libc/calls/copy_file_range.c +++ b/libc/calls/copy_file_range.c @@ -16,7 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" @@ -39,6 +41,7 @@ static bool HasCopyFileRange(void) { bool ok; int e, rc; e = errno; + BLOCK_CANCELLATIONS; if (IsLinux()) { // We modernize our detection by a few years for simplicity. // This system call is chosen since it's listed by pledge(). @@ -49,6 +52,7 @@ static bool HasCopyFileRange(void) { } else { ok = false; } + ALLOW_CANCELLATIONS; errno = e; return ok; } @@ -81,6 +85,7 @@ static void copy_file_range_init(void) { * @raise EBADF if `infd` or `outfd` aren't open files or append-only * @raise EPERM if `fdout` refers to an immutable file on Linux * @raise ENOTSUP if `infd` or `outfd` is a zip file descriptor + * @raise ECANCELED if thread was cancelled in masked mode * @raise EINVAL if ranges overlap or `flags` is non-zero * @raise EFBIG if `setrlimit(RLIMIT_FSIZE)` is exceeded * @raise EFAULT if one of the pointers memory is bad @@ -93,12 +98,15 @@ static void copy_file_range_init(void) { * @raise EIO if a low-level i/o error happens * @see sendfile() for seekable → socket * @see splice() for fd ↔ pipe + * @cancellationpoint */ ssize_t copy_file_range(int infd, int64_t *opt_in_out_inoffset, int outfd, int64_t *opt_in_out_outoffset, size_t uptobytes, uint32_t flags) { ssize_t rc; pthread_once(&g_copy_file_range.once, copy_file_range_init); + BEGIN_CANCELLATION_POINT; + if (!g_copy_file_range.ok) { rc = enosys(); } else if (IsAsan() && ((opt_in_out_inoffset && @@ -112,6 +120,8 @@ ssize_t copy_file_range(int infd, int64_t *opt_in_out_inoffset, int outfd, rc = sys_copy_file_range(infd, opt_in_out_inoffset, outfd, opt_in_out_outoffset, uptobytes, flags); } + + END_CANCELLATION_POINT; STRACE("copy_file_range(%d, %s, %d, %s, %'zu, %#x) → %'ld% m", infd, DescribeInOutInt64(rc, opt_in_out_inoffset), outfd, DescribeInOutInt64(rc, opt_in_out_outoffset), uptobytes, flags); diff --git a/libc/calls/_timeval_gt.c b/libc/calls/cp.c similarity index 65% rename from libc/calls/_timeval_gt.c rename to libc/calls/cp.c index 8d0812b06..623aae909 100644 --- a/libc/calls/_timeval_gt.c +++ b/libc/calls/cp.c @@ -16,19 +16,44 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/calls/struct/timeval.h" +#include "libc/calls/blockcancel.internal.h" +#include "libc/calls/cp.internal.h" +#include "libc/runtime/internal.h" +#include "libc/runtime/runtime.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/tls.h" +#ifdef MODE_DBG -/** - * Returns true if timeval `x` is greater than `y`. - */ -bool _timeval_gt(struct timeval x, struct timeval y) { - if (x.tv_sec > y.tv_sec) { - return true; - } - if (x.tv_sec == y.tv_sec) { - if (x.tv_usec > y.tv_usec) { - return true; +int begin_cancellation_point(void) { + int state = 0; + struct CosmoTib *tib; + struct PosixThread *pt; + if (__enable_tls) { + tib = __get_tls(); + if ((pt = (struct PosixThread *)tib->tib_pthread)) { + state = pt->flags & PT_INCANCEL; + pt->flags |= PT_INCANCEL; } } - return false; + return state; } + +void end_cancellation_point(int state) { + struct CosmoTib *tib; + struct PosixThread *pt; + if (__enable_tls) { + tib = __get_tls(); + if ((pt = (struct PosixThread *)tib->tib_pthread)) { + pt->flags &= ~PT_INCANCEL; + pt->flags |= state; + } + } +} + +void report_cancellation_point(void) { + BLOCK_CANCELLATIONS; + _bt("error: need BEGIN/END_CANCELLATION_POINT\n"); + ALLOW_CANCELLATIONS; +} + +#endif /* MODE_DBG */ diff --git a/libc/calls/cp.internal.h b/libc/calls/cp.internal.h new file mode 100644 index 000000000..61ce89810 --- /dev/null +++ b/libc/calls/cp.internal.h @@ -0,0 +1,25 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_CP_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_CALLS_CP_INTERNAL_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +int begin_cancellation_point(void); +void end_cancellation_point(int); + +#ifndef MODE_DBG +#define BEGIN_CANCELLATION_POINT (void)0 +#define END_CANCELLATION_POINT (void)0 +#else +#define BEGIN_CANCELLATION_POINT \ + do { \ + int _Cp; \ + _Cp = begin_cancellation_point() +#define END_CANCELLATION_POINT \ + end_cancellation_point(_Cp); \ + } \ + while (0) +#endif + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_CP_INTERNAL_H_ */ diff --git a/libc/calls/creat.c b/libc/calls/creat.c index e6a8b4ab8..1c1dfe73e 100644 --- a/libc/calls/creat.c +++ b/libc/calls/creat.c @@ -31,6 +31,7 @@ * @param mode is octal bits, e.g. 0644 usually * @return file descriptor, or -1 w/ errno * @see openat() for further documentation + * @cancellationpoint * @asyncsignalsafe * @restartable * @threadsafe diff --git a/libc/calls/dup-nt.c b/libc/calls/dup-nt.c index d33ea0312..05d041eda 100644 --- a/libc/calls/dup-nt.c +++ b/libc/calls/dup-nt.c @@ -31,13 +31,11 @@ // Implements dup(), dup2(), dup3(), and F_DUPFD for Windows. textwindows int sys_dup_nt(int oldfd, int newfd, int flags, int start) { int64_t rc, proc, handle; - _unassert(oldfd >= 0); - _unassert(newfd >= -1); _unassert(!(flags & ~O_CLOEXEC)); __fds_lock(); - if (oldfd >= g_fds.n || + if (!__isfdopen(oldfd) || newfd < -1 || (g_fds.p[oldfd].kind != kFdFile && g_fds.p[oldfd].kind != kFdSocket && g_fds.p[oldfd].kind != kFdConsole)) { __fds_unlock(); diff --git a/libc/calls/execve-sysv.c b/libc/calls/execve-sysv.c index cf275a93c..191b863e1 100644 --- a/libc/calls/execve-sysv.c +++ b/libc/calls/execve-sysv.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/blocksigs.internal.h" #include "libc/calls/calls.h" #include "libc/calls/syscall-sysv.internal.h" @@ -40,6 +41,8 @@ static bool IsApeBinary(const char *path) { int fd; char buf[8]; bool res = false; + // TODO(jart): Should we block signals too? + BLOCK_CANCELLATIONS; if ((fd = sys_open(path, O_RDONLY, 0)) != -1) { if (sys_read(fd, buf, 8) == 8 && // (READ64LE(buf) == READ64LE("MZqFpD='") || @@ -48,6 +51,7 @@ static bool IsApeBinary(const char *path) { } sys_close(fd); } + ALLOW_CANCELLATIONS; return res; } diff --git a/libc/calls/fcntl.c b/libc/calls/fcntl.c index 8a5adfd89..e77c4f1ce 100644 --- a/libc/calls/fcntl.c +++ b/libc/calls/fcntl.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/flock.h" #include "libc/calls/struct/flock.internal.h" @@ -94,7 +95,7 @@ * @raise EDEADLK if `cmd` was `F_SETLKW` and waiting would deadlock * @raise EMFILE if `cmd` is `F_DUPFD` or `F_DUPFD_CLOEXEC` and * `RLIMIT_NOFILE` would be exceeded - * @cancellationpoint when `cmd` is `F_SETLKW` + * @cancellationpoint when `cmd` is `F_SETLKW` or `F_OFD_SETLKW` * @asyncsignalsafe * @restartable */ @@ -113,7 +114,9 @@ int fcntl(int fd, int cmd, ...) { rc = _weaken(__zipos_fcntl)(fd, cmd, arg); } else if (!IsWindows()) { if (cmd == F_SETLKW || cmd == F_OFD_SETLKW) { + BEGIN_CANCELLATION_POINT; rc = sys_fcntl(fd, cmd, arg, __sys_fcntl_cp); + END_CANCELLATION_POINT; } else { rc = sys_fcntl(fd, cmd, arg, __sys_fcntl); } diff --git a/libc/calls/fdatasync-nt.c b/libc/calls/fdatasync-nt.c index 147d879fb..2cc02a178 100644 --- a/libc/calls/fdatasync-nt.c +++ b/libc/calls/fdatasync-nt.c @@ -23,5 +23,6 @@ textwindows int sys_fdatasync_nt(int fd) { // TODO(jart): what should we do with worker pipes? if (!__isfdkind(fd, kFdFile)) return ebadf(); + if (_check_interrupts(false, 0)) return -1; return FlushFileBuffers(g_fds.p[fd].handle) ? 0 : -1; } diff --git a/libc/calls/fdatasync.c b/libc/calls/fdatasync.c index 3fff9e44f..68822f83a 100644 --- a/libc/calls/fdatasync.c +++ b/libc/calls/fdatasync.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/struct/stat.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" @@ -30,6 +31,8 @@ * @return 0 on success, or -1 w/ errno * @see sync(), fsync(), sync_file_range() * @see __nosync to secretly disable + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @cancellationpoint * @asyncsignalsafe */ @@ -37,11 +40,13 @@ int fdatasync(int fd) { int rc; struct stat st; if (__nosync != 0x5453455454534146) { + BEGIN_CANCELLATION_POINT; if (!IsWindows()) { rc = sys_fdatasync(fd); } else { rc = sys_fdatasync_nt(fd); } + END_CANCELLATION_POINT; STRACE("fdatasync(%d) → %d% m", fd, rc); } else { rc = fstat(fd, &st); diff --git a/libc/calls/flock.c b/libc/calls/flock.c index c75017e89..c267630d4 100644 --- a/libc/calls/flock.c +++ b/libc/calls/flock.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" @@ -35,11 +36,15 @@ */ int flock(int fd, int op) { int rc; + BEGIN_CANCELLATION_POINT; + if (!IsWindows()) { rc = sys_flock(fd, op); } else { rc = sys_flock_nt(fd, op); } + + END_CANCELLATION_POINT; STRACE("flock(%d, %d) → %d% m", fd, op, rc); return rc; } diff --git a/libc/calls/fstatfs.c b/libc/calls/fstatfs.c index 52591f915..226ed634e 100644 --- a/libc/calls/fstatfs.c +++ b/libc/calls/fstatfs.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/statfs-meta.internal.h" #include "libc/calls/struct/statfs.internal.h" @@ -33,7 +34,9 @@ int fstatfs(int fd, struct statfs *sf) { int rc; union statfs_meta m; + BEGIN_CANCELLATION_POINT; CheckLargeStackAllocation(&m, sizeof(m)); + if (!IsWindows()) { if ((rc = sys_fstatfs(fd, &m)) != -1) { statfs2cosmo(sf, &m); @@ -43,6 +46,8 @@ int fstatfs(int fd, struct statfs *sf) { } else { rc = ebadf(); } + + END_CANCELLATION_POINT; STRACE("fstatfs(%d, [%s]) → %d% m", fd, DescribeStatfs(rc, sf)); return rc; } diff --git a/libc/calls/fsync.c b/libc/calls/fsync.c index 98ff4ced2..597359a37 100644 --- a/libc/calls/fsync.c +++ b/libc/calls/fsync.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/struct/stat.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" @@ -28,6 +29,8 @@ * Blocks until kernel flushes buffers for fd to disk. * * @return 0 on success, or -1 w/ errno + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @see fdatasync(), sync_file_range() * @see __nosync to secretly disable * @cancellationpoint @@ -37,11 +40,13 @@ int fsync(int fd) { int rc; struct stat st; if (__nosync != 0x5453455454534146) { + BEGIN_CANCELLATION_POINT; if (!IsWindows()) { rc = sys_fsync(fd); } else { rc = sys_fdatasync_nt(fd); } + END_CANCELLATION_POINT; STRACE("fysnc(%d) → %d% m", fd, rc); } else { rc = fstat(fd, &st); diff --git a/libc/calls/ftruncate.c b/libc/calls/ftruncate.c index 854114667..a1aa9e86a 100644 --- a/libc/calls/ftruncate.c +++ b/libc/calls/ftruncate.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" @@ -49,6 +50,7 @@ * @return 0 on success, or -1 w/ errno * @raise EINVAL if `length` is negative * @raise EINTR if signal was delivered instead + * @raise ECANCELED if thread was cancelled in masked mode * @raise EIO if a low-level i/o error happened * @raise EFBIG or EINVAL if `length` is too huge * @raise ENOTSUP if `fd` is a zip file descriptor @@ -62,6 +64,8 @@ */ int ftruncate(int fd, int64_t length) { int rc; + BEGIN_CANCELLATION_POINT; + if (fd < 0) { rc = ebadf(); } else if (__isfdkind(fd, kFdZip)) { @@ -78,6 +82,8 @@ int ftruncate(int fd, int64_t length) { } else { rc = ebadf(); } + + END_CANCELLATION_POINT; STRACE("ftruncate(%d, %'ld) → %d% m", fd, length, rc); return rc; } diff --git a/libc/calls/getcwd-xnu.greg.c b/libc/calls/getcwd-xnu.greg.c index 78b85ba1c..d134d6ce1 100644 --- a/libc/calls/getcwd-xnu.greg.c +++ b/libc/calls/getcwd-xnu.greg.c @@ -42,7 +42,7 @@ char *sys_getcwd_xnu(char *res, size_t size) { int fd; union metastat st[2]; char buf[XNU_MAXPATHLEN], *ret = NULL; - if ((fd = sys_openat(AT_FDCWD, ".", O_RDONLY | O_DIRECTORY, 0)) != -1) { + if ((fd = __sys_openat_nc(AT_FDCWD, ".", O_RDONLY | O_DIRECTORY, 0)) != -1) { if (__sys_fstat(fd, &st[0]) != -1) { if (st[0].xnu.st_dev && st[0].xnu.st_ino) { if (__sys_fcntl(fd, XNU_F_GETPATH, (uintptr_t)buf) != -1) { diff --git a/libc/calls/nanosleep-xnu.c b/libc/calls/nanosleep-xnu.c index 653147f62..5f9c4a083 100644 --- a/libc/calls/nanosleep-xnu.c +++ b/libc/calls/nanosleep-xnu.c @@ -29,7 +29,7 @@ int sys_nanosleep_xnu(const struct timespec *req, struct timespec *rem) { int rc; struct timeval wt, t1, t2, td; if (rem) sys_gettimeofday_xnu(&t1, 0, 0); - wt = _timespec_totimeval(*req); // rounds up + wt = timespec_totimeval(*req); // rounds up rc = sys_select(0, 0, 0, 0, &wt); if (rem) { if (!rc) { @@ -39,12 +39,12 @@ int sys_nanosleep_xnu(const struct timespec *req, struct timespec *rem) { // xnu select() doesn't modify timeout // so we need, yet another system call sys_gettimeofday_xnu(&t2, 0, 0); - td = _timeval_sub(t2, t1); - if (_timeval_gte(td, wt)) { + td = timeval_sub(t2, t1); + if (timeval_cmp(td, wt) >= 0) { rem->tv_sec = 0; rem->tv_nsec = 0; } else { - *rem = _timeval_totimespec(_timeval_sub(wt, td)); + *rem = timeval_totimespec(timeval_sub(wt, td)); } } } diff --git a/libc/calls/nanosleep.c b/libc/calls/nanosleep.c index 2cffc641d..00bce7e1a 100644 --- a/libc/calls/nanosleep.c +++ b/libc/calls/nanosleep.c @@ -28,6 +28,7 @@ * time when -1 w/ `EINTR` is returned otherwise `rem` is undefined * @return 0 on success, or -1 w/ errno * @raise EINVAL if `req->tv_nsec ∉ [0,1000000000)` + * @raise ECANCELED if thread was cancelled in masked mode * @raise EINTR if a signal was delivered and `rem` is updated * @raise EFAULT if `req` is NULL or `req` / `rem` is a bad pointer * @raise ENOSYS on bare metal diff --git a/libc/calls/openat.c b/libc/calls/openat.c index bd4fc3c95..fe73792f2 100644 --- a/libc/calls/openat.c +++ b/libc/calls/openat.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/syscall-nt.internal.h" @@ -130,6 +131,7 @@ * @raise ENOTSUP if `file` is on zip file system and process is vfork()'d * @raise ENOSPC if file system is full when `file` would be `O_CREAT`ed * @raise EINTR if we needed to block and a signal was delivered instead + * @raise ECANCELED if thread was cancelled in masked mode * @raise ENOENT if `file` doesn't exist when `O_CREAT` isn't in `flags` * @raise ENOENT if `file` points to a string that's empty * @raise ENOMEM if insufficient memory was available @@ -155,6 +157,8 @@ int openat(int dirfd, const char *file, int flags, ...) { va_start(va, flags); mode = va_arg(va, unsigned); va_end(va); + BEGIN_CANCELLATION_POINT; + if (file && (!IsAsan() || __asan_is_valid_str(file))) { if (!__isfdkind(dirfd, kFdZip)) { if (_weaken(__zipos_open) && @@ -177,6 +181,8 @@ int openat(int dirfd, const char *file, int flags, ...) { } else { rc = efault(); } + + END_CANCELLATION_POINT; STRACE("openat(%s, %#s, %s, %#o) → %d% m", DescribeDirfd(dirfd), file, DescribeOpenFlags(flags), (flags & (O_CREAT | O_TMPFILE)) ? mode : 0, rc); diff --git a/libc/calls/pause.c b/libc/calls/pause.c index 3d0a475a8..da04a2365 100644 --- a/libc/calls/pause.c +++ b/libc/calls/pause.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/dce.h" #include "libc/intrin/strace.internal.h" @@ -44,6 +45,7 @@ int pause(void) { int rc; STRACE("pause() → [...]"); + BEGIN_CANCELLATION_POINT; if (!IsWindows()) { // We'll polyfill pause() using select() with a null timeout, which @@ -64,6 +66,7 @@ int pause(void) { rc = sys_pause_nt(); } + END_CANCELLATION_POINT; STRACE("[...] pause → %d% m", rc); return rc; } diff --git a/libc/calls/pipe2.c b/libc/calls/pipe2.c index 133e77385..8b05aa0a4 100644 --- a/libc/calls/pipe2.c +++ b/libc/calls/pipe2.c @@ -16,17 +16,19 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/strace.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" /** * Creates file-less file descriptors for interprocess communication. * + * @raise EMFILE if process `RLIMIT_NOFILE` has been reached + * @raise ENFILE if system-wide file limit has been reached * @param pipefd is used to return (reader, writer) file descriptors * @param flags can have O_CLOEXEC or O_DIRECT or O_NONBLOCK * @return 0 on success, or -1 w/ errno and pipefd isn't modified diff --git a/libc/calls/poll.c b/libc/calls/poll.c index 8b20f9451..4faa51063 100644 --- a/libc/calls/poll.c +++ b/libc/calls/poll.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/strace.internal.h" @@ -55,6 +56,8 @@ * @return fds[𝑖].revents is always zero initializaed and then will * be populated with POLL{IN,OUT,PRI,HUP,ERR,NVAL} if something * was determined about the file descriptor + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @cancellationpoint * @asyncsignalsafe * @threadsafe @@ -64,6 +67,7 @@ int poll(struct pollfd *fds, size_t nfds, int timeout_ms) { int rc; size_t n; uint64_t millis; + BEGIN_CANCELLATION_POINT; if (IsAsan() && (__builtin_mul_overflow(nfds, sizeof(struct pollfd), &n) || !__asan_is_valid(fds, n))) { @@ -79,6 +83,7 @@ int poll(struct pollfd *fds, size_t nfds, int timeout_ms) { rc = sys_poll_nt(fds, nfds, &millis, 0); } + END_CANCELLATION_POINT; STRACE("poll(%s, %'zu, %'d) → %d% lm", DescribePollFds(rc, fds, nfds), nfds, timeout_ms, rc); return rc; diff --git a/libc/calls/ppoll.c b/libc/calls/ppoll.c index 87cea08dd..95cec83d5 100644 --- a/libc/calls/ppoll.c +++ b/libc/calls/ppoll.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/timespec.h" @@ -50,6 +51,8 @@ * * @param timeout if null will block indefinitely * @param sigmask may be null in which case no mask change happens + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @cancellationpoint * @asyncsignalsafe * @threadsafe @@ -62,6 +65,7 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout, uint64_t millis; sigset_t oldmask; struct timespec ts, *tsp; + BEGIN_CANCELLATION_POINT; if (IsAsan() && (__builtin_mul_overflow(nfds, sizeof(struct pollfd), &n) || !__asan_is_valid(fds, n) || @@ -96,6 +100,7 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout, rc = sys_poll_nt(fds, nfds, &millis, sigmask); } + END_CANCELLATION_POINT; STRACE("ppoll(%s, %'zu, %s, %s) → %d% lm", DescribePollFds(rc, fds, nfds), nfds, DescribeTimespec(0, timeout), DescribeSigset(0, sigmask), rc); return rc; diff --git a/libc/calls/pread.c b/libc/calls/pread.c index 3d3732d14..1bdf10459 100644 --- a/libc/calls/pread.c +++ b/libc/calls/pread.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -47,6 +48,7 @@ * @raise EBADF if `fd` isn't an open file descriptor * @raise EIO if a complicated i/o error happened * @raise EINTR if signal was delivered instead + * @raise ECANCELED if thread was cancelled in masked mode * @see pwrite(), write() * @cancellationpoint * @asyncsignalsafe @@ -55,6 +57,8 @@ */ ssize_t pread(int fd, void *buf, size_t size, int64_t offset) { ssize_t rc; + BEGIN_CANCELLATION_POINT; + if (offset < 0) { rc = einval(); } else if (fd < 0) { @@ -73,6 +77,8 @@ ssize_t pread(int fd, void *buf, size_t size, int64_t offset) { rc = ebadf(); } _npassert(rc == -1 || (size_t)rc <= size); + + END_CANCELLATION_POINT; DATATRACE("pread(%d, [%#.*hhs%s], %'zu, %'zd) → %'zd% m", fd, MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, offset, rc); return rc; diff --git a/libc/calls/preadv.c b/libc/calls/preadv.c index 1493f3007..83e8e57a8 100644 --- a/libc/calls/preadv.c +++ b/libc/calls/preadv.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -114,7 +115,9 @@ static ssize_t Preadv(int fd, struct iovec *iov, int iovlen, int64_t off) { */ ssize_t preadv(int fd, struct iovec *iov, int iovlen, int64_t off) { ssize_t rc; + BEGIN_CANCELLATION_POINT; rc = Preadv(fd, iov, iovlen, off); + END_CANCELLATION_POINT; STRACE("preadv(%d, [%s], %d, %'ld) → %'ld% m", fd, DescribeIovec(rc, iov, iovlen), iovlen, off, rc); return rc; diff --git a/libc/calls/pwrite.c b/libc/calls/pwrite.c index fc502c6da..425a2bdb8 100644 --- a/libc/calls/pwrite.c +++ b/libc/calls/pwrite.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -50,6 +51,8 @@ ssize_t pwrite(int fd, const void *buf, size_t size, int64_t offset) { ssize_t rc; size_t wrote; + BEGIN_CANCELLATION_POINT; + if (offset < 0) { rc = einval(); } else if (fd == -1) { @@ -71,6 +74,8 @@ ssize_t pwrite(int fd, const void *buf, size_t size, int64_t offset) { _npassert(wrote <= size); } } + + END_CANCELLATION_POINT; DATATRACE("pwrite(%d, %#.*hhs%s, %'zu, %'zd) → %'zd% m", fd, MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, offset, rc); return rc; diff --git a/libc/calls/pwritev.c b/libc/calls/pwritev.c index f8dd10fd8..924e93597 100644 --- a/libc/calls/pwritev.c +++ b/libc/calls/pwritev.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -120,7 +121,9 @@ static ssize_t Pwritev(int fd, const struct iovec *iov, int iovlen, */ ssize_t pwritev(int fd, const struct iovec *iov, int iovlen, int64_t off) { ssize_t rc; + BEGIN_CANCELLATION_POINT; rc = Pwritev(fd, iov, iovlen, off); + END_CANCELLATION_POINT; STRACE("pwritev(%d, %s, %d, %'ld) → %'ld% m", fd, DescribeIovec(rc != -1 ? rc : -2, iov, iovlen), iovlen, off, rc); return rc; diff --git a/libc/calls/read.c b/libc/calls/read.c index 2be56bb00..2b17753b7 100644 --- a/libc/calls/read.c +++ b/libc/calls/read.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -49,6 +50,7 @@ * @raise EPERM if pledge() is in play without the stdio promise * @raise EIO if low-level i/o error happened * @raise EINTR if signal was delivered instead + * @raise ECANCELED if thread was cancelled in masked mode * @raise ENOTCONN if `fd` is a socket and it isn't connected * @raise ECONNRESET if socket peer forcibly closed connection * @raise ETIMEDOUT if socket transmission timeout occurred @@ -63,6 +65,7 @@ */ ssize_t read(int fd, void *buf, size_t size) { ssize_t rc; + BEGIN_CANCELLATION_POINT; if (fd >= 0) { if ((!buf && size) || (IsAsan() && !__asan_is_valid(buf, size))) { rc = efault(); @@ -82,6 +85,7 @@ ssize_t read(int fd, void *buf, size_t size) { } else { rc = ebadf(); } + END_CANCELLATION_POINT; DATATRACE("read(%d, [%#.*hhs%s], %'zu) → %'zd% m", fd, MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, rc); return rc; diff --git a/libc/calls/readv.c b/libc/calls/readv.c index 5a05d51f3..ad4cbd860 100644 --- a/libc/calls/readv.c +++ b/libc/calls/readv.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -48,6 +49,7 @@ */ ssize_t readv(int fd, const struct iovec *iov, int iovlen) { ssize_t rc; + BEGIN_CANCELLATION_POINT; if (fd >= 0 && iovlen >= 0) { if (IsAsan() && !__asan_is_valid_iov(iov, iovlen)) { @@ -74,6 +76,7 @@ ssize_t readv(int fd, const struct iovec *iov, int iovlen) { rc = einval(); } + END_CANCELLATION_POINT; STRACE("readv(%d, [%s], %d) → %'ld% m", fd, DescribeIovec(rc, iov, iovlen), iovlen, rc); return rc; diff --git a/libc/calls/addrusage.c b/libc/calls/rusage_add.c similarity index 93% rename from libc/calls/addrusage.c rename to libc/calls/rusage_add.c index 8572da2c6..703d08ccd 100644 --- a/libc/calls/addrusage.c +++ b/libc/calls/rusage_add.c @@ -22,9 +22,9 @@ /** * Accumulates resource statistics in `y` to `x`. */ -void _addrusage(struct rusage *x, const struct rusage *y) { - x->ru_utime = _timeval_add(x->ru_utime, y->ru_utime); - x->ru_stime = _timeval_add(x->ru_stime, y->ru_stime); +void rusage_add(struct rusage *x, const struct rusage *y) { + x->ru_utime = timeval_add(x->ru_utime, y->ru_utime); + x->ru_stime = timeval_add(x->ru_stime, y->ru_stime); x->ru_maxrss = MAX(x->ru_maxrss, y->ru_maxrss); x->ru_ixrss += y->ru_ixrss; x->ru_idrss += y->ru_idrss; diff --git a/libc/calls/sigsuspend.c b/libc/calls/sigsuspend.c index 7e2da624d..872b6d267 100644 --- a/libc/calls/sigsuspend.c +++ b/libc/calls/sigsuspend.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/struct/sigset.h" @@ -49,6 +50,8 @@ int sigsuspend(const sigset_t *ignore) { long ms, totoms; sigset_t save, *arg, mask = {0}; STRACE("sigsuspend(%s) → ...", DescribeSigset(0, ignore)); + BEGIN_CANCELLATION_POINT; + if (IsAsan() && ignore && !__asan_is_valid(ignore, sizeof(*ignore))) { rc = efault(); } else if (IsXnu() || IsOpenbsd()) { @@ -94,6 +97,8 @@ int sigsuspend(const sigset_t *ignore) { // TODO(jart): sigsuspend metal support rc = enosys(); } + + END_CANCELLATION_POINT; STRACE("...sigsuspend → %d% m", rc); return rc; } diff --git a/libc/calls/sigtimedwait.c b/libc/calls/sigtimedwait.c index c2063f49c..86cdc3eeb 100644 --- a/libc/calls/sigtimedwait.c +++ b/libc/calls/sigtimedwait.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/asan.internal.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/sigtimedwait.h" #include "libc/calls/sigtimedwait.internal.h" #include "libc/calls/struct/siginfo.internal.h" @@ -36,6 +37,7 @@ * @param timeout is relative deadline and null means wait forever * @return signal number on success, or -1 w/ errno * @raise EINTR if an asynchronous signal was delivered instead + * @raise ECANCELED if thread was cancelled in masked mode * @raise EINVAL if nanoseconds parameter was out of range * @raise EAGAIN if deadline expired * @raise ENOSYS on Windows, XNU, OpenBSD, Metal @@ -48,6 +50,7 @@ int sigtimedwait(const sigset_t *set, siginfo_t *info, char strsig[15]; struct timespec ts; union siginfo_meta si = {0}; + BEGIN_CANCELLATION_POINT; if (IsAsan() && (!__asan_is_valid(set, sizeof(*set)) || (info && !__asan_is_valid(info, sizeof(*info))) || @@ -69,6 +72,7 @@ int sigtimedwait(const sigset_t *set, siginfo_t *info, rc = enosys(); } + END_CANCELLATION_POINT; STRACE("sigtimedwait(%s, [%s], %s) → %s% m", DescribeSigset(0, set), DescribeSiginfo(rc, info), DescribeTimespec(0, timeout), strsignal_r(rc, strsig)); diff --git a/libc/calls/sigwaitinfo.c b/libc/calls/sigwaitinfo.c index 394077983..18fc9285d 100644 --- a/libc/calls/sigwaitinfo.c +++ b/libc/calls/sigwaitinfo.c @@ -25,6 +25,7 @@ * @param info if not null shall receive info about signal * @return signal number on success, or -1 w/ errno * @raise EINTR if an asynchronous signal was delivered instead + * @raise ECANCELED if thread was cancelled in masked mode * @raise ENOSYS on OpenBSD, XNU, and Windows * @see sigtimedwait() * @cancellationpoint diff --git a/libc/calls/sleep.c b/libc/calls/sleep.c index 2631a82c3..d2c044b07 100644 --- a/libc/calls/sleep.c +++ b/libc/calls/sleep.c @@ -16,7 +16,9 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/struct/timespec.h" +#include "libc/errno.h" #include "libc/limits.h" #include "libc/sysv/consts/clock.h" #include "libc/time/time.h" @@ -27,16 +29,20 @@ * @return 0 if the full time elapsed, otherwise we assume an interrupt * was delivered, in which case the errno condition is ignored, and * this function shall return the number of unslept seconds rounded - * using the ceiling function + * using the ceiling function, and finally `-1u` may be returned if + * thread was cancelled with `PTHREAD_CANCEL_MASKED` in play * @see clock_nanosleep() * @cancellationpoint * @asyncsignalsafe * @norestart */ unsigned sleep(unsigned seconds) { + errno_t rc; unsigned unslept; struct timespec tv = {seconds}; - if (!clock_nanosleep(CLOCK_REALTIME, 0, &tv, &tv)) return 0; + if (!(rc = clock_nanosleep(CLOCK_REALTIME, 0, &tv, &tv))) return 0; + if (rc == ECANCELED) return -1u; + _npassert(rc == EINTR); unslept = tv.tv_sec; if (tv.tv_nsec && unslept < UINT_MAX) { ++unslept; diff --git a/libc/calls/statfs.c b/libc/calls/statfs.c index 97aa6be7a..0e8d01e0a 100644 --- a/libc/calls/statfs.c +++ b/libc/calls/statfs.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/struct/statfs-meta.internal.h" #include "libc/calls/struct/statfs.internal.h" @@ -29,11 +30,15 @@ /** * Returns information about filesystem. * @return 0 on success, or -1 w/ errno + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @cancellationpoint */ int statfs(const char *path, struct statfs *sf) { int rc; union statfs_meta m; + BEGIN_CANCELLATION_POINT; + CheckLargeStackAllocation(&m, sizeof(m)); if (!IsWindows()) { if ((rc = sys_statfs(path, &m)) != -1) { @@ -42,6 +47,8 @@ int statfs(const char *path, struct statfs *sf) { } else { rc = sys_statfs_nt(path, sf); } + + END_CANCELLATION_POINT; STRACE("statfs(%#s, [%s]) → %d% m", path, DescribeStatfs(rc, sf)); return rc; } diff --git a/libc/calls/struct/rusage.h b/libc/calls/struct/rusage.h index 343051aad..b85be7615 100644 --- a/libc/calls/struct/rusage.h +++ b/libc/calls/struct/rusage.h @@ -26,7 +26,7 @@ struct rusage { int getrusage(int, struct rusage *); int wait3(int *, int, struct rusage *); int wait4(int, int *, int, struct rusage *); -void _addrusage(struct rusage *, const struct rusage *); +void rusage_add(struct rusage *, const struct rusage *); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/calls/struct/timespec.h b/libc/calls/struct/timespec.h index 6b50a2612..4d8404f74 100644 --- a/libc/calls/struct/timespec.h +++ b/libc/calls/struct/timespec.h @@ -3,8 +3,8 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -#define _timespec_zero ((struct timespec){0}) -#define _timespec_max ((struct timespec){0x7fffffffffffffff, 999999999}) +#define timespec_zero ((struct timespec){0}) +#define timespec_max ((struct timespec){0x7fffffffffffffff, 999999999}) struct timespec { int64_t tv_sec; @@ -21,22 +21,19 @@ int utimensat(int, const char *, const struct timespec[2], int); int timespec_get(struct timespec *, int); int timespec_getres(struct timespec *, int); -int _timespec_cmp(struct timespec, struct timespec) pureconst; -bool _timespec_eq(struct timespec, struct timespec) pureconst; -bool _timespec_gt(struct timespec, struct timespec) pureconst; -bool _timespec_gte(struct timespec, struct timespec) pureconst; -int64_t _timespec_tomicros(struct timespec) pureconst; -int64_t _timespec_tomillis(struct timespec) pureconst; -int64_t _timespec_tonanos(struct timespec) pureconst; -struct timespec _timespec_add(struct timespec, struct timespec) pureconst; -struct timespec _timespec_fromnanos(int64_t) pureconst; -struct timespec _timespec_frommicros(int64_t) pureconst; -struct timespec _timespec_frommillis(int64_t) pureconst; -struct timespec _timespec_real(void); -struct timespec _timespec_mono(void); -struct timespec _timespec_sleep(struct timespec); -int _timespec_sleep_until(struct timespec); -struct timespec _timespec_sub(struct timespec, struct timespec) pureconst; +int timespec_cmp(struct timespec, struct timespec) pureconst; +int64_t timespec_tomicros(struct timespec) pureconst; +int64_t timespec_tomillis(struct timespec) pureconst; +int64_t timespec_tonanos(struct timespec) pureconst; +struct timespec timespec_add(struct timespec, struct timespec) pureconst; +struct timespec timespec_fromnanos(int64_t) pureconst; +struct timespec timespec_frommicros(int64_t) pureconst; +struct timespec timespec_frommillis(int64_t) pureconst; +struct timespec timespec_real(void); +struct timespec timespec_mono(void); +struct timespec timespec_sleep(struct timespec); +int timespec_sleep_until(struct timespec); +struct timespec timespec_sub(struct timespec, struct timespec) pureconst; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/calls/struct/timeval.h b/libc/calls/struct/timeval.h index 5d6a66ef3..384093411 100644 --- a/libc/calls/struct/timeval.h +++ b/libc/calls/struct/timeval.h @@ -16,14 +16,11 @@ int gettimeofday(struct timeval *, struct timezone *); int lutimes(const char *, const struct timeval[2]); int utimes(const char *, const struct timeval[2]); -int _timeval_cmp(struct timeval, struct timeval) pureconst; -bool _timeval_eq(struct timeval, struct timeval) pureconst; -bool _timeval_gt(struct timeval, struct timeval) pureconst; -bool _timeval_gte(struct timeval, struct timeval) pureconst; -struct timeval _timeval_add(struct timeval, struct timeval) pureconst; -struct timeval _timeval_sub(struct timeval, struct timeval) pureconst; -struct timeval _timespec_totimeval(struct timespec) pureconst; -struct timespec _timeval_totimespec(struct timeval) pureconst; +int timeval_cmp(struct timeval, struct timeval) pureconst; +struct timeval timeval_add(struct timeval, struct timeval) pureconst; +struct timeval timeval_sub(struct timeval, struct timeval) pureconst; +struct timeval timespec_totimeval(struct timespec) pureconst; +struct timespec timeval_totimespec(struct timeval) pureconst; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/calls/syscall-sysv.internal.h b/libc/calls/syscall-sysv.internal.h index 42d1e7f57..b8b37ac45 100644 --- a/libc/calls/syscall-sysv.internal.h +++ b/libc/calls/syscall-sysv.internal.h @@ -24,6 +24,7 @@ i32 __sys_fstatat(i32, const char *, void *, i32) hidden; i32 __sys_gettid(i64 *) hidden; i32 __sys_munmap(void *, u64) hidden; i32 __sys_openat(i32, const char *, i32, u32) hidden; +i32 __sys_openat_nc(i32, const char *, i32, u32) hidden; i32 __sys_pipe2(i32[hasatleast 2], u32) hidden; i32 sys_arch_prctl(i32, i64) hidden; i32 sys_chdir(const char *) hidden; diff --git a/libc/calls/syscall_support-sysv.internal.h b/libc/calls/syscall_support-sysv.internal.h index d50f79b2b..db674b75d 100644 --- a/libc/calls/syscall_support-sysv.internal.h +++ b/libc/calls/syscall_support-sysv.internal.h @@ -10,21 +10,23 @@ bool __is_linux_2_6_23(void) hidden; bool32 sys_isatty_metal(int); int __fixupnewfd(int, int) hidden; int __notziposat(int, const char *); +int __tkill(int, int, void *) hidden; +int _fork(uint32_t) hidden; +int _isptmaster(int) hidden; +int _ptsname(int, char *, size_t) hidden; int getdomainname_linux(char *, size_t) hidden; int gethostname_bsd(char *, size_t, int) hidden; int gethostname_linux(char *, size_t) hidden; int gethostname_nt(char *, size_t, int) hidden; int sys_msyscall(void *, size_t); long sys_bogus(void); +ssize_t __getrandom(void *, size_t, unsigned) hidden; void *__vdsosym(const char *, const char *) hidden; void __onfork(void) hidden; void __restore_rt() hidden; void __restore_rt_netbsd(void) hidden; void cosmo2flock(uintptr_t) hidden; void flock2cosmo(uintptr_t) hidden; -int _ptsname(int, char *, size_t) hidden; -int _isptmaster(int) hidden; -int _fork(uint32_t) hidden; COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/calls/sysinfo.c b/libc/calls/sysinfo.c index e23c7ab98..6a402793d 100644 --- a/libc/calls/sysinfo.c +++ b/libc/calls/sysinfo.c @@ -40,7 +40,7 @@ static int64_t GetUptime(void) { size_t n = sizeof(x); int mib[] = {CTL_KERN, KERN_BOOTTIME}; if (sys_sysctl(mib, ARRAYLEN(mib), &x, &n, 0, 0) == -1) return 0; - return _timespec_real().tv_sec - x.tv_sec; + return timespec_real().tv_sec - x.tv_sec; } static int64_t GetPhysmem(void) { diff --git a/libc/calls/tcdrain.c b/libc/calls/tcdrain.c index 40e5a41bd..b673ae904 100644 --- a/libc/calls/tcdrain.c +++ b/libc/calls/tcdrain.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall_support-nt.internal.h" @@ -27,6 +28,7 @@ static textwindows int sys_tcdrain_nt(int fd) { if (!__isfdopen(fd)) return ebadf(); + if (_check_interrupts(false, g_fds.p)) return -1; if (!FlushFileBuffers(g_fds.p[fd].handle)) return __winerr(); return 0; } @@ -39,12 +41,15 @@ static textwindows int sys_tcdrain_nt(int fd) { * @raise ENOTTY if `fd` is open but not a teletypewriter * @raise EIO if process group of writer is orphoned, calling thread is * not blocking `SIGTTOU`, and process isn't ignoring `SIGTTOU` + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @raise ENOSYS on bare metal * @cancellationpoint * @asyncsignalsafe */ int tcdrain(int fd) { int rc; + BEGIN_CANCELLATION_POINT; if (IsMetal()) { rc = enosys(); } else if (!IsWindows()) { @@ -52,6 +57,7 @@ int tcdrain(int fd) { } else { rc = sys_tcdrain_nt(fd); } + END_CANCELLATION_POINT; STRACE("tcdrain(%d) → %d% m", fd, rc); return rc; } diff --git a/libc/calls/_timespec_add.c b/libc/calls/timespec_add.c similarity index 96% rename from libc/calls/_timespec_add.c rename to libc/calls/timespec_add.c index ae9627da5..b235cdb17 100644 --- a/libc/calls/_timespec_add.c +++ b/libc/calls/timespec_add.c @@ -21,7 +21,7 @@ /** * Adds two nanosecond timestamps. */ -struct timespec _timespec_add(struct timespec x, struct timespec y) { +struct timespec timespec_add(struct timespec x, struct timespec y) { x.tv_sec += y.tv_sec; x.tv_nsec += y.tv_nsec; if (x.tv_nsec >= 1000000000) { diff --git a/libc/calls/_timespec_cmp.c b/libc/calls/timespec_cmp.c similarity index 94% rename from libc/calls/_timespec_cmp.c rename to libc/calls/timespec_cmp.c index 88c13f248..ddba4be00 100644 --- a/libc/calls/_timespec_cmp.c +++ b/libc/calls/timespec_cmp.c @@ -20,8 +20,10 @@ /** * Compares nanosecond timestamps. + * + * @return 0 if equal, -1 if `a < b`, or +1 if `a > b` */ -int _timespec_cmp(struct timespec a, struct timespec b) { +int timespec_cmp(struct timespec a, struct timespec b) { int cmp; if (!(cmp = (a.tv_sec > b.tv_sec) - (a.tv_sec < b.tv_sec))) { cmp = (a.tv_nsec > b.tv_nsec) - (a.tv_nsec < b.tv_nsec); diff --git a/libc/calls/_timespec_frommicros.c b/libc/calls/timespec_frommicros.c similarity index 97% rename from libc/calls/_timespec_frommicros.c rename to libc/calls/timespec_frommicros.c index 261faba9a..d53633e89 100644 --- a/libc/calls/_timespec_frommicros.c +++ b/libc/calls/timespec_frommicros.c @@ -21,7 +21,7 @@ /** * Converts timespec interval from microseconds. */ -struct timespec _timespec_frommicros(int64_t x) { +struct timespec timespec_frommicros(int64_t x) { struct timespec ts; ts.tv_sec = x / 1000000; ts.tv_nsec = x % 1000000 * 1000; diff --git a/libc/calls/_timespec_frommillis.c b/libc/calls/timespec_frommillis.c similarity index 97% rename from libc/calls/_timespec_frommillis.c rename to libc/calls/timespec_frommillis.c index e52cb6e5e..b84037f40 100644 --- a/libc/calls/_timespec_frommillis.c +++ b/libc/calls/timespec_frommillis.c @@ -21,7 +21,7 @@ /** * Converts timespec interval from milliseconds. */ -struct timespec _timespec_frommillis(int64_t x) { +struct timespec timespec_frommillis(int64_t x) { struct timespec ts; ts.tv_sec = x / 1000; ts.tv_nsec = x % 1000 * 1000000; diff --git a/libc/calls/_timespec_fromnanos.c b/libc/calls/timespec_fromnanos.c similarity index 97% rename from libc/calls/_timespec_fromnanos.c rename to libc/calls/timespec_fromnanos.c index 7e4948b9b..618548cc2 100644 --- a/libc/calls/_timespec_fromnanos.c +++ b/libc/calls/timespec_fromnanos.c @@ -21,7 +21,7 @@ /** * Converts timespec interval from nanoseconds. */ -struct timespec _timespec_fromnanos(int64_t x) { +struct timespec timespec_fromnanos(int64_t x) { struct timespec ts; ts.tv_sec = x / 1000000000; ts.tv_nsec = x % 1000000000; diff --git a/libc/calls/_timespec_get.c b/libc/calls/timespec_get.c similarity index 98% rename from libc/calls/_timespec_get.c rename to libc/calls/timespec_get.c index 1bd948775..5cc1c27a5 100644 --- a/libc/calls/_timespec_get.c +++ b/libc/calls/timespec_get.c @@ -26,7 +26,7 @@ * @param ts receives `CLOCK_REALTIME` timestamp * @param base must be `TIME_UTC` * @return `base` on success, or `0` on failure - * @see _timespec_real() + * @see timespec_real() */ int timespec_get(struct timespec *ts, int base) { if (base == TIME_UTC && !clock_gettime(CLOCK_REALTIME, ts)) { diff --git a/libc/calls/_timespec_getres.c b/libc/calls/timespec_getres.c similarity index 100% rename from libc/calls/_timespec_getres.c rename to libc/calls/timespec_getres.c diff --git a/libc/calls/_timespec_mono.c b/libc/calls/timespec_mono.c similarity index 97% rename from libc/calls/_timespec_mono.c rename to libc/calls/timespec_mono.c index b0e46c612..514db06a6 100644 --- a/libc/calls/_timespec_mono.c +++ b/libc/calls/timespec_mono.c @@ -25,9 +25,9 @@ * * This function uses a `CLOCK_MONOTONIC` clock and never fails. * - * @see _timespec_real() + * @see timespec_real() */ -struct timespec _timespec_mono(void) { +struct timespec timespec_mono(void) { struct timespec ts; _npassert(!clock_gettime(CLOCK_MONOTONIC_FAST, &ts)); return ts; diff --git a/libc/calls/_timespec_real.c b/libc/calls/timespec_real.c similarity index 97% rename from libc/calls/_timespec_real.c rename to libc/calls/timespec_real.c index f54a11a70..6b97142d2 100644 --- a/libc/calls/_timespec_real.c +++ b/libc/calls/timespec_real.c @@ -27,9 +27,9 @@ * clock_gettime() or timespec_real() this interface avoids the use of * pointers which lets time handling code become more elegant. * - * @see _timespec_mono() + * @see timespec_mono() */ -struct timespec _timespec_real(void) { +struct timespec timespec_real(void) { struct timespec ts; _npassert(!clock_gettime(CLOCK_REALTIME_FAST, &ts)); return ts; diff --git a/libc/calls/_timespec_sleep.c b/libc/calls/timespec_sleep.c similarity index 86% rename from libc/calls/_timespec_sleep.c rename to libc/calls/timespec_sleep.c index d660712c1..bbf65f79c 100644 --- a/libc/calls/_timespec_sleep.c +++ b/libc/calls/timespec_sleep.c @@ -17,8 +17,10 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/struct/timespec.h" #include "libc/errno.h" +#include "libc/str/str.h" #include "libc/sysv/consts/clock.h" /** @@ -26,13 +28,14 @@ * * @return unslept time which may be non-zero if the call was interrupted */ -struct timespec _timespec_sleep(struct timespec delay) { - int rc; +struct timespec timespec_sleep(struct timespec delay) { + errno_t rc; struct timespec remain; - if (!(rc = clock_nanosleep(CLOCK_REALTIME, 0, &delay, &remain))) { - return (struct timespec){0}; - } else { - _npassert(rc == EINTR || rc == ECANCELED); - return remain; + BLOCK_CANCELLATIONS; + bzero(&remain, sizeof(remain)); + if ((rc = clock_nanosleep(CLOCK_REALTIME, 0, &delay, &remain))) { + _npassert(rc == EINTR); } + ALLOW_CANCELLATIONS; + return remain; } diff --git a/libc/calls/_timespec_sleep_until.c b/libc/calls/timespec_sleep_until.c similarity index 91% rename from libc/calls/_timespec_sleep_until.c rename to libc/calls/timespec_sleep_until.c index f4c5adfe3..277b2d45d 100644 --- a/libc/calls/_timespec_sleep_until.c +++ b/libc/calls/timespec_sleep_until.c @@ -26,9 +26,12 @@ * Sleeps until the specified time. * * @return 0 on success, or EINTR if interrupted + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered + * @cancellationpoint */ -errno_t _timespec_sleep_until(struct timespec abs_deadline) { - int rc; +errno_t timespec_sleep_until(struct timespec abs_deadline) { + errno_t rc; rc = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &abs_deadline, 0); _npassert(!rc || rc == EINTR || rc == ECANCELED); return rc; diff --git a/libc/calls/_timespec_sub.c b/libc/calls/timespec_sub.c similarity index 96% rename from libc/calls/_timespec_sub.c rename to libc/calls/timespec_sub.c index 0f1fe4c35..e227924f1 100644 --- a/libc/calls/_timespec_sub.c +++ b/libc/calls/timespec_sub.c @@ -21,7 +21,7 @@ /** * Subtracts two nanosecond timestamps. */ -struct timespec _timespec_sub(struct timespec a, struct timespec b) { +struct timespec timespec_sub(struct timespec a, struct timespec b) { a.tv_sec -= b.tv_sec; if (a.tv_nsec < b.tv_nsec) { a.tv_nsec += 1000000000; diff --git a/libc/calls/_timespec_tomicros.c b/libc/calls/timespec_tomicros.c similarity index 97% rename from libc/calls/_timespec_tomicros.c rename to libc/calls/timespec_tomicros.c index 2155be4c0..011687fe8 100644 --- a/libc/calls/_timespec_tomicros.c +++ b/libc/calls/timespec_tomicros.c @@ -31,9 +31,9 @@ * `INT64_MIN` may be returned. The `errno` variable isn't changed. * * @return 64-bit scalar holding microseconds since epoch - * @see _timespec_totimeval() + * @see timespec_totimeval() */ -int64_t _timespec_tomicros(struct timespec ts) { +int64_t timespec_tomicros(struct timespec ts) { int64_t us; // reduce precision from nanos to micros if (ts.tv_nsec <= 999999000) { diff --git a/libc/calls/_timespec_tomillis.c b/libc/calls/timespec_tomillis.c similarity index 98% rename from libc/calls/_timespec_tomillis.c rename to libc/calls/timespec_tomillis.c index 7fb07dc35..fca87f986 100644 --- a/libc/calls/_timespec_tomillis.c +++ b/libc/calls/timespec_tomillis.c @@ -32,7 +32,7 @@ * * @return 64-bit scalar milliseconds since epoch */ -int64_t _timespec_tomillis(struct timespec ts) { +int64_t timespec_tomillis(struct timespec ts) { int64_t ms; // reduce precision from nanos to millis if (ts.tv_nsec <= 999000000) { diff --git a/libc/calls/_timespec_tonanos.c b/libc/calls/timespec_tonanos.c similarity index 98% rename from libc/calls/_timespec_tonanos.c rename to libc/calls/timespec_tonanos.c index 6edac6ac1..2e7ed66b9 100644 --- a/libc/calls/_timespec_tonanos.c +++ b/libc/calls/timespec_tonanos.c @@ -27,7 +27,7 @@ * * @return 64-bit integer holding nanoseconds since epoch */ -int64_t _timespec_tonanos(struct timespec x) { +int64_t timespec_tonanos(struct timespec x) { int64_t ns; if (!__builtin_mul_overflow(x.tv_sec, 1000000000ul, &ns) && !__builtin_add_overflow(ns, x.tv_nsec, &ns)) { diff --git a/libc/calls/_timespec_totimeval.c b/libc/calls/timespec_totimeval.c similarity index 96% rename from libc/calls/_timespec_totimeval.c rename to libc/calls/timespec_totimeval.c index 5786513a5..515227478 100644 --- a/libc/calls/_timespec_totimeval.c +++ b/libc/calls/timespec_totimeval.c @@ -27,9 +27,9 @@ * timestamp has a special meaning. * * @return microseconds since epoch - * @see _timespec_tomicros() + * @see timespec_tomicros() */ -struct timeval _timespec_totimeval(struct timespec ts) { +struct timeval timespec_totimeval(struct timespec ts) { if (ts.tv_nsec < 1000000000 - 999) { return (struct timeval){ts.tv_sec, (ts.tv_nsec + 999) / 1000}; } else { diff --git a/libc/calls/_timeval_add.c b/libc/calls/timeval_add.c similarity index 96% rename from libc/calls/_timeval_add.c rename to libc/calls/timeval_add.c index 6659bc085..152690452 100644 --- a/libc/calls/_timeval_add.c +++ b/libc/calls/timeval_add.c @@ -21,7 +21,7 @@ /** * Adds two microsecond timestamps. */ -struct timeval _timeval_add(struct timeval x, struct timeval y) { +struct timeval timeval_add(struct timeval x, struct timeval y) { x.tv_sec += y.tv_sec; x.tv_usec += y.tv_usec; if (x.tv_usec >= 1000000) { diff --git a/libc/calls/_timeval_cmp.c b/libc/calls/timeval_cmp.c similarity index 93% rename from libc/calls/_timeval_cmp.c rename to libc/calls/timeval_cmp.c index e29eebbad..3accebd00 100644 --- a/libc/calls/_timeval_cmp.c +++ b/libc/calls/timeval_cmp.c @@ -19,9 +19,11 @@ #include "libc/calls/struct/timeval.h" /** - * Compares microseconds timestamps. + * Compares microsecond timestamps. + * + * @return 0 if equal, -1 if `a < b`, or +1 if `a > b` */ -int _timeval_cmp(struct timeval a, struct timeval b) { +int timeval_cmp(struct timeval a, struct timeval b) { int cmp; if (!(cmp = (a.tv_sec > b.tv_sec) - (a.tv_sec < b.tv_sec))) { cmp = (a.tv_usec > b.tv_usec) - (a.tv_usec < b.tv_usec); diff --git a/libc/calls/_timeval_sub.c b/libc/calls/timeval_sub.c similarity index 96% rename from libc/calls/_timeval_sub.c rename to libc/calls/timeval_sub.c index bbf58c26b..934331f7c 100644 --- a/libc/calls/_timeval_sub.c +++ b/libc/calls/timeval_sub.c @@ -21,7 +21,7 @@ /** * Subtracts two nanosecond timestamps. */ -struct timeval _timeval_sub(struct timeval a, struct timeval b) { +struct timeval timeval_sub(struct timeval a, struct timeval b) { a.tv_sec -= b.tv_sec; if (a.tv_usec < b.tv_usec) { a.tv_usec += 1000000; diff --git a/libc/calls/_timeval_totimespec.c b/libc/calls/timeval_totimespec.c similarity index 97% rename from libc/calls/_timeval_totimespec.c rename to libc/calls/timeval_totimespec.c index e508155fd..16bccc97f 100644 --- a/libc/calls/_timeval_totimespec.c +++ b/libc/calls/timeval_totimespec.c @@ -21,6 +21,6 @@ /** * Coerces `tv` from 1e-6 to 1e-9 granularity. */ -struct timespec _timeval_totimespec(struct timeval tv) { +struct timespec timeval_totimespec(struct timeval tv) { return (struct timespec){tv.tv_sec, tv.tv_usec * 1000}; } diff --git a/libc/calls/tkill.c b/libc/calls/tkill.c index 0c655c86c..d767aa75d 100644 --- a/libc/calls/tkill.c +++ b/libc/calls/tkill.c @@ -19,29 +19,35 @@ #include "libc/calls/calls.h" #include "libc/calls/sig.internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" #include "libc/intrin/strace.internal.h" #include "libc/sysv/consts/sicode.h" +// OpenBSD has an optional `tib` parameter for extra safety +int __tkill(int tid, int sig, void *tib) { + int rc; + if (!IsWindows() && !IsMetal()) { + rc = sys_tkill(tid, sig, tib); + } else { + rc = __sig_add(tid, sig, SI_TKILL); + } + STRACE("tkill(%d, %G) → %d% m", tid, sig, rc); + return rc; +} + /** * Kills thread. * * @param tid is thread id * @param sig does nothing on xnu * @return 0 on success, or -1 w/ errno - * @raise ESRCH if `tid` was valid but no such thread existed * @raise EAGAIN if `RLIMIT_SIGPENDING` was exceeded - * @raise EINVAL if `tid` or `sig` was invalid + * @raise EINVAL if `tid` or `sig` were invalid + * @raise ESRCH if no such `tid` existed * @raise EPERM if permission was denied * @asyncsignalsafe */ int tkill(int tid, int sig) { - int rc; - if (!IsWindows() && !IsMetal()) { - rc = sys_tkill(tid, sig, 0); - } else { - rc = __sig_add(tid, sig, SI_TKILL); - } - STRACE("tkill(%d, %G) → %d% m", tid, sig, rc); - return rc; + return __tkill(tid, sig, 0); } diff --git a/libc/calls/tmpfd.c b/libc/calls/tmpfd.c index 083595b47..286b183a4 100644 --- a/libc/calls/tmpfd.c +++ b/libc/calls/tmpfd.c @@ -62,7 +62,10 @@ * other platforms. * * @return file descriptor on success, or -1 w/ errno + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @see tmpfile() for stdio version + * @cancellationpoint * @asyncsignalsafe * @threadsafe * @vforksafe diff --git a/libc/calls/touch.c b/libc/calls/touch.c index f3ec453c0..ac3160bc7 100644 --- a/libc/calls/touch.c +++ b/libc/calls/touch.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/struct/timeval.h" #include "libc/errno.h" @@ -34,7 +35,10 @@ int touch(const char *file, uint32_t mode) { olderr = errno; if ((rc = utimes(file, 0)) == -1 && errno == ENOENT) { errno = olderr; - if ((fd = open(file, O_CREAT | O_WRONLY, mode)) == -1) return -1; + BLOCK_CANCELLATIONS; + fd = open(file, O_CREAT | O_WRONLY, mode); + ALLOW_CANCELLATIONS; + if (fd == -1) return -1; return close(fd); } return rc; diff --git a/libc/calls/truncate.c b/libc/calls/truncate.c index 3af89ab91..bac9defbc 100644 --- a/libc/calls/truncate.c +++ b/libc/calls/truncate.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" @@ -46,6 +47,7 @@ * @return 0 on success, or -1 w/ errno * @raise EINVAL if `length` is negative * @raise EINTR if signal was delivered instead + * @raise ECANCELED if thread was cancelled in masked mode * @raise EFBIG or EINVAL if `length` is too huge * @raise EFAULT if `path` points to invalid memory * @raise ENOTSUP if `path` is a zip filesystem path @@ -65,6 +67,8 @@ int truncate(const char *path, int64_t length) { int rc; struct ZiposUri zipname; + BEGIN_CANCELLATION_POINT; + if (IsMetal()) { rc = enosys(); } else if (!path || (IsAsan() && !__asan_is_valid_str(path))) { @@ -80,6 +84,8 @@ int truncate(const char *path, int64_t length) { } else { rc = sys_truncate_nt(path, length); } + + END_CANCELLATION_POINT; STRACE("truncate(%#s, %'ld) → %d% m", path, length, rc); return rc; } diff --git a/libc/calls/unveil.c b/libc/calls/unveil.c index 7162077cb..2fd55effc 100644 --- a/libc/calls/unveil.c +++ b/libc/calls/unveil.c @@ -17,9 +17,9 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/landlock.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/bpf.h" #include "libc/calls/struct/filter.h" #include "libc/calls/struct/seccomp.h" @@ -29,8 +29,8 @@ #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/errno.h" #include "libc/fmt/conv.h" +#include "libc/intrin/strace.internal.h" #include "libc/macros.internal.h" -#include "libc/thread/tls.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/runtime/stack.h" @@ -45,6 +45,7 @@ #include "libc/sysv/consts/pr.h" #include "libc/sysv/consts/s.h" #include "libc/sysv/errfuns.h" +#include "libc/thread/tls.h" #define OFF(f) offsetof(struct seccomp_data, f) @@ -234,7 +235,9 @@ int sys_unveil_linux(const char *path, const char *permissions) { } // now we can open the path + BLOCK_CANCELLATIONS; rc = sys_open(path, O_PATH | O_NOFOLLOW | O_CLOEXEC, 0); + ALLOW_CANCELLATIONS; if (rc == -1) return rc; pb.parent_fd = rc; diff --git a/libc/calls/usleep.c b/libc/calls/usleep.c index 820786004..408393825 100644 --- a/libc/calls/usleep.c +++ b/libc/calls/usleep.c @@ -26,12 +26,13 @@ * * @return 0 on success, or -1 w/ errno * @raise EINTR if a signal was delivered while sleeping + * @raise ECANCELED if thread was cancelled in masked mode * @see clock_nanosleep() * @cancellationpoint * @norestart */ int usleep(uint32_t micros) { - struct timespec ts = _timespec_frommicros(micros); + struct timespec ts = timespec_frommicros(micros); if (clock_nanosleep(CLOCK_REALTIME, 0, &ts, 0)) return eintr(); return 0; } diff --git a/libc/calls/utimensat-sysv.c b/libc/calls/utimensat-sysv.c index b449db299..8b26f5237 100644 --- a/libc/calls/utimensat-sysv.c +++ b/libc/calls/utimensat-sysv.c @@ -39,8 +39,8 @@ int sys_utimensat(int dirfd, const char *path, const struct timespec ts[2], if (rc == -1 && errno == ENOSYS && path) { errno = olderr; if (ts) { - tv[0] = _timespec_totimeval(ts[0]); - tv[1] = _timespec_totimeval(ts[1]); + tv[0] = timespec_totimeval(ts[0]); + tv[1] = timespec_totimeval(ts[1]); rc = sys_utimes(path, tv); } else { rc = sys_utimes(path, NULL); diff --git a/libc/calls/utimensat-xnu.c b/libc/calls/utimensat-xnu.c index c6a19b499..d6c801adf 100644 --- a/libc/calls/utimensat-xnu.c +++ b/libc/calls/utimensat-xnu.c @@ -42,16 +42,16 @@ int sys_utimensat_xnu(int dirfd, const char *path, const struct timespec ts[2], if (ts[0].tv_nsec == UTIME_NOW) { tv[0] = now; } else if (ts[0].tv_nsec == UTIME_OMIT) { - tv[0] = _timespec_totimeval(st.st_atim); + tv[0] = timespec_totimeval(st.st_atim); } else { - tv[0] = _timespec_totimeval(ts[0]); + tv[0] = timespec_totimeval(ts[0]); } if (ts[1].tv_nsec == UTIME_NOW) { tv[1] = now; } else if (ts[1].tv_nsec == UTIME_OMIT) { - tv[1] = _timespec_totimeval(st.st_mtim); + tv[1] = timespec_totimeval(st.st_mtim); } else { - tv[1] = _timespec_totimeval(ts[1]); + tv[1] = timespec_totimeval(ts[1]); } } else { tv[0] = now; diff --git a/libc/calls/wait4.c b/libc/calls/wait4.c index d9afd2b66..fdd8ba4af 100644 --- a/libc/calls/wait4.c +++ b/libc/calls/wait4.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/struct/rusage.internal.h" #include "libc/calls/wait4.h" #include "libc/dce.h" @@ -41,6 +42,8 @@ int wait4(int pid, int *opt_out_wstatus, int options, struct rusage *opt_out_rusage) { int rc, ws = 0; + BEGIN_CANCELLATION_POINT; + if (IsAsan() && ((opt_out_wstatus && !__asan_is_valid(opt_out_wstatus, sizeof(*opt_out_wstatus))) || @@ -53,6 +56,8 @@ int wait4(int pid, int *opt_out_wstatus, int options, rc = sys_wait4_nt(pid, &ws, options, opt_out_rusage); } if (rc != -1 && opt_out_wstatus) *opt_out_wstatus = ws; + + END_CANCELLATION_POINT; STRACE("wait4(%d, [%#x], %d, %p) → %d% m", pid, ws, options, opt_out_rusage, rc); return rc; diff --git a/libc/calls/write.c b/libc/calls/write.c index 8185c0fe6..b206f6c8e 100644 --- a/libc/calls/write.c +++ b/libc/calls/write.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -51,6 +52,7 @@ * @raise ENOSPC if device containing `fd` is full * @raise EIO if low-level i/o error happened * @raise EINTR if signal was delivered instead + * @raise ECANCELED if thread was cancelled in masked mode * @raise EAGAIN if `O_NONBLOCK` is in play and write needs to block * @raise ENOBUFS if kernel lacked internal resources; which FreeBSD * and XNU say could happen with sockets, and OpenBSD documents it @@ -64,6 +66,8 @@ */ ssize_t write(int fd, const void *buf, size_t size) { ssize_t rc; + BEGIN_CANCELLATION_POINT; + if (fd >= 0) { if ((!buf && size) || (IsAsan() && !__asan_is_valid(buf, size))) { rc = efault(); @@ -83,6 +87,8 @@ ssize_t write(int fd, const void *buf, size_t size) { } else { rc = ebadf(); } + + END_CANCELLATION_POINT; DATATRACE("write(%d, %#.*hhs%s, %'zu) → %'zd% m", fd, MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, rc); return rc; diff --git a/libc/calls/writev.c b/libc/calls/writev.c index 863cbb31a..177355ca9 100644 --- a/libc/calls/writev.c +++ b/libc/calls/writev.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.internal.h" #include "libc/calls/syscall-sysv.internal.h" @@ -52,6 +53,7 @@ */ ssize_t writev(int fd, const struct iovec *iov, int iovlen) { ssize_t rc; + BEGIN_CANCELLATION_POINT; if (fd >= 0 && iovlen >= 0) { if (IsAsan() && !__asan_is_valid_iov(iov, iovlen)) { @@ -78,6 +80,7 @@ ssize_t writev(int fd, const struct iovec *iov, int iovlen) { rc = einval(); } + END_CANCELLATION_POINT; STRACE("writev(%d, %s, %d) → %'ld% m", fd, DescribeIovec(rc != -1 ? rc : -2, iov, iovlen), iovlen, rc); return rc; diff --git a/libc/errno.h b/libc/errno.h index 78215f766..d41af97ce 100644 --- a/libc/errno.h +++ b/libc/errno.h @@ -8,7 +8,8 @@ COSMOPOLITAN_C_START_ * @see libc/sysv/consts.sh for numbers */ -#if defined(__GNUC__) && defined(__MNO_RED_ZONE__) && !defined(__STRICT_ANSI__) +#if defined(__GNUC__) && defined(__x86_64__) && defined(__MNO_RED_ZONE__) && \ + !defined(__STRICT_ANSI__) #define errno \ (*({ \ errno_t *_ep; \ diff --git a/libc/intrin/getenv.c b/libc/intrin/getenv.c index c5b856e2f..f61f073d4 100644 --- a/libc/intrin/getenv.c +++ b/libc/intrin/getenv.c @@ -34,10 +34,10 @@ char *getenv(const char *s) { if (!(p = environ)) return 0; e = _getenv(p, s); #if SYSDEBUG - if (!(s[0] == 'T' && s[1] == 'Z' && !s[2])) { - // TODO(jart): memoize TZ or something - STRACE("getenv(%#s) → %#s", s, e.s); - } + // if (!(s[0] == 'T' && s[1] == 'Z' && !s[2])) { + // TODO(jart): memoize TZ or something + STRACE("getenv(%#s) → %#s", s, e.s); + //} #endif return e.s; } diff --git a/libc/intrin/ksignalnames.S b/libc/intrin/ksignalnames.S index 97fffaeb8..c3a8a4924 100644 --- a/libc/intrin/ksignalnames.S +++ b/libc/intrin/ksignalnames.S @@ -61,12 +61,12 @@ kSignalNames: .e SIGWINCH,"SIGWINCH" .e SIGIO,"SIGIO" .e SIGSYS,"SIGSYS" - .e SIGINFO,"SIGINFO" - .e SIGCANCEL,"SIGCANCEL" + .e SIGPWR,"SIGPWR" + .e SIGINFO,"SIGINFO" # order matters + .e SIGTHR,"SIGTHR" # order matters .e SIGRTMAX,"SIGRTMAX" .e SIGRTMIN,"SIGRTMIN" - .e SIGEMT,"SIGEMT" - .e SIGPWR,"SIGPWR" + .e SIGEMT,"SIGEMT" # order matters .long MAGNUM_TERMINATOR .endobj kSignalNames,globl,hidden .overrun diff --git a/libc/intrin/pthread_key_create.c b/libc/intrin/pthread_key_create.c index 554b02fa2..e7e85de0b 100644 --- a/libc/intrin/pthread_key_create.c +++ b/libc/intrin/pthread_key_create.c @@ -29,6 +29,8 @@ * key's value is nonzero. The key's value is set to zero before it gets * called. The ordering for multiple destructor calls is unspecified. * + * The result should be passed to pthread_key_delete() later. + * * @param key is set to the allocated key on success * @param dtor specifies an optional destructor callback * @return 0 on success, or errno on error diff --git a/libc/intrin/pthread_setcancelstate.c b/libc/intrin/pthread_setcancelstate.c index 97364ae94..2de212601 100644 --- a/libc/intrin/pthread_setcancelstate.c +++ b/libc/intrin/pthread_setcancelstate.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" #include "libc/errno.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" @@ -76,3 +77,13 @@ errno_t pthread_setcancelstate(int state, int *oldstate) { return 0; } } + +int _pthread_block_cancellations(void) { + int oldstate; + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); + return oldstate; +} + +void _pthread_allow_cancellations(int oldstate) { + pthread_setcancelstate(oldstate, 0); +} diff --git a/libc/intrin/sigisprecious.inc b/libc/intrin/sigisprecious.inc index 923cc86b2..3a0fb66d2 100644 --- a/libc/intrin/sigisprecious.inc +++ b/libc/intrin/sigisprecious.inc @@ -1,4 +1,4 @@ M(SIGKILL) M(SIGABRT) M(SIGSTOP) -M(SIGCANCEL) +M(SIGTHR) diff --git a/libc/intrin/winerr.greg.c b/libc/intrin/winerr.greg.c index 7729c4dab..bf32daa35 100644 --- a/libc/intrin/winerr.greg.c +++ b/libc/intrin/winerr.greg.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #define ShouldUseMsabiAttribute() 1 +#include "libc/assert.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/nt/errors.h" @@ -34,6 +35,7 @@ privileged int64_t __winerr(void) { errno_t e; if (IsWindows()) { e = __dos2errno(__imp_GetLastError()); + _npassert(e > 0); } else { e = ENOSYS; } diff --git a/libc/log/backtrace2.c b/libc/log/backtrace2.c index e68360b43..0cdb519d6 100644 --- a/libc/log/backtrace2.c +++ b/libc/log/backtrace2.c @@ -54,6 +54,13 @@ static int PrintBacktraceUsingAddr2line(int fd, const struct StackFrame *bp) { char *debugbin, *p1, *p2, *p3, *addr2line; char buf[kBacktraceBufSize], *argv[kBacktraceMaxFrames]; + // DWARF is a weak standard. Platforms that use LLVM or old GNU + // usually can't be counted upon to print backtraces correctly. + if (!IsLinux() && !IsWindows()) { + ShowHint("won't print addr2line backtrace because probably llvm"); + return -1; + } + if (!PLEDGED(STDIO) || !PLEDGED(EXEC) || !PLEDGED(EXEC)) { return -1; } @@ -69,13 +76,6 @@ static int PrintBacktraceUsingAddr2line(int fd, const struct StackFrame *bp) { return -1; } - // DWARF is a weak standard. Platforms that use LLVM or old GNU - // usually can't be counted upon to print backtraces correctly. - if (!IsLinux() && !IsWindows()) { - ShowHint("won't print addr2line backtrace because probably llvm"); - return -1; - } - // backtrace_test.com failing on windows for some reason via runitd // don't want to pull in the high-level syscalls here anyway if (IsWindows()) { diff --git a/libc/log/showcrashreports.c b/libc/log/showcrashreports.c index 4a0b2f341..e668d48bd 100644 --- a/libc/log/showcrashreports.c +++ b/libc/log/showcrashreports.c @@ -16,9 +16,11 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigaltstack.h" +#include "libc/dce.h" #include "libc/errno.h" #include "libc/log/internal.h" #include "libc/log/log.h" @@ -40,17 +42,16 @@ STATIC_YOINK("PrintBacktraceUsingSymbols"); // for backtracing STATIC_YOINK("malloc_inspect_all"); // for asan memory origin STATIC_YOINK("GetSymbolByAddr"); // for asan memory origin -extern const unsigned char __oncrash_thunks[8][11]; -static struct sigaltstack g_oldsigaltstack; static struct sigaction g_oldcrashacts[8]; +extern const unsigned char __oncrash_thunks[8][11]; static void InstallCrashHandlers(int extraflags) { int e; size_t i; struct sigaction sa; bzero(&sa, sizeof(sa)); - sa.sa_flags = SA_SIGINFO | SA_NODEFER | extraflags; sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO | SA_NODEFER | extraflags; for (i = 0; i < ARRAYLEN(kCrashSigs); ++i) { sigdelset(&sa.sa_mask, kCrashSigs[i]); } @@ -81,11 +82,6 @@ relegated void RestoreDefaultCrashSignalHandlers(void) { strace_enabled(+1); } -static void FreeSigAltStack(void *p) { - sigaltstack(&g_oldsigaltstack, 0); - munmap(p, GetStackSize()); -} - /** * Installs crash signal handlers. * @@ -102,7 +98,6 @@ static void FreeSigAltStack(void *p) { * useful, for example, if a program is caught in an infinite loop. */ void ShowCrashReports(void) { - char *sp; struct sigaltstack ss; _wantcrashreports = true; /* : showcrashreports.c, oncrashthunks.S, oncrash.c */ @@ -116,19 +111,12 @@ void ShowCrashReports(void) { kCrashSigs[7] = SIGURG; /* placeholder */ /* : showcrashreports.c, oncrashthunks.S, oncrash.c */ if (!IsWindows()) { - bzero(&ss, sizeof(ss)); ss.ss_flags = 0; ss.ss_size = GetStackSize(); // FreeBSD sigaltstack() will EFAULT if we use MAP_STACK here // OpenBSD sigaltstack() auto-applies MAP_STACK to the memory - if ((sp = _mapanon(GetStackSize()))) { - ss.ss_sp = sp; - if (!sigaltstack(&ss, &g_oldsigaltstack)) { - __cxa_atexit(FreeSigAltStack, ss.ss_sp, 0); - } else { - munmap(ss.ss_sp, GetStackSize()); - } - } + _npassert((ss.ss_sp = _mapanon(GetStackSize()))); + _npassert(!sigaltstack(&ss, 0)); InstallCrashHandlers(SA_ONSTACK); } else { InstallCrashHandlers(0); diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index 075e014a2..4405ced1a 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/dprintf.h" #include "libc/calls/struct/stat.h" @@ -96,10 +97,11 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, if (!f) return; flockfile(f); strace_enabled(-1); + BLOCK_CANCELLATIONS; // We display TIMESTAMP.MICROS normally. However, when we log multiple // times in the same second, we display TIMESTAMP+DELTAMICROS instead. - t2 = _timespec_real(); + t2 = timespec_real(); if (t2.tv_sec == vflogf_ts.tv_sec) { sign = "+"; dots = t2.tv_nsec - vflogf_ts.tv_nsec; @@ -138,6 +140,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, unreachable; } + ALLOW_CANCELLATIONS; strace_enabled(+1); funlockfile(f); } diff --git a/libc/runtime/cocmd.c b/libc/runtime/cocmd.c index 2252d436d..3f935d9f8 100644 --- a/libc/runtime/cocmd.c +++ b/libc/runtime/cocmd.c @@ -309,10 +309,10 @@ static int Usleep(void) { struct timespec t; if (n > 1) { f = 0; - t = _timespec_frommicros(atoi(args[1])); + t = timespec_frommicros(atoi(args[1])); } else { f = TIMER_ABSTIME; - t = _timespec_max; + t = timespec_max; } return clock_nanosleep(0, f, &t, 0); } diff --git a/libc/runtime/cosmo.S b/libc/runtime/cosmo.S index 8d8459bf7..98d0a4d5c 100644 --- a/libc/runtime/cosmo.S +++ b/libc/runtime/cosmo.S @@ -148,8 +148,10 @@ cosmo: push %rbp or $-1,%r8 xor %r9d,%r9d push %rsi + push %rsi call mmap pop %r8 + pop %r8 pop %rsi pop %rdi cmp $-1,%rax diff --git a/libc/runtime/ezmap.c b/libc/runtime/ezmap.c index 8e019fca3..12627c15f 100644 --- a/libc/runtime/ezmap.c +++ b/libc/runtime/ezmap.c @@ -16,8 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/safemacros.internal.h" #include "libc/calls/calls.h" +#include "libc/intrin/safemacros.internal.h" #include "libc/limits.h" #include "libc/log/libfatal.internal.h" #include "libc/runtime/ezmap.internal.h" @@ -26,6 +26,8 @@ #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" +// TODO(jart): DELETE + hidden int MapFileRead(const char *filename, struct MappedFile *mf) { mf->addr = MAP_FAILED; if ((mf->fd = open(filename, O_RDONLY)) != -1 && diff --git a/libc/runtime/fenv.c b/libc/runtime/fenv.c deleted file mode 100644 index 89a2909af..000000000 --- a/libc/runtime/fenv.c +++ /dev/null @@ -1,41 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2021 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/runtime/fenv.h" - -/* TODO: implement these functions */ - -int feclearexcept(int mask) { - return 0; -} - -int fegetenv(fenv_t *envp) { - return 0; -} - -int feraiseexcept(int mask) { - return 0; -} - -int fetestexcept(int mask) { - return 0; -} - -int fesetenv(const fenv_t *envp) { - return 0; -} diff --git a/libc/runtime/isdynamicexecutable.c b/libc/runtime/isdynamicexecutable.c index 66c0a6be6..06e14823e 100644 --- a/libc/runtime/isdynamicexecutable.c +++ b/libc/runtime/isdynamicexecutable.c @@ -16,7 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/bits.h" +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/struct/stat.h" #include "libc/elf/def.h" @@ -24,6 +24,7 @@ #include "libc/elf/struct/ehdr.h" #include "libc/elf/struct/phdr.h" #include "libc/errno.h" +#include "libc/intrin/bits.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" @@ -38,6 +39,7 @@ bool IsDynamicExecutable(const char *prog) { Elf64_Phdr *p; struct stat st; int i, fd, err; + BLOCK_CANCELLATIONS; fd = -1; err = errno; e = MAP_FAILED; @@ -74,5 +76,6 @@ Finish: if (e != MAP_FAILED) munmap(e, st.st_size); if (fd != -1) close(fd); errno = err; + ALLOW_CANCELLATIONS; return res; } diff --git a/libc/runtime/msync.c b/libc/runtime/msync.c index c9c2ab193..b55e98992 100644 --- a/libc/runtime/msync.c +++ b/libc/runtime/msync.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" @@ -34,16 +35,22 @@ * @param addr needs to be 4096-byte page aligned * @param flags needs MS_ASYNC or MS_SYNC and can have MS_INVALIDATE * @return 0 on success or -1 w/ errno + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if we needed to block and a signal was delivered instead * @cancellationpoint */ int msync(void *addr, size_t size, int flags) { int rc; + BEGIN_CANCELLATION_POINT; + _unassert(((flags & MS_SYNC) ^ (flags & MS_ASYNC)) || !(MS_SYNC && MS_ASYNC)); if (!IsWindows()) { rc = sys_msync(addr, size, flags); } else { rc = sys_msync_nt(addr, size, flags); } + + END_CANCELLATION_POINT; STRACE("msync(%p, %'zu, %#x) → %d% m", addr, size, flags, rc); return rc; } diff --git a/libc/runtime/opensymboltable.greg.c b/libc/runtime/opensymboltable.greg.c index 6390bc5cc..d27c0602c 100644 --- a/libc/runtime/opensymboltable.greg.c +++ b/libc/runtime/opensymboltable.greg.c @@ -17,8 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" -#include "libc/intrin/strace.internal.h" #include "libc/dce.h" #include "libc/elf/def.h" #include "libc/elf/scalar.h" @@ -27,6 +27,7 @@ #include "libc/elf/struct/sym.h" #include "libc/errno.h" #include "libc/intrin/bits.h" +#include "libc/intrin/strace.internal.h" #include "libc/limits.h" #include "libc/log/libfatal.internal.h" #include "libc/macros.internal.h" @@ -100,12 +101,7 @@ static void GetImageRange(Elf64_Ehdr *elf, intptr_t *x, intptr_t *y) { if (y) *y = end; } -/** - * Maps debuggable binary into memory and indexes symbol addresses. - * - * @return object freeable with CloseSymbolTable(), or NULL w/ errno - */ -struct SymbolTable *OpenSymbolTable(const char *filename) { +static struct SymbolTable *OpenSymbolTableImpl(const char *filename) { int fd; void *map; long *stp; @@ -200,3 +196,16 @@ SystemError: close(fd); return 0; } + +/** + * Maps debuggable binary into memory and indexes symbol addresses. + * + * @return object freeable with CloseSymbolTable(), or NULL w/ errno + */ +struct SymbolTable *OpenSymbolTable(const char *filename) { + struct SymbolTable *st; + BLOCK_CANCELLATIONS; + st = OpenSymbolTableImpl(filename); + ALLOW_CANCELLATIONS; + return st; +} diff --git a/libc/sock/accept4.c b/libc/sock/accept4.c index 0499c65bb..175f7d124 100644 --- a/libc/sock/accept4.c +++ b/libc/sock/accept4.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" @@ -44,6 +45,8 @@ int accept4(int fd, struct sockaddr *out_addr, uint32_t *inout_addrsize, int flags) { int rc; char addrbuf[72]; + BEGIN_CANCELLATION_POINT; + if (!out_addr || !inout_addrsize || (IsAsan() && !__asan_is_valid(out_addr, *inout_addrsize))) { rc = efault(); @@ -54,6 +57,8 @@ int accept4(int fd, struct sockaddr *out_addr, uint32_t *inout_addrsize, } else { rc = ebadf(); } + + END_CANCELLATION_POINT; STRACE("accept4(%d, [%s]) -> %d% lm", fd, DescribeSockaddr(out_addr, inout_addrsize ? *inout_addrsize : 0), rc); return rc; diff --git a/libc/sock/connect.c b/libc/sock/connect.c index 15db1cc03..f6908e6b2 100644 --- a/libc/sock/connect.c +++ b/libc/sock/connect.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" @@ -41,6 +42,8 @@ */ int connect(int fd, const struct sockaddr *addr, uint32_t addrsize) { int rc; + BEGIN_CANCELLATION_POINT; + if (addr && !(IsAsan() && !__asan_is_valid(addr, addrsize))) { if (!IsWindows()) { rc = sys_connect(fd, addr, addrsize); @@ -52,6 +55,8 @@ int connect(int fd, const struct sockaddr *addr, uint32_t addrsize) { } else { rc = efault(); } + + END_CANCELLATION_POINT; STRACE("connect(%d, %s) → %d% lm", fd, DescribeSockaddr(addr, addrsize), rc); return rc; } diff --git a/libc/sock/epoll.c b/libc/sock/epoll.c index 8622e86d7..ea504746b 100644 --- a/libc/sock/epoll.c +++ b/libc/sock/epoll.c @@ -33,11 +33,13 @@ │ │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/strace.internal.h" #include "libc/limits.h" #include "libc/macros.internal.h" #include "libc/mem/mem.h" @@ -1431,8 +1433,14 @@ err: * @return epoll file descriptor, or -1 on failure */ int epoll_create(int size) { - if (size <= 0) return einval(); - return epoll_create1(0); + int rc; + if (size <= 0) { + rc = einval(); + } else { + rc = epoll_create1(0); + } + STRACE("epoll_create(%d) → %d% m", size, rc); + return rc; } /** @@ -1443,13 +1451,16 @@ int epoll_create(int size) { * @return epoll file descriptor, or -1 on failure */ int epoll_create1(int flags) { - int fd; - if (flags & ~O_CLOEXEC) return einval(); - if (!IsWindows()) { - return __fixupnewfd(sys_epoll_create(1337), flags); + int rc; + if (flags & ~O_CLOEXEC) { + rc = einval(); + } else if (!IsWindows()) { + rc = __fixupnewfd(sys_epoll_create(1337), flags); } else { - return sys_epoll_create1_nt(flags); + rc = sys_epoll_create1_nt(flags); } + STRACE("epoll_create1(%#x) → %d% m", flags, rc); + return rc; } /** @@ -1466,30 +1477,33 @@ int epoll_create1(int flags) { * @param fd is file descriptor to monitor * @param ev is ignored if op is EPOLL_CTL_DEL * @param ev->events can have these flags: - * - EPOLLIN: trigger on fd readable - * - EPOLLOUT: trigger on fd writeable - * - EPOLLERR: trigger on fd error (superfluous: always reported) - * - EPOLLHUP: trigger on fd remote hangup (superfluous: always reported) - * - EPOLLPRI: trigger on fd exceptional conditions, e.g. oob - * - EPOLLONESHOT: report event(s) only once - * - EPOLLEXCLUSIVE: not supported on windows - * - EPOLLWAKEUP: not supported on windows - * - EPOLLET: edge triggered mode (not supported on windows) - * - EPOLLRDNORM - * - EPOLLRDBAND - * - EPOLLWRNORM - * - EPOLLWRBAND - * - EPOLLRDHUP - * - EPOLLMSG + * - `EPOLLIN`: trigger on fd readable + * - `EPOLLOUT`: trigger on fd writeable + * - `EPOLLERR`: trigger on fd error (superfluous: always reported) + * - `EPOLLHUP`: trigger on fd remote hangup (superfluous: always reported) + * - `EPOLLPRI`: trigger on fd exceptional conditions, e.g. oob + * - `EPOLLONESHOT`: report event(s) only once + * - `EPOLLEXCLUSIVE`: not supported on windows + * - `EPOLLWAKEUP`: not supported on windows + * - `EPOLLET`: edge triggered mode (not supported on windows) + * - `EPOLLRDNORM` + * - `EPOLLRDBAND` + * - `EPOLLWRNORM` + * - `EPOLLWRBAND` + * - `EPOLLRDHUP` + * - `EPOLLMSG` * @error ENOTSOCK on Windows if fd isn't a socket :( * @return 0 on success, or -1 w/ errno */ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev) { + int rc; if (!IsWindows()) { - return sys_epoll_ctl(epfd, op, fd, ev); + rc = sys_epoll_ctl(epfd, op, fd, ev); } else { - return sys_epoll_ctl_nt(epfd, op, fd, ev); + rc = sys_epoll_ctl_nt(epfd, op, fd, ev); } + STRACE("epoll_ctl(%d, %d, %d, %p) → %d% m", epfd, op, fd, ev, rc); + return rc; } /** @@ -1504,9 +1518,15 @@ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev) { */ int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeoutms) { + int rc; + BEGIN_CANCELLATION_POINT; if (!IsWindows()) { - return sys_epoll_wait(epfd, events, maxevents, timeoutms); + rc = sys_epoll_wait(epfd, events, maxevents, timeoutms); } else { - return sys_epoll_wait_nt(epfd, events, maxevents, timeoutms); + rc = sys_epoll_wait_nt(epfd, events, maxevents, timeoutms); } + END_CANCELLATION_POINT; + STRACE("epoll_wait(%d, %p, %d, %d) → %d% m", epfd, events, maxevents, + timeoutms, rc); + return rc; } diff --git a/libc/sock/pselect.c b/libc/sock/pselect.c index 124ca1647..22215fc40 100644 --- a/libc/sock/pselect.c +++ b/libc/sock/pselect.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timeval.h" #include "libc/dce.h" @@ -45,6 +46,8 @@ * This system call is supported on all platforms. It's like select() * except that it atomically changes the sigprocmask() during the op. * + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @cancellationpoint * @asyncsignalsafe * @threadsafe @@ -60,6 +63,8 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const sigset_t *s; size_t n; } ss; + BEGIN_CANCELLATION_POINT; + if (nfds < 0) { rc = einval(); } else if (IsAsan() && @@ -91,6 +96,8 @@ int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, } rc = sys_select_nt(nfds, readfds, writefds, exceptfds, tvp, sigmask); } + + END_CANCELLATION_POINT; POLLTRACE("pselect(%d, %p, %p, %p, %s, %s) → %d% m", nfds, readfds, writefds, exceptfds, DescribeTimeval(0, timeout), DescribeSigset(0, sigmask), rc); diff --git a/libc/sock/recv.c b/libc/sock/recv.c index 4edb413aa..3baa2f0cb 100644 --- a/libc/sock/recv.c +++ b/libc/sock/recv.c @@ -16,11 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/iovec.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/sock/internal.h" #include "libc/sock/sock.h" #include "libc/sock/syscall_fd.internal.h" @@ -36,11 +37,14 @@ * @return number of bytes received, 0 on remote close, or -1 w/ errno * @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable), * EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc. + * @cancellationpoint * @asyncsignalsafe * @restartable (unless SO_RCVTIMEO) */ ssize_t recv(int fd, void *buf, size_t size, int flags) { ssize_t rc, got; + BEGIN_CANCELLATION_POINT; + if (IsAsan() && !__asan_is_valid(buf, size)) { rc = efault(); } else if (!IsWindows()) { @@ -60,6 +64,8 @@ ssize_t recv(int fd, void *buf, size_t size, int flags) { } else { rc = ebadf(); } + + END_CANCELLATION_POINT; DATATRACE("recv(%d, [%#.*hhs%s], %'zu, %#x) → %'ld% lm", fd, MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags); return rc; diff --git a/libc/sock/recvfrom.c b/libc/sock/recvfrom.c index d47991ce7..6bc88a154 100644 --- a/libc/sock/recvfrom.c +++ b/libc/sock/recvfrom.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -54,6 +55,8 @@ ssize_t recvfrom(int fd, void *buf, size_t size, int flags, ssize_t rc; uint32_t sz; union sockaddr_storage_bsd bsd; + BEGIN_CANCELLATION_POINT; + if (IsAsan() && (!__asan_is_valid(buf, size) || (opt_out_srcaddr && @@ -88,6 +91,8 @@ ssize_t recvfrom(int fd, void *buf, size_t size, int flags, } else { rc = ebadf(); } + + END_CANCELLATION_POINT; DATATRACE("recvfrom(%d, [%#.*hhs%s], %'zu, %#x) → %'ld% lm", fd, MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc); return rc; diff --git a/libc/sock/recvmsg.c b/libc/sock/recvmsg.c index 6e44829e9..5004fad79 100644 --- a/libc/sock/recvmsg.c +++ b/libc/sock/recvmsg.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -50,6 +51,7 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) { struct msghdr msg2; union sockaddr_storage_bsd bsd; + BEGIN_CANCELLATION_POINT; if (IsAsan() && !__asan_is_valid_msghdr(msg)) { rc = efault(); } else if (!IsWindows()) { @@ -92,6 +94,7 @@ ssize_t recvmsg(int fd, struct msghdr *msg, int flags) { } else { rc = ebadf(); } + END_CANCELLATION_POINT; #if defined(SYSDEBUG) && _DATATRACE if (__strace > 0 && strace_enabled(0) > 0) { diff --git a/libc/sock/select.c b/libc/sock/select.c index f11a1e435..533356c27 100644 --- a/libc/sock/select.c +++ b/libc/sock/select.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/struct/timeval.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" @@ -32,6 +33,8 @@ * this is polyfilled to translate into poll(). So it's recommended that * poll() be used instead. * + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered * @cancellationpoint * @asyncsignalsafe * @threadsafe @@ -42,6 +45,8 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, int rc; POLLTRACE("select(%d, %p, %p, %p, %s) → ...", nfds, readfds, writefds, exceptfds, DescribeTimeval(0, timeout)); + + BEGIN_CANCELLATION_POINT; if (nfds < 0) { rc = einval(); } else if (IsAsan() && @@ -55,6 +60,8 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, } else { rc = sys_select_nt(nfds, readfds, writefds, exceptfds, timeout, 0); } + END_CANCELLATION_POINT; + POLLTRACE("select(%d, %p, %p, %p, [%s]) → %d% m", nfds, readfds, writefds, exceptfds, DescribeTimeval(rc, timeout), rc); return rc; diff --git a/libc/sock/send.c b/libc/sock/send.c index c9b9ecf4e..a3d61ee40 100644 --- a/libc/sock/send.c +++ b/libc/sock/send.c @@ -16,12 +16,13 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/macros.internal.h" #include "libc/sock/internal.h" #include "libc/sock/sock.h" @@ -37,11 +38,14 @@ * @return number of bytes transmitted, or -1 w/ errno * @error EINTR, EHOSTUNREACH, ECONNRESET (UDP ICMP Port Unreachable), * EPIPE (if MSG_NOSIGNAL), EMSGSIZE, ENOTSOCK, EFAULT, etc. + * @cancellationpoint * @asyncsignalsafe * @restartable (unless SO_RCVTIMEO) */ ssize_t send(int fd, const void *buf, size_t size, int flags) { ssize_t rc; + BEGIN_CANCELLATION_POINT; + if (IsAsan() && !__asan_is_valid(buf, size)) { rc = efault(); } else if (!IsWindows()) { @@ -61,6 +65,8 @@ ssize_t send(int fd, const void *buf, size_t size, int flags) { } else { rc = ebadf(); } + + END_CANCELLATION_POINT; DATATRACE("send(%d, %#.*hhs%s, %'zu, %#x) → %'ld% lm", fd, MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, rc); return rc; diff --git a/libc/sock/sendmsg.c b/libc/sock/sendmsg.c index 67a555d36..d0c049b6c 100644 --- a/libc/sock/sendmsg.c +++ b/libc/sock/sendmsg.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -52,6 +53,7 @@ ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) { struct msghdr msg2; union sockaddr_storage_bsd bsd; + BEGIN_CANCELLATION_POINT; if (IsAsan() && !__asan_is_valid_msghdr(msg)) { rc = efault(); } else if (!IsWindows()) { @@ -79,6 +81,7 @@ ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) { } else { rc = ebadf(); } + END_CANCELLATION_POINT; #if defined(SYSDEBUG) && _DATATRACE if (__strace > 0 && strace_enabled(0) > 0) { diff --git a/libc/sock/sendto.c b/libc/sock/sendto.c index 43ac49e50..4765da36a 100644 --- a/libc/sock/sendto.c +++ b/libc/sock/sendto.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" @@ -58,6 +59,8 @@ ssize_t sendto(int fd, const void *buf, size_t size, int flags, ssize_t rc; uint32_t bsdaddrsize; union sockaddr_storage_bsd bsd; + BEGIN_CANCELLATION_POINT; + if (IsAsan() && (!__asan_is_valid(buf, size) || (opt_addr && !__asan_is_valid(opt_addr, addrsize)))) { rc = efault(); @@ -87,6 +90,8 @@ ssize_t sendto(int fd, const void *buf, size_t size, int flags, rc = ebadf(); } } + + END_CANCELLATION_POINT; DATATRACE("sendto(%d, %#.*hhs%s, %'zu, %#x, %p, %u) → %'ld% lm", fd, MAX(0, MIN(40, rc)), buf, rc > 40 ? "..." : "", size, flags, opt_addr, addrsize, rc); diff --git a/libc/stdio/dirstream.c b/libc/stdio/dirstream.c index 4c3c8f606..76d0f57a6 100644 --- a/libc/stdio/dirstream.c +++ b/libc/stdio/dirstream.c @@ -255,6 +255,8 @@ static textwindows dontinline struct dirent *readdir_nt(DIR *dir) { * * @returns newly allocated DIR object, or NULL w/ errno * @errors ENOENT, ENOTDIR, EACCES, EMFILE, ENFILE, ENOMEM + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if we needed to block and a signal was delivered instead * @cancellationpoint * @see glob() */ diff --git a/libc/stdio/getentropy.c b/libc/stdio/getentropy.c index 6e1070beb..3b18eca09 100644 --- a/libc/stdio/getentropy.c +++ b/libc/stdio/getentropy.c @@ -17,20 +17,41 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/blockcancel.internal.h" +#include "libc/calls/blocksigs.internal.h" +#include "libc/calls/syscall_support-sysv.internal.h" +#include "libc/dce.h" +#include "libc/intrin/asan.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/stdio/rand.h" #include "libc/sysv/errfuns.h" +int sys_getentropy(void *, size_t) asm("sys_getrandom"); + /** * Returns random seeding bytes, the XNU/OpenBSD way. * * @return 0 on success, or -1 w/ errno - * @raise EIO if more than 256 bytes are requested + * @raise EFAULT if the `n` bytes at `p` aren't valid memory + * @raise EIO is returned if more than 256 bytes are requested * @see getrandom() */ -int getentropy(void *buf, size_t size) { - if (size > 256) return eio(); - BLOCK_CANCELLATIONS; - if (getrandom(buf, size, 0) != size) notpossible; - ALLOW_CANCELLATIONS; - return 0; +int getentropy(void *p, size_t n) { + int rc; + if (n > 256) { + rc = eio(); + } else if ((!p && n) || (IsAsan() && !__asan_is_valid(p, n))) { + rc = efault(); + } else if (IsXnu() || IsOpenbsd()) { + if (sys_getentropy(p, n)) notpossible; + rc = 0; + } else { + BLOCK_SIGNALS; + BLOCK_CANCELLATIONS; + if (__getrandom(p, n, 0) != n) notpossible; + ALLOW_CANCELLATIONS; + ALLOW_SIGNALS; + rc = 0; + } + STRACE("getentropy(%p, %'zu) → %'ld% m", p, n, rc); + return rc; } diff --git a/libc/stdio/getrandom.c b/libc/stdio/getrandom.c index 7d8ae5d64..2532d8aac 100644 --- a/libc/stdio/getrandom.c +++ b/libc/stdio/getrandom.c @@ -16,15 +16,24 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" +#include "libc/calls/internal.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall_support-nt.internal.h" +#include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/asan.internal.h" +#include "libc/intrin/asmflag.h" #include "libc/intrin/bits.h" #include "libc/intrin/strace.internal.h" +#include "libc/intrin/weaken.h" +#include "libc/macros.internal.h" #include "libc/nexgen32e/kcpuids.h" #include "libc/nexgen32e/rdtsc.h" #include "libc/nexgen32e/vendor.internal.h" @@ -43,12 +52,120 @@ #include "libc/sysv/errfuns.h" #include "libc/thread/thread.h" +STATIC_YOINK("rdrand_init"); + +int sys_getentropy(void *, size_t) asm("sys_getrandom"); + static bool have_getrandom; -static ssize_t GetDevRandom(char *p, size_t n) { +static bool GetRandomRdseed(uint64_t *out) { + int i; + char cf; + uint64_t x; + for (i = 0; i < 10; ++i) { + asm volatile(CFLAG_ASM("rdseed\t%1") + : CFLAG_CONSTRAINT(cf), "=r"(x) + : /* no inputs */ + : "cc"); + if (cf) { + *out = x; + return true; + } + asm volatile("pause"); + } + return false; +} + +static bool GetRandomRdrand(uint64_t *out) { + int i; + char cf; + uint64_t x; + for (i = 0; i < 10; ++i) { + asm volatile(CFLAG_ASM("rdrand\t%1") + : CFLAG_CONSTRAINT(cf), "=r"(x) + : /* no inputs */ + : "cc"); + if (cf) { + *out = x; + return true; + } + asm volatile("pause"); + } + return false; +} + +static ssize_t GetRandomCpu(char *p, size_t n, int f, bool impl(uint64_t *)) { + uint64_t x; + size_t i, j; + for (i = 0; i < n; i += j) { + TryAgain: + if (!impl(&x)) { + if (f || i >= 256) break; + goto TryAgain; + } + for (j = 0; j < 8 && i + j < n; ++j) { + p[i + j] = x; + x >>= 8; + } + } + return n; +} + +static ssize_t GetRandomMetal(char *p, size_t n, int f) { + if (f & GRND_RANDOM) { + if (X86_HAVE(RDSEED)) { + return GetRandomCpu(p, n, f, GetRandomRdseed); + } else { + return enosys(); + } + } else { + if (X86_HAVE(RDRND)) { + return GetRandomCpu(p, n, f, GetRandomRdrand); + } else { + return enosys(); + } + } +} + +static void GetRandomEntropy(char *p, size_t n) { + _unassert(n <= 256); + if (sys_getentropy(p, n)) notpossible; +} + +static void GetRandomArnd(char *p, size_t n) { + size_t m; + int cmd[2]; + cmd[0] = 1; // CTL_KERN + cmd[1] = IsFreebsd() ? 37 : 81; // KERN_ARND + _unassert((m = n) <= 256); + if (sys_sysctl(cmd, 2, p, &n, 0, 0) == -1) notpossible; + if (m != n) notpossible; +} + +static ssize_t GetRandomBsd(char *p, size_t n, void impl(char *, size_t)) { + errno_t e; + size_t m, i; + if (_weaken(pthread_testcancel_np) && + (e = _weaken(pthread_testcancel_np)())) { + errno = e; + return -1; + } + for (i = 0;;) { + m = MIN(n - i, 256); + impl(p + i, m); + if ((i += m) == n) { + return n; + } + if (_weaken(pthread_testcancel)) { + _weaken(pthread_testcancel)(); + } + } +} + +static ssize_t GetDevUrandom(char *p, size_t n) { int fd; ssize_t rc; - fd = __sys_openat(AT_FDCWD, "/dev/urandom", O_RDONLY | O_CLOEXEC, 0); + fd = sys_openat(AT_FDCWD, "/dev/urandom", O_RDONLY | O_CLOEXEC, 0); if (fd == -1) return -1; pthread_cleanup_push((void *)sys_close, (void *)(intptr_t)fd); rc = sys_read(fd, p, n); @@ -56,26 +173,29 @@ static ssize_t GetDevRandom(char *p, size_t n) { return rc; } -static ssize_t GetKernArnd(char *p, size_t n) { - size_t m, i = 0; - int cmd[2]; - if (IsFreebsd()) { - cmd[0] = 1; /* CTL_KERN */ - cmd[1] = 37; /* KERN_ARND */ - } else { - cmd[0] = 1; /* CTL_KERN */ - cmd[1] = 81; /* KERN_ARND */ - } - for (;;) { - m = n - i; - if (sys_sysctl(cmd, 2, p + i, &m, 0, 0) != -1) { - if ((i += m) == n) { - return n; - } +ssize_t __getrandom(void *p, size_t n, unsigned f) { + ssize_t rc; + if (IsWindows()) { + if (_check_interrupts(true, 0)) return -1; + rc = RtlGenRandom(p, n) ? n : __winerr(); + } else if (have_getrandom) { + if (IsXnu() || IsOpenbsd()) { + rc = GetRandomBsd(p, n, GetRandomEntropy); } else { - return i ? i : -1; + BEGIN_CANCELLATION_POINT; + rc = sys_getrandom(p, n, f); + END_CANCELLATION_POINT; } + } else if (IsFreebsd() || IsNetbsd()) { + rc = GetRandomBsd(p, n, GetRandomArnd); + } else if (IsMetal()) { + rc = GetRandomMetal(p, n, f); + } else { + BEGIN_CANCELLATION_POINT; + rc = GetDevUrandom(p, n); + END_CANCELLATION_POINT; } + return rc; } /** @@ -83,27 +203,47 @@ static ssize_t GetKernArnd(char *p, size_t n) { * * This random number seed generator obtains information from: * - * - getrandom() on Linux * - RtlGenRandom() on Windows * - getentropy() on XNU and OpenBSD - * - sysctl(KERN_ARND) on FreeBSD and NetBSD + * - getrandom() on Linux, FreeBSD, and NetBSD + * - sysctl(KERN_ARND) on older versions of FreeBSD and NetBSD * - * The following flags may be specified: + * Unlike getentropy() this function is interruptible. However EINTR + * shouldn't be possible if `f` is zero and `n` is no more than 256, + * noting that kernels are a bit vague with their promises here, and + * if you're willing to trade some performance for a more assurances + * that EINTR won't happen, then either consider using getentropy(), + * or using the `SA_RESTART` flag on your signal handlers. * - * - `GRND_RANDOM`: Halt the entire system while I tap an entropy pool - * so small that it's hard to use statistics to test if it's random - * - `GRND_NONBLOCK`: Do not wait for i/o events or me to jiggle my - * mouse, and instead return immediately the moment data isn't - * available, even if the result needs to be -1 w/ EAGAIN + * Unlike getentropy() you may specify an `n` greater than 256. When + * larger amounts are specified, the caller must be prepared for the + * case where fewer than `n` bytes are returned. In that case, it is + * likely that a signal delivery occured. Cancellations in mask mode + * also need to be suppressed while processing the bytes beyond 256. + * On BSD OSes, this entire process is uninterruptible so be careful + * when using large sizes if interruptibility is needed. * - * This function is safe to use with fork() and vfork(). It will also - * close any file descriptor it ends up needing before it returns. + * Unlike getentropy() this function is a cancellation point. But it + * shouldn't be a problem, unless you're using masked mode, in which + * case extra care must be taken to consider the result. + * + * It's recommended that `f` be set to zero, although it may include + * the following flags: + * + * - `GRND_NONBLOCK` when you want to elevate the insecurity of your + * random data + * + * - `GRND_RANDOM` if you want to have the best possible chance your + * program will freeze and the system operator is paged to address + * the outage by driving to the data center and jiggling the mouse * * @note this function could block a nontrivial time on old computers * @note this function is indeed intended for cryptography * @note this function takes around 900 cycles * @raise EINVAL if `f` is invalid - * @raise ENOSYS on bare metal + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EFAULT if the `n` bytes at `p` aren't valid memory + * @raise EINTR if we needed to block and a signal was delivered instead * @cancellationpoint * @asyncsignalsafe * @restartable @@ -111,39 +251,27 @@ static ssize_t GetKernArnd(char *p, size_t n) { */ ssize_t getrandom(void *p, size_t n, unsigned f) { ssize_t rc; - const char *via; - if ((f & ~(GRND_RANDOM | GRND_NONBLOCK))) { + if ((!p && n) || (IsAsan() && !__asan_is_valid(p, n))) { + rc = efault(); + } else if ((f & ~(GRND_RANDOM | GRND_NONBLOCK))) { rc = einval(); - via = "n/a"; - } else if (IsWindows()) { - via = "RtlGenRandom"; - rc = RtlGenRandom(p, n) ? n : __winerr(); - } else if (IsFreebsd() || IsNetbsd()) { - via = "KERN_ARND"; - rc = GetKernArnd(p, n); - } else if (have_getrandom) { - via = "getrandom"; - if ((rc = sys_getrandom(p, n, f & (GRND_RANDOM | GRND_NONBLOCK))) != -1) { - if (!rc && (IsXnu() || IsOpenbsd())) { - rc = n; - } - } } else { - via = "/dev/urandom"; - rc = GetDevRandom(p, n); + rc = __getrandom(p, n, f); } - STRACE("getrandom(%p, %'zu, %#x) via %s → %'ld% m", p, n, f, via, rc); + STRACE("getrandom(%p, %'zu, %#x) → %'ld% m", p, n, f, rc); return rc; } __attribute__((__constructor__)) static textstartup void getrandom_init(void) { int e, rc; - if (IsWindows()) return; + if (IsWindows() || IsMetal()) return; + BLOCK_CANCELLATIONS; e = errno; if (!(rc = sys_getrandom(0, 0, 0))) { have_getrandom = true; } else { errno = e; } + ALLOW_CANCELLATIONS; STRACE("sys_getrandom(0,0,0) → %d% m", rc); } diff --git a/libc/stdio/pclose.c b/libc/stdio/pclose.c index 380c66221..dbeec66ba 100644 --- a/libc/stdio/pclose.c +++ b/libc/stdio/pclose.c @@ -25,19 +25,45 @@ /** * Closes stream created by popen(). + * + * This function may be interrupted or cancelled, however it won't + * actually return until the child process has terminated. Thus we + * always release the resource, and errors are purely advisory. + * * @return termination status of subprocess, or -1 w/ ECHILD + * @raise ECANCELED if thread was cancelled in masked mode + * @raise ECHILD if child pid didn't exist + * @raise EINTR if signal was delivered + * @cancellationpoint */ int pclose(FILE *f) { - int ws, pid; + int e, rc, ws, pid; + bool iscancelled, wasinterrupted; pid = f->pid; fclose(f); if (!pid) return 0; -TryAgain: - if (wait4(pid, &ws, 0, 0) != -1) { - return ws; - } else if (errno == EINTR) { - goto TryAgain; + iscancelled = false; + wasinterrupted = false; + for (e = errno;;) { + if (wait4(pid, &ws, 0, 0) != -1) { + rc = ws; + break; + } else if (errno == ECANCELED) { + iscancelled = true; + errno = e; + } else if (errno == EINTR) { + wasinterrupted = true; + errno = e; + } else { + rc = echild(); + break; + } + } + if (iscancelled) { + return ecanceled(); + } else if (wasinterrupted) { + return eintr(); } else { - return echild(); + return rc; } } diff --git a/libc/stdio/popen.c b/libc/stdio/popen.c index 5fb577315..6d86497fa 100644 --- a/libc/stdio/popen.c +++ b/libc/stdio/popen.c @@ -33,10 +33,22 @@ /** * Spawns subprocess and returns pipe stream. * + * The returned resource needs to be passed to pclose(). + * * This embeds the Cosmopolitan Command Interpreter which provides * Bourne-like syntax on all platforms including Windows. * - * @see pclose() + * @param cmdline is a unix shell script + * @param mode can be: + * - `"r"` for reading from subprocess standard output + * - `"w"` for writing to subprocess standard input + * @raise EINVAL if `mode` is invalid or specifies read+write + * @raise EMFILE if process `RLIMIT_NOFILE` has been reached + * @raise ENFILE if system-wide file limit has been reached + * @raise ECANCELED if thread was cancelled in masked mode + * @raise ENOMEM if we require more vespene gas + * @raise EAGAIN if `RLIMIT_NPROC` was exceeded + * @raise EINTR if signal was delivered * @cancellationpoint * @threadsafe */ diff --git a/libc/stdio/posix_spawn.c b/libc/stdio/posix_spawn.c index 953f55961..bb33c605d 100644 --- a/libc/stdio/posix_spawn.c +++ b/libc/stdio/posix_spawn.c @@ -68,7 +68,6 @@ static int RunFileActions(struct _posix_faction *a) { * @param envp is environment variables, or `environ` if null * @return 0 on success or error number on failure * @see posix_spawnp() for `$PATH` searching - * @cancellationpoint * @tlsrequired * @threadsafe */ @@ -81,9 +80,6 @@ int posix_spawn(int *pid, const char *path, int s, child, policy; struct sched_param param; struct sigaction dfl = {0}; - if (_weaken(pthread_testcancel)) { - _weaken(pthread_testcancel)(); - } if (!(child = vfork())) { if (attrp && *attrp) { posix_spawnattr_getflags(attrp, &flags); diff --git a/libc/stdio/system.c b/libc/stdio/system.c index 8b820ab79..671c1869d 100644 --- a/libc/stdio/system.c +++ b/libc/stdio/system.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/struct/rusage.h" #include "libc/calls/struct/sigaction.h" @@ -36,10 +37,10 @@ * This embeds the Cosmopolitan Command Interpreter which provides * Bourne-like syntax on all platforms including Windows. * - * @param cmdline is an interpreted Turing-complete command + * @param cmdline is a unix shell script * @return -1 if child process couldn't be created, otherwise a wait - * status that can be accessed using macros like WEXITSTATUS(s) - * @cancellationpoint + * status that can be accessed using macros like WEXITSTATUS(s), + * WIFSIGNALED(s), WTERMSIG(s), etc. * @threadsafe */ int system(const char *cmdline) { @@ -47,9 +48,7 @@ int system(const char *cmdline) { sigset_t chldmask, savemask; struct sigaction ignore, saveint, savequit; if (!cmdline) return 1; - if (_weaken(pthread_testcancel)) { - _weaken(pthread_testcancel)(); - } + BLOCK_CANCELLATIONS; ignore.sa_flags = 0; ignore.sa_handler = SIG_IGN; sigemptyset(&ignore.sa_mask); @@ -76,5 +75,6 @@ int system(const char *cmdline) { sigaction(SIGINT, &saveint, 0); sigaction(SIGQUIT, &savequit, 0); sigprocmask(SIG_SETMASK, &savemask, 0); + ALLOW_CANCELLATIONS; return wstatus; } diff --git a/libc/stdio/tmpfile.c b/libc/stdio/tmpfile.c index d9e9a339b..e9cfa724f 100644 --- a/libc/stdio/tmpfile.c +++ b/libc/stdio/tmpfile.c @@ -54,6 +54,9 @@ * is Linux-only and will cause open() failures on all other platforms. * * @see tmpfd() if you don't want to link stdio/malloc + * @raise ECANCELED if thread was cancelled in masked mode + * @raise EINTR if signal was delivered + * @cancellationpoint * @asyncsignalsafe * @threadsafe * @vforksafe diff --git a/libc/str/update.sh b/libc/str/update.sh index 62ca31faa..5dbec103a 100755 --- a/libc/str/update.sh +++ b/libc/str/update.sh @@ -1,5 +1,5 @@ #!/bin/sh -[ -d libc/unicode ] || exit +[ -d libc/str ] || exit [ -x o//examples/curl.com ] || make -j8 o//examples/curl.com || exit mkdir -p o/tmp/ || exit diff --git a/libc/sysv/calls/__sys_openat_nc.s b/libc/sysv/calls/__sys_openat_nc.s new file mode 100644 index 000000000..47fde4b62 --- /dev/null +++ b/libc/sysv/calls/__sys_openat_nc.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/macros.internal.inc" +.scall __sys_openat_nc,0x1d41411f321d0101,globl,hidden diff --git a/libc/sysv/calls/sys_copy_file_range.s b/libc/sysv/calls/sys_copy_file_range.s index a7bef39c2..4964e885f 100644 --- a/libc/sysv/calls/sys_copy_file_range.s +++ b/libc/sysv/calls/sys_copy_file_range.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/macros.internal.inc" -.scall sys_copy_file_range,0xffffff239ffff146,globl,hidden +.scall sys_copy_file_range,0xffffffa39ffff946,globl,hidden diff --git a/libc/sysv/calls/sys_getrandom.s b/libc/sysv/calls/sys_getrandom.s index fb8772447..ab7289f04 100644 --- a/libc/sysv/calls/sys_getrandom.s +++ b/libc/sysv/calls/sys_getrandom.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/macros.internal.inc" -.scall sys_getrandom,0xfff807a3329f493e,globl,hidden +.scall sys_getrandom,0x85b007a3321f493e,globl,hidden diff --git a/libc/sysv/calls/sys_tgkill.s b/libc/sysv/calls/sys_tgkill.s index 18b2b2a38..ba562509e 100644 --- a/libc/sysv/calls/sys_tgkill.s +++ b/libc/sysv/calls/sys_tgkill.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/macros.internal.inc" -.scall sys_tgkill,0xfffffffffffff0ea,globl,hidden +.scall sys_tgkill,0xffffff1e1ffff0ea,globl,hidden diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 075963a2a..b747bd913 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -168,10 +168,10 @@ syscon sig SIGPROF 27 27 27 27 27 27 # profiling timer expired; syscon sig SIGWINCH 28 28 28 28 28 28 # terminal resized; unix consensus & faked on nt syscon sig SIGIO 29 23 23 23 23 29 # bsd consensus syscon sig SIGSYS 31 12 12 12 12 31 # wut; bsd consensus -syscon sig SIGINFO 0 29 29 29 29 0 # bsd consensus -syscon sig SIGEMT 0 7 7 7 7 0 # not implemented in most community editions of system five; consider doing this using SIGUSR1 or SIGUSR2 instead +syscon sig SIGINFO 63 29 29 29 29 63 # bsd consensus +syscon sig SIGEMT 64 7 7 7 7 64 # not implemented in most community editions of system five; consider doing this using SIGUSR1 or SIGUSR2 instead syscon sig SIGPWR 30 30 30 30 32 30 # not implemented in most community editions of system five; consider doing this using SIGUSR1 or SIGUSR2 instead -syscon sig SIGCANCEL 32 7 65 7 33 32 # SIGRTMIN+0; faked as SIGEMT on XNU and OpenBSD +syscon sig SIGTHR 32 7 32 32 33 32 # used by pthread_cancel(); SIGRTMIN+0 on Linux/NetBSD; faked as SIGEMT on XNU (what is SIG32 on XNU anyway?) syscon sig SIGRTMIN 32 0 65 0 33 32 syscon sig SIGRTMAX 64 0 126 0 63 64 syscon compat SIGPOLL 29 23 23 23 23 29 # same as SIGIO diff --git a/libc/sysv/consts/SIGEMT.s b/libc/sysv/consts/SIGEMT.s index ed28d5d7f..bf0b0df8d 100644 --- a/libc/sysv/consts/SIGEMT.s +++ b/libc/sysv/consts/SIGEMT.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon sig,SIGEMT,0,7,7,7,7,0 +.syscon sig,SIGEMT,64,7,7,7,7,64 diff --git a/libc/sysv/consts/SIGINFO.s b/libc/sysv/consts/SIGINFO.s index 7f491fe2a..d62ae73ef 100644 --- a/libc/sysv/consts/SIGINFO.s +++ b/libc/sysv/consts/SIGINFO.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon sig,SIGINFO,0,29,29,29,29,0 +.syscon sig,SIGINFO,63,29,29,29,29,63 diff --git a/libc/sysv/consts/SIGCANCEL.s b/libc/sysv/consts/SIGTHR.s similarity index 56% rename from libc/sysv/consts/SIGCANCEL.s rename to libc/sysv/consts/SIGTHR.s index b7ab88be7..a6f1a11b3 100644 --- a/libc/sysv/consts/SIGCANCEL.s +++ b/libc/sysv/consts/SIGTHR.s @@ -1,2 +1,2 @@ .include "o/libc/sysv/consts/syscon.internal.inc" -.syscon sig,SIGCANCEL,32,7,65,7,33,32 +.syscon sig,SIGTHR,32,7,32,32,33,32 diff --git a/libc/sysv/consts/sig.h b/libc/sysv/consts/sig.h index a2f996688..31723e2a6 100644 --- a/libc/sysv/consts/sig.h +++ b/libc/sysv/consts/sig.h @@ -7,7 +7,7 @@ COSMOPOLITAN_C_START_ extern const int SIGABRT; extern const int SIGALRM; extern const int SIGBUS; -extern const int SIGCANCEL; +extern const int SIGTHR; extern const int SIGCHLD; extern const int SIGCONT; extern const int SIGEMT; @@ -73,7 +73,7 @@ COSMOPOLITAN_C_END_ #define SIGXFSZ LITERALLY(25) #define SIGBUS SYMBOLIC(SIGBUS) -#define SIGCANCEL SYMBOLIC(SIGCANCEL) +#define SIGTHR SYMBOLIC(SIGTHR) #define SIGCHLD SYMBOLIC(SIGCHLD) #define SIGCONT SYMBOLIC(SIGCONT) #define SIGEMT SYMBOLIC(SIGEMT) diff --git a/libc/sysv/syscalls.sh b/libc/sysv/syscalls.sh index 2d338097b..fda997452 100755 --- a/libc/sysv/syscalls.sh +++ b/libc/sysv/syscalls.sh @@ -100,7 +100,8 @@ scall __sys_wait4 0x9c180b807280783d globl hidden scall sys_kill 0x02507a025202503e globl hidden # kill(pid, sig, 1) b/c xnu scall sys_killpg 0x092fff092fffffff globl hidden scall sys_clone 0x11fffffffffff038 globl hidden -scall sys_tkill 0x13e0771b121480c8 globl hidden # thr_kill() on freebsd; _lwp_kill() on netbsd; thrkill() on openbsd where arg3 should be 0; __pthread_kill() on XNU +scall sys_tkill 0x13e0771b121480c8 globl hidden # thr_kill() on FreeBSD; _lwp_kill() on NetBSD; thrkill() on OpenBSD where arg3 should be 0 or tcb; __pthread_kill() on XNU +scall sys_tgkill 0xffffff1e1ffff0ea globl hidden # thr_kill2() on FreeBSD scall sys_futex 0x0a60531c6ffff0ca globl hidden # raises SIGSYS on NetBSD; _umtx_op() on FreeBSD scall sys_futex_cp 0x8a68539c6ffff8ca globl hidden # intended for futex wait ops scall sys_set_robust_list 0x0a7ffffffffff111 globl # no wrapper @@ -265,7 +266,6 @@ scall sys_ktimer_settime 0xffffff0edfffffff globl # no wrapper scall sys_clock_settime 0x1ac0580e9ffff0e3 globl # no wrapper scall sys_clock_gettime 0x1ab0570e8ffff0e4 globl hidden # Linux 2.6+ (c. 2003); XNU uses magic address scall sys_clock_getres 0x1ad0590eaffff0e5 globl hidden -scall sys_tgkill 0xfffffffffffff0ea globl hidden scall sys_mbind 0xfffffffffffff0ed globl # no wrapper; numa numa yeah scall set_mempolicy 0xfffffffffffff0ee globl scall get_mempolicy 0xfffffffffffff0ef globl @@ -286,6 +286,7 @@ scall sys_inotify_init 0xfffffffffffff0fd globl # wicked # no wrapper scall sys_inotify_add_watch 0xfffffffffffff0fe globl # no wrapper scall sys_inotify_rm_watch 0xfffffffffffff0ff globl # no wrapper scall __sys_openat 0x9d49419f329cf901 globl hidden # Linux 2.6.16+ (c. 2007) +scall __sys_openat_nc 0x1d41411f321d0101 globl hidden # openat_nocancel() on xnu scall sys_mkdirat 0x1cd13e1f021db102 globl hidden scall sys_fchownat 0x1d013b1eb21d4104 globl hidden # @asyncsignalsafe scall sys_utime 0xfffffffffffff084 globl hidden @@ -350,7 +351,7 @@ scall sys_sched_setattr 0xfffffffffffff13a globl # ├─ desktop replaced with scall sys_sched_getattr 0xfffffffffffff13b globl # ├─ karen sandler requires systemd init and boot for tablet gui scall sys_renameat2 0xfffffffffffff13c globl # └─ debian founder ian murdock found strangled with vacuum cord #scall seccomp 0xfffffffffffff13d globl # wrapped manually -scall sys_getrandom 0xfff807a3329f493e globl hidden # Linux 3.17+ and getentropy() on XNU/OpenBSD, coming to NetBSD in 9.2 +scall sys_getrandom 0x85b007a3321f493e globl hidden # Linux 3.17+; FreeBSD 12+; NetBSD v9.2+; getentropy() on XNU/OpenBSD scall sys_memfd_create 0xfffffffffffff13f globl hidden scall sys_kexec_file_load 0xfffffffffffff140 globl # no wrapper scall sys_bpf 0xfffffffffffff141 globl # no wrapper @@ -358,7 +359,7 @@ scall sys_execveat 0xfffffffffffff142 globl # no wrapper scall sys_userfaultfd 0xfffffffffffff143 globl # no wrapper; Linux 4.3+ (c. 2015) scall sys_membarrier 0xfffffffffffff144 globl # no wrapper; Linux 4.3+ (c. 2015) scall sys_mlock2 0xfffffffffffff145 globl # no wrapper; Linux 4.5+ (c. 2016) -scall sys_copy_file_range 0xffffff239ffff146 globl hidden # Linux 4.5+ (c. 2016), FreeBSD 13+ +scall sys_copy_file_range 0xffffffa39ffff946 globl hidden # Linux 4.5+ (c. 2016), FreeBSD 13+ scall sys_preadv2 0xfffffffffffff147 globl # no wrapper scall sys_pwritev2 0xfffffffffffff148 globl # no wrapper scall sys_pkey_mprotect 0xfffffffffffff149 globl # no wrapper diff --git a/libc/sysv/systemfive.S b/libc/sysv/systemfive.S index 0efda018e..6359e2481 100644 --- a/libc/sysv/systemfive.S +++ b/libc/sysv/systemfive.S @@ -110,7 +110,7 @@ __pid: .quad 0 systemfive_cp: push %rbp - mov %rsp,%rbp + mov %rsp,%rbp # so backtraces work systemfive_cancellable: # our pthread_cancel() miracle code cmpb $0,__tls_enabled(%rip) # inspired by the musl libc design! je 1f # we handle linux and bsd together! @@ -120,21 +120,24 @@ systemfive_cancellable: # our pthread_cancel() miracle code jz 1f # it's spawn() probably testb $PT_NOCANCEL,0x00(%r10) # PosixThread::flags jnz 1f # canceler no cancelling +#if IsModeDbg() + testb $PT_INCANCEL,0x00(%r10) + jz 5f +#endif cmp $0,0x04(%r10) # PosixThread::cancelled - jne systemfive_cancel # tail call for masked mode + jne systemfive_cancel # we will tail call below 1: mov %rcx,%r10 # move the fourth argument clc # no cancellable system calls exist syscall # that have 7+ args on the bsd OSes systemfive_cancellable_end: # i/o calls park here for long time pop %rbp - jc systemfive_errno - jmp 0f - jc 3f # we're now out of the limbo state! -1: cmp $-4095,%rax # but we still check again on eintr - jae 2f + jnc 2f + neg %rax # turns bsd errno to system v errno +2: cmp $-4095,%rax # but we still check again on eintr + jae 3f # branch because system call failed ret # done if the system call succeeded -2: neg %eax # now examine the nature of failure -3: cmp EINTR(%rip),%eax # did SIGCANCEL cancel our i/o call +3: neg %eax # now examine the nature of failure + cmp EINTR(%rip),%eax # did the SIGTHR cancel our IO call jne systemfive_errno # werent interrupted by OnSigCancel cmpb $0,__tls_enabled(%rip) # make sure it's safe to grab %fs:0 je systemfive_errno # tls is disabled we can't continue @@ -146,11 +149,22 @@ systemfive_cancellable_end: # i/o calls park here for long time jnz systemfive_errno # cancellation is disabled cmp $0,0x04(%rcx) # PosixThread::cancelled je systemfive_errno # we aren't actually cancelled - jmp 1f # now we are in fact cancelled -systemfive_cancel: + jmp 4f # now we are in fact cancelled +systemfive_cancel: # SIGTHR will jump here too pop %rbp -1: jmp _pthread_cancel_sys # must be linked if we're cancelled - .weak _pthread_cancel_sys +4: jmp _pthread_cancel_sys # tail call + .weak _pthread_cancel_sys # must be linked if we're cancelled +#if IsModeDbg() +not_a_cancellation_point: # need BEGIN/END_CANCELLATION_POINT + nop + .weak report_cancellation_point +5: ezlea report_cancellation_point,cx + test %rcx,%rcx + jz 6f + call *%rcx +6: ud2 + nop +#endif .globl systemfive_cancellable_end .globl systemfive_cancellable .globl systemfive_cancel @@ -169,7 +183,7 @@ systemfive_linux: mov %rsp,%rbp # having frame will help backtraces syscall # this is known as a context switch pop %rbp # next we check to see if it failed -0: cmp $-4095,%rax # system five nexgen32e abi § a.2.1 + cmp $-4095,%rax # system five nexgen32e abi § a.2.1 jae systemfive_error # encodes errno as neg return value ret .endfn systemfive_linux,globl,hidden diff --git a/libc/testlib/testrunner.c b/libc/testlib/testrunner.c index 92aa966b3..419b0ee2e 100644 --- a/libc/testlib/testrunner.c +++ b/libc/testlib/testrunner.c @@ -225,8 +225,10 @@ void testlib_runtestcases(testfn_t *start, testfn_t *end, testfn_t warmup) { if (!IsWindows()) sys_getpid(); if (warmup) warmup(); testlib_clearxmmregisters(); - STRACE("running test %t", fn); + STRACE(""); + STRACE("# running test %t", fn); (*fn)(); + STRACE(""); if (!IsWindows()) sys_getpid(); if (_weaken(TearDown)) _weaken(TearDown)(); if (_weaken(testlib_enable_tmp_setup_teardown)) TearDownTmpDir(); diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index e09f50a28..45629691d 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -6,11 +6,13 @@ #include "libc/thread/thread.h" #include "libc/thread/tls.h" -#define PT_OWNSTACK 1 -#define PT_MAINTHREAD 2 -#define PT_ASYNC 4 -#define PT_NOCANCEL 8 -#define PT_MASKED 16 +#define PT_OWNSTACK 1 +#define PT_MAINTHREAD 2 +#define PT_ASYNC 4 +#define PT_NOCANCEL 8 +#define PT_MASKED 16 +#define PT_INCANCEL 32 +#define PT_OPENBSD_KLUDGE 64 #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ diff --git a/libc/thread/pthread_atfork.c b/libc/thread/pthread_atfork.c index a19b86e05..5bf8204fa 100644 --- a/libc/thread/pthread_atfork.c +++ b/libc/thread/pthread_atfork.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/errno.h" +#include "libc/intrin/atomic.h" #include "libc/intrin/kmalloc.h" #include "libc/runtime/memtrack.internal.h" #include "libc/str/str.h" @@ -65,8 +66,14 @@ void _pthread_onfork_parent(void) { } void _pthread_onfork_child(void) { + struct CosmoTib *tib; + struct PosixThread *pt; pthread_mutexattr_t attr; extern pthread_mutex_t __mmi_lock_obj; + tib = __get_tls(); + pt = (struct PosixThread *)tib->tib_pthread; + atomic_store_explicit(&pt->cancelled, false, memory_order_relaxed); + pt->tid = atomic_load_explicit(&tib->tib_tid, memory_order_relaxed); pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&__mmi_lock_obj, &attr); diff --git a/libc/thread/pthread_cancel.c b/libc/thread/pthread_cancel.c index 389a29028..ec2285824 100644 --- a/libc/thread/pthread_cancel.c +++ b/libc/thread/pthread_cancel.c @@ -21,7 +21,9 @@ #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" #include "libc/calls/struct/sigset.h" +#include "libc/calls/syscall_support-sysv.internal.h" #include "libc/calls/ucontext.h" +#include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" #include "libc/intrin/kprintf.h" @@ -45,7 +47,7 @@ int _pthread_cancel_sys(void) { if (!(pt->flags & (PT_NOCANCEL | PT_MASKED)) || (pt->flags & PT_ASYNC)) { pthread_exit(PTHREAD_CANCELED); } - pt->flags |= PT_NOCANCEL; + pt->flags |= PT_NOCANCEL | PT_OPENBSD_KLUDGE; return ecanceled(); } @@ -56,12 +58,14 @@ static void OnSigCancel(int sig, siginfo_t *si, void *ctx) { if (pt && !(pt->flags & PT_NOCANCEL) && atomic_load_explicit(&pt->cancelled, memory_order_acquire)) { sigaddset(&uc->uc_sigmask, sig); - if ((pt->flags & PT_ASYNC) || - (systemfive_cancellable <= (char *)uc->uc_mcontext.rip && - (char *)uc->uc_mcontext.rip < systemfive_cancellable_end)) { + if (systemfive_cancellable <= (char *)uc->uc_mcontext.rip && + (char *)uc->uc_mcontext.rip < systemfive_cancellable_end) { uc->uc_mcontext.rip = (intptr_t)systemfive_cancel; + } else if (pt->flags & PT_ASYNC) { + pthread_exit(PTHREAD_CANCELED); } else { - tkill(atomic_load_explicit(&tib->tib_tid, memory_order_relaxed), sig); + __tkill(atomic_load_explicit(&tib->tib_tid, memory_order_relaxed), sig, + tib); } } } @@ -71,7 +75,7 @@ static void ListenForSigCancel(void) { sa.sa_sigaction = OnSigCancel; sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK; memset(&sa.sa_mask, -1, sizeof(sa.sa_mask)); - _npassert(!sigaction(SIGCANCEL, &sa, 0)); + _npassert(!sigaction(SIGTHR, &sa, 0)); } /** @@ -79,28 +83,142 @@ static void ListenForSigCancel(void) { * * When a thread is cancelled, it'll interrupt blocking i/o calls, * invoke any cleanup handlers that were pushed on the thread's stack - * as well as key destructors, and then the thread exits. + * before the cancellation occurred, in addition to destructing pthread + * keys, before finally, the thread shall abruptly exit. * - * By default, pthread_cancel() can only take effect when a thread is - * blocked on a @cancellationpoint, which is any system call that's - * specified as raising `EINTR`. For example, `openat`, `poll`, `ppoll`, - * `select`, `pselect`, `read`, `readv`, `pread`, `preadv`, `write`, - * `writev`, `pwrite`, `pwritev`, `accept`, `connect`, `recvmsg`, - * `sendmsg`, `recv`, `send`, `tcdrain`, `clock_nanosleep`, `fsync`, - * `fdatasync`, `fcntl(F_SETLKW)`, `epoll`, `sigsuspend`, `msync`, - * `wait4`, `getrandom`, `pthread_cond_timedwait`, and `sem_timedwait` - * are most cancellation points, plus many userspace libraries that call - * the above functions, unless they're using pthread_setcancelstate() to - * temporarily disable the cancellation mechanism. Some userspace - * functions, e.g. system() will eagerly call pthread_testcancel_np() to - * help avoid the potential for resource leaks later on. + * By default, pthread_cancel() can only take effect when a thread + * reaches a cancellation point. Such functions are documented with + * @cancellationpoint. They check the cancellation state before the + * underlying system call is issued. If the system call is issued and + * blocks, then pthread_cancel() will interrupt the operation in which + * case the syscall wrapper will check the cancelled state a second + * time, only if the raw system call returned EINTR. + * + * The following system calls are implemented as cancellation points. + * + * - `accept4` + * - `accept` + * - `clock_nanosleep` + * - `connect` + * - `copy_file_range` + * - `creat` + * - `epoll_wait` + * - `fcntl(F_OFD_SETLKW)` + * - `fcntl(F_SETLKW)` + * - `fdatasync` + * - `flock` + * - `fstatfs` + * - `fsync` + * - `ftruncate` + * - `getrandom` + * - `msync` + * - `nanosleep` + * - `open` + * - `openat` + * - `pause` + * - `poll` + * - `ppoll` + * - `pread` + * - `preadv` + * - `pselect` + * - `pwrite` + * - `pwritev` + * - `read` + * - `readv` + * - `recvfrom` + * - `recvmsg` + * - `select` + * - `sendmsg` + * - `sendto` + * - `sigsuspend` + * - `sigtimedwait` + * - `sigwaitinfo` + * - `statfs` + * - `tcdrain` + * - `truncate` + * - `wait3` + * - `wait4` + * - `wait` + * - `waitpid` + * - `write` + * - `writev` + * + * The following library calls are implemented as cancellation points. + * + * - `fopen` + * - `gzopen`, `gzread`, `gzwrite`, etc. + * - `lockf(F_LOCK)` + * - `nsync_cv_wait_with_deadline` + * - `nsync_cv_wait` + * - `opendir` + * - `pclose` + * - `popen` + * - `fwrite`, `printf`, `fprintf`, `putc`, etc. + * - `pthread_cond_timedwait` + * - `pthread_cond_wait` + * - `pthread_join` + * - `sem_timedwait` + * - `sem_wait` + * - `sleep` + * - `timespec_sleep_until` + * - `tmpfd` + * - `tmpfile` + * - `usleep` + * + * Other userspace libraries provided by Cosmopolitan Libc that call the + * cancellation points above will block cancellations while running. The + * following are examples of functions that *aren't* cancellation points + * + * - `INFOF()`, `WARNF()`, etc. + * - `getentropy` + * - `gmtime_r` + * - `kprintf` + * - `localtime_r` + * - `nsync_mu_lock` + * - `nsync_mu_unlock` + * - `openpty` + * - `pthread_getname_np` + * - `pthread_mutex_lock` + * - `pthread_mutex_unlock` + * - `pthread_setname_np` + * - `sem_open` + * - `system` + * - `timespec_sleep` + * - `touch` + * + * The way to block cancellations temporarily is: + * + * int cs; + * pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); + * // ... + * pthread_setcancelstate(cs, 0); + * + * In order to support cancellations all your code needs to be rewritten + * so that when resources such as file descriptors are managed they must + * have a cleanup crew pushed to the stack. For example even malloc() is + * technically unsafe w.r.t. leaks without doing something like this: + * + * void *p = malloc(123); + * pthread_cleanup_push(free, p); + * read(0, p, 123); + * pthread_cleanup_pop(1); + * + * Consider using Cosmopolitan Libc's garbage collector since it will be + * executed when a thread exits due to a cancellation. + * + * void *p = _gc(malloc(123)); + * read(0, p, 123); * * It's possible to put a thread in asynchronous cancellation mode using - * pthread_setcanceltype(), thus allowing a cancellation to occur at any - * assembly opcode. Please be warned that doing so is risky since it can - * easily result in resource leaks. For example, a cancellation might be - * triggered between calling open() and pthread_cleanup_push(), in which - * case the application will leak a file descriptor. + * + * pthread_setcancelstate(PTHREAD_CANCEL_ASYNCHRONOUS, 0); + * for (;;) donothing; + * + * In which case a thread may be cancelled at any assembly opcode. This + * is useful for immediately halting threads that consume cpu and don't + * use any system calls. It shouldn't be used on threads that will call + * cancellation points since in that case asynchronous mode could cause + * resource leaks to happen, in such a way that can't be worked around. * * If none of the above options seem savory to you, then a third way is * offered for doing cancellations. Cosmopolitan Libc supports the Musl @@ -109,6 +227,33 @@ static void ListenForSigCancel(void) { * be abruptly destroyed upon cancellation and have their stack unwound; * instead, cancellation points will simply raise an `ECANCELED` error, * which can be more safely and intuitively handled for many use cases. + * For example: + * + * pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0); + * void *p = malloc(123); + * int rc = read(0, p, 123); + * free(p); + * if (rc == ECANCELED) { + * pthread_exit(0); + * } + * + * Shows how the masked cancellations paradigm can be safely used. Note + * that it's so important that cancellation point error return codes be + * checked. Code such as the following: + * + * pthread_setcancelstate(PTHREAD_CANCEL_MASKED, 0); + * void *p = malloc(123); + * write(2, "log\n", 4); // XXX: fails to check result + * int rc = read(0, p, 123); + * free(p); + * if (rc == ECANCELED) { + * pthread_exit(0); // XXX: not run if write() was cancelled + * } + * + * Isn't safe to use in masked mode. That's because if a cancellation + * occurs during the write() operation then cancellations are blocked + * while running read(). Masked mode doesn't have second chances. You + * must rigorously check the results of each cancellation point call. * * @return 0 on success, or errno on error * @raise ESRCH if thread isn't alive @@ -117,7 +262,6 @@ errno_t pthread_cancel(pthread_t thread) { int e, rc, tid; static bool once; struct PosixThread *pt; - __require_tls(); if (!once) ListenForSigCancel(), once = true; pt = (struct PosixThread *)thread; switch (atomic_load_explicit(&pt->status, memory_order_acquire)) { @@ -127,18 +271,18 @@ errno_t pthread_cancel(pthread_t thread) { default: break; } - atomic_exchange_explicit(&pt->cancelled, 1, memory_order_release); + atomic_store_explicit(&pt->cancelled, 1, memory_order_release); if (thread == __get_tls()->tib_pthread) { if (!(pt->flags & (PT_NOCANCEL | PT_MASKED)) && (pt->flags & PT_ASYNC)) { pthread_exit(PTHREAD_CANCELED); } return 0; } - if (IsWindows()) return 0; // TODO(jart): Should we do this? + if (IsWindows()) return 0; // no true cancellations on Windows yet tid = atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire); - if (tid <= 0) return 0; // TODO(jart): Do we need this? + if (tid <= 0) return 0; // -1 means still starting, 0 means exited e = errno; - if (!tkill(tid, SIGCANCEL)) { + if (!__tkill(tid, SIGTHR, pt->tib)) { return 0; } else { rc = errno; diff --git a/libc/thread/pthread_cond_timedwait.c b/libc/thread/pthread_cond_timedwait.c index b213c040f..3cc9dc364 100644 --- a/libc/thread/pthread_cond_timedwait.c +++ b/libc/thread/pthread_cond_timedwait.c @@ -27,7 +27,7 @@ * Waits for condition with optional time limit, e.g. * * struct timespec ts; // one second timeout - * ts = _timespec_add(_timespec_real(), _timespec_frommillis(1000)); + * ts = timespec_add(timespec_real(), timespec_frommillis(1000)); * if (pthread_cond_timedwait(cond, mutex, &ts) == ETIMEDOUT) { * // handle timeout... * } diff --git a/libc/thread/pthread_kill.c b/libc/thread/pthread_kill.c index 615340b71..570302615 100644 --- a/libc/thread/pthread_kill.c +++ b/libc/thread/pthread_kill.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/calls/syscall_support-sysv.internal.h" #include "libc/errno.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" @@ -34,7 +35,7 @@ errno_t pthread_kill(pthread_t thread, int sig) { int rc, e = errno; struct PosixThread *pt = (struct PosixThread *)thread; - if (!tkill(pt->tid, sig)) { + if (!__tkill(pt->tid, sig, pt->tib)) { rc = 0; } else { rc = errno; diff --git a/libc/thread/pthread_setcanceltype.c b/libc/thread/pthread_setcanceltype.c index b61b8fcfa..b421dc81c 100644 --- a/libc/thread/pthread_setcanceltype.c +++ b/libc/thread/pthread_setcanceltype.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/dce.h" #include "libc/errno.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" @@ -26,17 +27,22 @@ * * @param type may be one of: * - `PTHREAD_CANCEL_DEFERRED` (default) - * - `PTHREAD_CANCEL_ASYNCHRONOUS` (cray cray) + * - `PTHREAD_CANCEL_ASYNCHRONOUS` * @param oldtype optionally receives old value * @return 0 on success, or errno on error + * @raise ENOTSUP on Windows if asynchronous * @raise EINVAL if `type` has bad value * @see pthread_cancel() for docs */ errno_t pthread_setcanceltype(int type, int *oldtype) { struct PosixThread *pt; switch (type) { - case PTHREAD_CANCEL_DEFERRED: case PTHREAD_CANCEL_ASYNCHRONOUS: + if (IsWindows()) { + return ENOTSUP; + } + // fallthrough + case PTHREAD_CANCEL_DEFERRED: pt = (struct PosixThread *)__get_tls()->tib_pthread; if (oldtype) { if (pt->flags & PT_ASYNC) { diff --git a/libc/thread/sem_timedwait.c b/libc/thread/sem_timedwait.c index c57cf7ebb..ea1f295fa 100644 --- a/libc/thread/sem_timedwait.c +++ b/libc/thread/sem_timedwait.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/struct/timespec.h" #include "libc/calls/struct/timespec.internal.h" #include "libc/errno.h" @@ -42,11 +43,11 @@ static struct timespec *sem_timeout(struct timespec *memory, *memory = *abstime; return memory; } else { - now = _timespec_real(); - if (_timespec_cmp(now, *abstime) > 0) { + now = timespec_real(); + if (timespec_cmp(now, *abstime) > 0) { *memory = (struct timespec){0}; } else { - *memory = _timespec_sub(*abstime, now); + *memory = timespec_sub(*abstime, now); } return memory; } @@ -87,6 +88,7 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) { } } + BEGIN_CANCELLATION_POINT; _unassert(atomic_fetch_add_explicit(&sem->sem_waiters, +1, memory_order_acq_rel) >= 0); pthread_cleanup_push(sem_timedwait_cleanup, sem); @@ -102,7 +104,7 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) { rc = 0; } else if (rc == -ETIMEDOUT) { _npassert(abstime); - if (_timespec_cmp(*abstime, _timespec_real()) <= 0) { + if (timespec_cmp(*abstime, timespec_real()) <= 0) { rc = etimedout(); } else { rc = 0; @@ -122,6 +124,7 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) { memory_order_relaxed))); pthread_cleanup_pop(1); + END_CANCELLATION_POINT; return rc; } diff --git a/libc/thread/wait0.c b/libc/thread/wait0.c index 1d9d34ec2..abacc9a93 100644 --- a/libc/thread/wait0.c +++ b/libc/thread/wait0.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/dce.h" #include "libc/errno.h" #include "libc/intrin/atomic.h" @@ -33,13 +34,17 @@ * * @return 0 on success, or errno on error * @raise ECANCELED if calling thread was cancelled in masked mode + * @cancellationpoint */ errno_t _wait0(const atomic_int *ctid) { - int x, rc; + int x, rc = 0; + BEGIN_CANCELLATION_POINT; while ((x = atomic_load_explicit(ctid, memory_order_acquire))) { if (nsync_futex_wait_(ctid, x, !IsWindows(), 0) == -ECANCELED) { - return ECANCELED; + rc = ECANCELED; + break; } } - return 0; + END_CANCELLATION_POINT; + return rc; } diff --git a/libc/time/localtime.c b/libc/time/localtime.c index b20a3ed93..ceba2eb91 100644 --- a/libc/time/localtime.c +++ b/libc/time/localtime.c @@ -2,6 +2,7 @@ │vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│ ╚─────────────────────────────────────────────────────────────────────────────*/ #define LOCALTIME_IMPLEMENTATION +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/intrin/bits.h" #include "libc/intrin/nopl.internal.h" @@ -365,8 +366,8 @@ union local_storage { format if DOEXTEND. Use *LSP for temporary storage. Return 0 on success, an errno value on failure. */ static int -localtime_tzloadbody(char const *name, struct state *sp, bool doextend, - union local_storage *lsp) +localtime_tzloadbody_(char const *name, struct state *sp, bool doextend, + union local_storage *lsp) { register int i; register int fid; @@ -742,6 +743,17 @@ localtime_tzloadbody(char const *name, struct state *sp, bool doextend, return 0; } +static int /* [jart] pthread cancellation safe */ +localtime_tzloadbody(char const *name, struct state *sp, bool doextend, + union local_storage *lsp) +{ + int rc; + BLOCK_CANCELLATIONS; + rc = localtime_tzloadbody_(name, sp, doextend, lsp); + ALLOW_CANCELLATIONS; + return rc; +} + /* Load tz data from the file named NAME into *SP. Read extended format if DOEXTEND. Return 0 on success, an errno value on failure. */ static int diff --git a/libc/vga/rlinit-vesa.S b/libc/vga/rlinit-vesa.S index fac5ad39b..a9721b770 100644 --- a/libc/vga/rlinit-vesa.S +++ b/libc/vga/rlinit-vesa.S @@ -582,7 +582,6 @@ _rlinit_vesa: mov $'\n',%al jmp .putc .endfn .putnl - .previous str.inform: #define SHGLYPH "\x7f" @@ -628,4 +627,5 @@ str.mode_list_start: .byte PC_VIDEO_BGR555, 16, 5,10, 5, 5, 5, 0 .byte PC_VIDEO_RGBX8888,32, 8, 0, 8, 8, 8,16 .byte PC_VIDEO_BGRX8888,32, 8,16, 8, 8, 8, 0 + .endobj .type_list .type_list_end: diff --git a/libc/zipos/open.c b/libc/zipos/open.c index fa7d2ef29..94414d790 100644 --- a/libc/zipos/open.c +++ b/libc/zipos/open.c @@ -190,7 +190,6 @@ static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, * Loads compressed file from αcτµαlly pδrταblε εxεcµταblε object store. * * @param uri is obtained via __zipos_parseuri() - * @cancellationpoint * @asyncsignalsafe * @threadsafe */ diff --git a/net/turfwar/blackholed.c b/net/turfwar/blackholed.c index a5859a682..91043e1c4 100644 --- a/net/turfwar/blackholed.c +++ b/net/turfwar/blackholed.c @@ -314,8 +314,7 @@ void UseLog(void) { } void UninterruptibleSleep(int ms) { - struct timespec ts = - _timespec_add(_timespec_real(), _timespec_frommillis(ms)); + struct timespec ts = timespec_add(timespec_real(), timespec_frommillis(ms)); while (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, 0)) errno = 0; } diff --git a/net/turfwar/turfwar.c b/net/turfwar/turfwar.c index 5947d8d44..07bc972f2 100644 --- a/net/turfwar/turfwar.c +++ b/net/turfwar/turfwar.c @@ -373,7 +373,7 @@ ssize_t Write(int fd, const char *s) { // turns relative timeout into an absolute timeout struct timespec WaitFor(int millis) { - return _timespec_add(_timespec_real(), _timespec_frommillis(millis)); + return timespec_add(timespec_real(), timespec_frommillis(millis)); } // helper functions for check macro implementation @@ -478,7 +478,7 @@ char *FormatUnixHttpDateTime(char *s, int64_t t) { void UpdateNow(void) { int64_t secs; struct tm tm; - g_nowish.ts = _timespec_real(); + g_nowish.ts = timespec_real(); secs = g_nowish.ts.tv_sec; gmtime_r(&secs, &tm); //!//!//!//!//!//!//!//!//!//!//!//!//!/ @@ -703,7 +703,7 @@ void ServeStatusz(int client, char *outbuf) { char *p; struct rusage ru; struct timespec now; - now = _timespec_real(); + now = timespec_real(); p = outbuf; p = stpcpy(outbuf, "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" @@ -711,7 +711,7 @@ void ServeStatusz(int client, char *outbuf) { "Connection: close\r\n" "\r\n"); p = Statusz(p, "qps", - g_messages / MAX(1, _timespec_sub(now, g_started).tv_sec)); + g_messages / MAX(1, timespec_sub(now, g_started).tv_sec)); p = Statusz(p, "started", g_started.tv_sec); p = Statusz(p, "now", now.tv_sec); p = Statusz(p, "messages", g_messages); @@ -859,7 +859,7 @@ void *HttpWorker(void *arg) { AllowSigusr1(); DestroyHttpMessage(msg); InitHttpMessage(msg, kHttpRequest); - g_worker[id].startread = _timespec_real(); + g_worker[id].startread = timespec_real(); if ((got = read(client.sock, inbuf, INBUF_SIZE)) <= 0) { ++g_readfails; break; @@ -1079,10 +1079,9 @@ void *HttpWorker(void *arg) { if (ipv6) goto Ipv6Warning; struct Claim v = {.ip = ip, .created = g_nowish.ts.tv_sec}; if (GetNick(inbuf, msg, &v)) { - if (AddClaim( - &g_claims, &v, - _timespec_add(_timespec_real(), - _timespec_frommillis(CLAIM_DEADLINE_MS)))) { + if (AddClaim(&g_claims, &v, + timespec_add(timespec_real(), + timespec_frommillis(CLAIM_DEADLINE_MS)))) { ++g_claimsenqueued; DEBUG("%s claimed by %s\n", ipbuf, v.name); if (HasHeader(kHttpAccept) && @@ -1298,7 +1297,7 @@ bool ReloadAsset(struct Asset *a) { struct Data gzip = {0}; CHECK_SYS((fd = open(a->path, O_RDONLY))); CHECK_SYS(fstat(fd, &st)); - if (_timespec_gt(st.st_mtim, a->mtim)) { + if (timespec_cmp(st.st_mtim, a->mtim) > 0) { FormatUnixHttpDateTime(lastmodified, st.st_mtim.tv_sec); CHECK_MEM((data.p = malloc(st.st_size))); CHECK_SYS((rc = read(fd, data.p, st.st_size))); @@ -1439,7 +1438,7 @@ bool GenerateScore(struct Asset *out, long secs, long cash) { DEBUG("GenerateScore %ld\n", secs); a.type = "application/json"; a.cash = cash; - a.mtim = _timespec_real(); + a.mtim = timespec_real(); FormatUnixHttpDateTime(a.lastmodified, a.mtim.tv_sec); CHECK_SYS(appends(&a.data.p, "{\n")); CHECK_SYS(appendf(&a.data.p, "\"now\":[%ld,%ld],\n", a.mtim.tv_sec, @@ -1610,7 +1609,7 @@ StartOver: "LIMIT 50")); do { // regenerate json - t.mtim = _timespec_real(); + t.mtim = timespec_real(); FormatUnixHttpDateTime(t.lastmodified, t.mtim.tv_sec); CHECK_SYS(appends(&t.data.p, "{\n")); CHECK_SYS(appendf(&t.data.p, "\"now\":[%ld,%ld],\n", t.mtim.tv_sec, @@ -1740,7 +1739,7 @@ void *NowWorker(void *arg) { UpdateNow(); OnlyRunOnCpu(0); nsync_counter_add(g_ready, -1); // #8 - for (struct timespec ts = {_timespec_real().tv_sec};; ++ts.tv_sec) { + for (struct timespec ts = {timespec_real().tv_sec};; ++ts.tv_sec) { if (!nsync_note_wait(g_shutdown[1], ts)) { UpdateNow(); } else { @@ -1758,8 +1757,8 @@ void *ReplenishWorker(void *arg) { LOG("%P Replenisher started\n"); UpdateNow(); OnlyRunOnCpu(0); - for (struct timespec ts = _timespec_real();; - ts = _timespec_add(ts, _timespec_frommillis(TB_INTERVAL))) { + for (struct timespec ts = timespec_real();; + ts = timespec_add(ts, timespec_frommillis(TB_INTERVAL))) { if (!nsync_note_wait(g_shutdown[1], ts)) { ReplenishTokens(g_tok.w, TB_WORDS); } else { @@ -1780,12 +1779,12 @@ void Meltdown(void) { ++g_meltdowns; LOG("Panicking because %d out of %d workers is connected\n", g_connections, g_workers); - now = _timespec_real(); + now = timespec_real(); for (marks = i = 0; i < g_workers; ++i) { if (g_worker[i].connected && (g_worker[i].msgcount > PANIC_MSGS || - _timespec_gte(_timespec_sub(now, g_worker[i].startread), - _timespec_frommillis(MELTALIVE_MS)))) { + timespec_cmp(timespec_sub(now, g_worker[i].startread), + timespec_frommillis(MELTALIVE_MS)) >= 0)) { pthread_kill(g_worker[i].th, SIGUSR1); ++marks; } @@ -1884,7 +1883,7 @@ int main(int argc, char *argv[]) { memset(g_tok.b, 127, TB_BYTES); // server lifecycle locks - g_started = _timespec_real(); + g_started = timespec_real(); for (int i = 0; i < ARRAYLEN(g_shutdown); ++i) { g_shutdown[i] = nsync_note_new(0, nsync_time_no_deadline); } diff --git a/test/libc/calls/_timespec_test.c b/test/libc/calls/_timespec_test.c index 5da410048..7869594bf 100644 --- a/test/libc/calls/_timespec_test.c +++ b/test/libc/calls/_timespec_test.c @@ -23,73 +23,65 @@ #include "libc/testlib/testlib.h" #include "third_party/nsync/time.h" -TEST(_timespec_gte, test) { - EXPECT_FALSE(_timespec_gte((struct timespec){1}, (struct timespec){2})); - EXPECT_TRUE(_timespec_gte((struct timespec){2}, (struct timespec){2})); - EXPECT_TRUE(_timespec_gte((struct timespec){2}, (struct timespec){1})); - EXPECT_FALSE(_timespec_gte((struct timespec){2}, (struct timespec){2, 1})); - EXPECT_TRUE(_timespec_gte((struct timespec){2, 1}, (struct timespec){2})); -} - -TEST(_timespec_sub, test) { +TEST(timespec_sub, test) { EXPECT_TRUE( - _timespec_eq((struct timespec){-1}, - _timespec_sub((struct timespec){1}, (struct timespec){2}))); + !timespec_cmp((struct timespec){-1}, + timespec_sub((struct timespec){1}, (struct timespec){2}))); EXPECT_TRUE( - _timespec_eq((struct timespec){1}, - _timespec_sub((struct timespec){2}, (struct timespec){1}))); - EXPECT_TRUE(_timespec_eq( + !timespec_cmp((struct timespec){1}, + timespec_sub((struct timespec){2}, (struct timespec){1}))); + EXPECT_TRUE(!timespec_cmp( (struct timespec){1, 1}, - _timespec_sub((struct timespec){2, 2}, (struct timespec){1, 1}))); - EXPECT_TRUE(_timespec_eq( + timespec_sub((struct timespec){2, 2}, (struct timespec){1, 1}))); + EXPECT_TRUE(!timespec_cmp( (struct timespec){0, 999999999}, - _timespec_sub((struct timespec){2, 1}, (struct timespec){1, 2}))); + timespec_sub((struct timespec){2, 1}, (struct timespec){1, 2}))); } -TEST(_timespec_frommillis, test) { +TEST(timespec_frommillis, test) { EXPECT_TRUE( - _timespec_eq((struct timespec){0, 1000000}, _timespec_frommillis(1))); + !timespec_cmp((struct timespec){0, 1000000}, timespec_frommillis(1))); EXPECT_TRUE( - _timespec_eq((struct timespec){0, 2000000}, _timespec_frommillis(2))); - EXPECT_TRUE(_timespec_eq((struct timespec){1}, _timespec_frommillis(1000))); + !timespec_cmp((struct timespec){0, 2000000}, timespec_frommillis(2))); + EXPECT_TRUE(!timespec_cmp((struct timespec){1}, timespec_frommillis(1000))); } -TEST(_timespec_frommicros, test) { +TEST(timespec_frommicros, test) { EXPECT_TRUE( - _timespec_eq((struct timespec){0, 1000}, _timespec_frommicros(1))); + !timespec_cmp((struct timespec){0, 1000}, timespec_frommicros(1))); EXPECT_TRUE( - _timespec_eq((struct timespec){0, 2000}, _timespec_frommicros(2))); + !timespec_cmp((struct timespec){0, 2000}, timespec_frommicros(2))); EXPECT_TRUE( - _timespec_eq((struct timespec){1}, _timespec_frommicros(1000000))); - EXPECT_TRUE(_timespec_eq((struct timespec){2, 123000}, - _timespec_frommicros(2000123))); + !timespec_cmp((struct timespec){1}, timespec_frommicros(1000000))); + EXPECT_TRUE(!timespec_cmp((struct timespec){2, 123000}, + timespec_frommicros(2000123))); } -TEST(_timespec_tomillis, test) { - EXPECT_EQ(0, _timespec_tomillis((struct timespec){0, 0})); - EXPECT_EQ(1, _timespec_tomillis((struct timespec){0, 1})); - EXPECT_EQ(1, _timespec_tomillis((struct timespec){0, 999999})); - EXPECT_EQ(1, _timespec_tomillis((struct timespec){0, 1000000})); - EXPECT_EQ(1000, _timespec_tomillis((struct timespec){0, 999999999})); - EXPECT_EQ(2123, _timespec_tomillis((struct timespec){2, 123000000})); - EXPECT_EQ(INT64_MAX, _timespec_tomillis((struct timespec){INT64_MAX, 0})); - EXPECT_EQ(INT64_MIN, _timespec_tomillis((struct timespec){INT64_MIN, 0})); - EXPECT_EQ(INT64_MAX, _timespec_tomillis( +TEST(timespec_tomillis, test) { + EXPECT_EQ(0, timespec_tomillis((struct timespec){0, 0})); + EXPECT_EQ(1, timespec_tomillis((struct timespec){0, 1})); + EXPECT_EQ(1, timespec_tomillis((struct timespec){0, 999999})); + EXPECT_EQ(1, timespec_tomillis((struct timespec){0, 1000000})); + EXPECT_EQ(1000, timespec_tomillis((struct timespec){0, 999999999})); + EXPECT_EQ(2123, timespec_tomillis((struct timespec){2, 123000000})); + EXPECT_EQ(INT64_MAX, timespec_tomillis((struct timespec){INT64_MAX, 0})); + EXPECT_EQ(INT64_MIN, timespec_tomillis((struct timespec){INT64_MIN, 0})); + EXPECT_EQ(INT64_MAX, timespec_tomillis( (struct timespec){0x7fffffffffffffff, 999999999})); } -TEST(_timespec_tomicros, test) { - EXPECT_EQ(0, _timespec_tomicros((struct timespec){0, 0})); - EXPECT_EQ(1, _timespec_tomicros((struct timespec){0, 1})); - EXPECT_EQ(2000123, _timespec_tomicros((struct timespec){2, 123000})); - EXPECT_EQ(INT64_MAX, _timespec_tomicros((struct timespec){INT64_MAX, 0})); - EXPECT_EQ(INT64_MIN, _timespec_tomicros((struct timespec){INT64_MIN, 0})); +TEST(timespec_tomicros, test) { + EXPECT_EQ(0, timespec_tomicros((struct timespec){0, 0})); + EXPECT_EQ(1, timespec_tomicros((struct timespec){0, 1})); + EXPECT_EQ(2000123, timespec_tomicros((struct timespec){2, 123000})); + EXPECT_EQ(INT64_MAX, timespec_tomicros((struct timespec){INT64_MAX, 0})); + EXPECT_EQ(INT64_MIN, timespec_tomicros((struct timespec){INT64_MIN, 0})); } -TEST(_timespec_tonanos, test) { - EXPECT_EQ(2000123000, _timespec_tonanos((struct timespec){2, 123000})); - EXPECT_EQ(INT64_MAX, _timespec_tonanos((struct timespec){INT64_MAX, 0})); - EXPECT_EQ(INT64_MIN, _timespec_tonanos((struct timespec){INT64_MIN, 0})); +TEST(timespec_tonanos, test) { + EXPECT_EQ(2000123000, timespec_tonanos((struct timespec){2, 123000})); + EXPECT_EQ(INT64_MAX, timespec_tonanos((struct timespec){INT64_MAX, 0})); + EXPECT_EQ(INT64_MIN, timespec_tonanos((struct timespec){INT64_MIN, 0})); } static long mod(long x, long y) { @@ -97,11 +89,11 @@ static long mod(long x, long y) { return x - y * (x / y - (x % y && (x ^ y) < 0)); } -TEST(_timespec_sub, math) { +TEST(timespec_sub, math) { for (int i = 0; i < 1000; ++i) { struct timespec x = {mod(lemur64(), 10), mod(lemur64(), 10)}; struct timespec y = {mod(lemur64(), 10), mod(lemur64(), 10)}; - struct timespec z = _timespec_add(_timespec_sub(x, y), y); - EXPECT_TRUE(_timespec_eq(x, _timespec_add(_timespec_sub(x, y), y))); + struct timespec z = timespec_add(timespec_sub(x, y), y); + EXPECT_TRUE(!timespec_cmp(x, timespec_add(timespec_sub(x, y), y))); } } diff --git a/test/libc/calls/clock_gettime_test.c b/test/libc/calls/clock_gettime_test.c index 736548d83..947449ad2 100644 --- a/test/libc/calls/clock_gettime_test.c +++ b/test/libc/calls/clock_gettime_test.c @@ -57,7 +57,7 @@ BENCH(clock_gettime, bench) { EZBENCH2("nowl", donothing, nowl()); EZBENCH2("rdtsc", donothing, rdtsc()); EZBENCH2("gettimeofday", donothing, gettimeofday(&tv, 0)); - EZBENCH2("_timespec_real", donothing, _timespec_real()); + EZBENCH2("timespec_real", donothing, timespec_real()); EZBENCH2("clock_gettime 0", donothing, clock_gettime(CLOCK_REALTIME_FAST, &ts)); EZBENCH2("clock_gettime 1", donothing, diff --git a/test/libc/calls/lock_ofd_test.c b/test/libc/calls/lock_ofd_test.c index 05eda094b..67ef5eac6 100644 --- a/test/libc/calls/lock_ofd_test.c +++ b/test/libc/calls/lock_ofd_test.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/struct/flock.h" #include "libc/calls/syscall-sysv.internal.h" @@ -50,7 +51,9 @@ bool SupportsOfdLocks(void) { // getrandom() was introduced in linux 3.17 // testing for getrandom() should be a sure thing w/o creating an fd e = errno; + BLOCK_CANCELLATIONS; r = !sys_getrandom(0, 0, 0); + ALLOW_CANCELLATIONS; errno = e; return r; } diff --git a/test/libc/intrin/describesigset_test.c b/test/libc/intrin/describesigset_test.c index 567734487..277f180a6 100644 --- a/test/libc/intrin/describesigset_test.c +++ b/test/libc/intrin/describesigset_test.c @@ -26,10 +26,12 @@ TEST(DescribeSigset, full) { sigset_t ss; sigfillset(&ss); - if (IsXnu() || IsOpenbsd()) { - EXPECT_STREQ("~{ABRT,CANCEL,KILL,STOP}", DescribeSigset(0, &ss)); + if (IsXnu()) { + EXPECT_STREQ("~{ABRT,THR,KILL,STOP}", DescribeSigset(0, &ss)); + } else if (IsOpenbsd()) { + EXPECT_STREQ("~{ABRT,KILL,STOP,THR}", DescribeSigset(0, &ss)); } else { - EXPECT_STREQ("~{ABRT,KILL,STOP,CANCEL}", DescribeSigset(0, &ss)); + EXPECT_STREQ("~{ABRT,KILL,STOP,THR}", DescribeSigset(0, &ss)); } } @@ -46,11 +48,11 @@ TEST(DescribeSigset, absent) { sigfillset(&ss); sigdelset(&ss, SIGINT); sigdelset(&ss, SIGUSR1); - if (IsOpenbsd() || IsXnu()) { - EXPECT_STREQ("~{INT,ABRT,CANCEL,KILL,STOP,USR1}", DescribeSigset(0, &ss)); - } else if (IsFreebsd() || IsNetbsd()) { - EXPECT_STREQ("~{INT,ABRT,KILL,STOP,USR1,CANCEL}", DescribeSigset(0, &ss)); + if (IsXnu()) { + EXPECT_STREQ("~{INT,ABRT,THR,KILL,STOP,USR1}", DescribeSigset(0, &ss)); + } else if (IsBsd()) { + EXPECT_STREQ("~{INT,ABRT,KILL,STOP,USR1,THR}", DescribeSigset(0, &ss)); } else { - EXPECT_STREQ("~{INT,ABRT,KILL,USR1,STOP,CANCEL}", DescribeSigset(0, &ss)); + EXPECT_STREQ("~{INT,ABRT,KILL,USR1,STOP,THR}", DescribeSigset(0, &ss)); } } diff --git a/test/libc/intrin/lock_test.c b/test/libc/intrin/lock_test.c index 3df9b7475..d2de39a3f 100644 --- a/test/libc/intrin/lock_test.c +++ b/test/libc/intrin/lock_test.c @@ -131,7 +131,7 @@ void TestContendedLock(const char *name, int kind) { _Exit(1); } while (!atomic_load(&ready)) donothing; - t1 = _timespec_real(); + t1 = timespec_real(); for (i = 0; i < n; ++i) { ASSERT_EQ(0, pthread_mutex_lock(&mu)); x = atomic_load_explicit(&counter, memory_order_relaxed); @@ -139,13 +139,13 @@ void TestContendedLock(const char *name, int kind) { ASSERT_EQ(x - 1, atomic_load_explicit(&counter, memory_order_relaxed)); ASSERT_EQ(0, pthread_mutex_unlock(&mu)); } - t2 = _timespec_real(); + t2 = timespec_real(); while (tib.tib_tid) donothing; ASSERT_EQ(1, atomic_load(&success)); ASSERT_EQ(0, atomic_load(&counter)); _freestack(stk); ASSERT_EQ(0, pthread_mutex_destroy(&mu)); - ns = time2dbl(_timespec_sub(t2, t1)) / n; + ns = time2dbl(timespec_sub(t2, t1)) / n; kprintf("%s contended took %s\n", name, time2str(ns)); } @@ -159,14 +159,14 @@ void TestUncontendedLock(const char *name, int kind) { pthread_mutexattr_settype(&attr, kind); pthread_mutex_init(&lock, &attr); pthread_mutexattr_destroy(&attr); - t1 = _timespec_real(); + t1 = timespec_real(); for (i = 0; i < n; ++i) { pthread_mutex_lock(&lock); pthread_mutex_unlock(&lock); } - t2 = _timespec_real(); + t2 = timespec_real(); pthread_mutex_destroy(&lock); - ns = time2dbl(_timespec_sub(t2, t1)) / n; + ns = time2dbl(timespec_sub(t2, t1)) / n; kprintf("%s took %s\n", name, time2str(ns)); } diff --git a/test/libc/intrin/lockscale_test.c b/test/libc/intrin/lockscale_test.c index dbb465c81..5859e14a1 100644 --- a/test/libc/intrin/lockscale_test.c +++ b/test/libc/intrin/lockscale_test.c @@ -67,7 +67,7 @@ void *Waiter(void *arg) { BENCH(lock, scalability) { int i; struct timespec t1, t2; - t1 = _timespec_real(); + t1 = timespec_real(); pthread_mutex_init(&lock, 0); pthread_barrier_init(&barrier, 0, WAITERS + 1); for (i = 0; i < WAITERS; ++i) { @@ -79,8 +79,8 @@ BENCH(lock, scalability) { } pthread_barrier_destroy(&barrier); pthread_mutex_destroy(&lock); - t2 = _timespec_real(); + t2 = timespec_real(); printf("consumed %10g seconds real time and %10g seconds cpu time\n", - _timespec_tonanos(_timespec_sub(t2, t1)) / 1e9, + timespec_tonanos(timespec_sub(t2, t1)) / 1e9, (double)clock() / CLOCKS_PER_SEC); } diff --git a/test/libc/intrin/strsignal_r_test.c b/test/libc/intrin/strsignal_r_test.c index e0636cdb0..412a89c91 100644 --- a/test/libc/intrin/strsignal_r_test.c +++ b/test/libc/intrin/strsignal_r_test.c @@ -34,5 +34,6 @@ TEST(strsignal, test) { TEST(strsignal, realtime) { if (!SIGRTMIN) return; + EXPECT_STREQ("SIGTHR", strsignal(SIGTHR)); ASSERT_STREQ("SIGRTMIN+1", strsignal(SIGRTMIN + 1)); } diff --git a/test/libc/mem/malloc_test.c b/test/libc/mem/malloc_test.c index a1608c713..75037a557 100644 --- a/test/libc/mem/malloc_test.c +++ b/test/libc/mem/malloc_test.c @@ -171,16 +171,16 @@ BENCH(malloc, torture) { printf("\nmalloc torture test w/ %d threads and %d iterations\n", n, ITERATIONS); SPAWN(fork); - struct timespec t1 = _timespec_real(); + struct timespec t1 = timespec_real(); for (i = 0; i < n; ++i) { ASSERT_EQ(0, pthread_create(t + i, 0, Worker, 0)); } for (i = 0; i < n; ++i) { ASSERT_EQ(0, pthread_join(t[i], 0)); } - struct timespec t2 = _timespec_real(); + struct timespec t2 = timespec_real(); printf("consumed %g wall and %g cpu seconds\n", - _timespec_tomicros(_timespec_sub(t2, t1)) * 1e-6, + timespec_tomicros(timespec_sub(t2, t1)) * 1e-6, (double)clock() / CLOCKS_PER_SEC); EXITS(0); } diff --git a/test/libc/stdio/getentropy_test.c b/test/libc/stdio/getentropy_test.c new file mode 100644 index 000000000..4e324f0fc --- /dev/null +++ b/test/libc/stdio/getentropy_test.c @@ -0,0 +1,95 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 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/atomic.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/mem/gc.h" +#include "libc/mem/mem.h" +#include "libc/runtime/runtime.h" +#include "libc/stdio/rand.h" +#include "libc/stdio/stdio.h" +#include "libc/str/tab.internal.h" +#include "libc/sysv/consts/sig.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" + +// TODO(jart): Why can EINTR happen on Windows? + +atomic_int done; +atomic_int ready; +pthread_t parent; +atomic_int gotsome; + +void OnSig(int sig) { + ++gotsome; +} + +void *TortureWorker(void *arg) { + sigset_t ss; + sigfillset(&ss); + ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &ss, 0)); + ready = true; + while (!done) { + if (!IsWindows()) pthread_kill(parent, SIGUSR1); + usleep(3); + if (!IsWindows()) pthread_kill(parent, SIGUSR2); + usleep(3); + } + return 0; +} + +TEST(getentropy, test) { + pthread_t child; + double e, w = 7.7; + struct sigaction sa; + int i, j, k, n = 999; + char *buf = _gc(calloc(1, n)); + sa.sa_flags = 0; + sa.sa_handler = OnSig; + sigemptyset(&sa.sa_mask); + ASSERT_SYS(0, 0, sigaction(SIGUSR1, &sa, 0)); + ASSERT_SYS(0, 0, sigaction(SIGUSR2, &sa, 0)); + parent = pthread_self(); + ASSERT_EQ(0, pthread_create(&child, 0, TortureWorker, 0)); + while (!ready) pthread_yield(); + for (k = 0; k < 200; ++k) { + ASSERT_SYS(0, 0, getrandom(0, 0, 0)); + ASSERT_SYS(0, n, getrandom(buf, n, 0)); + ASSERT_SYS(EFAULT, -1, getrandom(0, n, 0)); + ASSERT_SYS(EINVAL, -1, getrandom(buf, n, -1)); + if ((e = MeasureEntropy(buf, n)) < w) { + fprintf(stderr, "error: entropy suspect! got %g but want >=%g\n", e, w); + for (i = 0; i < n;) { + if (!(i % 16)) fprintf(stderr, "%6x ", i); + fprintf(stderr, "%lc", kCp437[buf[i] & 255]); + if (!(++i % 16)) fprintf(stderr, "\n"); + } + fprintf(stderr, "\n"); + done = true; + pthread_join(child, 0); + exit(1); + } + } + done = true; + ASSERT_EQ(0, pthread_join(child, 0)); + if (!IsWindows()) ASSERT_GT(gotsome, 0); +} diff --git a/test/libc/stdio/getrandom_test.c b/test/libc/stdio/getrandom_test.c index 3f8f96001..430fa2dc0 100644 --- a/test/libc/stdio/getrandom_test.c +++ b/test/libc/stdio/getrandom_test.c @@ -17,19 +17,49 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "ape/sections.internal.h" +#include "libc/atomic.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" +#include "libc/calls/struct/sigset.h" #include "libc/errno.h" #include "libc/intrin/bits.h" #include "libc/log/check.h" #include "libc/math.h" +#include "libc/mem/gc.h" +#include "libc/mem/mem.h" #include "libc/nexgen32e/x86feature.h" #include "libc/runtime/runtime.h" #include "libc/stdio/lcg.internal.h" #include "libc/stdio/rand.h" #include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/str/tab.internal.h" #include "libc/sysv/consts/grnd.h" +#include "libc/sysv/consts/sig.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" + +TEST(getrandom, test) { + double e, w = 7.7; + int i, j, n = 999; + char *buf = _gc(calloc(1, n)); + ASSERT_SYS(0, 0, getrandom(0, 0, 0)); + ASSERT_SYS(0, n, getrandom(buf, n, 0)); + ASSERT_SYS(EFAULT, -1, getrandom(0, n, 0)); + ASSERT_SYS(EINVAL, -1, getrandom(buf, n, -1)); + if ((e = MeasureEntropy(buf, n)) < w) { + fprintf(stderr, "error: entropy is suspect! got %g but want >=%g\n", e, w); + for (i = 0; i < n;) { + if (!(i % 16)) fprintf(stderr, "%6x ", i); + fprintf(stderr, "%lc", kCp437[buf[i] & 255]); + if (!(++i % 16)) fprintf(stderr, "\n"); + } + fprintf(stderr, "\n"); + exit(1); + } +} /* JustReturnZero */ /* entropy: 0 */ diff --git a/test/libc/thread/pthread_cancel_test.c b/test/libc/thread/pthread_cancel_test.c index e59f99e4c..3d9db4891 100644 --- a/test/libc/thread/pthread_cancel_test.c +++ b/test/libc/thread/pthread_cancel_test.c @@ -21,6 +21,9 @@ #include "libc/calls/calls.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/mem/gc.h" +#include "libc/mem/mem.h" +#include "libc/nexgen32e/nexgen32e.h" #include "libc/runtime/runtime.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" @@ -62,27 +65,6 @@ TEST(pthread_cancel, self_deferred_waitsForCancellationPoint) { ASSERT_SYS(0, 0, close(pfds[0])); } -void *CancelSelfWorkerAsync(void *arg) { - char buf[8]; - pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0); - pthread_cleanup_push(OnCleanup, 0); - pthread_cancel(pthread_self()); - pthread_cleanup_pop(0); - return 0; -} - -TEST(pthread_cancel, self_asynchronous_takesImmediateEffect) { - void *rc; - pthread_t th; - ASSERT_SYS(0, 0, pipe(pfds)); - ASSERT_EQ(0, pthread_create(&th, 0, CancelSelfWorkerAsync, 0)); - ASSERT_EQ(0, pthread_join(th, &rc)); - ASSERT_EQ(PTHREAD_CANCELED, rc); - ASSERT_TRUE(gotcleanup); - ASSERT_SYS(0, 0, close(pfds[1])); - ASSERT_SYS(0, 0, close(pfds[0])); -} - void *Worker(void *arg) { int n; char buf[8]; @@ -212,3 +194,84 @@ TEST(pthread_cancel, condDeferredWaitDelayed) { ASSERT_EQ(0, pthread_mutex_trylock(&mu)); ASSERT_EQ(0, pthread_mutex_unlock(&mu)); } + +char *wouldleak; +pthread_key_t key; +bool key_destructor_was_run; +atomic_bool is_in_infinite_loop; + +void KeyDestructor(void *arg) { + CheckStackIsAligned(); + ASSERT_EQ(31337, (intptr_t)arg); + key_destructor_was_run = true; +} + +void TortureStack(void) { + asm("sub\t$4,%rsp\n\t" + "pause\n\t" + "sub\t$2,%rsp\n\t" + "pause\n\t" + "add\t$6,%rsp"); +} + +void *CpuBoundWorker(void *arg) { + char *volatile wontleak1; + char *volatile wontleak2; + CheckStackIsAligned(); + wouldleak = malloc(123); + wontleak1 = malloc(123); + pthread_cleanup_push(free, wontleak1); + wontleak2 = _gc(malloc(123)); + ASSERT_EQ(0, pthread_setspecific(key, (void *)31337)); + ASSERT_EQ(0, pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0)); + for (;;) { + TortureStack(); + is_in_infinite_loop = true; + } + pthread_cleanup_pop(1); + free(wouldleak); +} + +TEST(pthread_cancel, async) { + void *rc; + pthread_t th; + ASSERT_EQ(0, pthread_key_create(&key, KeyDestructor)); + if (IsWindows()) { + // asynchronous cancellations aren't possible on windows yet + ASSERT_EQ(ENOTSUP, pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0)); + return; + } + wouldleak = NULL; + is_in_infinite_loop = false; + key_destructor_was_run = false; + ASSERT_EQ(0, pthread_create(&th, 0, CpuBoundWorker, 0)); + while (!is_in_infinite_loop) pthread_yield(); + ASSERT_EQ(0, pthread_cancel(th)); + ASSERT_EQ(0, pthread_join(th, &rc)); + ASSERT_EQ(PTHREAD_CANCELED, rc); + ASSERT_TRUE(key_destructor_was_run); + ASSERT_EQ(0, pthread_key_delete(key)); + free(wouldleak); +} + +void *CancelSelfWorkerAsync(void *arg) { + char buf[8]; + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0); + pthread_cleanup_push(OnCleanup, 0); + pthread_cancel(pthread_self()); + pthread_cleanup_pop(0); + return 0; +} + +TEST(pthread_cancel, self_asynchronous_takesImmediateEffect) { + void *rc; + pthread_t th; + if (IsWindows()) return; + ASSERT_SYS(0, 0, pipe(pfds)); + ASSERT_EQ(0, pthread_create(&th, 0, CancelSelfWorkerAsync, 0)); + ASSERT_EQ(0, pthread_join(th, &rc)); + ASSERT_EQ(PTHREAD_CANCELED, rc); + ASSERT_TRUE(gotcleanup); + ASSERT_SYS(0, 0, close(pfds[1])); + ASSERT_SYS(0, 0, close(pfds[0])); +} diff --git a/test/libc/thread/sem_open_test.c b/test/libc/thread/sem_open_test.c index 958fa55e0..8a86a39a0 100644 --- a/test/libc/thread/sem_open_test.c +++ b/test/libc/thread/sem_open_test.c @@ -103,7 +103,7 @@ TEST(sem_open, test) { TEST(sem_close, withUnnamedSemaphore_isUndefinedBehavior) { if (!IsModeDbg()) return; sem_t sem; - ASSERT_SYS(0, 0, sem_init(&sem, 1, 0)); + ASSERT_SYS(0, 0, sem_init(&sem, 0, 0)); SPAWN(fork); IgnoreStderr(); sem_close(&sem); diff --git a/test/net/http/tokenbucket_test.c b/test/net/http/tokenbucket_test.c index cd177151e..3cb5b197f 100644 --- a/test/net/http/tokenbucket_test.c +++ b/test/net/http/tokenbucket_test.c @@ -82,18 +82,18 @@ BENCH(tokenbucket, bench) { NaiveReplenishTokens(tok.b, TB_BYTES); clock_gettime(0, &t2); kprintf("NaiveReplenishTokens took %'ld us\n", - _timespec_tomicros(_timespec_sub(t2, t1))); + timespec_tomicros(timespec_sub(t2, t1))); clock_gettime(0, &t1); ReplenishTokens(tok.w, TB_WORDS); clock_gettime(0, &t2); kprintf("ReplenishTokens empty took %'ld us\n", - _timespec_tomicros(_timespec_sub(t2, t1))); + timespec_tomicros(timespec_sub(t2, t1))); memset(tok.b, 127, TB_BYTES); clock_gettime(0, &t1); ReplenishTokens(tok.w, TB_WORDS); clock_gettime(0, &t2); kprintf("ReplenishTokens full took %'ld us\n", - _timespec_tomicros(_timespec_sub(t2, t1))); + timespec_tomicros(timespec_sub(t2, t1))); } diff --git a/third_party/lua/lunix.c b/third_party/lua/lunix.c index 8173b16c1..5973ca113 100644 --- a/third_party/lua/lunix.c +++ b/third_party/lua/lunix.c @@ -19,6 +19,7 @@ #include "libc/assert.h" #include "libc/atomic.h" #include "libc/calls/calls.h" +#include "libc/calls/cp.internal.h" #include "libc/calls/ioctl.h" #include "libc/calls/makedev.h" #include "libc/calls/pledge.h" @@ -1549,7 +1550,7 @@ static int LuaUnixPoll(lua_State *L) { int i, fd, events, olderr = errno; luaL_checktype(L, 1, LUA_TTABLE); if (!lua_isnoneornil(L, 2)) { - ts = _timespec_frommillis(luaL_checkinteger(L, 2)); + ts = timespec_frommillis(luaL_checkinteger(L, 2)); tsp = &ts; } else { tsp = 0; @@ -2847,17 +2848,19 @@ static int LuaUnixMemoryWait(lua_State *L) { ts.tv_sec = luaL_checkinteger(L, 4); ts.tv_nsec = luaL_optinteger(L, 5, 0); if (!FUTEX_TIMEOUT_IS_ABSOLUTE) { - now = _timespec_real(); - if (_timespec_gt(now, ts)) { + now = timespec_real(); + if (timespec_cmp(now, ts) > 0) { ts = (struct timespec){0}; } else { - ts = _timespec_sub(ts, now); + ts = timespec_sub(ts, now); } } deadline = &ts; } + BEGIN_CANCELLATION_POINT; rc = nsync_futex_wait_((atomic_int *)GetWord(L), expect, PTHREAD_PROCESS_SHARED, deadline); + END_CANCELLATION_POINT; if (rc < 0) errno = -rc, rc = -1; return SysretInteger(L, "futex_wait", olderr, rc); } diff --git a/third_party/nsync/README.cosmo b/third_party/nsync/README.cosmo index 159da96ef..c327a93f0 100644 --- a/third_party/nsync/README.cosmo +++ b/third_party/nsync/README.cosmo @@ -18,3 +18,4 @@ LOCAL CHANGES - nsync_malloc_() is implemented as kmalloc() - nsync_mu_semaphore uses Cosmopolitan Futexes - block pthread cancellations in nsync_mu_lock_slow_ + - support posix thread cancellations in nsync_cv_wait diff --git a/third_party/nsync/compat.S b/third_party/nsync/compat.S index 98bf9c00d..35f8d07a3 100644 --- a/third_party/nsync/compat.S +++ b/third_party/nsync/compat.S @@ -19,33 +19,33 @@ #include "libc/macros.internal.h" nsync_time_now: - jmp _timespec_real + jmp timespec_real .endfn nsync_time_now,globl nsync_time_add: - jmp _timespec_add + jmp timespec_add .endfn nsync_time_add,globl nsync_time_sub: - jmp _timespec_sub + jmp timespec_sub .endfn nsync_time_sub,globl nsync_time_cmp: - jmp _timespec_cmp + jmp timespec_cmp .endfn nsync_time_cmp,globl nsync_time_ms: - jmp _timespec_frommillis + jmp timespec_frommillis .endfn nsync_time_ms,globl nsync_time_us: - jmp _timespec_frommicros + jmp timespec_frommicros .endfn nsync_time_us,globl nsync_time_sleep: - jmp _timespec_sleep + jmp timespec_sleep .endfn nsync_time_us,globl nsync_time_sleep_until: - jmp _timespec_sleep_until + jmp timespec_sleep_until .endfn nsync_time_us,globl diff --git a/third_party/nsync/cv.h b/third_party/nsync/cv.h index bd6b5ad91..d7456cbde 100644 --- a/third_party/nsync/cv.h +++ b/third_party/nsync/cv.h @@ -114,8 +114,10 @@ void nsync_cv_broadcast(nsync_cv *cv); return. Equivalent to a call to nsync_mu_wait_with_deadline() with abs_deadline==nsync_time_no_deadline, and cancel_note==NULL. Callers should use nsync_cv_wait() in a loop, as with all standard Mesa-style - condition variables. See examples above. */ -void nsync_cv_wait(nsync_cv *cv, nsync_mu *mu); + condition variables. See examples above. Returns 0 normally, otherwise + ECANCELED may be returned if calling POSIX thread is cancelled only when + the PTHREAD_CANCEL_MASKED mode is in play. */ +int nsync_cv_wait(nsync_cv *cv, nsync_mu *mu); /* Atomically release "mu" (which must be held on entry) and block the calling thread on *cv. It then waits until awakened by a call to @@ -124,9 +126,11 @@ void nsync_cv_wait(nsync_cv *cv, nsync_mu *mu); In all cases, it reacquires "mu", and returns the reason for the call returned (0, ETIMEDOUT, or ECANCELED). Use abs_deadline==nsync_time_no_deadline for no deadline, and - cancel_note==NULL for no cancellation. wait_with_deadline() should be - used in a loop, as with all Mesa-style condition variables. See - examples above. + cancel_note==NULL for no nsync cancellations (however POSIX thread + cancellations may still happen, and ECANCELED could still be returned + when the calling thread is cancelled only if PTHREAD_CANCEL_MASKED is + in play). wait_with_deadline() should be used in a loop, as with all + Mesa-style condition variables. See examples above. There are two reasons for using an absolute deadline, rather than a relative timeout---these are why pthread_cond_timedwait() also uses diff --git a/third_party/nsync/futex.c b/third_party/nsync/futex.c index c69504e9c..1da653a97 100644 --- a/third_party/nsync/futex.c +++ b/third_party/nsync/futex.c @@ -28,6 +28,7 @@ #include "libc/errno.h" #include "libc/intrin/atomic.h" #include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" #include "libc/limits.h" @@ -38,6 +39,7 @@ #include "libc/sysv/consts/timer.h" #include "libc/sysv/errfuns.h" #include "libc/thread/freebsd.internal.h" +#include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" #include "third_party/nsync/atomic.h" @@ -118,26 +120,26 @@ static int nsync_futex_polyfill_ (atomic_int *w, int expect, struct timespec *ti int64_t nanos, maxnanos; struct timespec ts, deadline; - ts = nsync_time_now (); + ts = timespec_real (); if (!timeout) { - deadline = nsync_time_no_deadline; + deadline = timespec_max; } else if (FUTEX_TIMEOUT_IS_ABSOLUTE) { deadline = *timeout; } else { - deadline = nsync_time_add (ts, *timeout); + deadline = timespec_add (ts, *timeout); } nanos = 100; maxnanos = __SIG_POLLING_INTERVAL_MS * 1000L * 1000; - while (nsync_time_cmp (deadline, ts) > 0) { - ts = nsync_time_add (ts, _timespec_fromnanos (nanos)); - if (nsync_time_cmp (ts, deadline) > 0) { + while (timespec_cmp (deadline, ts) > 0) { + ts = timespec_add (ts, timespec_fromnanos (nanos)); + if (timespec_cmp (ts, deadline) > 0) { ts = deadline; } if (atomic_load_explicit (w, memory_order_acquire) != expect) { return 0; } - if ((rc = nsync_time_sleep_until (ts))) { + if ((rc = timespec_sleep_until (ts))) { return -rc; } if (nanos < maxnanos) { @@ -159,22 +161,22 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare, stru if (timeout) { deadline = *timeout; } else { - deadline = nsync_time_no_deadline; + deadline = timespec_max; } while (!(rc = _check_interrupts (false, 0))) { - now = nsync_time_now (); - if (nsync_time_cmp (now, deadline) > 0) { + now = timespec_real (); + if (timespec_cmp (now, deadline) > 0) { rc = etimedout(); break; } - remain = nsync_time_sub (deadline, now); - interval = _timespec_frommillis (__SIG_POLLING_INTERVAL_MS); - wait = nsync_time_cmp (remain, interval) > 0 ? interval : remain; + remain = timespec_sub (deadline, now); + interval = timespec_frommillis (__SIG_POLLING_INTERVAL_MS); + wait = timespec_cmp (remain, interval) > 0 ? interval : remain; if (atomic_load_explicit (w, memory_order_acquire) != expect) { break; } - if (WaitOnAddress (w, &expect, sizeof(int), _timespec_tomillis (wait))) { + if (WaitOnAddress (w, &expect, sizeof(int), timespec_tomillis (wait))) { break; } else { ASSERT (GetLastError () == ETIMEDOUT); @@ -186,6 +188,7 @@ static int nsync_futex_wait_win32_ (atomic_int *w, int expect, char pshare, stru int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, struct timespec *timeout) { int e, rc, op, fop; + struct PosixThread *pt = 0; if (atomic_load_explicit (w, memory_order_acquire) != expect) { return -EAGAIN; @@ -210,21 +213,31 @@ int nsync_futex_wait_ (atomic_int *w, int expect, char pshare, struct timespec * } else if (IsFreebsd ()) { rc = sys_umtx_timedwait_uint (w, expect, pshare, timeout); } else { - rc = sys_futex_cp (w, op, expect, timeout, 0, FUTEX_WAIT_BITS_); - if (IsOpenbsd() && rc > 0) { - // OpenBSD futex() returns errors as - // positive numbers without setting the - // carry flag. It's an irregular miracle - // because OpenBSD returns ECANCELED if - // futex() is interrupted w/ SA_RESTART - // so we're able to tell it apart from - // PT_MASKED which causes the wrapper - // to put ECANCELED into errno. - if (rc == ECANCELED) { - rc = EINTR; + if (IsOpenbsd()) { + // OpenBSD 6.8 futex() returns errors as + // positive numbers, without setting CF. + // This irregularity is fixed in 7.2 but + // unfortunately OpenBSD futex() defines + // its own ECANCELED condition, and that + // overlaps with our system call wrapper + if ((pt = (struct PosixThread *)__get_tls()->tib_pthread)) { + pt->flags &= ~PT_OPENBSD_KLUDGE; + } + } + rc = sys_futex_cp (w, op, expect, timeout, 0, FUTEX_WAIT_BITS_); + if (IsOpenbsd()) { + // Handle the OpenBSD 6.x irregularity. + if (rc > 0) { + errno = rc; + rc = -1; + } + // Check if ECANCELED came from the kernel + // because a SA_RESTART signal handler was + // invoked, such as our SIGTHR callback. + if (rc == -1 && errno == ECANCELED && + pt && (~pt->flags & PT_OPENBSD_KLUDGE)) { + errno = EINTR; } - errno = rc; - rc = -1; } } if (rc == -1) { diff --git a/third_party/nsync/mem/nsync_cv.c b/third_party/nsync/mem/nsync_cv.c index fb42829b4..806e712e5 100644 --- a/third_party/nsync/mem/nsync_cv.c +++ b/third_party/nsync/mem/nsync_cv.c @@ -15,6 +15,7 @@ │ See the License for the specific language governing permissions and │ │ limitations under the License. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/cp.internal.h" #include "libc/str/str.h" #include "libc/thread/thread.h" #include "third_party/nsync/atomic.internal.h" @@ -202,6 +203,7 @@ int nsync_cv_wait_with_deadline_generic (nsync_cv *pcv, void *pmu, int outcome = 0; waiter *w; IGNORE_RACES_START (); + BEGIN_CANCELLATION_POINT; w = nsync_waiter_new_ (); pthread_cleanup_push((void *)nsync_waiter_free_, w); ATM_STORE (&w->nw.waiting, 1); @@ -315,6 +317,7 @@ int nsync_cv_wait_with_deadline_generic (nsync_cv *pcv, void *pmu, } } pthread_cleanup_pop(0); + END_CANCELLATION_POINT; IGNORE_RACES_END (); return (outcome); } @@ -444,9 +447,9 @@ void nsync_cv_broadcast (nsync_cv *pcv) { } /* Wait with deadline, using an nsync_mu. */ -int nsync_cv_wait_with_deadline (nsync_cv *pcv, nsync_mu *pmu, - nsync_time abs_deadline, - nsync_note cancel_note) { +errno_t nsync_cv_wait_with_deadline (nsync_cv *pcv, nsync_mu *pmu, + nsync_time abs_deadline, + nsync_note cancel_note) { return (nsync_cv_wait_with_deadline_generic (pcv, pmu, &void_mu_lock, &void_mu_unlock, abs_deadline, cancel_note)); @@ -457,9 +460,11 @@ int nsync_cv_wait_with_deadline (nsync_cv *pcv, nsync_mu *pmu, wakeup. Then reacquires *pmu, and return. The call is equivalent to a call to nsync_cv_wait_with_deadline() with abs_deadline==nsync_time_no_deadline, and a NULL cancel_note. It should be used in a loop, as with all standard Mesa-style - condition variables. See examples above. */ -void nsync_cv_wait (nsync_cv *pcv, nsync_mu *pmu) { - nsync_cv_wait_with_deadline (pcv, pmu, nsync_time_no_deadline, NULL); + condition variables. See examples above. Returns 0 normally, otherwise + ECANCELED may be returned if calling POSIX thread is cancelled only when + the PTHREAD_CANCEL_MASKED mode is in play. */ +errno_t nsync_cv_wait (nsync_cv *pcv, nsync_mu *pmu) { + return nsync_cv_wait_with_deadline (pcv, pmu, nsync_time_no_deadline, NULL); } static nsync_time cv_ready_time (void *v, struct nsync_waiter_s *nw) { diff --git a/third_party/nsync/time.h b/third_party/nsync/time.h index 50f2ac92d..4fb28f9f8 100644 --- a/third_party/nsync/time.h +++ b/third_party/nsync/time.h @@ -17,39 +17,39 @@ COSMOPOLITAN_C_START_ typedef struct timespec nsync_time; /* A deadline infinitely far in the future. */ -#define nsync_time_no_deadline _timespec_max +#define nsync_time_no_deadline timespec_max /* The zero delay, or an expired deadline. */ -#define nsync_time_zero _timespec_zero +#define nsync_time_zero timespec_zero /* Return the current time since the epoch. */ -#define nsync_time_now() _timespec_real() +#define nsync_time_now() timespec_real() /* Sleep for the specified delay. Returns the unslept time which may be non-zero if the call was interrupted. */ -#define nsync_time_sleep(a) _timespec_sleep(a) +#define nsync_time_sleep(a) timespec_sleep(a) /* Sleep until the specified time. Returns 0 on success, and EINTR if the call was interrupted. */ -#define nsync_time_sleep_until(a) _timespec_sleep_until(a) +#define nsync_time_sleep_until(a) timespec_sleep_until(a) /* Return a+b */ -#define nsync_time_add(a, b) _timespec_add(a, b) +#define nsync_time_add(a, b) timespec_add(a, b) /* Return a-b */ -#define nsync_time_sub(a, b) _timespec_sub(a, b) +#define nsync_time_sub(a, b) timespec_sub(a, b) /* Return +ve, 0, or -ve according to whether a>b, a==b, or abrk, size)) == -1) return -1; m->brk = virt + size; @@ -278,7 +282,7 @@ static int64_t OpMmap(struct Machine *m, int64_t virt, size_t size, int prot, } if (ReserveVirtual(m, virt, size, key) != -1) { if (fd != -1 && !(flags & MAP_ANONYMOUS)) { - /* TODO: lazy file mappings */ + // TODO: lazy file mappings CHECK_NOTNULL((tmp = malloc(size))); for (e = errno;;) { rc = pread(fd, tmp, size, offset); @@ -290,11 +294,13 @@ static int64_t OpMmap(struct Machine *m, int64_t virt, size_t size, int prot, VirtualRecvWrite(m, virt, tmp, size); free(tmp); } - } else { + return virt; + } else if (recoverable) { FreeVirtual(m, virt, size); return -1; + } else { + FATALF("unrecoverable oom with mmap(MAP_FIXED)"); } - return virt; } else { return FreeVirtual(m, virt, size); } @@ -1137,7 +1143,7 @@ static int OpPoll(struct Machine *m, int64_t fdsaddr, uint64_t nfds, if ((gfds = malloc(gfdssize))) { rc = 0; VirtualSendRead(m, gfds, fdsaddr, gfdssize); - ts1 = _timespec_mono(); + ts1 = timespec_mono(); for (;;) { for (i = 0; i < nfds; ++i) { fd = Read32(gfds[i].fd); @@ -1183,8 +1189,8 @@ static int OpPoll(struct Machine *m, int64_t fdsaddr, uint64_t nfds, goto Finished; } } else { - ts2 = _timespec_mono(); - elapsed = _timespec_tomicros(_timespec_sub(ts2, ts1)); + ts2 = timespec_mono(); + elapsed = timespec_tomicros(timespec_sub(ts2, ts1)); if (elapsed >= timeout) { break; } diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 20bdc089d..f0c5bf49c 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -1334,7 +1334,7 @@ static void ReportWorkerResources(int pid, struct rusage *ru) { static void HandleWorkerExit(int pid, int ws, struct rusage *ru) { LockInc(&shared->c.connectionshandled); - _addrusage(&shared->children, ru); + rusage_add(&shared->children, ru); ReportWorkerExit(pid, ws); ReportWorkerResources(pid, ru); if (hasonprocessdestroy) { @@ -1418,8 +1418,8 @@ static ssize_t ReadAll(int fd, char *p, size_t n) { } static bool IsTakingTooLong(void) { - return meltdown && _timespec_gte(_timespec_sub(_timespec_real(), startread), - (struct timespec){2}); + return meltdown && timespec_cmp(timespec_sub(timespec_real(), startread), + (struct timespec){2}) >= 0; } static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) { @@ -3032,7 +3032,7 @@ td { padding-right: 3em; }\r\n\ } appends(&cpm.outbuf, "\r\n"); and = ""; - x = _timespec_sub(_timespec_real(), startserver).tv_sec; + x = timespec_sub(timespec_real(), startserver).tv_sec; y = ldiv(x, 24L * 60 * 60); if (y.quot) { appendf(&cpm.outbuf, "%,ld day%s ", y.quot, y.quot == 1 ? "" : "s"); @@ -3121,7 +3121,7 @@ static char *ServeStatusz(void) { } AppendLong1("pid", getpid()); AppendLong1("ppid", getppid()); - AppendLong1("now", _timespec_real().tv_sec); + AppendLong1("now", timespec_real().tv_sec); AppendLong1("nowish", shared->nowish.tv_sec); AppendLong1("gmtoff", gmtoff); AppendLong1("CLK_TCK", CLK_TCK); @@ -3634,7 +3634,7 @@ static void StoreAsset(char *path, size_t pathlen, char *data, size_t datalen, return; } OpenZip(false); - now = _timespec_real(); + now = timespec_real(); a = GetAssetZip(path, pathlen); if (!mode) mode = a ? GetMode(a) : 0644; if (!(mode & S_IFMT)) mode |= S_IFREG; @@ -4491,9 +4491,9 @@ static int LuaProgramHeartbeatInterval(lua_State *L) { if (!lua_isnoneornil(L, 1)) { millis = luaL_checkinteger(L, 1); millis = MAX(100, millis); - heartbeatinterval = _timespec_frommillis(millis); + heartbeatinterval = timespec_frommillis(millis); } - lua_pushinteger(L, _timespec_tomillis(heartbeatinterval)); + lua_pushinteger(L, timespec_tomillis(heartbeatinterval)); return 1; } @@ -4794,14 +4794,14 @@ wontreturn static void Replenisher(void) { signal(SIGTERM, OnTerm); signal(SIGUSR1, SIG_IGN); // make sure reload won't kill this signal(SIGUSR2, SIG_IGN); // make sure meltdown won't kill this - ts = _timespec_real(); + ts = timespec_real(); while (!terminated) { if (clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, 0)) { errno = 0; continue; } ReplenishTokens(tokenbucket.w, (1ul << tokenbucket.cidr) / 8); - ts = _timespec_add(ts, tokenbucket.replenish); + ts = timespec_add(ts, tokenbucket.replenish); DEBUGF("(token) replenished tokens"); } VERBOSEF("(token) replenish worker exiting"); @@ -4907,7 +4907,7 @@ static int LuaProgramTokenBucket(lua_State *L) { tokenbucket.reject = reject; tokenbucket.ignore = ignore; tokenbucket.ban = ban; - tokenbucket.replenish = _timespec_fromnanos(1 / replenish * 1e9); + tokenbucket.replenish = timespec_fromnanos(1 / replenish * 1e9); int pid = fork(); _npassert(pid != -1); if (!pid) Replenisher(); @@ -5591,13 +5591,13 @@ Content-Length: 22\r\n\ } static void EnterMeltdownMode(void) { - if (shared->lastmeltdown.tv_sec && - !_timespec_gte(_timespec_sub(_timespec_real(), shared->lastmeltdown), - (struct timespec){1})) + if (timespec_cmp(timespec_sub(timespec_real(), shared->lastmeltdown), + (struct timespec){1}) < 0) { return; + } WARNF("(srvr) server is melting down (%,d workers)", shared->workers); LOGIFNEG1(kill(0, SIGUSR2)); - shared->lastmeltdown = _timespec_real(); + shared->lastmeltdown = timespec_real(); ++shared->c.meltdowns; } @@ -5727,7 +5727,7 @@ static void HandleReload(void) { static void HandleHeartbeat(void) { size_t i; sigset_t mask; - UpdateCurrentDate(_timespec_real()); + UpdateCurrentDate(timespec_real()); Reindex(); getrusage(RUSAGE_SELF, &shared->server); #ifndef STATIC @@ -5822,8 +5822,8 @@ static char *ReadMore(void) { } else if (errno == EINTR) { LockInc(&shared->c.readinterrupts); if (killed || ((meltdown || terminated) && - _timespec_gte(_timespec_sub(_timespec_real(), startread), - (struct timespec){1}))) { + timespec_cmp(timespec_sub(timespec_real(), startread), + (struct timespec){1}) >= 0)) { return HandlePayloadDrop(); } } else { @@ -6350,9 +6350,9 @@ static bool HandleMessageActual(void) { p = stpcpy(p, "\r\n"); } if (loglatency || LOGGABLE(kLogDebug) || hasonloglatency) { - now = _timespec_real(); - reqtime = _timespec_tomicros(_timespec_sub(now, startrequest)); - contime = _timespec_tomicros(_timespec_sub(now, startconnection)); + now = timespec_real(); + reqtime = timespec_tomicros(timespec_sub(now, startrequest)); + contime = timespec_tomicros(timespec_sub(now, startconnection)); if (hasonloglatency) LuaOnLogLatency(reqtime, contime); if (loglatency || LOGGABLE(kLogDebug)) LOGF(kLogDebug, "(stat) %`'.*s latency r: %,ldµs c: %,ldµs", @@ -6394,14 +6394,14 @@ static void HandleMessages(void) { size_t got; for (once = false;;) { InitRequest(); - startread = _timespec_real(); + startread = timespec_real(); for (;;) { if (!cpm.msg.i && amtread) { - startrequest = _timespec_real(); + startrequest = timespec_real(); if (HandleMessage()) break; } if ((rc = reader(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) { - startrequest = _timespec_real(); + startrequest = timespec_real(); got = rc; amtread += got; if (amtread) { @@ -6460,9 +6460,8 @@ static void HandleMessages(void) { } if (killed || (terminated && !amtread) || (meltdown && - (!amtread || - _timespec_gte(_timespec_sub(_timespec_real(), startread), - (struct timespec){1})))) { + (!amtread || timespec_cmp(timespec_sub(timespec_real(), startread), + (struct timespec){1}) >= 0))) { if (amtread) { LockInc(&shared->c.dropped); SendServiceUnavailable(); @@ -6758,7 +6757,7 @@ static int HandleConnection(size_t i) { } else { DEBUGF("(token) can't acquire accept() token for client"); } - startconnection = _timespec_real(); + startconnection = timespec_real(); if (UNLIKELY(maxworkers) && shared->workers >= maxworkers) { EnterMeltdownMode(); SendServiceUnavailable(); @@ -6811,9 +6810,8 @@ static int HandleConnection(size_t i) { CloseServerFds(); } HandleMessages(); - DEBUGF( - "(stat) %s closing after %,ldµs", DescribeClient(), - _timespec_tomicros(_timespec_sub(_timespec_real(), startconnection))); + DEBUGF("(stat) %s closing after %,ldµs", DescribeClient(), + timespec_tomicros(timespec_sub(timespec_real(), startconnection))); if (!pid) { if (hasonworkerstop) { CallSimpleHook("OnWorkerStop"); @@ -7110,9 +7108,8 @@ int EventLoop(int ms) { EnterMeltdownMode(); lua_repl_unlock(); meltdown = false; - } else if (_timespec_gte( - _timespec_sub((t = _timespec_real()), lastheartbeat), - heartbeatinterval)) { + } else if (timespec_cmp(timespec_sub((t = timespec_real()), lastheartbeat), + heartbeatinterval) >= 0) { lastheartbeat = t; HandleHeartbeat(); } else if (HandlePoll(ms) == -1) { @@ -7350,7 +7347,7 @@ void RedBean(int argc, char *argv[]) { } reader = read; writer = WritevAll; - gmtoff = GetGmtOffset((lastrefresh = startserver = _timespec_real()).tv_sec); + gmtoff = GetGmtOffset((lastrefresh = startserver = timespec_real()).tv_sec); mainpid = getpid(); heartbeatinterval.tv_sec = 5; CHECK_GT(CLK_TCK, 0); @@ -7406,7 +7403,7 @@ void RedBean(int argc, char *argv[]) { close(fd); } ChangeUser(); - UpdateCurrentDate(_timespec_real()); + UpdateCurrentDate(timespec_real()); CollectGarbage(); hdrbuf.n = 4 * 1024; hdrbuf.p = xmalloc(hdrbuf.n); @@ -7424,12 +7421,12 @@ void RedBean(int argc, char *argv[]) { } } #ifdef STATIC - EventLoop(_timespec_tomillis(heartbeatinterval)); + EventLoop(timespec_tomillis(heartbeatinterval)); #else GetHostsTxt(); // for effect GetResolvConf(); // for effect if (daemonize || uniprocess || !linenoiseIsTerminal()) { - EventLoop(_timespec_tomillis(heartbeatinterval)); + EventLoop(timespec_tomillis(heartbeatinterval)); } else if (IsWindows()) { CHECK_NE(-1, _spawn(WindowsReplThread, 0, &replth)); EventLoop(100);