[libc-commits] [libc] [libc] Add 'x' and 'e' mode support to fopen() (PR #224207)

Jeong Jihyeon via libc-commits libc-commits at lists.llvm.org
Thu Sep 17 07:55:44 PDT 2026


https://github.com/JihyeonJeong129 updated https://github.com/llvm/llvm-project/pull/224207

>From eae873df6de481be9b9275764142448fb2270d06 Mon Sep 17 00:00:00 2001
From: Jihyeon Jeong <jh.jeong129 at gmail.com>
Date: Thu, 17 Sep 2026 07:01:55 +0000
Subject: [PATCH 1/2] [libc] Add 'x' and 'e' mode support to fopen family

---
 libc/src/__support/File/file_mode.h           | 14 ++-
 libc/src/__support/File/linux/CMakeLists.txt  |  1 +
 libc/src/__support/File/linux/file.cpp        | 29 +++++-
 libc/src/__support/File/linux/file_flags.h    |  2 +
 .../src/__support/File/file_mode_test.cpp     | 47 ++++++++++
 libc/test/src/stdio/CMakeLists.txt            | 18 ++++
 libc/test/src/stdio/fdopen_test.cpp           | 32 +++++++
 libc/test/src/stdio/fopen_test.cpp            | 90 +++++++++++++++++++
 libc/test/src/stdio/freopen_test.cpp          | 29 ++++++
 9 files changed, 259 insertions(+), 3 deletions(-)

diff --git a/libc/src/__support/File/file_mode.h b/libc/src/__support/File/file_mode.h
index f9aece8d22a36..8049516da261f 100644
--- a/libc/src/__support/File/file_mode.h
+++ b/libc/src/__support/File/file_mode.h
@@ -62,6 +62,9 @@ class FileMode {
       case 'x':
         file_mode_ |= static_cast<Mode>(CreateType::EXCLUSIVE);
         break;
+      case 'e':
+        file_mode_ |= static_cast<Mode>(Extension::CLOSE_ON_EXEC);
+        break;
       default:
         file_mode_ = 0;
       }
@@ -108,6 +111,10 @@ class FileMode {
     return (file_mode_ & static_cast<Mode>(CreateType::EXCLUSIVE)) != 0;
   }
 
+  constexpr bool is_close_on_exec() const {
+    return (file_mode_ & static_cast<Mode>(Extension::CLOSE_ON_EXEC)) != 0;
+  }
+
 private:
   // Mode is a generic or abstract mode bit for all kinds of modes
   // (open-mode, 'content-mode', 'create-modes')
@@ -115,7 +122,7 @@ class FileMode {
 
   // Denotes the mode of the file.
   //
-  // The three different types of flags below are to be used with '|' operator.
+  // The different types of flags below are to be used with '|' operator.
   // Their values correspond to mutually exclusive bits in a 32-bit unsigned
   // integer value. A flag set can include both READ and WRITE if the file
   // is opened in update mode (ie. if the file was opened with a '+' the mode
@@ -138,6 +145,11 @@ class FileMode {
     EXCLUSIVE = 0x100,
   };
 
+  // POSIX extensions to the ISO C file modes.
+  enum class Extension : Mode {
+    CLOSE_ON_EXEC = 0x1000,
+  };
+
   // This property tracks the mode for the particular file instance (i.e
   // currently opened file)
   Mode file_mode_;
diff --git a/libc/src/__support/File/linux/CMakeLists.txt b/libc/src/__support/File/linux/CMakeLists.txt
index d0e1df252895c..05efc0f6f6139 100644
--- a/libc/src/__support/File/linux/CMakeLists.txt
+++ b/libc/src/__support/File/linux/CMakeLists.txt
@@ -14,6 +14,7 @@ add_object_library(
     libc.hdr.types.off_t
     libc.src.__support.CPP.new
     libc.src.__support.OSUtil.linux.syscall_wrappers.close
+    libc.src.__support.OSUtil.linux.syscall_wrappers.dup3
     libc.src.__support.OSUtil.linux.syscall_wrappers.fcntl
     libc.src.__support.OSUtil.linux.syscall_wrappers.lseek
     libc.src.__support.OSUtil.linux.syscall_wrappers.open
diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index cc6a1fcbd3168..29d177f36328d 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -15,7 +15,7 @@
 #include "src/__support/File/file.h"
 #include "src/__support/File/file_mode.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/dup2.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/dup3.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/fcntl.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/lseek.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
@@ -87,6 +87,14 @@ static int map_c_mode_flags_to_linux_open_flags(const FileMode &file_mode) {
   else if (file_mode.is_write())
     open_flags |= LinuxFileFlags::CREATE_OR_TRUNCATE;
 
+  // Ignore 'x' for read modes: O_EXCL is only meaningful with O_CREAT here.
+  if (file_mode.is_exclusive_create() &&
+      (file_mode.is_write() || file_mode.is_append()))
+    open_flags |= LinuxFileFlags::EXCLUSIVE_CREATE;
+
+  if (file_mode.is_close_on_exec())
+    open_flags |= LinuxFileFlags::CLOSE_ON_EXEC;
+
   return open_flags;
 }
 
@@ -119,6 +127,14 @@ ErrorOr<File *> openfile(const char *path, const char *mode) {
   return file;
 }
 
+static ErrorOr<int> set_close_on_exec(int fd) {
+  auto flags = linux_syscalls::fcntl(fd, F_GETFD);
+  if (!flags)
+    return Error(flags.error());
+  return linux_syscalls::fcntl(
+      fd, F_SETFD, reinterpret_cast<void *>(flags.value() | FD_CLOEXEC));
+}
+
 ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode) {
   const FileMode file_mode(mode);
 
@@ -139,6 +155,12 @@ ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode) {
     return Error(EINVAL);
   }
 
+  if (file_mode.is_close_on_exec()) {
+    auto cloexec_result = set_close_on_exec(fd);
+    if (!cloexec_result)
+      return Error(cloexec_result.error());
+  }
+
   bool do_seek = false;
   if (file_mode.is_append() &&
       !LinuxFileFlags::file_has_append_flag(fd_flags)) {
@@ -215,7 +237,10 @@ int LinuxFile::reopen_unlocked(const char *path, const char *mode) {
     // Else the new file successfully opened, so we move it into the fd the old
     // file was using if the old fd exists.
     if (old_fd >= 0) {
-      auto dup_result = linux_syscalls::dup2(new_fd.value(), old_fd);
+      // Preserve close-on-exec atomically when replacing the old descriptor.
+      int dup_flags =
+          file_mode.is_close_on_exec() ? LinuxFileFlags::CLOSE_ON_EXEC : 0;
+      auto dup_result = linux_syscalls::dup3(new_fd.value(), old_fd, dup_flags);
       if (!dup_result) {
         linux_syscalls::close(new_fd.value());
         reset_stream_state_unlocked(file_mode);
diff --git a/libc/src/__support/File/linux/file_flags.h b/libc/src/__support/File/linux/file_flags.h
index deb357c261d55..ce68496101639 100644
--- a/libc/src/__support/File/linux/file_flags.h
+++ b/libc/src/__support/File/linux/file_flags.h
@@ -27,6 +27,8 @@ class LinuxFileFlags {
   static constexpr int WRITE_ONLY = O_WRONLY;
   static constexpr int READ_ONLY = O_RDONLY;
   static constexpr int CREATE_OR_TRUNCATE = O_CREAT | O_TRUNC;
+  static constexpr int EXCLUSIVE_CREATE = O_EXCL;
+  static constexpr int CLOSE_ON_EXEC = O_CLOEXEC;
 
   // File created will have 0666 permissions.
   LIBC_INLINE static constexpr mode_t OPEN_MODE =
diff --git a/libc/test/src/__support/File/file_mode_test.cpp b/libc/test/src/__support/File/file_mode_test.cpp
index 92f58bd154ee3..cb683aa41f3a4 100644
--- a/libc/test/src/__support/File/file_mode_test.cpp
+++ b/libc/test/src/__support/File/file_mode_test.cpp
@@ -47,6 +47,11 @@ TEST(LlvmLibcFileModeTest, FirstCharacterMustBeAValidMode) {
   constexpr FileMode exclusive_create("x");
   EXPECT_FALSE(exclusive_create.is_valid());
   EXPECT_FALSE(exclusive_create.is_exclusive_create());
+
+  // close-on-exec set as first character => invalid
+  constexpr FileMode close_on_exec("e");
+  EXPECT_FALSE(close_on_exec.is_valid());
+  EXPECT_FALSE(close_on_exec.is_close_on_exec());
 }
 
 TEST(LlvmLibcFileModeTest, AllPossibleValidCombinations) {
@@ -59,6 +64,7 @@ TEST(LlvmLibcFileModeTest, AllPossibleValidCombinations) {
   EXPECT_TRUE(readonly.is_valid());
   EXPECT_TRUE(readonly.is_read());
   EXPECT_TRUE(readonly.read_allowed());
+  EXPECT_FALSE(readonly.is_close_on_exec());
 
   // b. Read and Update mode
   constexpr FileMode read_and_update("r+");
@@ -91,6 +97,7 @@ TEST(LlvmLibcFileModeTest, AllPossibleValidCombinations) {
   EXPECT_TRUE(writeonly.is_valid());
   EXPECT_TRUE(writeonly.is_write());
   EXPECT_TRUE(writeonly.write_allowed());
+  EXPECT_FALSE(writeonly.is_close_on_exec());
 
   // b. Write and Update mode
   constexpr FileMode write_and_update("w+");
@@ -157,6 +164,7 @@ TEST(LlvmLibcFileModeTest, AllPossibleValidCombinations) {
   EXPECT_TRUE(appendonly.is_valid());
   EXPECT_TRUE(appendonly.is_append());
   EXPECT_TRUE(appendonly.write_allowed());
+  EXPECT_FALSE(appendonly.is_close_on_exec());
 
   // b. Append and Update
   constexpr FileMode append_and_update("a+");
@@ -181,6 +189,45 @@ TEST(LlvmLibcFileModeTest, AllPossibleValidCombinations) {
   EXPECT_TRUE(append_update_binary.is_binary_format());
   EXPECT_TRUE(append_update_binary.write_allowed());
   EXPECT_TRUE(append_update_binary.read_allowed());
+
+  // 4. Close-on-exec: possible valid close-on-exec combinations
+
+  // a. Read Close-on-exec
+  constexpr FileMode read_close_on_exec("re");
+  EXPECT_TRUE(read_close_on_exec.is_valid());
+  EXPECT_TRUE(read_close_on_exec.is_read());
+  EXPECT_TRUE(read_close_on_exec.is_close_on_exec());
+  EXPECT_TRUE(read_close_on_exec.read_allowed());
+  EXPECT_FALSE(read_close_on_exec.write_allowed());
+  EXPECT_FALSE(read_close_on_exec.is_binary_format());
+  EXPECT_FALSE(read_close_on_exec.is_exclusive_create());
+
+  // b. Write Close-on-exec
+  constexpr FileMode write_close_on_exec("we");
+  EXPECT_TRUE(write_close_on_exec.is_valid());
+  EXPECT_TRUE(write_close_on_exec.is_write());
+  EXPECT_TRUE(write_close_on_exec.is_close_on_exec());
+  EXPECT_TRUE(write_close_on_exec.write_allowed());
+  EXPECT_FALSE(write_close_on_exec.read_allowed());
+
+  // c. Append Close-on-exec
+  constexpr FileMode append_close_on_exec("ae");
+  EXPECT_TRUE(append_close_on_exec.is_valid());
+  EXPECT_TRUE(append_close_on_exec.is_append());
+  EXPECT_TRUE(append_close_on_exec.is_close_on_exec());
+  EXPECT_TRUE(append_close_on_exec.write_allowed());
+  EXPECT_FALSE(append_close_on_exec.read_allowed());
+
+  // d. Write Close-on-exec Exclusive Update Binary
+  constexpr FileMode write_all_modifiers("wex+b");
+  EXPECT_TRUE(write_all_modifiers.is_valid());
+  EXPECT_TRUE(write_all_modifiers.is_write());
+  EXPECT_TRUE(write_all_modifiers.is_close_on_exec());
+  EXPECT_TRUE(write_all_modifiers.is_exclusive_create());
+  EXPECT_TRUE(write_all_modifiers.is_update());
+  EXPECT_TRUE(write_all_modifiers.is_binary_format());
+  EXPECT_TRUE(write_all_modifiers.read_allowed());
+  EXPECT_TRUE(write_all_modifiers.write_allowed());
 }
 
 TEST(LlvmLibcFileModeTest, InvalidCombinations) {
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index e0ca1ab2ca162..9fc984b08280d 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -10,6 +10,7 @@ add_libc_test(
     libc.include.stdio
     libc.hdr.fcntl_macros
     libc.hdr.stdio_macros
+    libc.src.__support.CPP.scope
     libc.src.__support.macros.properties.os
     libc.src.errno.errno
     libc.src.fcntl.fcntl
@@ -482,6 +483,19 @@ add_libc_test(
     libc.src.stdio.stderr
 )
 
+set(fopen_test_deps)
+if(LIBC_TARGET_OS STREQUAL "linux")
+  list(APPEND fopen_test_deps
+    libc.hdr.fcntl_macros
+    libc.src.errno.errno
+    libc.src.fcntl.fcntl
+    libc.src.stdio.fileno
+    libc.src.stdio.remove
+    libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
+  )
+endif()
+
 add_libc_test(
   fopen_test
   SUITE
@@ -489,7 +503,9 @@ add_libc_test(
   SRCS
     fopen_test.cpp
   DEPENDS
+    ${fopen_test_deps}
     libc.src.__support.CPP.scope
+    libc.src.__support.macros.properties.os
     libc.src.stdio.fread
     libc.src.stdio.fwrite
     libc.src.stdio.fclose
@@ -555,6 +571,8 @@ if(${LIBC_TARGET_OS} STREQUAL "linux")
     DEPENDS
       libc.hdr.fcntl_macros
       libc.hdr.sys_stat_macros
+      libc.src.__support.CPP.scope
+      libc.src.fcntl.fcntl
       libc.src.fcntl.open
       libc.src.stdio.fclose
       libc.src.stdio.fdopen
diff --git a/libc/test/src/stdio/fdopen_test.cpp b/libc/test/src/stdio/fdopen_test.cpp
index 0a5e9863055aa..d37b315890598 100644
--- a/libc/test/src/stdio/fdopen_test.cpp
+++ b/libc/test/src/stdio/fdopen_test.cpp
@@ -10,6 +10,8 @@
 
 #include "hdr/fcntl_macros.h"
 #include "hdr/sys_stat_macros.h" // For S_IRWXU
+#include "src/__support/CPP/scope.h"
+#include "src/fcntl/fcntl.h"
 #include "src/fcntl/open.h"
 #include "src/stdio/fclose.h"
 #include "src/stdio/fgets.h"
@@ -84,3 +86,33 @@ TEST_F(LlvmLibcStdioFdopenTest, InvalidMode) {
   LIBC_NAMESPACE::close(fd);
   ASSERT_ERRNO_SUCCESS();
 }
+
+TEST_F(LlvmLibcStdioFdopenTest, CloseOnExecPreservedWithoutModifier) {
+  using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+  int fd = LIBC_NAMESPACE::open("/dev/null", O_WRONLY | O_CLOEXEC);
+  ASSERT_GE(fd, 0);
+  FILE *file = LIBC_NAMESPACE::fdopen(fd, "w");
+  LIBC_NAMESPACE::cpp::scope_exit close_file([&] {
+    if (file != nullptr)
+      EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0));
+    else
+      EXPECT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+  });
+  ASSERT_NE(file, nullptr);
+  EXPECT_THAT(LIBC_NAMESPACE::fcntl(fd, F_GETFD), Succeeds(FD_CLOEXEC));
+}
+
+TEST_F(LlvmLibcStdioFdopenTest, CloseOnExecEnabledByModifier) {
+  using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+  int fd = LIBC_NAMESPACE::open("/dev/null", O_WRONLY);
+  ASSERT_GE(fd, 0);
+  FILE *file = LIBC_NAMESPACE::fdopen(fd, "we");
+  LIBC_NAMESPACE::cpp::scope_exit close_file([&] {
+    if (file != nullptr)
+      EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0));
+    else
+      EXPECT_THAT(LIBC_NAMESPACE::close(fd), Succeeds(0));
+  });
+  ASSERT_NE(file, nullptr);
+  EXPECT_THAT(LIBC_NAMESPACE::fcntl(fd, F_GETFD), Succeeds(FD_CLOEXEC));
+}
diff --git a/libc/test/src/stdio/fopen_test.cpp b/libc/test/src/stdio/fopen_test.cpp
index 249ef1356ab71..38cc6498e728b 100644
--- a/libc/test/src/stdio/fopen_test.cpp
+++ b/libc/test/src/stdio/fopen_test.cpp
@@ -8,6 +8,7 @@
 
 #include "src/__support/CPP/scope.h"
 #include "src/__support/File/file.h"
+#include "src/__support/macros/properties/os.h"
 #include "src/stdio/fclose.h"
 #include "src/stdio/fopen.h"
 #include "src/stdio/fread.h"
@@ -15,8 +16,22 @@
 
 #include "test/UnitTest/Test.h"
 
+#ifdef LIBC_TARGET_OS_IS_LINUX
+#include "hdr/fcntl_macros.h"
+#include "src/fcntl/fcntl.h"
+#include "src/stdio/fileno.h"
+#include "src/stdio/remove.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#endif
+
 using LIBC_NAMESPACE::cpp::scope_exit;
 
+#ifdef LIBC_TARGET_OS_IS_LINUX
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+#endif
+
 TEST(LlvmLibcFOpenTest, PrintToFile) {
   size_t result;
 
@@ -42,3 +57,78 @@ TEST(LlvmLibcFOpenTest, PrintToFile) {
     ASSERT_STREQ(data, STRING);
   }
 }
+
+#ifdef LIBC_TARGET_OS_IS_LINUX
+class LlvmLibcFOpenModeTest
+    : public LIBC_NAMESPACE::testing::ErrnoCheckingTest {
+protected:
+  void check_exclusive_create(const char *mode) {
+    const auto FILENAME = libc_make_test_file_path("fopen_exclusive.test");
+    constexpr char CONTENT[] = "Preserve this content";
+
+    // Remove a file left by an interrupted test run.
+    LIBC_NAMESPACE::remove(FILENAME);
+    libc_errno = 0;
+    FILE *file = LIBC_NAMESPACE::fopen(FILENAME, mode);
+    ASSERT_NE(file, nullptr);
+    scope_exit remove_file(
+        [&] { EXPECT_THAT(LIBC_NAMESPACE::remove(FILENAME), Succeeds(0)); });
+    {
+      scope_exit close_file(
+          [&] { EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); });
+      ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT, 1, sizeof(CONTENT) - 1, file),
+                sizeof(CONTENT) - 1);
+    }
+
+    FILE *existing = LIBC_NAMESPACE::fopen(FILENAME, mode);
+    EXPECT_THAT(existing, Fails(EEXIST, static_cast<void *>(nullptr)));
+    if (existing != nullptr)
+      EXPECT_THAT(LIBC_NAMESPACE::fclose(existing), Succeeds(0));
+
+    file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+    ASSERT_NE(file, nullptr);
+    scope_exit close_file(
+        [&] { EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); });
+    char buffer[sizeof(CONTENT)] = {};
+    ASSERT_EQ(LIBC_NAMESPACE::fread(buffer, 1, sizeof(buffer) - 1, file),
+              sizeof(CONTENT) - 1);
+    EXPECT_STREQ(buffer, CONTENT);
+  }
+};
+
+TEST_F(LlvmLibcFOpenModeTest, ExclusiveWrite) { check_exclusive_create("wx"); }
+
+TEST_F(LlvmLibcFOpenModeTest, ExclusiveAppend) { check_exclusive_create("ax"); }
+
+TEST_F(LlvmLibcFOpenModeTest, CloseOnExec) {
+  {
+    FILE *file = LIBC_NAMESPACE::fopen("/dev/null", "w");
+    ASSERT_NE(file, nullptr);
+    scope_exit close_file(
+        [&] { EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); });
+    EXPECT_THAT(LIBC_NAMESPACE::fcntl(LIBC_NAMESPACE::fileno(file), F_GETFD),
+                Succeeds(0));
+  }
+  {
+    FILE *file = LIBC_NAMESPACE::fopen("/dev/null", "we");
+    ASSERT_NE(file, nullptr);
+    scope_exit close_file(
+        [&] { EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); });
+    EXPECT_THAT(LIBC_NAMESPACE::fcntl(LIBC_NAMESPACE::fileno(file), F_GETFD),
+                Succeeds(FD_CLOEXEC));
+  }
+}
+
+TEST_F(LlvmLibcFOpenModeTest, ReadIgnoresExclusiveModifier) {
+  const auto FILENAME = libc_make_test_file_path("fopen_read_exclusive.test");
+  FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_NE(file, nullptr);
+  scope_exit remove_file(
+      [&] { EXPECT_THAT(LIBC_NAMESPACE::remove(FILENAME), Succeeds(0)); });
+  ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0));
+
+  file = LIBC_NAMESPACE::fopen(FILENAME, "rx");
+  ASSERT_NE(file, nullptr);
+  EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0));
+}
+#endif
diff --git a/libc/test/src/stdio/freopen_test.cpp b/libc/test/src/stdio/freopen_test.cpp
index 6f824c841b2d7..8de6171cc323e 100644
--- a/libc/test/src/stdio/freopen_test.cpp
+++ b/libc/test/src/stdio/freopen_test.cpp
@@ -11,6 +11,7 @@
 ///
 //===----------------------------------------------------------------------===//
 
+#include "src/__support/CPP/scope.h"
 #include "src/fcntl/fcntl.h"
 #include "src/stdio/clearerr.h"
 #include "src/stdio/fclose.h"
@@ -88,6 +89,34 @@ TEST_F(LlvmLibcFreopenTest, ReopenFile) {
   verify_file_content(FILENAME_B, CONTENT_B);
 }
 
+#ifdef LIBC_TARGET_OS_IS_LINUX
+TEST_F(LlvmLibcFreopenTest, CloseOnExec) {
+  FILE *file = LIBC_NAMESPACE::fopen("/dev/null", "w");
+  ASSERT_NE(file, nullptr);
+  LIBC_NAMESPACE::cpp::scope_exit close_file(
+      [&] { EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); });
+  ASSERT_THAT(LIBC_NAMESPACE::fcntl(LIBC_NAMESPACE::fileno(file), F_GETFD),
+              Succeeds(0));
+
+  ASSERT_EQ(LIBC_NAMESPACE::freopen("/dev/null", "we", file), file);
+  EXPECT_THAT(LIBC_NAMESPACE::fcntl(LIBC_NAMESPACE::fileno(file), F_GETFD),
+              Succeeds(FD_CLOEXEC));
+}
+
+TEST_F(LlvmLibcFreopenTest, ClearCloseOnExec) {
+  FILE *file = LIBC_NAMESPACE::fopen("/dev/null", "we");
+  ASSERT_NE(file, nullptr);
+  LIBC_NAMESPACE::cpp::scope_exit close_file(
+      [&] { EXPECT_THAT(LIBC_NAMESPACE::fclose(file), Succeeds(0)); });
+  ASSERT_THAT(LIBC_NAMESPACE::fcntl(LIBC_NAMESPACE::fileno(file), F_GETFD),
+              Succeeds(FD_CLOEXEC));
+
+  ASSERT_EQ(LIBC_NAMESPACE::freopen("/dev/null", "w", file), file);
+  EXPECT_THAT(LIBC_NAMESPACE::fcntl(LIBC_NAMESPACE::fileno(file), F_GETFD),
+              Succeeds(0));
+}
+#endif
+
 TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
   const auto FILENAME = libc_make_test_file_path("freopen_null_filename.test");
 

>From fc314937a286213bc459b876fdca47abc811b3eb Mon Sep 17 00:00:00 2001
From: Jihyeon Jeong <jh.jeong129 at gmail.com>
Date: Thu, 17 Sep 2026 14:55:17 +0000
Subject: [PATCH 2/2] [libc] Prevent temporary fd leaks across exec

---
 libc/src/__support/File/linux/file.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index 29d177f36328d..1d360c15bfa83 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -217,6 +217,10 @@ int LinuxFile::reopen_unlocked(const char *path, const char *mode) {
 
     int open_flags = map_c_mode_flags_to_linux_open_flags(file_mode);
 
+    // Prevent the temporary descriptor from being inherited across exec.
+    if (old_fd >= 0)
+      open_flags |= LinuxFileFlags::CLOSE_ON_EXEC;
+
     ErrorOr<int> new_fd =
         linux_syscalls::open(path, open_flags, LinuxFileFlags::OPEN_MODE);
 



More information about the libc-commits mailing list