| //===----------------------------------------------------------------------===// | |
| // | |
| // 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 |