mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-08-03 08:20:28 +00:00
Implement pthread_atfork()
If threads are being used, then fork() will now acquire and release and runtime locks so that fork() may be safely used from threads. This also makes vfork() thread safe, because pthread mutexes will do nothing when the process is a child of vfork(). More torture tests have been written to confirm this all works like a charm. Additionally: - Invent hexpcpy() api - Rename nsync_malloc_() to kmalloc() - Complete posix named semaphore implementation - Make pthread_create() asynchronous signal safe - Add rm, rmdir, and touch to command interpreter builtins - Invent sigisprecious() and modify sigset functions to use it - Add unit tests for posix_spawn() attributes and fix its bugs One unresolved problem is the reclaiming of *NSYNC waiter memory in the forked child processes, within apps which have threads waiting on locks
This commit is contained in:
parent
64c284003d
commit
60cb435cb4
124 changed files with 2169 additions and 718 deletions
|
@ -18,60 +18,298 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/stat.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/nopl.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/at.h"
|
||||
#include "libc/sysv/consts/map.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/prot.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/thread/semaphore.h"
|
||||
#include "libc/thread/semaphore.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
/**
|
||||
* Initializes and opens named semaphore.
|
||||
*
|
||||
* @param name can be absolute path or should be component w/o slashes
|
||||
* @param oflga can have `O_CREAT` and/or `O_EXCL`
|
||||
* @return semaphore object which needs sem_close(), or SEM_FAILED w/ errno
|
||||
* @raise ENOTDIR if a directory component in `name` exists as non-directory
|
||||
* @raise ENAMETOOLONG if symlink-resolved `name` length exceeds `PATH_MAX`
|
||||
* @raise ENAMETOOLONG if component in `name` exists longer than `NAME_MAX`
|
||||
* @raise ELOOP if `flags` had `O_NOFOLLOW` and `name` is a symbolic link
|
||||
* @raise ENOSPC if file system is full when `name` would be `O_CREAT`ed
|
||||
* @raise ELOOP if a loop was detected resolving components of `name`
|
||||
* @raise EEXIST if `O_CREAT|O_EXCL` is used and semaphore exists
|
||||
* @raise EACCES if we didn't have permission to create semaphore
|
||||
* @raise EMFILE if process `RLIMIT_NOFILE` has been reached
|
||||
* @raise ENFILE if system-wide file limit has been reached
|
||||
* @raise EINTR if signal was delivered instead
|
||||
*/
|
||||
sem_t *sem_open(const char *name, int oflag, ...) {
|
||||
static struct Semaphores {
|
||||
pthread_once_t once;
|
||||
pthread_mutex_t lock;
|
||||
struct Semaphore {
|
||||
struct Semaphore *next;
|
||||
sem_t *sem;
|
||||
char *path;
|
||||
bool dead;
|
||||
int refs;
|
||||
} * list;
|
||||
} g_semaphores;
|
||||
|
||||
static void sem_open_lock(void) {
|
||||
pthread_mutex_lock(&g_semaphores.lock);
|
||||
}
|
||||
|
||||
static void sem_open_unlock(void) {
|
||||
pthread_mutex_unlock(&g_semaphores.lock);
|
||||
}
|
||||
|
||||
static void sem_open_funlock(void) {
|
||||
pthread_mutex_init(&g_semaphores.lock, 0);
|
||||
}
|
||||
|
||||
static void sem_open_setup(void) {
|
||||
sem_open_funlock();
|
||||
pthread_atfork(sem_open_lock, sem_open_unlock, sem_open_funlock);
|
||||
}
|
||||
|
||||
static void sem_open_init(void) {
|
||||
pthread_once(&g_semaphores.once, sem_open_setup);
|
||||
}
|
||||
|
||||
#ifdef _NOPL0
|
||||
#define sem_open_lock() _NOPL0("__threadcalls", sem_open_lock)
|
||||
#define sem_open_unlock() _NOPL0("__threadcalls", sem_open_unlock)
|
||||
#endif
|
||||
|
||||
static sem_t *sem_open_impl(const char *path, int oflag, unsigned mode,
|
||||
unsigned value) {
|
||||
int fd;
|
||||
sem_t *sem;
|
||||
va_list va;
|
||||
unsigned mode;
|
||||
char path[PATH_MAX];
|
||||
|
||||
va_start(va, oflag);
|
||||
mode = va_arg(va, unsigned);
|
||||
va_end(va);
|
||||
|
||||
struct stat st;
|
||||
oflag |= O_RDWR | O_CLOEXEC;
|
||||
if ((fd = openat(AT_FDCWD, __sem_name(name, path), oflag, mode)) == -1) {
|
||||
if ((fd = openat(AT_FDCWD, path, oflag, mode)) == -1) {
|
||||
return SEM_FAILED;
|
||||
}
|
||||
|
||||
if (ftruncate(fd, sizeof(sem_t)) == -1) {
|
||||
_npassert(!fstat(fd, &st));
|
||||
if (st.st_size < PAGESIZE && ftruncate(fd, PAGESIZE) == -1) {
|
||||
_npassert(!close(fd));
|
||||
return SEM_FAILED;
|
||||
}
|
||||
|
||||
sem = mmap(0, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
sem = mmap(0, PAGESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (sem != MAP_FAILED) {
|
||||
atomic_store_explicit(&sem->sem_value, value, memory_order_relaxed);
|
||||
sem->sem_magic = SEM_MAGIC_NAMED;
|
||||
sem->sem_dev = st.st_dev;
|
||||
sem->sem_ino = st.st_ino;
|
||||
sem->sem_pshared = true;
|
||||
} else {
|
||||
sem = SEM_FAILED;
|
||||
}
|
||||
|
||||
_npassert(!close(fd));
|
||||
return sem;
|
||||
}
|
||||
|
||||
static struct Semaphore *sem_open_find(const char *path) {
|
||||
struct Semaphore *s;
|
||||
for (s = g_semaphores.list; s; s = s->next) {
|
||||
if (!strcmp(path, s->path)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct Semaphore *sem_open_reopen(const char *path) {
|
||||
int e = errno;
|
||||
struct stat st;
|
||||
struct Semaphore *s;
|
||||
for (s = g_semaphores.list; s; s = s->next) {
|
||||
if (!s->dead && //
|
||||
!strcmp(path, s->path)) {
|
||||
if (!fstatat(AT_FDCWD, path, &st, 0) && //
|
||||
st.st_dev == s->sem->sem_dev && //
|
||||
st.st_ino == s->sem->sem_ino) {
|
||||
return s;
|
||||
} else {
|
||||
errno = e;
|
||||
s->dead = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct Semaphore *sem_open_get(const sem_t *sem,
|
||||
struct Semaphore ***out_prev) {
|
||||
struct Semaphore *s, *t, **p;
|
||||
for (p = &g_semaphores.list, s = *p; s; p = &s->next, s = s->next) {
|
||||
if (s && sem == s->sem) {
|
||||
*out_prev = p;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and opens named semaphore.
|
||||
*
|
||||
* This function tracks open semaphore objects within a process. When a
|
||||
* process calls sem_open() multiple times with the same name, then the
|
||||
* same shared memory address will be returned, unless it was unlinked.
|
||||
*
|
||||
* @param name is arbitrary string that begins with a slash character
|
||||
* @param oflag can have any of:
|
||||
* - `O_CREAT` to create the named semaphore if it doesn't exist,
|
||||
* in which case two additional arguments must be supplied
|
||||
* - `O_EXCL` to raise `EEXIST` if semaphore already exists
|
||||
* @param mode is octal mode bits, required if `oflag & O_CREAT`
|
||||
* @param value is initial of semaphore, required if `oflag & O_CREAT`
|
||||
* @return semaphore object which needs sem_close(), or SEM_FAILED w/ errno
|
||||
* @raise ENOSPC if file system is full when `name` would be `O_CREAT`ed
|
||||
* @raise EINVAL if `oflag` has bits other than `O_CREAT | O_EXCL`
|
||||
* @raise EINVAL if `value` is negative or exceeds `SEM_VALUE_MAX`
|
||||
* @raise EEXIST if `O_CREAT|O_EXCL` is used and semaphore exists
|
||||
* @raise EACCES if we didn't have permission to create semaphore
|
||||
* @raise EACCES if recreating open semaphore pending an unlink
|
||||
* @raise EMFILE if process `RLIMIT_NOFILE` has been reached
|
||||
* @raise ENFILE if system-wide file limit has been reached
|
||||
* @raise ENOMEM if we require more vespene gas
|
||||
* @raise EINTR if signal handler was called
|
||||
* @threadsafe
|
||||
*/
|
||||
sem_t *sem_open(const char *name, int oflag, ...) {
|
||||
sem_t *sem;
|
||||
va_list va;
|
||||
struct Semaphore *s;
|
||||
unsigned mode = 0, value = 0;
|
||||
char *path, pathbuf[PATH_MAX];
|
||||
if (oflag & ~(O_CREAT | O_EXCL)) {
|
||||
einval();
|
||||
return SEM_FAILED;
|
||||
}
|
||||
if (oflag & O_CREAT) {
|
||||
va_start(va, oflag);
|
||||
mode = va_arg(va, unsigned);
|
||||
value = va_arg(va, unsigned);
|
||||
va_end(va);
|
||||
if (value > SEM_VALUE_MAX) {
|
||||
einval();
|
||||
return SEM_FAILED;
|
||||
}
|
||||
}
|
||||
if (!(path = sem_path_np(name, pathbuf, sizeof(pathbuf)))) {
|
||||
return SEM_FAILED;
|
||||
}
|
||||
sem_open_init();
|
||||
sem_open_lock();
|
||||
if ((s = sem_open_reopen(path))) {
|
||||
if (s->sem->sem_lazydelete) {
|
||||
if (oflag & O_CREAT) {
|
||||
eacces();
|
||||
} else {
|
||||
enoent();
|
||||
}
|
||||
sem = SEM_FAILED;
|
||||
} else if (~oflag & O_EXCL) {
|
||||
sem = s->sem;
|
||||
atomic_fetch_add_explicit(&sem->sem_prefs, 1, memory_order_acquire);
|
||||
++s->refs;
|
||||
} else {
|
||||
eexist();
|
||||
sem = SEM_FAILED;
|
||||
}
|
||||
} else if ((s = calloc(1, sizeof(struct Semaphore)))) {
|
||||
if ((s->path = strdup(path))) {
|
||||
if ((sem = sem_open_impl(path, oflag, mode, value)) != SEM_FAILED) {
|
||||
atomic_fetch_add_explicit(&sem->sem_prefs, 1, memory_order_relaxed);
|
||||
s->next = g_semaphores.list;
|
||||
s->sem = sem;
|
||||
s->refs = 1;
|
||||
g_semaphores.list = s;
|
||||
} else {
|
||||
free(s->path);
|
||||
free(s);
|
||||
}
|
||||
} else {
|
||||
free(s);
|
||||
sem = SEM_FAILED;
|
||||
}
|
||||
} else {
|
||||
sem = SEM_FAILED;
|
||||
}
|
||||
sem_open_unlock();
|
||||
return sem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes named semaphore.
|
||||
*
|
||||
* Calling sem_close() on a semaphore not created by sem_open() has
|
||||
* undefined behavior. Using `sem` after calling sem_close() from either
|
||||
* the current process or forked processes sharing the same address is
|
||||
* also undefined behavior. If any threads in this process or forked
|
||||
* children are currently blocked on `sem` then calling sem_close() has
|
||||
* undefined behavior.
|
||||
*
|
||||
* @param sem was created with sem_open()
|
||||
* @return 0 on success, or -1 w/ errno
|
||||
*/
|
||||
int sem_close(sem_t *sem) {
|
||||
int rc, prefs;
|
||||
bool unmap, delete;
|
||||
struct Semaphore *s, **p;
|
||||
_npassert(sem->sem_magic == SEM_MAGIC_NAMED);
|
||||
sem_open_init();
|
||||
sem_open_lock();
|
||||
_npassert((s = sem_open_get(sem, &p)));
|
||||
prefs = atomic_fetch_add_explicit(&sem->sem_prefs, -1, memory_order_release);
|
||||
_npassert(s->refs > 0);
|
||||
if ((unmap = !--s->refs)) {
|
||||
_npassert(prefs > 0);
|
||||
delete = sem->sem_lazydelete && prefs == 1;
|
||||
*p = s->next;
|
||||
} else {
|
||||
_npassert(prefs > 1);
|
||||
delete = false;
|
||||
}
|
||||
sem_open_unlock();
|
||||
if (unmap) {
|
||||
_npassert(!munmap(sem, PAGESIZE));
|
||||
}
|
||||
if (delete) {
|
||||
rc = unlink(s->path);
|
||||
}
|
||||
if (unmap) {
|
||||
free(s->path);
|
||||
free(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes named semaphore.
|
||||
*
|
||||
* This causes the file resource to be deleted. If processes have this
|
||||
* semaphore currently opened, then on platforms like Windows deletion
|
||||
* may be postponed until the last process calls sem_close().
|
||||
*
|
||||
* @param name can be absolute path or should be component w/o slashes
|
||||
* @return 0 on success, or -1 w/ errno
|
||||
* @raise ACCESS if Windows is being fussy about deleting open files
|
||||
* @raise EPERM if pledge() is in play w/o `cpath` promise
|
||||
* @raise ENOENT if named semaphore doesn't exist
|
||||
* @raise EACCES if permission is denied
|
||||
* @raise ENAMETOOLONG if too long
|
||||
*/
|
||||
int sem_unlink(const char *name) {
|
||||
int rc, e = errno;
|
||||
struct Semaphore *s;
|
||||
char *path, pathbuf[PATH_MAX];
|
||||
if (!(path = sem_path_np(name, pathbuf, sizeof(pathbuf)))) return -1;
|
||||
if ((rc = unlink(path)) == -1 && IsWindows() && errno == EACCES) {
|
||||
sem_open_init();
|
||||
sem_open_lock();
|
||||
if ((s = sem_open_find(path))) {
|
||||
s->sem->sem_lazydelete = true;
|
||||
errno = e;
|
||||
rc = 0;
|
||||
}
|
||||
sem_open_unlock();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue