[libc-commits] [libc] [libc] implement mkstemp (PR #199220)

Hardik Chona via libc-commits libc-commits at lists.llvm.org
Sat Jun 6 21:27:41 PDT 2026


https://github.com/un-pixelated updated https://github.com/llvm/llvm-project/pull/199220

>From a688253a85bb741ea78b2dc9158e78426ee920e9 Mon Sep 17 00:00:00 2001
From: Hardik Chona <iamhardikchona at gmail.com>
Date: Fri, 22 May 2026 14:12:32 +0530
Subject: [PATCH] [libc] implement mkstemp

---
 libc/config/linux/aarch64/entrypoints.txt |   1 +
 libc/config/linux/riscv/entrypoints.txt   |   1 +
 libc/config/linux/x86_64/entrypoints.txt  |   1 +
 libc/include/stdlib.yaml                  |   6 +
 libc/src/stdlib/CMakeLists.txt            |  17 ++
 libc/src/stdlib/mkstemp.cpp               |  87 +++++++++
 libc/src/stdlib/mkstemp.h                 |  31 ++++
 libc/test/src/stdlib/CMakeLists.txt       |  21 +++
 libc/test/src/stdlib/mkstemp_test.cpp     | 207 ++++++++++++++++++++++
 9 files changed, 372 insertions(+)
 create mode 100644 libc/src/stdlib/mkstemp.cpp
 create mode 100644 libc/src/stdlib/mkstemp.h
 create mode 100644 libc/test/src/stdlib/mkstemp_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 9994a9294173d..40f856c3790d0 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -194,6 +194,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdlib.llabs
     libc.src.stdlib.lldiv
     libc.src.stdlib.memalignment
+    libc.src.stdlib.mkstemp
     libc.src.stdlib.qsort
     libc.src.stdlib.qsort_r
     libc.src.stdlib.rand
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 2748b2b8e6a5d..1724cdcda4c87 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -196,6 +196,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdlib.llabs
     libc.src.stdlib.lldiv
     libc.src.stdlib.memalignment
+    libc.src.stdlib.mkstemp
     libc.src.stdlib.qsort
     libc.src.stdlib.qsort_r
     libc.src.stdlib.rand
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 4b551ced82138..3f203f16195d4 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -198,6 +198,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdlib.llabs
     libc.src.stdlib.lldiv
     libc.src.stdlib.memalignment
+    libc.src.stdlib.mkstemp
     libc.src.stdlib.qsort
     libc.src.stdlib.qsort_r
     libc.src.stdlib.rand
diff --git a/libc/include/stdlib.yaml b/libc/include/stdlib.yaml
index 4c958cd9d28ad..06b57f0968329 100644
--- a/libc/include/stdlib.yaml
+++ b/libc/include/stdlib.yaml
@@ -164,6 +164,12 @@ functions:
     return_type: size_t
     arguments:
       - type: const void *
+  - name: mkstemp
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: char *
   - name: posix_memalign
     standards:
       - posix
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index e79353eb2a581..15fa9457b26c0 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -397,6 +397,23 @@ add_entrypoint_object(
     libc.hdr.types.size_t
 )
 
+add_entrypoint_object(
+  mkstemp
+  SRCS
+    mkstemp.cpp
+  HDRS
+    mkstemp.h
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.fcntl_macros
+    libc.src.__support.OSUtil.linux.syscall_wrappers.getrandom
+    libc.src.__support.OSUtil.linux.syscall_wrappers.open
+    libc.src.__support.common
+    libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+)
+
 add_entrypoint_object(
   mbtowc
   SRCS
diff --git a/libc/src/stdlib/mkstemp.cpp b/libc/src/stdlib/mkstemp.cpp
new file mode 100644
index 0000000000000..3fb0d902160c2
--- /dev/null
+++ b/libc/src/stdlib/mkstemp.cpp
@@ -0,0 +1,87 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 of mkstemp, a POSIX function that creates a unique temporary
+/// file from a template string ending in at least six 'X' characters.
+///
+/// Replaces the trailing X's with random characters from the POSIX portable
+/// filename character set, opens the file exclusively, and returns an open
+/// file descriptor, retrying automatically on name collision. See:
+/// https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkdtemp.html
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/mkstemp.h"
+#include "hdr/errno_macros.h"
+#include "hdr/fcntl_macros.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/getrandom.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, mkstemp, (char *tmpl)) {
+  LIBC_CRASH_ON_NULLPTR(tmpl);
+
+  cpp::string_view str_view(tmpl);
+  size_t count = 0;
+  size_t len = str_view.size();
+
+  for (size_t i = len; i > 0; i--) {
+    if (str_view[i - 1] != 'X')
+      break;
+    count++;
+  }
+
+  if (count < 6) {
+    libc_errno = EINVAL;
+    return -1;
+  }
+
+  char *suffix = tmpl + len - count;
+
+  // POSIX portable filename character set, sorted by ASCII value.
+  // See
+  // https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_265
+  const char charset[] = "-._0123456789"
+                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                         "abcdefghijklmnopqrstuvwxyz";
+
+  int result = -1;
+  bool file_created = false;
+  while (!file_created) {
+    for (size_t i = 0; i < count; i++) {
+      uint8_t rand_byte;
+      auto ret = linux_syscalls::getrandom(&rand_byte, 1, 0);
+      if (!ret.has_value()) {
+        libc_errno = ret.error();
+        return -1;
+      }
+      // sizeof(charset) - 1 to account for the null terminator
+      suffix[i] = charset[rand_byte % (sizeof(charset) - 1)];
+    }
+
+    auto fd = linux_syscalls::open(tmpl, O_RDWR | O_CREAT | O_EXCL, 0600);
+    if (!fd.has_value()) {
+      if (fd.error() == EEXIST)
+        continue;
+      libc_errno = fd.error();
+      return -1;
+    }
+    result = fd.value();
+    file_created = true;
+  }
+  return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/mkstemp.h b/libc/src/stdlib/mkstemp.h
new file mode 100644
index 0000000000000..4e113780a72dc
--- /dev/null
+++ b/libc/src/stdlib/mkstemp.h
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Declaration of mkstemp, a POSIX function that creates a unique temporary
+/// file from a template string ending in at least six 'X' characters.
+///
+/// Replaces the trailing X's with random characters from the POSIX portable
+/// filename character set, opens the file exclusively, and returns an open
+/// file descriptor, retrying automatically on name collision. See:
+/// https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkdtemp.html
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDLIB_MKSTEMP_H
+#define LLVM_LIBC_SRC_STDLIB_MKSTEMP_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int mkstemp(char *tmpl);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDLIB_MKSTEMP_H
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index a7b7c9269fcee..0f1ae2dd93d5c 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -388,6 +388,27 @@ add_libc_test(
     libc.src.stdlib.memalignment
 )
 
+add_libc_test(
+  mkstemp_test
+  SUITE
+    libc-stdlib-tests
+  SRCS
+    mkstemp_test.cpp
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.hdr.fcntl_macros
+    libc.hdr.signal_macros
+    libc.src.string.strlen
+    libc.src.stdlib.mkstemp
+    libc.src.string.strdup
+    libc.src.unistd.access
+    libc.src.unistd.close
+    libc.src.unistd.read
+    libc.src.unistd.unlink
+    libc.src.unistd.write
+    libc.test.UnitTest.ErrnoCheckingTest
+)
+
 add_libc_test(
   mbtowc_test
   SUITE
diff --git a/libc/test/src/stdlib/mkstemp_test.cpp b/libc/test/src/stdlib/mkstemp_test.cpp
new file mode 100644
index 0000000000000..09b472681fdf6
--- /dev/null
+++ b/libc/test/src/stdlib/mkstemp_test.cpp
@@ -0,0 +1,207 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Tests for mkstemp
+/// See: https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkdtemp.html
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/fcntl_macros.h"
+#include "hdr/signal_macros.h"
+#include "src/stdlib/mkstemp.h"
+#include "src/string/strdup.h"
+#include "src/string/strlen.h"
+#include "src/unistd/access.h"
+#include "src/unistd/close.h"
+#include "src/unistd/read.h"
+#include "src/unistd/unlink.h"
+#include "src/unistd/write.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcMkstempTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcMkstempTest, ValidTemplate) {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  ASSERT_GE(fd, 0);
+  LIBC_NAMESPACE::close(fd);
+  LIBC_NAMESPACE::unlink(tmpl);
+  ::free(tmpl);
+}
+
+TEST_F(LlvmLibcMkstempTest, TemplateModifiedInPlace) {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+  size_t len = LIBC_NAMESPACE::strlen(tmpl);
+  size_t count = 0;
+  for (size_t i = len; i > 0 && tmpl[i - 1] == 'X'; i--)
+    count++;
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  ASSERT_GE(fd, 0);
+  bool modified = false;
+  for (size_t i = len - count; i < len; i++)
+    if (tmpl[i] != 'X') {
+      modified = true;
+      break;
+    }
+  EXPECT_TRUE(modified);
+  LIBC_NAMESPACE::close(fd);
+  LIBC_NAMESPACE::unlink(tmpl);
+  ::free(tmpl);
+}
+
+TEST_F(LlvmLibcMkstempTest, AllCharactersInCharset) {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+  size_t len = LIBC_NAMESPACE::strlen(tmpl);
+  size_t count = 0;
+  for (size_t i = len; i > 0 && tmpl[i - 1] == 'X'; i--)
+    count++;
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  ASSERT_GE(fd, 0);
+  // POSIX portable filename character set, sorted by ASCII value.
+  // See
+  // https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_265
+  const char charset[] = "-._0123456789"
+                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                         "abcdefghijklmnopqrstuvwxyz";
+  for (size_t i = len - count; i < len; i++) {
+    bool found = false;
+    for (size_t j = 0; j < sizeof(charset) - 1; j++) {
+      if (tmpl[i] == charset[j]) {
+        found = true;
+        break;
+      }
+    }
+    EXPECT_TRUE(found);
+  }
+  LIBC_NAMESPACE::close(fd);
+  LIBC_NAMESPACE::unlink(tmpl);
+  ::free(tmpl);
+}
+
+TEST_F(LlvmLibcMkstempTest, FileOpenedCorrectly) {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  ASSERT_GE(fd, 0);
+  // Check for two things:
+  // 1. The file exists on the disk
+  EXPECT_EQ(LIBC_NAMESPACE::access(tmpl, F_OK), 0);
+  // 2. The file can be written to
+  EXPECT_EQ(LIBC_NAMESPACE::write(fd, "llvm", 4), static_cast<ssize_t>(4));
+  LIBC_NAMESPACE::close(fd);
+  LIBC_NAMESPACE::unlink(tmpl);
+  ::free(tmpl);
+}
+
+TEST_F(LlvmLibcMkstempTest, Uniqueness) {
+  char *tmpl1 = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+  char *tmpl2 = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+  int fd1 = LIBC_NAMESPACE::mkstemp(tmpl1);
+  int fd2 = LIBC_NAMESPACE::mkstemp(tmpl2);
+  ASSERT_GE(fd1, 0);
+  ASSERT_GE(fd2, 0);
+  bool different = false;
+  for (size_t i = 0; tmpl1[i] != '\0'; i++)
+    if (tmpl1[i] != tmpl2[i]) {
+      different = true;
+      break;
+    }
+  EXPECT_TRUE(different);
+  LIBC_NAMESPACE::close(fd1);
+  LIBC_NAMESPACE::close(fd2);
+  LIBC_NAMESPACE::unlink(tmpl1);
+  LIBC_NAMESPACE::unlink(tmpl2);
+  ::free(tmpl1);
+  ::free(tmpl2);
+}
+
+TEST_F(LlvmLibcMkstempTest, FileIsEmpty) {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  ASSERT_GE(fd, 0);
+  // read should return 0 on empty file
+  char buf[1];
+  EXPECT_EQ(LIBC_NAMESPACE::read(fd, buf, 1), static_cast<ssize_t>(0));
+  LIBC_NAMESPACE::close(fd);
+  LIBC_NAMESPACE::unlink(tmpl);
+  ::free(tmpl);
+}
+
+TEST_F(LlvmLibcMkstempTest, SixXsNoPrefix) {
+  char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("XXXXXX"));
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  ASSERT_GE(fd, 0);
+  LIBC_NAMESPACE::close(fd);
+  LIBC_NAMESPACE::unlink(tmpl);
+  ::free(tmpl);
+}
+
+TEST_F(LlvmLibcMkstempTest, MoreThanSixXs) {
+  char *tmpl =
+      LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXXXXXX"));
+  size_t len = LIBC_NAMESPACE::strlen(tmpl);
+  size_t count = 0;
+  for (size_t i = len; i > 0 && tmpl[i - 1] == 'X'; i--)
+    count++;
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  ASSERT_GE(fd, 0);
+  bool modified = false;
+  for (size_t i = len - count; i < len; i++)
+    if (tmpl[i] != 'X') {
+      modified = true;
+      break;
+    }
+  EXPECT_TRUE(modified);
+  EXPECT_EQ(LIBC_NAMESPACE::access(tmpl, F_OK), 0);
+  LIBC_NAMESPACE::close(fd);
+  LIBC_NAMESPACE::unlink(tmpl);
+  ::free(tmpl);
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+TEST_F(LlvmLibcMkstempTest, NullPointer) {
+  EXPECT_DEATH([] { LIBC_NAMESPACE::mkstemp(nullptr); }, WITH_SIGNAL(-1));
+}
+#endif
+
+TEST_F(LlvmLibcMkstempTest, TemplateTooShort) {
+  char tmpl[] = "XXXXX";
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  EXPECT_EQ(fd, -1);
+  ASSERT_ERRNO_EQ(EINVAL);
+}
+
+TEST_F(LlvmLibcMkstempTest, DoesNotEndInXs) {
+  char tmpl[] = "tmp_XXXXXY";
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  EXPECT_EQ(fd, -1);
+  ASSERT_ERRNO_EQ(EINVAL);
+}
+
+TEST_F(LlvmLibcMkstempTest, XsNotAtEnd) {
+  char tmpl[] = "XXXXXXtmp";
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  EXPECT_EQ(fd, -1);
+  ASSERT_ERRNO_EQ(EINVAL);
+}
+
+TEST_F(LlvmLibcMkstempTest, FiveXsAtEnd) {
+  char tmpl[] = "tmp_XXXXX";
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  EXPECT_EQ(fd, -1);
+  ASSERT_ERRNO_EQ(EINVAL);
+}
+
+TEST_F(LlvmLibcMkstempTest, EmptyString) {
+  char tmpl[] = "";
+  int fd = LIBC_NAMESPACE::mkstemp(tmpl);
+  EXPECT_EQ(fd, -1);
+  ASSERT_ERRNO_EQ(EINVAL);
+}



More information about the libc-commits mailing list