[libc-commits] [libc] [libc] Implement mkdtemp (PR #222106)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Tue Sep 8 12:11:56 PDT 2026
https://github.com/kaladron created https://github.com/llvm/llvm-project/pull/222106
Add the POSIX mkdtemp entrypoint for Linux targets (aarch64, riscv, x86_64).
Extract the shared template replacement and random character retry loop into linux/mktemp_util.h, relocating mkstemp to linux/mkstemp.cpp and reusing the helper across both entrypoints.
Add comprehensive unit tests covering directory creation, permissions, uniqueness, and invalid template handling.
Assisted-by: Automated tooling, human reviewed.
>From 1b48a14a36089f8ba1b96517b677d58ca4a5245b Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Mon, 7 Sep 2026 15:57:07 +0100
Subject: [PATCH] [libc] Implement mkdtemp
Add the POSIX mkdtemp entrypoint for Linux targets (aarch64, riscv,
x86_64).
Extract the shared template replacement and random character retry
loop into linux/mktemp_util.h, relocating mkstemp to linux/mkstemp.cpp
and reusing the helper across both entrypoints.
Add comprehensive unit tests covering directory creation,
permissions, uniqueness, and invalid template handling.
Assisted-by: Automated tooling, human reviewed.
---
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 | 31 ++--
libc/src/stdlib/linux/CMakeLists.txt | 50 ++++++
libc/src/stdlib/linux/mkdtemp.cpp | 44 ++++++
libc/src/stdlib/linux/mkstemp.cpp | 44 ++++++
libc/src/stdlib/linux/mktemp_util.h | 96 ++++++++++++
libc/src/stdlib/mkdtemp.h | 36 +++++
libc/src/stdlib/mkstemp.cpp | 87 -----------
libc/src/stdlib/mkstemp.h | 2 +-
libc/test/src/stdlib/CMakeLists.txt | 23 +++
libc/test/src/stdlib/mkdtemp_test.cpp | 181 ++++++++++++++++++++++
14 files changed, 498 insertions(+), 105 deletions(-)
create mode 100644 libc/src/stdlib/linux/mkdtemp.cpp
create mode 100644 libc/src/stdlib/linux/mkstemp.cpp
create mode 100644 libc/src/stdlib/linux/mktemp_util.h
create mode 100644 libc/src/stdlib/mkdtemp.h
delete mode 100644 libc/src/stdlib/mkstemp.cpp
create mode 100644 libc/test/src/stdlib/mkdtemp_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 03053ac04f743..03f0af377f254 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -219,6 +219,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdlib.llabs
libc.src.stdlib.lldiv
libc.src.stdlib.memalignment
+ libc.src.stdlib.mkdtemp
libc.src.stdlib.mkstemp
libc.src.stdlib.qsort
libc.src.stdlib.qsort_r
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 5db369a9d871d..1823cc27070cf 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -243,6 +243,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdlib.llabs
libc.src.stdlib.lldiv
libc.src.stdlib.memalignment
+ libc.src.stdlib.mkdtemp
libc.src.stdlib.mkstemp
libc.src.stdlib.qsort
libc.src.stdlib.qsort_r
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 3cbbb7b3e9f79..801c623ed3c97 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -243,6 +243,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdlib.llabs
libc.src.stdlib.lldiv
libc.src.stdlib.memalignment
+ libc.src.stdlib.mkdtemp
libc.src.stdlib.mkstemp
libc.src.stdlib.qsort
libc.src.stdlib.qsort_r
diff --git a/libc/include/stdlib.yaml b/libc/include/stdlib.yaml
index 784c52c7bdcf7..eb0ca6ce42eef 100644
--- a/libc/include/stdlib.yaml
+++ b/libc/include/stdlib.yaml
@@ -178,6 +178,12 @@ functions:
return_type: size_t
arguments:
- type: const void *
+ - name: mkdtemp
+ standards:
+ - posix
+ return_type: char *
+ arguments:
+ - type: char *
- name: mkstemp
standards:
- posix
diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt
index 36f3799cb30f1..8c5c8c8d82ea3 100644
--- a/libc/src/stdlib/CMakeLists.txt
+++ b/libc/src/stdlib/CMakeLists.txt
@@ -410,23 +410,6 @@ 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
@@ -614,6 +597,20 @@ add_entrypoint_object(
.${LIBC_TARGET_OS}.getloadavg
)
+add_entrypoint_object(
+ mkdtemp
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.mkdtemp
+)
+
+add_entrypoint_object(
+ mkstemp
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.mkstemp
+)
+
if(NOT LLVM_LIBC_FULL_BUILD)
return()
endif()
diff --git a/libc/src/stdlib/linux/CMakeLists.txt b/libc/src/stdlib/linux/CMakeLists.txt
index f4af7075cbad5..a996d131fb580 100644
--- a/libc/src/stdlib/linux/CMakeLists.txt
+++ b/libc/src/stdlib/linux/CMakeLists.txt
@@ -119,3 +119,53 @@ add_entrypoint_object(
libc.src.__support.macros.config
)
+add_header_library(
+ mktemp_util
+ HDRS
+ mktemp_util.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.stdint_proxy
+ libc.hdr.types.size_t
+ libc.src.__support.CPP.array
+ libc.src.__support.CPP.span
+ libc.src.__support.CPP.string_view
+ libc.src.__support.OSUtil.linux.syscall_wrappers.getrandom
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.macros.attributes
+ libc.src.__support.macros.config
+)
+
+add_entrypoint_object(
+ mkdtemp
+ SRCS
+ mkdtemp.cpp
+ HDRS
+ ../mkdtemp.h
+ DEPENDS
+ .mktemp_util
+ libc.hdr.sys_stat_macros
+ libc.src.__support.OSUtil.linux.syscall_wrappers.mkdir
+ libc.src.__support.common
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
+ libc.src.__support.macros.null_check
+)
+
+add_entrypoint_object(
+ mkstemp
+ SRCS
+ mkstemp.cpp
+ HDRS
+ ../mkstemp.h
+ DEPENDS
+ .mktemp_util
+ libc.hdr.fcntl_macros
+ 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
+)
+
diff --git a/libc/src/stdlib/linux/mkdtemp.cpp b/libc/src/stdlib/linux/mkdtemp.cpp
new file mode 100644
index 0000000000000..8a7f1bc4c5175
--- /dev/null
+++ b/libc/src/stdlib/linux/mkdtemp.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 mkdtemp, a POSIX function that creates a unique temporary
+/// directory 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, creates the directory with 0700 permissions,
+/// and returns the pathname, retrying automatically on name collision. See:
+/// https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkdtemp.html
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/mkdtemp.h"
+#include "hdr/sys_stat_macros.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/mkdir.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"
+#include "src/stdlib/linux/mktemp_util.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(char *, mkdtemp, (char *tmpl)) {
+ LIBC_CRASH_ON_NULLPTR(tmpl);
+
+ auto res = internal::mktemp_core(tmpl, [](const char *path) {
+ return linux_syscalls::mkdir(path, S_IRWXU);
+ });
+ if (!res.has_value()) {
+ libc_errno = res.error();
+ return nullptr;
+ }
+ return tmpl;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/linux/mkstemp.cpp b/libc/src/stdlib/linux/mkstemp.cpp
new file mode 100644
index 0000000000000..c1546958de891
--- /dev/null
+++ b/libc/src/stdlib/linux/mkstemp.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/mkstemp.html
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdlib/mkstemp.h"
+#include "hdr/fcntl_macros.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"
+#include "src/stdlib/linux/mktemp_util.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, mkstemp, (char *tmpl)) {
+ LIBC_CRASH_ON_NULLPTR(tmpl);
+
+ auto res = internal::mktemp_core(tmpl, [](const char *path) {
+ return linux_syscalls::open(path, O_RDWR | O_CREAT | O_EXCL, 0600);
+ });
+ if (!res.has_value()) {
+ libc_errno = res.error();
+ return -1;
+ }
+ return res.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/linux/mktemp_util.h b/libc/src/stdlib/linux/mktemp_util.h
new file mode 100644
index 0000000000000..b39fb0f506db8
--- /dev/null
+++ b/libc/src/stdlib/linux/mktemp_util.h
@@ -0,0 +1,96 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Shared helper functions for creating unique temporary files and directories.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDLIB_LINUX_MKTEMP_UTIL_H
+#define LLVM_LIBC_SRC_STDLIB_LINUX_MKTEMP_UTIL_H
+
+#include "hdr/errno_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/getrandom.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace internal {
+
+// POSIX portable filename character set, sorted by ASCII value.
+// See
+// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_265
+LIBC_INLINE_VAR constexpr cpp::string_view MKTEMP_CHARSET =
+ "-._0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
+
+/// Core helper function for creating temporary files and directories.
+///
+/// \param tmpl Template string ending in at least six 'X' characters.
+/// \param create_fn Callable taking `const char *path` and returning
+/// `ErrorOr<T>`.
+/// \return `ErrorOr<T>` with the result of `create_fn` on success, or Error on
+/// failure.
+template <typename CreateFn>
+LIBC_INLINE auto mktemp_core(char *tmpl, CreateFn create_fn)
+ -> decltype(create_fn(tmpl)) {
+ cpp::string_view str_view(tmpl);
+ size_t len = str_view.size();
+ if (len < 6)
+ return Error(EINVAL);
+
+ size_t pos = str_view.find_last_not_of('X');
+ size_t count = (pos == cpp::string_view::npos) ? len : len - pos - 1;
+
+ if (count < 6)
+ return Error(EINVAL);
+
+ cpp::span<char> suffix(tmpl + (len - count), count);
+
+ // Maximum collision retry attempts before returning EEXIST per POSIX.
+ constexpr size_t MAX_ATTEMPTS = 10000;
+ // Read random bytes in batches to minimize getrandom syscall overhead.
+ constexpr size_t BATCH_SIZE = 64;
+ cpp::array<uint8_t, BATCH_SIZE> rand_buf;
+
+ for (size_t attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
+ for (size_t offset = 0; offset < count;) {
+ size_t chunk =
+ (count - offset < BATCH_SIZE) ? (count - offset) : BATCH_SIZE;
+ auto ret = linux_syscalls::getrandom(rand_buf.data(), chunk, 0);
+ if (!ret.has_value())
+ return Error(ret.error());
+ if (ret.value() == 0)
+ return Error(EIO);
+ for (size_t j = 0; j < static_cast<size_t>(ret.value()); ++j) {
+ suffix[offset + j] =
+ MKTEMP_CHARSET[rand_buf[j] % MKTEMP_CHARSET.size()];
+ }
+ offset += static_cast<size_t>(ret.value());
+ }
+
+ auto result = create_fn(tmpl);
+ if (!result.has_value() && result.error() == EEXIST)
+ continue;
+ return result;
+ }
+ return Error(EEXIST);
+}
+
+} // namespace internal
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDLIB_LINUX_MKTEMP_UTIL_H
diff --git a/libc/src/stdlib/mkdtemp.h b/libc/src/stdlib/mkdtemp.h
new file mode 100644
index 0000000000000..322aabf54ea55
--- /dev/null
+++ b/libc/src/stdlib/mkdtemp.h
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 mkdtemp, a POSIX function that creates a unique temporary
+/// directory 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, creates the directory with 0700 permissions,
+/// and returns the pathname, retrying automatically on name collision. See:
+/// https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkdtemp.html
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDLIB_MKDTEMP_H
+#define LLVM_LIBC_SRC_STDLIB_MKDTEMP_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+/// Create a unique temporary directory from a template string.
+///
+/// \param tmpl Template string ending in at least six 'X' characters.
+/// \return Pointer to the modified template string on success, nullptr on
+/// error.
+char *mkdtemp(char *tmpl);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDLIB_MKDTEMP_H
diff --git a/libc/src/stdlib/mkstemp.cpp b/libc/src/stdlib/mkstemp.cpp
deleted file mode 100644
index 3fb0d902160c2..0000000000000
--- a/libc/src/stdlib/mkstemp.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
index 4e113780a72dc..1b3503d1beac5 100644
--- a/libc/src/stdlib/mkstemp.h
+++ b/libc/src/stdlib/mkstemp.h
@@ -13,7 +13,7 @@
/// 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
+/// https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkstemp.html
///
//===----------------------------------------------------------------------===//
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 1bcffea447ed6..c8912ea348cb1 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -417,6 +417,29 @@ add_libc_test(
libc.src.stdlib.memalignment
)
+add_libc_test(
+ mkdtemp_test
+ SUITE
+ libc-stdlib-tests
+ SRCS
+ mkdtemp_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.sys_stat_macros
+ libc.hdr.types.struct_stat
+ libc.hdr.unistd_macros
+ libc.src.__support.CPP.scope
+ libc.src.__support.CPP.string_view
+ libc.src.stdlib.mkdtemp
+ libc.src.string.strdup
+ libc.src.string.strlen
+ libc.src.sys.stat.stat
+ libc.src.unistd.access
+ libc.src.unistd.rmdir
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+)
+
add_libc_test(
mkstemp_test
SUITE
diff --git a/libc/test/src/stdlib/mkdtemp_test.cpp b/libc/test/src/stdlib/mkdtemp_test.cpp
new file mode 100644
index 0000000000000..c91879335b044
--- /dev/null
+++ b/libc/test/src/stdlib/mkdtemp_test.cpp
@@ -0,0 +1,181 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 mkdtemp
+/// See: https://pubs.opengroup.org/onlinepubs/9799919799/functions/mkdtemp.html
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/sys_stat_macros.h"
+#include "hdr/types/struct_stat.h"
+#include "hdr/unistd_macros.h"
+#include "src/__support/CPP/scope.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/stdlib/mkdtemp.h"
+#include "src/string/strdup.h"
+#include "src/string/strlen.h"
+#include "src/sys/stat/stat.h"
+#include "src/unistd/access.h"
+#include "src/unistd/rmdir.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
+using LIBC_NAMESPACE::cpp::string_view;
+using LlvmLibcMkdtempTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+TEST_F(LlvmLibcMkdtempTest, ValidTemplate) {
+ char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+ ASSERT_NE(tmpl, nullptr);
+ auto cleanup = LIBC_NAMESPACE::cpp::scope_exit([&] {
+ LIBC_NAMESPACE::rmdir(tmpl);
+ ::free(tmpl);
+ });
+
+ ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+ EXPECT_THAT(LIBC_NAMESPACE::access(tmpl, F_OK), Succeeds(0));
+
+ struct stat st;
+ ASSERT_THAT(LIBC_NAMESPACE::stat(tmpl, &st), Succeeds(0));
+ EXPECT_EQ(st.st_mode & S_IFMT, static_cast<mode_t>(S_IFDIR));
+ EXPECT_EQ(st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO),
+ static_cast<mode_t>(S_IRWXU));
+}
+
+TEST_F(LlvmLibcMkdtempTest, TemplateModifiedInPlace) {
+ char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+ ASSERT_NE(tmpl, nullptr);
+ char *orig = LIBC_NAMESPACE::strdup(tmpl);
+ ASSERT_NE(orig, nullptr);
+ auto cleanup = LIBC_NAMESPACE::cpp::scope_exit([&] {
+ LIBC_NAMESPACE::rmdir(tmpl);
+ ::free(tmpl);
+ ::free(orig);
+ });
+
+ size_t len = LIBC_NAMESPACE::strlen(tmpl);
+ ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+
+ EXPECT_EQ(string_view(tmpl, len - 6), string_view(orig, len - 6));
+ EXPECT_NE(string_view(tmpl + len - 6, 6), string_view("XXXXXX"));
+}
+
+TEST_F(LlvmLibcMkdtempTest, AllCharactersInCharset) {
+ char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXX"));
+ ASSERT_NE(tmpl, nullptr);
+ auto cleanup = LIBC_NAMESPACE::cpp::scope_exit([&] {
+ LIBC_NAMESPACE::rmdir(tmpl);
+ ::free(tmpl);
+ });
+
+ size_t len = LIBC_NAMESPACE::strlen(tmpl);
+ ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+
+ // POSIX portable filename character set, sorted by ASCII value.
+ // See
+ // https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_265
+ constexpr string_view CHARSET = "-._0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz";
+ for (char c : string_view(tmpl + len - 6, 6))
+ EXPECT_NE(CHARSET.find_first_of(c), string_view::npos);
+}
+
+TEST_F(LlvmLibcMkdtempTest, 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"));
+ ASSERT_NE(tmpl1, nullptr);
+ ASSERT_NE(tmpl2, nullptr);
+ auto cleanup1 = LIBC_NAMESPACE::cpp::scope_exit([&] {
+ LIBC_NAMESPACE::rmdir(tmpl1);
+ ::free(tmpl1);
+ });
+ auto cleanup2 = LIBC_NAMESPACE::cpp::scope_exit([&] {
+ LIBC_NAMESPACE::rmdir(tmpl2);
+ ::free(tmpl2);
+ });
+
+ ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl1), Succeeds(tmpl1));
+ ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl2), Succeeds(tmpl2));
+
+ EXPECT_STRNE(tmpl1, tmpl2);
+}
+
+TEST_F(LlvmLibcMkdtempTest, SixXsNoPrefix) {
+ char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path("XXXXXX"));
+ ASSERT_NE(tmpl, nullptr);
+ auto cleanup = LIBC_NAMESPACE::cpp::scope_exit([&] {
+ LIBC_NAMESPACE::rmdir(tmpl);
+ ::free(tmpl);
+ });
+
+ ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+ EXPECT_THAT(LIBC_NAMESPACE::access(tmpl, F_OK), Succeeds(0));
+}
+
+TEST_F(LlvmLibcMkdtempTest, MoreThanSixXs) {
+ char *tmpl =
+ LIBC_NAMESPACE::strdup(libc_make_test_file_path("tmp_XXXXXXXXXX"));
+ ASSERT_NE(tmpl, nullptr);
+ char *orig = LIBC_NAMESPACE::strdup(tmpl);
+ ASSERT_NE(orig, nullptr);
+ auto cleanup = LIBC_NAMESPACE::cpp::scope_exit([&] {
+ LIBC_NAMESPACE::rmdir(tmpl);
+ ::free(tmpl);
+ ::free(orig);
+ });
+
+ size_t len = LIBC_NAMESPACE::strlen(tmpl);
+ ASSERT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Succeeds(tmpl));
+
+ EXPECT_EQ(string_view(tmpl, len - 10), string_view(orig, len - 10));
+ EXPECT_NE(string_view(tmpl + len - 10, 10), string_view("XXXXXXXXXX"));
+ EXPECT_THAT(LIBC_NAMESPACE::access(tmpl, F_OK), Succeeds(0));
+}
+
+#if defined(LIBC_ADD_NULL_CHECKS)
+TEST_F(LlvmLibcMkdtempTest, NullPointer) {
+ ASSERT_DEATH([] { LIBC_NAMESPACE::mkdtemp(nullptr); }, WITH_SIGNAL(-1));
+}
+#endif
+
+TEST_F(LlvmLibcMkdtempTest, TemplateTooShort) {
+ char tmpl[] = "XXXXX";
+ EXPECT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Fails<char *>(EINVAL, nullptr));
+}
+
+TEST_F(LlvmLibcMkdtempTest, DoesNotEndInXs) {
+ char tmpl[] = "tmp_XXXXXY";
+ EXPECT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Fails<char *>(EINVAL, nullptr));
+}
+
+TEST_F(LlvmLibcMkdtempTest, XsNotAtEnd) {
+ char tmpl[] = "XXXXXXtmp";
+ EXPECT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Fails<char *>(EINVAL, nullptr));
+}
+
+TEST_F(LlvmLibcMkdtempTest, FiveXsAtEnd) {
+ char tmpl[] = "tmp_XXXXX";
+ EXPECT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Fails<char *>(EINVAL, nullptr));
+}
+
+TEST_F(LlvmLibcMkdtempTest, EmptyString) {
+ char tmpl[] = "";
+ EXPECT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Fails<char *>(EINVAL, nullptr));
+}
+
+TEST_F(LlvmLibcMkdtempTest, NonExistentParentDirectory) {
+ char *tmpl = LIBC_NAMESPACE::strdup(
+ libc_make_test_file_path("non_existent_dir/tmp_XXXXXX"));
+ ASSERT_NE(tmpl, nullptr);
+ auto cleanup = LIBC_NAMESPACE::cpp::scope_exit([&] { ::free(tmpl); });
+ EXPECT_THAT(LIBC_NAMESPACE::mkdtemp(tmpl), Fails<char *>(ENOENT, nullptr));
+}
More information about the libc-commits
mailing list