blob: 2dbd87c0da5ca1a491e5d67213ddbcce85175986 [file] [edit]
//===-- 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