| //===-- Named semaphore implementation for Linux --------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "src/semaphore/linux/semaphore.h" |
| |
| #include "hdr/errno_macros.h" |
| #include "hdr/fcntl_macros.h" |
| #include "src/__support/CPP/array.h" |
| #include "src/__support/CPP/limits.h" |
| #include "src/__support/CPP/new.h" |
| #include "src/__support/CPP/string_view.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/close.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/ftruncate.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/getrandom.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/link.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/open.h" |
| #include "src/__support/OSUtil/linux/syscall_wrappers/unlink.h" |
| #include "src/__support/ctype_utils.h" |
| #include "src/__support/error_or.h" |
| #include "src/__support/macros/config.h" |
| #include "src/string/memory_utils/inline_memcpy.h" |
| #include "src/sys/mman/linux/shm_common.h" |
| |
| #include <linux/mman.h> // PROT_READ, PROT_WRITE, MAP_SHARED |
| |
| namespace LIBC_NAMESPACE_DECL { |
| |
| namespace { |
| |
| // define SEM_VALUE_MAX as INT_MAX |
| constexpr unsigned int SEM_VALUE_MAX = |
| static_cast<unsigned int>(cpp::numeric_limits<int>::max()); |
| |
| // Named semaphores are backed by files in /dev/shm/. |
| // a prefix "sem." is added to avoid name collision. |
| constexpr cpp::string_view SEM_PREFIX = "/dev/shm/sem."; |
| |
| // use temporary file to solve data race and guarantee atomic publish. |
| // Temporary file use different prefix. |
| constexpr cpp::string_view SEM_TMP_PREFIX = "/dev/shm/sem.tmp_"; |
| |
| // 8 random bytes from getrandom() produce a 16 character hex suffix, giving |
| // 2^64 possible temp names to avoid collision. |
| constexpr size_t RANDOM_SUFFIX_BYTES = 8; |
| constexpr size_t RANDOM_SUFFIX_HEX_LEN = RANDOM_SUFFIX_BYTES * 2; |
| |
| // fixed-size buffer for the temp path. |
| using TmpPath = |
| cpp::array<char, SEM_TMP_PREFIX.size() + RANDOM_SUFFIX_HEX_LEN + 1>; |
| |
| // O_NOFOLLOW prevents symlink attacks to /dev/shm/. O_CLOEXEC ensures the |
| // fd is not leaked to child processes across exec. |
| constexpr int DEFAULT_OFLAGS = O_NOFOLLOW | O_CLOEXEC; |
| |
| ErrorOr<TmpPath> generate_tmp_path() { |
| // fill out 8 random bytes. |
| cpp::array<uint8_t, RANDOM_SUFFIX_BYTES> rand_bytes{}; |
| auto ret = linux_syscalls::getrandom(rand_bytes.data(), rand_bytes.size(), 0); |
| if (!ret.has_value()) |
| return Error(ret.error()); |
| |
| TmpPath path; |
| inline_memcpy(path.data(), SEM_TMP_PREFIX.data(), SEM_TMP_PREFIX.size()); |
| |
| // Encode each random byte as two hex digits, and fill out tmp path. |
| char *dst = path.data() + SEM_TMP_PREFIX.size(); |
| for (size_t i = 0; i < RANDOM_SUFFIX_BYTES; ++i) { |
| *dst++ = internal::int_to_b36_char(rand_bytes[i] >> 4); |
| *dst++ = internal::int_to_b36_char(rand_bytes[i] & 0xf); |
| } |
| *dst = '\0'; |
| return path; |
| } |
| |
| // map an open semaphore fd into memory. |
| ErrorOr<Semaphore *> map_semaphore(int fd) { |
| auto mmap_or = linux_syscalls::mmap( |
| nullptr, sizeof(Semaphore), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
| linux_syscalls::close(fd); |
| |
| if (!mmap_or.has_value()) |
| return Error(mmap_or.error()); |
| |
| return reinterpret_cast<Semaphore *>(mmap_or.value()); |
| } |
| |
| // open an existing named semaphore file and map it. |
| ErrorOr<Semaphore *> open_existing(const char *path) { |
| auto fd_or = linux_syscalls::open(path, O_RDWR | DEFAULT_OFLAGS, 0); |
| if (!fd_or.has_value()) |
| return Error(fd_or.error()); |
| return map_semaphore(fd_or.value()); |
| } |
| |
| } // anonymous namespace |
| |
| ErrorOr<Semaphore *> Semaphore::open(const char *name, int oflag, mode_t mode, |
| unsigned int value) { |
| auto path_or = shm_common::translate_name<SEM_PREFIX>(name); |
| if (!path_or.has_value()) |
| return Error(path_or.error()); |
| |
| // open an existing semaphore. |
| if (!(oflag & O_CREAT)) |
| return open_existing(path_or->data()); |
| |
| // check semaphore value. |
| if (value > SEM_VALUE_MAX) |
| return Error(EINVAL); |
| |
| // two step creation: |
| // 1. create and fully initialize a temporary file. |
| // 2. link() it and publish to the final path atomically. |
| // This ensures no other process can observe a partially-initialized |
| // semaphore through the final path. If link() fails with EEXIST and |
| // O_EXCL is not set, fall back to opening the existing semaphore. |
| |
| auto tmp_or = generate_tmp_path(); |
| if (!tmp_or.has_value()) |
| return Error(tmp_or.error()); |
| |
| // if two process happen to map the same random tmp_path, though rare |
| // in 2^64 namespace, one succees and the other return EEXIST. |
| auto fd_or = linux_syscalls::open( |
| tmp_or->data(), O_RDWR | O_CREAT | O_EXCL | DEFAULT_OFLAGS, mode); |
| if (!fd_or.has_value()) |
| return Error(fd_or.error()); |
| |
| int fd = fd_or.value(); |
| |
| // resizing temporary semaphore backing file. |
| auto trunc_or = |
| linux_syscalls::ftruncate(fd, static_cast<off_t>(sizeof(Semaphore))); |
| if (!trunc_or.has_value()) { |
| linux_syscalls::close(fd); |
| linux_syscalls::unlink(tmp_or->data()); |
| return Error(trunc_or.error()); |
| } |
| |
| // map_semaphore closes the fd. |
| auto sem_or = map_semaphore(fd); |
| if (!sem_or.has_value()) { |
| linux_syscalls::unlink(tmp_or->data()); |
| return Error(sem_or.error()); |
| } |
| |
| Semaphore *sem = sem_or.value(); |
| new (sem) Semaphore(value); |
| |
| // atomically publish the fully initialized semaphore. |
| auto link_or = linux_syscalls::link(tmp_or->data(), path_or->data()); |
| |
| // temp file is no longer needed. |
| linux_syscalls::unlink(tmp_or->data()); |
| |
| // link() succees |
| if (link_or.has_value()) |
| return sem; |
| |
| // link() fail, clean up the mapping. |
| linux_syscalls::munmap(sem, sizeof(Semaphore)); |
| |
| // if the name already exists and O_EXCL was not set, open existing. |
| if (link_or.error() == EEXIST && !(oflag & O_EXCL)) |
| return open_existing(path_or->data()); |
| |
| return Error(link_or.error()); |
| } |
| |
| int Semaphore::close(Semaphore *sem) { |
| auto result = linux_syscalls::munmap(sem, sizeof(Semaphore)); |
| if (!result.has_value()) |
| return result.error(); |
| return 0; |
| } |
| |
| int Semaphore::unlink(const char *name) { |
| auto path_or = shm_common::translate_name<SEM_PREFIX>(name); |
| if (!path_or.has_value()) |
| return path_or.error(); |
| |
| auto result = linux_syscalls::unlink(path_or->data()); |
| if (!result.has_value()) |
| return result.error(); |
| return 0; |
| } |
| |
| } // namespace LIBC_NAMESPACE_DECL |