LibPthread: Implement named semaphores

Note that as part of this commit semaphore.cpp is excluded from the
DynamicLoader, as the dynamic loader does not build with pthread.cpp
which semaphore.cpp uses.
This commit is contained in:
Idan Horowitz 2022-07-14 03:31:12 +03:00 committed by Andreas Kling
parent 23f3857cdd
commit 01f0ae20b6
Notes: sideshowbarker 2024-07-17 11:34:34 +09:00
3 changed files with 179 additions and 10 deletions

View file

@ -26,7 +26,7 @@ if (ENABLE_UNDEFINED_SANITIZER)
endif()
# pthread requires thread local storage, which DynamicLoader does not have.
list(FILTER LIBC_SOURCES1 EXCLUDE REGEX ".*/LibC/pthread\\.cpp")
list(FILTER LIBC_SOURCES1 EXCLUDE REGEX ".*/LibC/(pthread|semaphore)\\.cpp")
add_definitions(-D_DYNAMIC_LOADER)

View file

@ -1,16 +1,26 @@
/*
* Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
* Copyright (c) 2021, Sergey Bugaev <bugaevc@serenityos.org>
* Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Assertions.h>
#include <AK/Atomic.h>
#include <AK/HashMap.h>
#include <AK/ScopeGuard.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <serenity.h>
#include <string.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/stat.h>
static constexpr u32 SEM_MAGIC = 0x78951230;
@ -18,25 +28,178 @@ static constexpr u32 SEM_MAGIC = 0x78951230;
// threads.
static constexpr u32 POST_WAKES = 1 << 31;
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_open.html
sem_t* sem_open(char const*, int, ...)
static constexpr auto sem_path_prefix = "/tmp/semaphore/"sv;
static constexpr auto SEM_NAME_MAX = PATH_MAX - sem_path_prefix.length();
static ErrorOr<String> sem_name_to_path(char const* name)
{
errno = ENOSYS;
return nullptr;
if (name[0] != '/')
return EINVAL;
++name;
auto name_length = strnlen(name, SEM_NAME_MAX);
if (name[name_length])
return ENAMETOOLONG;
auto name_view = StringView { name, name_length };
if (name_view.contains('/'))
return EINVAL;
StringBuilder builder;
TRY(builder.try_append(sem_path_prefix));
TRY(builder.try_append(name_view));
return builder.build();
}
struct NamedSemaphore {
size_t times_opened { 0 };
dev_t dev { 0 };
ino_t ino { 0 };
sem_t* sem { nullptr };
};
static HashMap<String, NamedSemaphore> s_named_semaphores;
static pthread_mutex_t s_sem_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t s_sem_once = PTHREAD_ONCE_INIT;
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_open.html
sem_t* sem_open(char const* name, int flags, ...)
{
auto path_or_error = sem_name_to_path(name);
if (path_or_error.is_error()) {
errno = path_or_error.error().code();
return SEM_FAILED;
}
auto path = path_or_error.release_value();
if (flags & ~(O_CREAT | O_EXCL)) {
errno = EINVAL;
return SEM_FAILED;
}
mode_t mode = 0;
unsigned int value = 0;
if (flags & O_CREAT) {
va_list ap;
va_start(ap, flags);
mode = va_arg(ap, unsigned int);
value = va_arg(ap, unsigned int);
va_end(ap);
}
// Ensure we are not in the middle of modifying this structure while a child is being forked, which will cause the child to end up with a partially-modified entry
pthread_once(&s_sem_once, []() {
pthread_atfork([]() { pthread_mutex_lock(&s_sem_mutex); }, []() { pthread_mutex_unlock(&s_sem_mutex); }, []() { pthread_mutex_unlock(&s_sem_mutex); });
});
pthread_mutex_lock(&s_sem_mutex);
ScopeGuard unlock_guard = [] { pthread_mutex_unlock(&s_sem_mutex); };
int fd = open(path.characters(), O_RDWR | O_CLOEXEC | flags, mode);
if (fd == -1)
return SEM_FAILED;
ScopeGuard close_guard = [&fd] {
if (fd != -1)
close(fd);
};
if (flock(fd, LOCK_EX) == -1)
return SEM_FAILED;
struct stat statbuf;
if (fstat(fd, &statbuf) == -1)
return SEM_FAILED;
auto existing_semaphore = s_named_semaphores.get(path);
if (existing_semaphore.has_value()) {
// If the file did not exist (aka if O_CREAT && O_EXCL but no EEXIST), or if the inode was replaced, remove the entry and start from scratch
if ((flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL) || existing_semaphore->dev != statbuf.st_dev || existing_semaphore->ino != statbuf.st_ino) {
s_named_semaphores.remove(path);
} else { // otherwise, this is valid pre-existing named semaphore, so just increase the count and return it
existing_semaphore->times_opened++;
return existing_semaphore->sem;
}
}
// If the file is smaller than the size, it's an uninitialized semaphore, so let's write an initial value
if (statbuf.st_size < (off_t)sizeof(sem_t)) {
sem_t init_sem;
init_sem.magic = SEM_MAGIC;
init_sem.value = value;
init_sem.flags = SEM_FLAG_PROCESS_SHARED | SEM_FLAG_NAMED;
if (write(fd, &init_sem, sizeof(sem_t)) != sizeof(sem_t))
return SEM_FAILED;
}
if (flock(fd, LOCK_UN) == -1)
return SEM_FAILED;
auto* sem = (sem_t*)mmap(nullptr, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (sem == MAP_FAILED)
return SEM_FAILED;
ArmedScopeGuard munmap_guard = [&sem] {
munmap(sem, sizeof(sem_t));
};
if (sem->magic != SEM_MAGIC) {
errno = EINVAL;
return SEM_FAILED;
}
auto result = s_named_semaphores.try_set(move(path), { .times_opened = 1, .dev = statbuf.st_dev, .ino = statbuf.st_ino, .sem = sem });
if (result.is_error()) {
errno = result.error().code();
return SEM_FAILED;
}
munmap_guard.disarm();
return sem;
}
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_close.html
int sem_close(sem_t*)
int sem_close(sem_t* sem)
{
errno = ENOSYS;
if (sem->magic != SEM_MAGIC) {
errno = EINVAL;
return -1;
}
if ((sem->flags & SEM_FLAG_NAMED) == 0) {
errno = EINVAL;
return -1;
}
pthread_mutex_lock(&s_sem_mutex);
ScopeGuard unlock_guard = [] { pthread_mutex_unlock(&s_sem_mutex); };
auto it = s_named_semaphores.begin();
for (; it != s_named_semaphores.end(); ++it) {
if (it->value.sem != sem)
continue;
auto is_last = --it->value.times_opened == 0;
if (is_last) {
munmap(it->value.sem, sizeof(sem_t));
s_named_semaphores.remove(it);
}
return 0;
}
errno = EINVAL;
return -1;
}
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_unlink.html
int sem_unlink(char const*)
int sem_unlink(char const* name)
{
errno = ENOSYS;
auto path_or_error = sem_name_to_path(name);
if (path_or_error.is_error()) {
errno = path_or_error.error().code();
return -1;
}
auto path = path_or_error.release_value();
return unlink(path.characters());
}
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_init.html
@ -61,6 +224,11 @@ int sem_destroy(sem_t* sem)
return -1;
}
if (sem->flags & SEM_FLAG_NAMED) {
errno = EINVAL;
return -1;
}
sem->magic = 0;
return 0;
}

View file

@ -14,6 +14,7 @@
__BEGIN_DECLS
#define SEM_FLAG_PROCESS_SHARED (1 << 0)
#define SEM_FLAG_NAMED (1 << 1)
typedef struct {
uint32_t magic;
uint32_t value;