[libc-commits] [libc] [libc] implement mkstemp (PR #199220)
Hardik Chona via libc-commits
libc-commits at lists.llvm.org
Sat Jun 6 21:11:42 PDT 2026
https://github.com/un-pixelated updated https://github.com/llvm/llvm-project/pull/199220
>From 01d92f0e00eeafaeead4aaa2093330f08cb2c8a4 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
[fix] stdc -> posix
Fix headers in CMakeLists.txt and clean up testcase file
Changed EXPECTs to ASSERTs
formatting changes
[libc] implement mkstemp
Added new line at the end of files
[libc] implement mkstemp
[fix] charset expanded
[refactor] one rand_byte at a time
[fix] added mkstemp to aarch64 entrypoint
[fix] added mkstemp to riscv entrypoints
[fix] add newline to eof
[fix] add strdup and signal_macros to mkstemp depends
[refactor] refactor tests
[format] formatting change
[fix] dangling pointer bug
[fix] unsigned value bug
[fix] use len
[libc] implement mkstemp
[refactor] simplify more than six X test
nits fixed (sort alphabetically and magic numbers)
replace while(true) with a file_created boolean
nit: test naming
combined redundant tests
.
Using namespaced free
Using strlen to simplify
.
.
ASCIIbetical sorting
Better comment for charset
new headers
Update charset link to latest spec
Add new test checking whether all characters replaced by mkstemp are in the posix charset or not
Update CMakeLists.txt
revert namespaced free
fix write amounts
[libc] implement mkstemp
clang-format
---
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