diff --git a/libc/intrin/pthread.h b/libc/intrin/pthread.h index 62dfe4dfc..2f466f2ed 100644 --- a/libc/intrin/pthread.h +++ b/libc/intrin/pthread.h @@ -91,6 +91,8 @@ void pthread_exit(void *) wontreturn; pthread_t pthread_self(void) pureconst; pthread_id_np_t pthread_getthreadid_np(void); int64_t pthread_getunique_np(pthread_t); +int pthread_setname_np(pthread_t, const char *); +int pthread_getname_np(pthread_t, char *, size_t); int pthread_getattr_np(pthread_t, pthread_attr_t *); int pthread_attr_init(pthread_attr_t *); int pthread_attr_destroy(pthread_attr_t *); diff --git a/libc/runtime/mapstack.c b/libc/runtime/mapstack.c index e421f5bfb..2ef19c01e 100644 --- a/libc/runtime/mapstack.c +++ b/libc/runtime/mapstack.c @@ -37,7 +37,7 @@ * The top 16 bytes of a stack can't be used due to openbsd:stackbound * and those bytes are also poisoned under ASAN build modes. * - * @return stack bottom address on success, or null w/ errrno + * @return stack bottom address on success, or null w/ errno */ void *_mapstack(void) { char *p; diff --git a/libc/sysv/syscalls.sh b/libc/sysv/syscalls.sh index 2ef0cdb00..ef71e3a9a 100755 --- a/libc/sysv/syscalls.sh +++ b/libc/sysv/syscalls.sh @@ -798,6 +798,24 @@ scall setrtable 0xfff136ffffffffff globl scall swapctl 0x10f0c1ffffffffff globl scall thrkill 0xfff077ffffffffff globl scall sys_unveil 0xfff072ffffffffff globl hidden +#──────────────────────────NETBSD──────────────────────────── +#scall _lwp_create 0x135fffffffffffff globl # int _lwp_create(const struct ucontext_netbsd *ucp, uint64_t flags, int *new_lwp) +#scall _lwp_exit 0x136fffffffffffff globl # int _lwp_exit(void) +#scall _lwp_self 0x137fffffffffffff globl # int _lwp_self(void) +#scall _lwp_wait 0x138fffffffffffff globl # int _lwp_wait(int wait_for, int *departed) +#scall _lwp_suspend 0x139fffffffffffff globl # int _lwp_suspend(int target) +#scall _lwp_continue 0x13afffffffffffff globl # int _lwp_continue(int target) +#scall _lwp_wakeup 0x13bfffffffffffff globl # int _lwp_wakeup(int target) +#scall _lwp_getprivate 0x13cfffffffffffff globl # void *_lwp_getprivate(void) +#scall _lwp_setprivate 0x13dfffffffffffff globl # int _lwp_setprivate(void *ptr) +#scall _lwp_kill 0x13efffffffffffff globl # int _lwp_kill(int target, int signo) +#scall _lwp_detach 0x13ffffffffffffff globl # int _lwp_park(int clock_id, int flags, struct timespec *ts, int unpark, const void *hint, const void *unparkhint) +#scall _lwp_park 0x1defffffffffffff globl # int _lwp_park(int clock_id, int flags, struct timespec *ts, int unpark, const void *hint, const void *unparkhint) +#scall _lwp_unpark 0x141fffffffffffff globl # int _lwp_unpark_all(int target, const void *hint) +#scall _lwp_unpark_all 0x142fffffffffffff globl # int _lwp_unpark_all(const int *targets, size_t ntargets, const void *hint) +#scall _lwp_setname 0x143fffffffffffff globl # int _lwp_setname(int target, const char *name) +#scall _lwp_getname 0x144fffffffffffff globl # int _lwp_getname(int target, char *name, size_t len) +#scall _lwp_ctl 0x145fffffffffffff globl # int _lwp_ctl(int features, struct lwpctl **address) # The Fifth Bell System Interface, Community Edition # » beyond the pale diff --git a/libc/thread/pthread_attr_setstack.c b/libc/thread/pthread_attr_setstack.c index 1772c6195..dedd4793b 100644 --- a/libc/thread/pthread_attr_setstack.c +++ b/libc/thread/pthread_attr_setstack.c @@ -35,12 +35,13 @@ * * pthread_t id; * pthread_attr_t attr; + * char *stk = _mapstack(); * pthread_attr_init(&attr); - * pthread_attr_setstack(&attr, gc(malloc(GetStackSize())), - * GetStackSize()); + * pthread_attr_setstack(&attr, stk, GetStackSize()); * pthread_create(&id, &attr, func, 0); * pthread_attr_destroy(&attr); * pthread_join(id, 0); + * _freestack(stk); * * Your stack must have at least `PTHREAD_STACK_MIN` bytes, which * Cosmpolitan Libc defines as `GetStackSize()`. It's a link-time @@ -52,11 +53,16 @@ * (e.g. kprintf) assumes that stack sizes are two-powers and are * aligned to that two-power. Conformance isn't required since we * say caveat emptor to those who don't maintain these invariants + * please consider using _mapstack() which always does it perfect + * or use `mmap(0, GetStackSize() << 1, ...)` for a bigger stack. * * Unlike pthread_attr_setstacksize(), this function permits just * about any parameters and will change the values and allocation * as needed to conform to the mandatory requirements of the host - * operating system. + * operating system even if it doesn't meet the stricter needs of + * Cosmopolitan Libc userspace libraries. For example with malloc + * allocations, things like page size alignment, shall be handled + * automatically for compatibility with existing codebases. * * @param stackaddr is address of stack allocated by caller, and * may be NULL in which case default behavior is restored diff --git a/libc/thread/pthread_getname_np.c b/libc/thread/pthread_getname_np.c new file mode 100644 index 000000000..d2c796c57 --- /dev/null +++ b/libc/thread/pthread_getname_np.c @@ -0,0 +1,118 @@ +/*-*- 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/calls.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/asmflag.h" +#include "libc/macros.internal.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/pr.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +/** + * Gets name of thread registered with system, e.g. + * + * char name[64]; + * pthread_getname_np(thread, name, sizeof(name)); + * + * If the thread doesn't have a name, then empty string is returned. + * This implementation guarantees `buf` is always modified, even on + * error, and will always be nul-terminated. If `size` is 0 then this + * function returns 0. Your `buf` is also chomped to remove newlines. + * + * @return 0 on success, or errno on error + * @raise ERANGE if `size` wasn't large enough, in which case your + * result will still be returned truncated if possible + * @raise ENOSYS on MacOS, Windows, FreeBSD, and OpenBSD + */ +int pthread_getname_np(pthread_t thread, char *name, size_t size) { + int e, fd, rc, tid, len; + + if (!size) return 0; + bzero(name, size); + tid = ((struct PosixThread *)thread)->spawn.ptid; + + if (IsLinux()) { + // TASK_COMM_LEN is 16 on Linux so we're just being paranoid. + char buf[256] = {0}; + if (tid == gettid()) { + e = errno; + if (prctl(PR_GET_NAME, buf) == -1) { + rc = errno; + errno = e; + return rc; + } + } else { + char path[128], *p = path; + p = stpcpy(p, "/proc/self/task/"); + p = FormatUint32(p, tid); + p = stpcpy(p, "/comm"); + e = errno; + if ((fd = sys_open(path, O_RDONLY | O_CLOEXEC, 0)) == -1) { + rc = errno; + errno = e; + return rc; + } + rc = sys_read(fd, buf, sizeof(buf) - 1); + rc |= sys_close(fd); + if (rc == -1) { + rc = errno; + errno = e; + return rc; + } + _chomp(buf); + } + if ((len = strlen(buf))) { + memcpy(name, buf, MIN(len, size - 1)); + } + if (len > size - 1) { + return ERANGE; + } + return 0; + + } else if (IsNetbsd()) { + char cf; + int ax, dx; + // NetBSD doesn't document the subtleties of its nul-terminator + // behavior, so like Linux we shall take the paranoid approach. + asm volatile(CFLAG_ASM("syscall") + : CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx) + : "1"(324 /* _lwp_getname */), "D"(tid), "S"(name), + "d"(size - 1) + : "rcx", "r11", "memory"); + if (!cf) { + // if size + our nul + kernel's nul is the buffer size, then we + // can't say with absolute confidence truncation didn't happen. + if (strlen(name) + 1 + 1 <= size) { + return 0; + } else { + return ERANGE; + } + } else { + return ax; + } + + } else { + return ENOSYS; + } +} diff --git a/libc/thread/pthread_setname_np.c b/libc/thread/pthread_setname_np.c new file mode 100644 index 000000000..1ea57df50 --- /dev/null +++ b/libc/thread/pthread_setname_np.c @@ -0,0 +1,118 @@ +/*-*- 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/calls.h" +#include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/asan.internal.h" +#include "libc/intrin/asmflag.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/pr.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/thread.h" + +/** + * Registers custom name of thread with system, e.g. + * + * void *worker(void *arg) { + * pthread_setname_np(pthread_self(), "justine"); + * pause(); + * return 0; + * } + * + * int main(int argc, char *argv[]) { + * pthread_t id; + * pthread_create(&id, 0, worker, 0); + * pthread_join(id, 0); + * } + * + * ProTip: The `htop` software is good at displaying thread names. + * + * @return 0 on success, or errno on error + * @raise ERANGE if length of `name` exceeded system limit, in which + * case the name may have still been set with os using truncation + * @raise ENOSYS on MacOS, Windows, and OpenBSD + * @see pthread_getname_np() + */ +int pthread_setname_np(pthread_t thread, const char *name) { + char path[128], *p; + int e, fd, rc, tid, len; + + tid = ((struct PosixThread *)thread)->spawn.ptid; + len = strlen(name); + + if (IsLinux()) { + if (tid == gettid()) { + e = errno; + if (prctl(PR_SET_NAME, name) == -1) { + rc = errno; + errno = e; + return rc; + } + } else { + p = path; + p = stpcpy(p, "/proc/self/task/"); + p = FormatUint32(p, tid); + p = stpcpy(p, "/comm"); + e = errno; + if ((fd = sys_open(path, O_WRONLY | O_CLOEXEC, 0)) == -1) { + rc = errno; + errno = e; + return rc; + } + rc = sys_write(fd, name, len); + rc |= sys_close(fd); + if (rc == -1) { + rc = errno; + errno = e; + return rc; + } + } + if (len > 15) { + // linux is documented as truncating here. we still set the name + // since the limit might be raised in the future checking return + // value of this function is a bummer. Grep it for TASK_COMM_LEN + return ERANGE; + } + return 0; + + } else if (IsFreebsd()) { + char cf; + int ax, dx; + asm volatile(CFLAG_ASM("syscall") + : CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx) + : "1"(323 /* thr_set_name */), "D"(tid), "S"(name) + : "rcx", "r11", "memory"); + return !cf ? 0 : ax; + + } else if (IsNetbsd()) { + char cf; + int ax, dx; + asm volatile(CFLAG_ASM("syscall") + : CFLAG_CONSTRAINT(cf), "=a"(ax), "=d"(dx) + : "1"(323 /* _lwp_setname */), "D"(tid), "S"(name) + : "rcx", "r11", "memory"); + return !cf ? 0 : ax; + + } else { + return ENOSYS; + } +} diff --git a/libc/thread/thread.mk b/libc/thread/thread.mk index b1d4f15ef..a953bde03 100644 --- a/libc/thread/thread.mk +++ b/libc/thread/thread.mk @@ -29,6 +29,7 @@ LIBC_THREAD_A_DIRECTDEPS = \ LIBC_INTRIN \ LIBC_MEM \ LIBC_RUNTIME \ + LIBC_STR \ LIBC_SYSV \ LIBC_SYSV_CALLS \ LIBC_NEXGEN32E diff --git a/test/libc/thread/pthread_setname_np_test.c b/test/libc/thread/pthread_setname_np_test.c new file mode 100644 index 000000000..e39226e75 --- /dev/null +++ b/test/libc/thread/pthread_setname_np_test.c @@ -0,0 +1,88 @@ +/*-*- 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/dce.h" +#include "libc/intrin/atomic.h" +#include "libc/intrin/pthread.h" +#include "libc/testlib/testlib.h" +#include "libc/thread/thread.h" + +static void *SetName(void *arg) { + ASSERT_EQ(0, pthread_setname_np(pthread_self(), "justine")); + return 0; +} + +TEST(pthread_setname_np, SetName_SystemCallSucceeds) { + pthread_t id; + if (!IsLinux() || !IsNetbsd() || !IsFreebsd()) return; + ASSERT_EQ(0, pthread_create(&id, 0, SetName, 0)); + ASSERT_EQ(0, pthread_join(id, 0)); +} + +static void *SetGetNameOfSelf(void *arg) { + char me[16]; + ASSERT_EQ(0, pthread_setname_np(pthread_self(), "justine")); + ASSERT_EQ(0, pthread_getname_np(pthread_self(), me, sizeof(me))); + EXPECT_STREQ("justine", me); + return 0; +} + +TEST(pthread_setname_np, SetGetNameOfSelf) { + pthread_t id; + if (!IsLinux() || !IsNetbsd()) return; + ASSERT_EQ(0, pthread_create(&id, 0, SetGetNameOfSelf, 0)); + ASSERT_EQ(0, pthread_join(id, 0)); +} + +static void *GetDefaultName(void *arg) { + char me[16]; + ASSERT_EQ(0, pthread_getname_np(pthread_self(), me, sizeof(me))); + EXPECT_STREQ("", me); + return 0; +} + +TEST(pthread_setname_np, GetDefaultName_IsEmptyString) { + pthread_t id; + if (!IsLinux() || !IsNetbsd()) return; + ASSERT_EQ(0, pthread_create(&id, 0, GetDefaultName, 0)); + ASSERT_EQ(0, pthread_join(id, 0)); +} + +_Atomic(char) sync1, sync2; + +static void *GetNameOfOtherThreadWorker(void *arg) { + pthread_setname_np(pthread_self(), "justine"); + atomic_store(&sync1, 1); + while (!atomic_load(&sync2)) pthread_yield(); + return 0; +} + +TEST(pthread_setname_np, GetNameOfOtherThread) { + char me[16]; + pthread_t id; + if (!IsLinux() || !IsNetbsd()) return; + ASSERT_EQ(0, pthread_create(&id, 0, GetNameOfOtherThreadWorker, 0)); + while (!atomic_load(&sync1)) pthread_yield(); + ASSERT_EQ(0, pthread_getname_np(id, me, sizeof(me))); + EXPECT_STREQ("justine", me); + ASSERT_EQ(0, pthread_setname_np(id, "tunney")); + ASSERT_EQ(0, pthread_getname_np(id, me, sizeof(me))); + EXPECT_STREQ("tunney", me); + atomic_store(&sync2, 1); + ASSERT_EQ(0, pthread_join(id, 0)); +}