[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