[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