From 01f0ae20b68c4da3156ee40768da432d411c7e13 Mon Sep 17 00:00:00 2001 From: Idan Horowitz Date: Thu, 14 Jul 2022 03:31:12 +0300 Subject: [PATCH] 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. --- Userland/DynamicLoader/CMakeLists.txt | 2 +- Userland/Libraries/LibC/semaphore.cpp | 184 ++++++++++++++++++++++++-- Userland/Libraries/LibC/semaphore.h | 1 + 3 files changed, 178 insertions(+), 9 deletions(-) diff --git a/Userland/DynamicLoader/CMakeLists.txt b/Userland/DynamicLoader/CMakeLists.txt index 4b2c9aadc2533e..7505bf5148287c 100644 --- a/Userland/DynamicLoader/CMakeLists.txt +++ b/Userland/DynamicLoader/CMakeLists.txt @@ -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) diff --git a/Userland/Libraries/LibC/semaphore.cpp b/Userland/Libraries/LibC/semaphore.cpp index 5703eb8cc95470..355500730fabb4 100644 --- a/Userland/Libraries/LibC/semaphore.cpp +++ b/Userland/Libraries/LibC/semaphore.cpp @@ -1,16 +1,26 @@ /* * Copyright (c) 2021, Gunnar Beutner * Copyright (c) 2021, Sergey Bugaev + * Copyright (c) 2022, Idan Horowitz * * SPDX-License-Identifier: BSD-2-Clause */ #include #include +#include +#include +#include #include #include +#include +#include #include #include +#include +#include +#include +#include static constexpr u32 SEM_MAGIC = 0x78951230; @@ -18,25 +28,178 @@ static constexpr u32 SEM_MAGIC = 0x78951230; // threads. static constexpr u32 POST_WAKES = 1 << 31; +static constexpr auto sem_path_prefix = "/tmp/semaphore/"sv; +static constexpr auto SEM_NAME_MAX = PATH_MAX - sem_path_prefix.length(); +static ErrorOr sem_name_to_path(char const* name) +{ + 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 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*, int, ...) +sem_t* sem_open(char const* name, int flags, ...) { - errno = ENOSYS; - return nullptr; + 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; - return -1; + 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; } diff --git a/Userland/Libraries/LibC/semaphore.h b/Userland/Libraries/LibC/semaphore.h index 6171ecd6cec08e..ed009f0c7ed0b0 100644 --- a/Userland/Libraries/LibC/semaphore.h +++ b/Userland/Libraries/LibC/semaphore.h @@ -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;