blob: 5f3e5c14d59cfd953ddf18e1dacd9d7e9570a075 [file]
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Linux implementation of tmpnam.
///
//===----------------------------------------------------------------------===//
#include "src/stdio/tmpnam.h"
#include "hdr/errno_macros.h" // For ENOENT
#include "hdr/stdio_macros.h" // For L_tmpnam and TMP_MAX
#include "hdr/unistd_macros.h" // For F_OK
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/access.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/getrandom.h"
#include "src/__support/macros/config.h"
#include "src/string/memory_utils/inline_memcpy.h"
namespace LIBC_NAMESPACE_DECL {
static char TMPBUF[L_tmpnam];
static cpp::Atomic<size_t> tmpnam_budget = TMP_MAX;
// Thread-safety guarantees per ISO C & POSIX:
// - When 's' is nullptr, callers share internal static storage ('TMPBUF'),
// which is not thread-safe per standard specification allowance.
// - Process-wide call budget ('tmpnam_budget') is tracked atomically using a
// lock-free compare-and-swap loop.
LLVM_LIBC_FUNCTION(char *, tmpnam, (char *s)) {
if (s == nullptr)
s = TMPBUF;
// Subset of the POSIX portable filename character set.
// Deliberately sized to 64 (a power of 2, rather than the full set)
// to prevent slight modulo bias also helps in performance.
const char CHARSET[] = "0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"_.";
constexpr size_t CHARSET_SIZE = sizeof(CHARSET) - 1;
static_assert((CHARSET_SIZE & (CHARSET_SIZE - 1)) == 0,
"CHARSET_SIZE must be a power of 2");
constexpr size_t MASK = CHARSET_SIZE - 1;
constexpr cpp::string_view PREFIX = "/tmp/";
constexpr size_t PREFIX_SIZE = PREFIX.size();
static_assert(PREFIX_SIZE + 1 < L_tmpnam, "L_tmpnam is too small");
inline_memcpy(s, PREFIX.data(), PREFIX_SIZE);
constexpr size_t SUFFIX_SIZE = L_tmpnam - PREFIX_SIZE - 1;
while (true) {
size_t curr_budget = tmpnam_budget.load(cpp::MemoryOrder::RELAXED);
do {
if (curr_budget == 0)
break;
} while (
!tmpnam_budget.compare_exchange_strong(curr_budget, curr_budget - 1));
if (curr_budget == 0)
break;
uint8_t rand_bytes[L_tmpnam];
auto ret = linux_syscalls::getrandom(rand_bytes, SUFFIX_SIZE, 0);
if (!ret.has_value() || ret.value() != SUFFIX_SIZE) {
// return nullptr when getrandom fails but consume tmpnam budget
return nullptr;
}
char *suffix = s + PREFIX_SIZE;
for (size_t i = 0; i < SUFFIX_SIZE; i++) {
suffix[i] = CHARSET[rand_bytes[i] & MASK];
}
s[L_tmpnam - 1] = '\0';
auto res = linux_syscalls::access(s, F_OK);
if (!res.has_value() && res.error() == ENOENT)
return s;
}
// implementation-defined: if we exhaust budget we return nullptr
return nullptr;
}
} // namespace LIBC_NAMESPACE_DECL