[libc-commits] [libc] [libc] Add tmpnam implementation (PR #204901)
via libc-commits
libc-commits at lists.llvm.org
Sun Sep 6 15:03:47 PDT 2026
https://github.com/shubhe25p updated https://github.com/llvm/llvm-project/pull/204901
>From 0885b600ee8a85450b9fd3e25a44c077a3f39237 Mon Sep 17 00:00:00 2001
From: shubhe25p <33875085+shubhe25p at users.noreply.github.com>
Date: Sun, 6 Sep 2026 15:03:06 -0700
Subject: [PATCH] [libc] Add tmpnam implementation Implements tmpnam per the
POSIX specification and adds unit tests.
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/include/llvm-libc-macros/stdio-macros.h | 18 ++++
libc/include/stdio.yaml | 10 ++
libc/src/stdio/CMakeLists.txt | 7 ++
libc/src/stdio/linux/CMakeLists.txt | 18 ++++
libc/src/stdio/linux/tmpnam.cpp | 94 ++++++++++++++++
libc/src/stdio/tmpnam.h | 25 +++++
libc/test/src/stdio/CMakeLists.txt | 15 +++
libc/test/src/stdio/tmpnam_test.cpp | 106 +++++++++++++++++++
11 files changed, 296 insertions(+)
create mode 100644 libc/src/stdio/linux/tmpnam.cpp
create mode 100644 libc/src/stdio/tmpnam.h
create mode 100644 libc/test/src/stdio/tmpnam_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 68ce4dd591806..7f3e752c79bd9 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -256,6 +256,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdio.stdin
libc.src.stdio.stdout
libc.src.stdio.tmpfile
+ libc.src.stdio.tmpnam
libc.src.stdio.vsscanf
libc.src.stdio.vfprintf
libc.src.stdio.vprintf
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 40fef13470322..76a85b8ac1c5b 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -279,6 +279,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdio.stdin
libc.src.stdio.stdout
libc.src.stdio.tmpfile
+ libc.src.stdio.tmpnam
libc.src.stdio.vsscanf
libc.src.stdio.vfprintf
libc.src.stdio.vprintf
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 99020a03ff3f7..60a561a68e78b 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -279,6 +279,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdio.stdin
libc.src.stdio.stdout
libc.src.stdio.tmpfile
+ libc.src.stdio.tmpnam
libc.src.stdio.vsscanf
libc.src.stdio.vfprintf
libc.src.stdio.vprintf
diff --git a/libc/include/llvm-libc-macros/stdio-macros.h b/libc/include/llvm-libc-macros/stdio-macros.h
index 96f0e6933ade6..192c161b8ec8d 100644
--- a/libc/include/llvm-libc-macros/stdio-macros.h
+++ b/libc/include/llvm-libc-macros/stdio-macros.h
@@ -55,4 +55,22 @@ extern FILE *stderr;
#define SEEK_END 2
#endif
+/*
+ * L_tmpnam = 20 ("/tmp/" + 14 random chars + '\0').
+ * Suffix length 14 (base-64) ensures a < 10^-12 collision
+ * probability for up to 10^6 calls (via birthday bound).
+ */
+#ifndef L_tmpnam
+#define L_tmpnam 20
+#endif
+
+/*
+ * TMP_MAX = 1,000,000 (10^6 calls per process).
+ * Generous decimal call ceiling for the L_tmpnam guarantee;
+ * provides a 6x safety margin below the 6.2M limit (P ~= 2.6 x 10^-14).
+ */
+#ifndef TMP_MAX
+#define TMP_MAX 1000000
+#endif
+
#endif // LLVM_LIBC_MACROS_STDIO_MACROS_H
diff --git a/libc/include/stdio.yaml b/libc/include/stdio.yaml
index ef725dd4fe619..1fe493c4f8b80 100644
--- a/libc/include/stdio.yaml
+++ b/libc/include/stdio.yaml
@@ -26,6 +26,10 @@ macros:
macro_header: stdio-macros.h
- macro_name: _IOFBF
macro_header: stdio-macros.h
+ - macro_name: L_tmpnam
+ macro_header: stdio-macros.h
+ - macro_name: TMP_MAX
+ macro_header: stdio-macros.h
types:
- type_name: rsize_t
- type_name: size_t
@@ -410,6 +414,12 @@ functions:
return_type: FILE *
arguments:
- type: void
+ - name: tmpnam
+ standards:
+ - stdc
+ return_type: char *
+ arguments:
+ - type: char *
- name: ungetc
standards:
- stdc
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 3ef6e4f7b7d01..2607a005ead8a 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -263,6 +263,13 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.tmpfile
)
+add_entrypoint_object(
+ tmpnam
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.tmpnam
+)
+
# These entrypoints have multiple potential implementations.
add_stdio_entrypoint_object(feof)
add_stdio_entrypoint_object(feof_unlocked)
diff --git a/libc/src/stdio/linux/CMakeLists.txt b/libc/src/stdio/linux/CMakeLists.txt
index 6631f2ca4814a..a4eb1e597b122 100644
--- a/libc/src/stdio/linux/CMakeLists.txt
+++ b/libc/src/stdio/linux/CMakeLists.txt
@@ -82,3 +82,21 @@ add_entrypoint_object(
libc.src.__support.macros.config
libc.src.__support.OSUtil.linux.syscall_wrappers.open
)
+
+add_entrypoint_object(
+ tmpnam
+ SRCS
+ tmpnam.cpp
+ HDRS
+ ../tmpnam.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.stdio_macros
+ libc.hdr.unistd_macros
+ libc.src.string.memory_utils.inline_memcpy
+ libc.src.__support.CPP.atomic
+ libc.src.__support.CPP.string_view
+ libc.src.__support.macros.config
+ libc.src.__support.OSUtil.linux.syscall_wrappers.access
+ libc.src.__support.OSUtil.linux.syscall_wrappers.getrandom
+)
diff --git a/libc/src/stdio/linux/tmpnam.cpp b/libc/src/stdio/linux/tmpnam.cpp
new file mode 100644
index 0000000000000..5f3e5c14d59cf
--- /dev/null
+++ b/libc/src/stdio/linux/tmpnam.cpp
@@ -0,0 +1,94 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
diff --git a/libc/src/stdio/tmpnam.h b/libc/src/stdio/tmpnam.h
new file mode 100644
index 0000000000000..5916adfa3d554
--- /dev/null
+++ b/libc/src/stdio/tmpnam.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Implementation header for tmpnam.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_TMPNAM_H
+#define LLVM_LIBC_SRC_STDIO_TMPNAM_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+char *tmpnam(char *s);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_TMPNAM_H
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index b25219ef3ca03..1949c456df874 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -635,6 +635,21 @@ add_libc_test(
libc.src.stdio.fwrite
)
+add_libc_test(
+ tmpnam_test
+ SUITE
+ libc_stdio_unittests
+ SRCS
+ tmpnam_test.cpp
+ DEPENDS
+ libc.hdr.stdio_macros
+ libc.hdr.types.size_t
+ libc.src.stdio.tmpnam
+ libc.src.__support.CPP.string
+ libc.src.__support.CPP.string_view
+ libc.src.__support.macros.config
+)
+
# Create an output directory for any temporary test files.
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testdata)
diff --git a/libc/test/src/stdio/tmpnam_test.cpp b/libc/test/src/stdio/tmpnam_test.cpp
new file mode 100644
index 0000000000000..19a17b4152eea
--- /dev/null
+++ b/libc/test/src/stdio/tmpnam_test.cpp
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Unittests for tmpnam
+///
+//===----------------------------------------------------------------------===//
+#include "src/stdio/tmpnam.h"
+
+#include "hdr/stdio_macros.h" // For L_tmpnam
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/string.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/macros/config.h"
+#include "test/UnitTest/Test.h"
+
+using LIBC_NAMESPACE::cpp::string;
+using LIBC_NAMESPACE::cpp::string_view;
+
+namespace {
+
+constexpr string_view TMPDIR = "/tmp/";
+
+// POSIX portable filename character set.
+constexpr string_view ALLOWED = "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "_.";
+
+bool only_allowed_chars(string_view sv) {
+ for (char c : sv) {
+ if (!ALLOWED.contains(c))
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+// For caller-supplied buffer the return value should be
+// the argument pointer
+TEST(LlvmLibcTmpnamTest, NonNullBufferReturnsSamePointer) {
+ char buf[L_tmpnam];
+ char *result = LIBC_NAMESPACE::tmpnam(buf);
+ ASSERT_EQ(result, buf);
+}
+
+TEST(LlvmLibcTmpnamTest, NonNullBufferIsNullTerminated) {
+ char buf[L_tmpnam];
+ char *result = LIBC_NAMESPACE::tmpnam(buf);
+ ASSERT_NE(result, static_cast<char *>(nullptr));
+ string_view sv(result);
+ ASSERT_EQ(sv.size(), static_cast<size_t>(L_tmpnam - 1));
+ ASSERT_EQ(sv[L_tmpnam - 1], '\0');
+}
+
+TEST(LlvmLibcTmpnamTest, ResultUsesOnlyPortableChars) {
+ char buf[L_tmpnam];
+ char *result = LIBC_NAMESPACE::tmpnam(buf);
+ ASSERT_NE(result, static_cast<char *>(nullptr));
+ ASSERT_TRUE(only_allowed_chars(string_view(result)));
+}
+
+// For null argument, the result lives in an internal static object.
+TEST(LlvmLibcTmpnamTest, NullBufferReturnsInternalObject) {
+ char *result = LIBC_NAMESPACE::tmpnam(nullptr);
+ ASSERT_NE(result, static_cast<char *>(nullptr));
+ string_view sv(result);
+ ASSERT_TRUE(sv.starts_with(TMPDIR));
+ ASSERT_TRUE(only_allowed_chars(sv));
+}
+
+// Successive calls should produce distinct strings.
+TEST(LlvmLibcTmpnamTest, SuccessiveCallsDiffer) {
+ char a[L_tmpnam];
+ char b[L_tmpnam];
+ char *ra = LIBC_NAMESPACE::tmpnam(a);
+ char *rb = LIBC_NAMESPACE::tmpnam(b);
+ ASSERT_NE(ra, static_cast<char *>(nullptr));
+ ASSERT_NE(rb, static_cast<char *>(nullptr));
+ ASSERT_FALSE(string_view(ra) == string_view(rb));
+}
+
+// Two calls with a null argument must return the same
+// pointer. The contents, however, are overwritten by
+// the second call.
+TEST(LlvmLibcTmpnamTest, NullCallsShareObjectButDifferInContent) {
+ char *first = LIBC_NAMESPACE::tmpnam(nullptr);
+ ASSERT_NE(first, static_cast<char *>(nullptr));
+
+ // Snapshot the first result before it is overwritten.
+ string snapshot(first);
+
+ char *second = LIBC_NAMESPACE::tmpnam(nullptr);
+ ASSERT_NE(second, static_cast<char *>(nullptr));
+ ASSERT_EQ(first, second);
+
+ string secondstr(second);
+ // But the generated string changed
+ ASSERT_FALSE(snapshot == secondstr);
+}
More information about the libc-commits
mailing list