Переглянути джерело

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.
Idan Horowitz 3 роки тому
батько
коміт
01f0ae20b6

+ 1 - 1
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)
 

+ 176 - 8
Userland/Libraries/LibC/semaphore.cpp

@@ -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;
 
+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)
+{
+    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*, 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;
 }

+ 1 - 0
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;