diff --git a/common/cancellation.h b/common/cancellation.h new file mode 100644 index 000000000..4a8ce6304 --- /dev/null +++ b/common/cancellation.h @@ -0,0 +1,42 @@ +/* + * Phoenix-RTOS + * + * libphoenix + * + * Cancellation point handling + * + * Copyright 2026 Phoenix Systems + * Author: Adam Greloch + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _LIBPHOENIX_WRAP_CANCEL_H_ +#define _LIBPHOENIX_WRAP_CANCEL_H_ + +#include + +#define CANCELLATION_POINT(rettype, function, args) ({ \ + int __oldval = _pthread_enable_asynccancel(); \ + rettype __ret = function args; \ + _pthread_disable_asynccancel(__oldval); \ + __ret; \ +}) + + +#define WRAP_ERRNO_DEF_CANCELLATION(rettype, function, arguments, argnames) \ + extern rettype sys_##function arguments; \ + rettype function arguments \ + { \ + return SET_ERRNO(CANCELLATION_POINT(rettype, sys_##function, argnames)); \ + } + + +#define WRAP_CANCELLATION(rettype, function, arguments, argnames) \ + extern rettype sys_##function arguments; \ + rettype function arguments \ + { \ + return CANCELLATION_POINT(rettype, sys_##function, argnames); \ + } + +#endif diff --git a/include/pthread.h b/include/pthread.h index aa61027bb..c44d0d389 100644 --- a/include/pthread.h +++ b/include/pthread.h @@ -57,7 +57,11 @@ extern "C" { #define PTHREAD_CANCEL_DISABLE 0 #define PTHREAD_CANCEL_ENABLE 1 -#define PTHREAD_CANCELED 2 + +#define PTHREAD_CANCELED ((void *)-1) + +#define PTHREAD_CANCEL_DEFERRED 0 +#define PTHREAD_CANCEL_ASYNCHRONOUS 1 /* clang-format off */ #define PTHREAD_MUTEX_INITIALIZER { 0, 0 } @@ -78,6 +82,9 @@ int pthread_detach(pthread_t thread); int pthread_setcancelstate(int state, int *oldstate); +int pthread_setcanceltype(int type, int *oldtype); + + int pthread_cancel(pthread_t thread); @@ -293,6 +300,21 @@ void _pthread_atfork_parent(void); void _pthread_atfork_child(void); +void _pthread_fork_child_reinit(pthread_t self); + + +void _pthread_nocancel_begin(void); + + +void _pthread_nocancel_end(void); + + +int _pthread_enable_asynccancel(void); + + +void _pthread_disable_asynccancel(int oldval); + + void pthread_cleanup_push(void (*routine)(void *), void *arg); diff --git a/pthread/pthread.c b/pthread/pthread.c index 2aec8c964..6bed367e1 100644 --- a/pthread/pthread.c +++ b/pthread/pthread.c @@ -25,6 +25,7 @@ #include #include "../common/util.h" +#include "../common/cancellation.h" #define ALIGN(value, size) ((((value) + (size) - 1) / (size)) * (size)) @@ -38,6 +39,32 @@ #define RESOURCE_INITIALIZING 1 #define RESOURCE_INITIALIZED 2 + +#define CANCEL_DISABLED_BIT (1 << 0) /* PTHREAD_CANCEL_DISABLE is in effect */ +#define CANCEL_ASYNC_BIT (1 << 1) /* PTHREAD_CANCEL_ASYNCHRONOUS is in effect */ +#define CANCEL_REQUESTED_BIT (1 << 2) /* pthread_cancel() has been called on the thread */ +#define CANCEL_INPROGRESS_BIT (1 << 3) /* a canceller has claimed the right to destroy the thread */ +#define CANCEL_EXITING_BIT (1 << 4) /* the thread has begun tearing itself down */ + +/* + * The all-zero word is the POSIX default for a new thread: cancellation enabled, + * deferred, and not requested. + */ +#define CANCEL_DEFAULT (0) + +/* Cancellation is enabled and requested: act on it at the next cancellation point. */ +#define CANCEL_IS_PENDING(val) (((val) & (CANCEL_DISABLED_BIT | CANCEL_REQUESTED_BIT)) == CANCEL_REQUESTED_BIT) + +/* As above, but asynchronous, so it has to be acted upon immediately. */ +#define CANCEL_IS_ACTIVE(val) \ + (((val) & (CANCEL_DISABLED_BIT | CANCEL_ASYNC_BIT | CANCEL_REQUESTED_BIT)) == (CANCEL_ASYNC_BIT | CANCEL_REQUESTED_BIT)) + +#define CANCEL_WAIT_INTERVAL_NS (1000 * 1000) + + +int nsleep(time_t *sec, long *nsec, int clockid, int flags); + + typedef struct pthread_ctx { handle_t id; void *(*start_routine)(void *); @@ -55,8 +82,11 @@ typedef struct pthread_ctx { struct pthread_ctx *next; struct pthread_ctx *prev; int is_detached; - int cancelstate; - int cancelled; + /* + * Cancellation state lives in a single atomic word, so that a canceller + * and its victim always transact on one memory location. + */ + int cancellation; struct __errno_t e; int refcount; struct pthread_key_data_t *key_data_list; @@ -94,9 +124,19 @@ static struct { int pthread_min_prio_rr; int pthread_max_prio_rr; int pthread_rr_interval; + + pthread_ctx main_ctx; } pthread_common; +#ifdef __LIBPHOENIX_ARCH_TLS_SUPPORTED +static __thread pthread_t __self = (pthread_t)NULL; + +/* Non-zero in a region that must not act on cancellation (usually between vfork() and execve()). */ +static __thread int __nocancel_depth = 0; +#endif + + typedef struct __pthread_key_t { void (*destructor)(void *); } __pthread_key_t; @@ -140,6 +180,40 @@ static const pthread_rwlockattr_t pthread_rwlockattr_default = { static __attribute__((noreturn)) void pthread_do_exit(pthread_ctx *ctx, void *value_ptr, int cleanup); +static inline int _pthread_cancel_get(pthread_ctx *ctx) +{ + return __atomic_load_n(&ctx->cancellation, __ATOMIC_SEQ_CST); +} + + +static inline int _pthread_cancel_set(pthread_ctx *ctx, int bits) +{ + return __atomic_fetch_or(&ctx->cancellation, bits, __ATOMIC_SEQ_CST); +} + + +static inline int _pthread_cancel_clear(pthread_ctx *ctx, int bits) +{ + return __atomic_fetch_and(&ctx->cancellation, ~bits, __ATOMIC_SEQ_CST); +} + + +/* + * Wait out a canceller that has claimed this thread. The claim is withdrawn if the canceller + * fails to post the signal, so this is not an unconditional wait. + */ +static void _pthread_wait_for_cancel(pthread_ctx *ctx) +{ + while ((_pthread_cancel_get(ctx) & CANCEL_INPROGRESS_BIT) != 0) { + time_t sec = 0; + long nsec = CANCEL_WAIT_INTERVAL_NS; + + /* use nsleep as it is not a cancellation point */ + (void)nsleep(&sec, &nsec, CLOCK_REALTIME, 0); + } +} + + static void _pthread_ctx_get(pthread_ctx *ctx) { ++ctx->refcount; @@ -159,7 +233,7 @@ static void _pthread_ctx_put(pthread_ctx *ctx) int refcnt = --ctx->refcount; mutexUnlock(pthread_common.pthread_list_lock); - if (refcnt == 0) { + if (refcnt == 0 && ctx != &pthread_common.main_ctx) { free(ctx); } } @@ -176,6 +250,11 @@ static void pthread_start_point(void *args) { pthread_ctx *ctx = (pthread_ctx *)args; + ctx->id = gettid(); +#ifdef __LIBPHOENIX_ARCH_TLS_SUPPORTED + __self = (pthread_t)ctx; +#endif + _errno_new(&ctx->e); void *retval = (void *)(ctx->start_routine(ctx->arg)); @@ -221,27 +300,35 @@ static pthread_ctx *pthread_find(handle_t id) } -static int pthread_create_main(void) +static void _pthread_init_ctx(pthread_ctx *ctx, const pthread_attr_t *attrs) { - pthread_ctx *ctx = (pthread_ctx *)malloc(sizeof(pthread_ctx)); - if (ctx == NULL) { - return ENOMEM; - } - ctx->id = gettid(); + ctx->refcount = 1; ctx->arg = NULL; - ctx->retval = NULL; ctx->stack = NULL; ctx->stacksize = 0; - ctx->is_detached = (pthread_attr_default.detachstate == PTHREAD_CREATE_DETACHED) ? 1 : 0; - ctx->cancelstate = PTHREAD_CANCEL_ENABLE; - ctx->cancelled = 0; - ctx->refcount = 1; + ctx->start_routine = 0; + ctx->retval = NULL; + ctx->exiting = 0; + ctx->cancellation = CANCEL_DEFAULT; ctx->key_data_list = NULL; ctx->cleanup_list = NULL; - ctx->exiting = 0; + ctx->is_detached = (attrs->detachstate == PTHREAD_CREATE_DETACHED) ? 1 : 0; +} + + +static int pthread_create_main(void) +{ + pthread_ctx *ctx = &pthread_common.main_ctx; + + _pthread_init_ctx(ctx, &pthread_attr_default); + ctx->id = gettid(); LIST_ADD(&pthread_common.pthread_list, ctx); +#ifdef __LIBPHOENIX_ARCH_TLS_SUPPORTED + __self = (pthread_t)ctx; +#endif + return 0; } @@ -297,17 +384,11 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, return EAGAIN; } - ctx->refcount = 1; - ctx->retval = NULL; - ctx->is_detached = (attrs->detachstate == PTHREAD_CREATE_DETACHED) ? 1 : 0; + _pthread_init_ctx(ctx, attrs); ctx->start_routine = start_routine; ctx->arg = arg; ctx->stack = stack; ctx->stacksize = stacksize; - ctx->key_data_list = NULL; - ctx->cancelstate = PTHREAD_CANCEL_ENABLE; - ctx->cancelled = 0; - ctx->cleanup_list = NULL; *thread = (pthread_t)ctx; mutexLock(pthread_common.pthread_list_lock); @@ -390,7 +471,7 @@ int pthread_join(pthread_t thread, void **value_ptr) mutexUnlock(pthread_common.pthread_list_lock); do { - err = threadJoin(id, 0); + err = CANCELLATION_POINT(int, threadJoin, (id, 0)); } while (err == -EINTR); if (err < 0) { @@ -434,22 +515,63 @@ int pthread_detach(pthread_t thread) int pthread_setcancelstate(int state, int *oldstate) { - int err = 0; + int oldVal, newVal; pthread_ctx *ctx = (pthread_ctx *)pthread_self(); - if (state != PTHREAD_CANCEL_ENABLE && state != PTHREAD_CANCEL_DISABLE) { - err = EINVAL; + if ((ctx == NULL) || ((state != PTHREAD_CANCEL_ENABLE) && (state != PTHREAD_CANCEL_DISABLE))) { + return EINVAL; + } + + if (state == PTHREAD_CANCEL_DISABLE) { + oldVal = _pthread_cancel_set(ctx, CANCEL_DISABLED_BIT); + newVal = oldVal | CANCEL_DISABLED_BIT; } else { - mutexLock(pthread_common.pthread_list_lock); - _pthread_ctx_get(ctx); - if (oldstate != NULL) { - *oldstate = ctx->cancelstate; - } - ctx->cancelstate = state; - _pthread_ctx_put(ctx); + oldVal = _pthread_cancel_clear(ctx, CANCEL_DISABLED_BIT); + newVal = oldVal & ~CANCEL_DISABLED_BIT; } - return err; + + if (oldstate != NULL) { + *oldstate = ((oldVal & CANCEL_DISABLED_BIT) != 0) ? PTHREAD_CANCEL_DISABLE : PTHREAD_CANCEL_ENABLE; + } + + if (CANCEL_IS_ACTIVE(newVal)) { + pthread_exit((void *)PTHREAD_CANCELED); + /* no return */ + } + + return 0; +} + + +int pthread_setcanceltype(int type, int *oldtype) +{ + int oldVal, newVal; + pthread_ctx *ctx = (pthread_ctx *)pthread_self(); + + if ((ctx == NULL) || ((type != PTHREAD_CANCEL_DEFERRED) && (type != PTHREAD_CANCEL_ASYNCHRONOUS))) { + return EINVAL; + } + + if (type == PTHREAD_CANCEL_ASYNCHRONOUS) { + oldVal = _pthread_cancel_set(ctx, CANCEL_ASYNC_BIT); + newVal = oldVal | CANCEL_ASYNC_BIT; + } + else { + oldVal = _pthread_cancel_clear(ctx, CANCEL_ASYNC_BIT); + newVal = oldVal & ~CANCEL_ASYNC_BIT; + } + + if (oldtype != NULL) { + *oldtype = ((oldVal & CANCEL_ASYNC_BIT) != 0) ? PTHREAD_CANCEL_ASYNCHRONOUS : PTHREAD_CANCEL_DEFERRED; + } + + if (CANCEL_IS_ACTIVE(newVal)) { + pthread_exit((void *)PTHREAD_CANCELED); + /* no return */ + } + + return 0; } @@ -497,70 +619,100 @@ static void pthread_key_cleanup(pthread_ctx *ctx) int pthread_cancel(pthread_t thread) { - int err = 0, id; + int err, id, oldVal, newVal, isSelf; pthread_ctx *ctx = (pthread_ctx *)thread; - pthread_t self; if (ctx == NULL) { - err = -ESRCH; + return ESRCH; } - else { - self = pthread_self(); - mutexLock(pthread_common.pthread_list_lock); - _pthread_ctx_get(ctx); - ctx->cancelled = 1; - if (thread == self) { - if (ctx->cancelstate == PTHREAD_CANCEL_ENABLE) { - _pthread_ctx_put(ctx); - pthread_exit((void *)PTHREAD_CANCELED); - /* no return */ - } - _pthread_ctx_put(ctx); + + isSelf = (thread == pthread_self()) ? 1 : 0; + + mutexLock(pthread_common.pthread_list_lock); + _pthread_ctx_get(ctx); + + oldVal = _pthread_cancel_get(ctx); + do { + newVal = oldVal | CANCEL_REQUESTED_BIT; + if ((isSelf == 0) && ((newVal & CANCEL_EXITING_BIT) == 0) && CANCEL_IS_ACTIVE(newVal)) { + /* claim the right to destroy the thread and post the signal */ + newVal |= CANCEL_INPROGRESS_BIT; } - else { - if (ctx->cancelstate == PTHREAD_CANCEL_ENABLE) { - _pthread_do_cleanup(ctx); - ctx->retval = (void *)PTHREAD_CANCELED; - id = ctx->id; - mutexUnlock(pthread_common.pthread_list_lock); - pthread_key_cleanup(ctx); - pthread_ctx_put(ctx); - err = signalPost(getpid(), id, signal_cancel); - } - else { - _pthread_ctx_put(ctx); - } + if (newVal == oldVal) { + break; + } + } while (__atomic_compare_exchange_n(&ctx->cancellation, &oldVal, newVal, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) == false); + + if (isSelf != 0) { + _pthread_ctx_put(ctx); + if (CANCEL_IS_PENDING(newVal)) { + pthread_exit((void *)PTHREAD_CANCELED); + /* no return */ } + return EOK; } - return -err; + + if (((newVal & CANCEL_INPROGRESS_BIT) == 0) || ((oldVal & CANCEL_INPROGRESS_BIT) != 0)) { + /* + * Either the thread is not inside a cancellation point or another caller + * already claimed the destroy. + */ + _pthread_ctx_put(ctx); + return EOK; + } + + id = ctx->id; + + /* + * POSIX-DEVIATION: the handlers still run here rather than in the victim, which + * POSIX requires. signal_cancel has no user-space handler to run them from, which + * also means a thread cancelled in pthread_cond_wait() never reacquires the + * mutex before they run. + */ + err = signalPost(getpid(), id, signal_cancel); + if (err != EOK) { + (void)_pthread_cancel_clear(ctx, CANCEL_INPROGRESS_BIT); + _pthread_ctx_put(ctx); + return -err; + } + + ctx->retval = (void *)PTHREAD_CANCELED; + _pthread_do_cleanup(ctx); + mutexUnlock(pthread_common.pthread_list_lock); + + pthread_key_cleanup(ctx); + pthread_ctx_put(ctx); + + return EOK; } void pthread_testcancel(void) { pthread_ctx *ctx = (pthread_ctx *)pthread_self(); + if (ctx == NULL) { return; } - mutexLock(pthread_common.pthread_list_lock); - _pthread_ctx_get(ctx); - if (ctx->cancelstate == PTHREAD_CANCEL_ENABLE && ctx->cancelled != 0) { - _pthread_ctx_put(ctx); + if (CANCEL_IS_PENDING(_pthread_cancel_get(ctx))) { pthread_exit((void *)PTHREAD_CANCELED); /* no return */ } - _pthread_ctx_put(ctx); } pthread_t pthread_self(void) { +#ifdef __LIBPHOENIX_ARCH_TLS_SUPPORTED + return __self; +#else pthread_ctx *ctx = pthread_find(gettid()); if (ctx != NULL) { pthread_ctx_put(ctx); } return (pthread_t)ctx; +#endif } @@ -573,6 +725,12 @@ int pthread_equal(pthread_t t1, pthread_t t2) static __attribute__((noreturn)) void pthread_do_exit(pthread_ctx *ctx, void *value_ptr, int cleanup) { if (ctx != NULL) { + /* Announce the teardown before taking any lock to prevent race with the potential cleanup in pthread_cancel() */ + if ((_pthread_cancel_set(ctx, CANCEL_EXITING_BIT) & CANCEL_INPROGRESS_BIT) != 0) { + /* Wait for already pending cancel to happen. otherwise, we risk leaving a lock held */ + _pthread_wait_for_cancel(ctx); + } + if (cleanup != 0) { mutexLock(pthread_common.pthread_list_lock); _pthread_do_cleanup(ctx); @@ -1453,15 +1611,14 @@ int pthread_cond_wait(pthread_cond_t *__restrict cond, pthread_mutex_t *__restri } if (err == EOK) { - err = -condWait(cond->condh, mutex->mutexh, 0); + err = -CANCELLATION_POINT(int, condWait, (cond->condh, mutex->mutexh, 0)); } return err; } -int pthread_cond_timedwait(pthread_cond_t *__restrict cond, - pthread_mutex_t *__restrict mutex, +int pthread_cond_timedwait(pthread_cond_t *__restrict cond, pthread_mutex_t *__restrict mutex, const struct timespec *__restrict abstime) { int err = 0; @@ -1480,7 +1637,7 @@ int pthread_cond_timedwait(pthread_cond_t *__restrict cond, } if (err == EOK) { - err = -condWait(cond->condh, mutex->mutexh, abstime_us); + err = -CANCELLATION_POINT(int, condWait, (cond->condh, mutex->mutexh, abstime_us)); } if (err == ETIME) { @@ -1649,6 +1806,60 @@ int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) } +static void _pthread_free_orphaned_ctx(pthread_ctx *ctx) +{ + while (ctx->cleanup_list != NULL) { + pthread_cleanup_t *head = ctx->cleanup_list; + ctx->cleanup_list = head->next; + free(head); + } + + while (ctx->key_data_list != NULL) { + pthread_key_data_t *head = ctx->key_data_list; + ctx->key_data_list = head->next; + free(head); + } + + if (ctx->stack != NULL) { + munmap(ctx->stack, ctx->stacksize); + } + + if (ctx != &pthread_common.main_ctx) { + free(ctx); + } +} + + +void _pthread_fork_child_reinit(pthread_t self_thread) +{ + pthread_ctx *self = (pthread_ctx *)self_thread; + + mutexLock(pthread_common.pthread_list_lock); + + self->id = gettid(); + self->refcount = 1; + (void)_pthread_cancel_clear(self, CANCEL_INPROGRESS_BIT | CANCEL_EXITING_BIT); + +#ifdef __LIBPHOENIX_ARCH_TLS_SUPPORTED + assert(__self == (pthread_t)self); +#endif + + while (pthread_common.pthread_list != NULL && pthread_common.pthread_list != self) { + pthread_ctx *ctx = pthread_common.pthread_list; + LIST_REMOVE(&pthread_common.pthread_list, ctx); + _pthread_free_orphaned_ctx(ctx); + } + + while (self->next != self) { + pthread_ctx *ctx = self->next; + LIST_REMOVE(&pthread_common.pthread_list, ctx); + _pthread_free_orphaned_ctx(ctx); + } + + mutexUnlock(pthread_common.pthread_list_lock); +} + + int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) { int err = 0; @@ -2041,6 +2252,85 @@ int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int } +void _pthread_nocancel_begin(void) +{ +#ifdef __LIBPHOENIX_ARCH_TLS_SUPPORTED + ++__nocancel_depth; +#endif +} + + +void _pthread_nocancel_end(void) +{ +#ifdef __LIBPHOENIX_ARCH_TLS_SUPPORTED + --__nocancel_depth; +#endif +} + + +int _pthread_enable_asynccancel(void) +{ +#ifndef __LIBPHOENIX_ARCH_TLS_SUPPORTED + /* + * pthread_self() has huge performance penalty on non-TLS targets, so keep + * this as no-op. + */ + return PTHREAD_CANCEL_ASYNCHRONOUS; +#else + pthread_ctx *ctx; + int oldval; + + if (__nocancel_depth != 0) { + /* not a cancellation point, or the context here is another thread's */ + return PTHREAD_CANCEL_ASYNCHRONOUS; + } + + ctx = (pthread_ctx *)pthread_self(); + + if (ctx == NULL) { + /* + * No ctx to track cancellation state against - a thread not started + * through pthread_create(), i.e. a bare beginthread() one. + */ + return PTHREAD_CANCEL_ASYNCHRONOUS; + } + + oldval = _pthread_cancel_set(ctx, CANCEL_ASYNC_BIT); + + if (CANCEL_IS_ACTIVE(oldval | CANCEL_ASYNC_BIT)) { + pthread_exit((void *)PTHREAD_CANCELED); + /* no return */ + } + + return ((oldval & CANCEL_ASYNC_BIT) != 0) ? PTHREAD_CANCEL_ASYNCHRONOUS : PTHREAD_CANCEL_DEFERRED; +#endif +} + + +void _pthread_disable_asynccancel(int oldtype) +{ + pthread_ctx *ctx; + int oldval; + + if (oldtype == PTHREAD_CANCEL_ASYNCHRONOUS) { + return; + } + + ctx = (pthread_ctx *)pthread_self(); + + if (ctx == NULL) { + /* See note in _pthread_enable_asynccancel() */ + return; + } + + oldval = _pthread_cancel_clear(ctx, CANCEL_ASYNC_BIT); + + if ((oldval & CANCEL_INPROGRESS_BIT) != 0) { + _pthread_wait_for_cancel(ctx); + } +} + + int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared) { int err = EOK; diff --git a/signal/signal.c b/signal/signal.c index c6dd99c21..794dac998 100644 --- a/signal/signal.c +++ b/signal/signal.c @@ -21,6 +21,8 @@ #include #include +#include "../common/cancellation.h" + extern int sys_tkill(int pid, int tid, int signal); @@ -357,7 +359,7 @@ int sigsuspend(const sigset_t *sigmask) } } - return SET_ERRNO(signalSuspend(phxv)); + return SET_ERRNO(CANCELLATION_POINT(int, signalSuspend, (phxv))); } diff --git a/stdio/file.c b/stdio/file.c index 2dfff7126..f8fab5517 100644 --- a/stdio/file.c +++ b/stdio/file.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "../unistd/file-internal.h" @@ -1373,10 +1374,10 @@ FILE *popen(const char *command, const char *mode) goto failed; } - if ((pid = vfork()) < 0) { - goto failed; - } - else if (!pid) { + _pthread_nocancel_begin(); + + pid = vfork(); + if (pid == 0) { if (mode[0] == 'r') { dup2(fd[1], 1); } @@ -1391,6 +1392,12 @@ FILE *popen(const char *command, const char *mode) exit(EXIT_FAILURE); } + _pthread_nocancel_end(); + + if (pid < 0) { + goto failed; + } + pf->pid = pid; pf->file.bufpos = pf->file.bufeof = 0; pf->file.bufsz = BUFSIZ; diff --git a/stdlib/env.c b/stdlib/env.c index 9536f5530..a94b22c0d 100644 --- a/stdlib/env.c +++ b/stdlib/env.c @@ -21,6 +21,8 @@ #include #include +#include "../common/cancellation.h" + extern char **environ; static size_t _size = 0; /* Total number of slots. */ @@ -305,7 +307,7 @@ int system(const char *command) exit(EXIT_FAILURE); } - waitpid(pid, &ret, 0); + (void)CANCELLATION_POINT(pid_t, waitpid, (pid, &ret, 0)); sigprocmask(SIG_SETMASK, &old_mask, NULL); return ret; diff --git a/sys/select.c b/sys/select.c index aeb34811b..3e09a1c1a 100644 --- a/sys/select.c +++ b/sys/select.c @@ -23,6 +23,7 @@ #include #include "../common/util.h" +#include "../common/cancellation.h" /* POSIX requires the maximum timeout in select to be at least 31 days */ #define POSIX_MAX_TIMEOUT_MS (31LL * 24LL * 60LL * 60LL * 1000LL) @@ -31,7 +32,7 @@ /* clang-format off */ -WRAP_ERRNO_DEF(int, poll, (struct pollfd *fds, nfds_t nfds, int timeout_ms), (fds, nfds, timeout_ms)) +WRAP_ERRNO_DEF_CANCELLATION(int, poll, (struct pollfd *fds, nfds_t nfds, int timeout_ms), (fds, nfds, timeout_ms)) /* clang-format on */ @@ -81,7 +82,7 @@ int select(int nfds, fd_set *rd, fd_set *wr, fd_set *ex, struct timeval *to) sec = min(to->tv_sec, POSIX_MAX_TIMEOUT_MS / 1000); nsec = to->tv_usec * 1000; if (sec != 0 || nsec != 0) { - rv = SET_ERRNO(nsleep(&sec, &nsec, CLOCK_MONOTONIC, 0)); + rv = SET_ERRNO(CANCELLATION_POINT(int, nsleep, (&sec, &nsec, CLOCK_MONOTONIC, 0))); } else { rv = 0; diff --git a/sys/socket.c b/sys/socket.c index c75e048e8..e53090cae 100644 --- a/sys/socket.c +++ b/sys/socket.c @@ -31,15 +31,17 @@ #include #include -WRAP_ERRNO_DEF(int, accept4, (int socket, struct sockaddr *address, socklen_t *address_len, int flags), (socket, address, address_len, flags)) +#include "../common/cancellation.h" + +WRAP_ERRNO_DEF_CANCELLATION(int, accept4, (int socket, struct sockaddr *address, socklen_t *address_len, int flags), (socket, address, address_len, flags)) WRAP_ERRNO_DEF(int, bind, (int socket, const struct sockaddr *address, socklen_t address_len), (socket, address, address_len)) -WRAP_ERRNO_DEF(int, connect, (int socket, const struct sockaddr *address, socklen_t address_len), (socket, address, address_len)) +WRAP_ERRNO_DEF_CANCELLATION(int, connect, (int socket, const struct sockaddr *address, socklen_t address_len), (socket, address, address_len)) WRAP_ERRNO_DEF(int, getpeername, (int socket, struct sockaddr *address, socklen_t *address_len), (socket, address, address_len)) WRAP_ERRNO_DEF(int, getsockname, (int socket, struct sockaddr *address, socklen_t *address_len), (socket, address, address_len)) WRAP_ERRNO_DEF(int, getsockopt, (int socket, int level, int optname, void *optval, socklen_t *optlen), (socket, level, optname, optval, optlen)) WRAP_ERRNO_DEF(int, listen, (int socket, int backlog), (socket, backlog)) -WRAP_ERRNO_DEF(ssize_t, recvfrom, (int socket, void *message, size_t length, int flags, struct sockaddr *src_addr, socklen_t *src_len), (socket, message, length, flags, src_addr, src_len)) -WRAP_ERRNO_DEF(ssize_t, sendto, (int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len), (socket, message, length, flags, dest_addr, dest_len)) +WRAP_ERRNO_DEF_CANCELLATION(ssize_t, recvfrom, (int socket, void *message, size_t length, int flags, struct sockaddr *src_addr, socklen_t *src_len), (socket, message, length, flags, src_addr, src_len)) +WRAP_ERRNO_DEF_CANCELLATION(ssize_t, sendto, (int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len), (socket, message, length, flags, dest_addr, dest_len)) WRAP_ERRNO_DEF(int, socket, (int domain, int type, int protocol), (domain, type, protocol)) WRAP_ERRNO_DEF(int, socketpair, (int domain, int type, int protocol, int sv[2]), (domain, type, protocol, sv)) WRAP_ERRNO_DEF(int, shutdown, (int socket, int how), (socket, how)) @@ -119,7 +121,7 @@ ssize_t sendmsg(int socket, const struct msghdr *msg, int flags) if (len >= 0) { if (msg->msg_iovlen <= 1) { - len = sys_sendmsg(socket, msg, flags); + len = CANCELLATION_POINT(ssize_t, sys_sendmsg, (socket, msg, flags)); } else { /* copy data from scatter-gather buffers to a temporary buffer */ struct iovec _iov = { @@ -140,7 +142,7 @@ ssize_t sendmsg(int socket, const struct msghdr *msg, int flags) _iov.iov_base = buf; copy_from_iov(buf, msg->msg_iov, msg->msg_iovlen); - len = sys_sendmsg(socket, &_msg, flags); + len = CANCELLATION_POINT(ssize_t, sys_sendmsg, (socket, &_msg, flags)); } else { void *buf; @@ -151,7 +153,7 @@ ssize_t sendmsg(int socket, const struct msghdr *msg, int flags) _iov.iov_base = buf; copy_from_iov(buf, msg->msg_iov, msg->msg_iovlen); - len = sys_sendmsg(socket, &_msg, flags); + len = CANCELLATION_POINT(ssize_t, sys_sendmsg, (socket, &_msg, flags)); free(buf); } } @@ -167,7 +169,7 @@ ssize_t recvmsg(int socket, struct msghdr *msg, int flags) if (len >= 0) { if (msg->msg_iovlen <= 1) { - len = sys_recvmsg(socket, msg, flags); + len = CANCELLATION_POINT(ssize_t, sys_recvmsg, (socket, msg, flags)); } else { /* copy data from a temporary buffer to scatter-gather buffers */ struct iovec _iov = { @@ -187,7 +189,7 @@ ssize_t recvmsg(int socket, struct msghdr *msg, int flags) char buf[64]; /* small buffer optimization */ _iov.iov_base = buf; - len = sys_recvmsg(socket, &_msg, flags); + len = CANCELLATION_POINT(ssize_t, sys_recvmsg, (socket, &_msg, flags)); copy_to_iov(buf, msg->msg_iov, msg->msg_iovlen, len); } else { @@ -198,7 +200,7 @@ ssize_t recvmsg(int socket, struct msghdr *msg, int flags) return SET_ERRNO(-ENOMEM); _iov.iov_base = buf; - len = sys_recvmsg(socket, &_msg, flags); + len = CANCELLATION_POINT(ssize_t, sys_recvmsg, (socket, &_msg, flags)); copy_to_iov(buf, msg->msg_iov, msg->msg_iovlen, len); free(buf); } diff --git a/sys/stat.c b/sys/stat.c index 89ca4cf39..5c7e4c2d3 100644 --- a/sys/stat.c +++ b/sys/stat.c @@ -25,6 +25,7 @@ #include "posix/utils.h" +#include "../common/cancellation.h" /* path needs to be canonical */ static int _stat_abs(const char *path, struct stat *buf) diff --git a/sys/wait.c b/sys/wait.c index d8877cc24..0829db3e2 100644 --- a/sys/wait.c +++ b/sys/wait.c @@ -16,6 +16,6 @@ #include #include +#include "../common/cancellation.h" -WRAP_ERRNO_DEF(pid_t, waitpid, (pid_t pid, int *status, int options), (pid, status, options)) - +WRAP_ERRNO_DEF_CANCELLATION(pid_t, waitpid, (pid_t pid, int *status, int options), (pid, status, options)) diff --git a/termios/termios.c b/termios/termios.c index a99b09c36..b7656d4b9 100644 --- a/termios/termios.c +++ b/termios/termios.c @@ -17,6 +17,8 @@ #include #include +#include "../common/cancellation.h" + int tcgetattr(int fildes, struct termios *termios_p) { @@ -75,7 +77,7 @@ int tcflush(int fd, int queue_selector) return ret; } -int tcdrain(int fd) +static int _tcdrain(int fd) { int ret; do { @@ -85,6 +87,11 @@ int tcdrain(int fd) return ret; } +int tcdrain(int fd) +{ + return CANCELLATION_POINT(int, _tcdrain, (fd)); +} + int tcflow(int fd, int action) { int ret; diff --git a/time/time.c b/time/time.c index 3de0e59f0..392245808 100644 --- a/time/time.c +++ b/time/time.c @@ -23,6 +23,7 @@ #include #include "../common/util.h" +#include "../common/cancellation.h" char *tzname[2]; @@ -533,7 +534,7 @@ int nanosleep(const struct timespec *req, struct timespec *rem) long nsec = req->tv_nsec; int ret; - ret = nsleep(&sec, &nsec, CLOCK_MONOTONIC, 0); + ret = CANCELLATION_POINT(int, nsleep, (&sec, &nsec, CLOCK_MONOTONIC, 0)); if (ret == -EINTR && rem != NULL) { rem->tv_sec = sec; @@ -555,7 +556,7 @@ int clock_nanosleep(clockid_t clock, int flags, const struct timespec *req, stru return EINVAL; } - int ret = nsleep(&sec, &nsec, clock, flags); + int ret = CANCELLATION_POINT(int, nsleep, (&sec, &nsec, clock, flags)); if ((ret == -EINTR) && (rem != NULL) && ((flags & TIMER_ABSTIME) == 0)) { rem->tv_sec = sec; diff --git a/unistd/file.c b/unistd/file.c index c15bae57e..5047d93ad 100644 --- a/unistd/file.c +++ b/unistd/file.c @@ -28,6 +28,8 @@ #include #include +#include "../common/cancellation.h" + #include "posix/utils.h" #include "ioctl-helper.h" @@ -42,22 +44,22 @@ extern int sys_pipe(int fildes[2]); extern int sys_fstat(int fd, struct stat *buf); extern int sys_lseek(int fildes, off_t *offset, int whence); -WRAP_ERRNO_DEF(int, close, (int fildes), (fildes)) +WRAP_ERRNO_DEF_CANCELLATION(int, close, (int fildes), (fildes)) WRAP_ERRNO_DEF(int, ftruncate, (int fildes, off_t length), (fildes, length)) WRAP_ERRNO_DEF(int, dup, (int fildes), (fildes)) WRAP_ERRNO_DEF(int, dup2, (int fildes, int fildes2), (fildes, fildes2)) -WRAP_ERRNO_DEF(int, fsync, (int fildes), (fildes)) +WRAP_ERRNO_DEF_CANCELLATION(int, fsync, (int fildes), (fildes)) ssize_t read(int fildes, void *buf, size_t nbyte) { - return SET_ERRNO(sys_read(fildes, buf, nbyte, -1)); + return SET_ERRNO(CANCELLATION_POINT(ssize_t, sys_read, (fildes, buf, nbyte, -1))); } ssize_t write(int fildes, const void *buf, size_t nbyte) { - return SET_ERRNO(sys_write(fildes, buf, nbyte, -1)); + return SET_ERRNO(CANCELLATION_POINT(ssize_t, sys_write, (fildes, buf, nbyte, -1))); } @@ -67,7 +69,7 @@ ssize_t pread(int fildes, void *buf, size_t nbyte, off_t offset) errno = EINVAL; return -1; } - return SET_ERRNO(sys_read(fildes, buf, nbyte, offset)); + return SET_ERRNO(CANCELLATION_POINT(ssize_t, sys_read, (fildes, buf, nbyte, offset))); } @@ -77,7 +79,7 @@ ssize_t pwrite(int fildes, const void *buf, size_t nbyte, off_t offset) errno = EINVAL; return -1; } - return SET_ERRNO(sys_write(fildes, buf, nbyte, offset)); + return SET_ERRNO(CANCELLATION_POINT(ssize_t, sys_write, (fildes, buf, nbyte, offset))); } @@ -240,6 +242,8 @@ ssize_t __safe_pread_nb(int fd, void *buf, size_t size, off_t offset) } +/* FIXME: __safe_open/close should likely not be cancellation points */ + int __safe_open(const char *path, int oflag, mode_t mode) { int err; @@ -359,9 +363,9 @@ int open(const char *filename, int oflag, ...) if (canonical == NULL) return -1; /* errno set by resolve_path */ - do - err = sys_open(canonical, oflag, mode); - while (err == -EINTR); + do { + err = CANCELLATION_POINT(int, sys_open, (canonical, oflag, mode)); + } while (err == -EINTR); free(canonical); return SET_ERRNO(err); @@ -756,14 +760,22 @@ extern int sys_fcntl(int fd, int cmd, unsigned val); int fcntl(int fd, int cmd, ...) { va_list ap; - unsigned val; + unsigned int val; + int ret; /* FIXME: handle varargs properly */ va_start(ap, cmd); val = va_arg(ap, unsigned); va_end(ap); - return SET_ERRNO(sys_fcntl(fd, cmd, val)); + if (cmd == F_SETLKW) { + ret = CANCELLATION_POINT(int, sys_fcntl, (fd, cmd, val)); + } + else { + ret = sys_fcntl(fd, cmd, val); + } + + return SET_ERRNO(ret); } diff --git a/unistd/pause.c b/unistd/pause.c index 7bd802816..b852f3751 100644 --- a/unistd/pause.c +++ b/unistd/pause.c @@ -15,11 +15,18 @@ #include #include +#include int pause(void) { sigset_t mask; + int ret; + + int oldval = _pthread_enable_asynccancel(); (void)sigprocmask(SIG_BLOCK, NULL, &mask); - return sigsuspend(&mask); + ret = sigsuspend(&mask); + _pthread_disable_asynccancel(oldval); + + return ret; } diff --git a/unistd/sys.c b/unistd/sys.c index fcd13b807..820b39903 100644 --- a/unistd/sys.c +++ b/unistd/sys.c @@ -30,6 +30,9 @@ #include +#include "../common/cancellation.h" + + WRAP_ERRNO_DEF(int, setpgid, (pid_t pid, pid_t pgid), (pid, pgid)) WRAP_ERRNO_DEF(int, setpgrp, (void), ()) @@ -96,7 +99,7 @@ int execv(const char *path, char *const argv[]) } -int execve(const char *file, char *const argv[], char *const envp[]) +static int _execve(const char *file, char *const argv[], char *const envp[]) { int fd, noargs = 0, err; char *interp, *end; @@ -205,6 +208,23 @@ int execve(const char *file, char *const argv[], char *const envp[]) } +int execve(const char *file, char *const argv[], char *const envp[]) +{ + int ret; + + /* + * exec() is not a cancellation point, but the lookup above reaches open(), + * read() and close(), which are. It also can run in a vfork child, + * where those would operate on the parent thread's context/TLS. + */ + _pthread_nocancel_begin(); + ret = _execve(file, argv, envp); + _pthread_nocancel_end(); + + return ret; +} + + int execvp(const char *file, char *const argv[]) { return execvpe(file, argv, environ); @@ -321,7 +341,7 @@ int usleep(useconds_t usecs) time_t sec = usecs / (1000 * 1000); long nsec = (usecs % (1000 * 1000)) * 1000; - err = nsleep(&sec, &nsec, CLOCK_MONOTONIC, 0); + err = CANCELLATION_POINT(int, nsleep, (&sec, &nsec, CLOCK_MONOTONIC, 0)); SET_ERRNO(err); @@ -336,7 +356,7 @@ unsigned sleep(unsigned seconds) long nsec = 0; unsigned unslept; - err = nsleep(&sec, &nsec, CLOCK_MONOTONIC, 0); + err = CANCELLATION_POINT(int, nsleep, (&sec, &nsec, CLOCK_MONOTONIC, 0)); unslept = (err == -EINTR) ? (unsigned)sec : 0; return unslept; @@ -350,9 +370,11 @@ extern void release(void); pid_t fork(void) { pid_t pid; + pthread_t self = pthread_self(); _pthread_atfork_prepare(); if (!(pid = sys_fork())) { release(); + _pthread_fork_child_reinit(self); _pthread_atfork_child(); } else if (pid < 0) {