[libc-commits] [libc] [libc] Implement freopen (PR #207837)

Michael Jones via libc-commits libc-commits at lists.llvm.org
Tue Aug 18 10:13:44 PDT 2026


https://github.com/michaelrj-google updated https://github.com/llvm/llvm-project/pull/207837

>From 01daf0a71bf05f131eb94b02f769232fe20ef2a8 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 1 Jul 2026 21:41:17 +0000
Subject: [PATCH 1/8] [libc] Implement freopen

Add implementation of freopen. This involved some refactoring of the
internal FILE. Currently it's only implemented for linux, despite being
a C standard function. This is because POSIX adds several features and
ties the implementation tightly to the concept of file descriptors.
Other platforms will need separate implementations.

Needed for #201236

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/stdio.yaml                   |   8 +
 libc/src/__support/File/file.h            |  13 +
 libc/src/__support/File/linux/file.cpp    | 128 ++++++++-
 libc/src/__support/File/linux/file.h      |   1 +
 libc/src/stdio/CMakeLists.txt             |   1 +
 libc/src/stdio/freopen.h                  |  27 ++
 libc/src/stdio/generic/CMakeLists.txt     |  13 +
 libc/src/stdio/generic/freopen.cpp        |  47 ++++
 libc/test/src/stdio/CMakeLists.txt        |  26 ++
 libc/test/src/stdio/freopen_test.cpp      | 317 ++++++++++++++++++++++
 13 files changed, 573 insertions(+), 11 deletions(-)
 create mode 100644 libc/src/stdio/freopen.h
 create mode 100644 libc/src/stdio/generic/freopen.cpp
 create mode 100644 libc/test/src/stdio/freopen_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 5e670882c6889..6ece52108a3fe 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1209,6 +1209,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.stdio.fileno
     libc.src.stdio.flockfile
     libc.src.stdio.fopen
+    libc.src.stdio.freopen
     libc.src.stdio.fopencookie
     libc.src.stdio.fputc
     libc.src.stdio.fputs
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 81ef813a1fa64..3c234c7935c5a 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1395,6 +1395,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.stdio.fileno
     libc.src.stdio.flockfile
     libc.src.stdio.fopen
+    libc.src.stdio.freopen
     libc.src.stdio.fopencookie
     libc.src.stdio.fputc
     libc.src.stdio.fputs
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 1eeb008e9d019..9ac04a8abdf24 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1409,6 +1409,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.stdio.fileno
     libc.src.stdio.flockfile
     libc.src.stdio.fopen
+    libc.src.stdio.freopen
     libc.src.stdio.fopencookie
     libc.src.stdio.fputc
     libc.src.stdio.fputs
diff --git a/libc/include/stdio.yaml b/libc/include/stdio.yaml
index a5feb29272971..10fd623511d48 100644
--- a/libc/include/stdio.yaml
+++ b/libc/include/stdio.yaml
@@ -146,6 +146,14 @@ functions:
     arguments:
       - type: const char *
       - type: const char *
+  - name: freopen
+    standards:
+      - stdc
+    return_type: FILE *
+    arguments:
+      - type: const char *
+      - type: const char *
+      - type: FILE *
   - name: fopencookie
     standards:
       - gnu
diff --git a/libc/src/__support/File/file.h b/libc/src/__support/File/file.h
index bb4def114d3de..bd0e8a18e9aa9 100644
--- a/libc/src/__support/File/file.h
+++ b/libc/src/__support/File/file.h
@@ -344,6 +344,18 @@ class File {
   // OpenMode, ContentType and CreateType.
   static ModeFlags mode_flags(const char *mode);
 
+  void reset_stream_state(ModeFlags new_mode) {
+    mode = new_mode;
+    pos = 0;
+    prev_op = FileOp::NONE;
+    read_limit = 0;
+    eof = false;
+    err = false;
+    orientation = Orientation::UNORIENTED;
+    mbstate = internal::mbstate();
+    adjust_buf();
+  }
+
 private:
   FileIOResult write_unlocked_impl(const void *data, size_t len);
   FileIOResult read_unlocked_impl(void *data, size_t len);
@@ -385,6 +397,7 @@ class File {
 // The implementation of this function is provided by the platform_file
 // library.
 ErrorOr<File *> openfile(const char *path, const char *mode);
+int reopenfile(File *f, const char *path, const char *mode);
 
 // The platform_file library should implement it if it relevant for that
 // platform.
diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index 91f94d8f5ab99..1b4ce5e52bf9c 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -16,6 +16,8 @@
 #include "src/__support/File/file.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/fcntl.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/lseek.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
@@ -52,21 +54,17 @@ ErrorOr<off_t> linux_file_seek(File *f, off_t offset, int whence) {
 int linux_file_close(File *f) {
   File::remove_file(f);
   auto *lf = reinterpret_cast<LinuxFile *>(f);
-  auto ret = linux_syscalls::close(lf->get_fd());
-  if (!ret) {
-    return ret.error();
+  if (lf->get_fd() >= 0) {
+    auto result = linux_syscalls::close(lf->get_fd());
+    if (!result)
+      return result.error();
   }
   delete lf;
   return 0;
 }
 
-ErrorOr<File *> openfile(const char *path, const char *mode) {
+static int mode_flags_to_open_flags(File::ModeFlags modeflags) {
   using ModeFlags = File::ModeFlags;
-  auto modeflags = File::mode_flags(mode);
-  if (modeflags == 0) {
-    // return {nullptr, EINVAL};
-    return Error(EINVAL);
-  }
   int open_flags = 0;
   if (modeflags & ModeFlags(File::OpenMode::APPEND)) {
     open_flags = O_CREAT | O_APPEND;
@@ -86,6 +84,15 @@ ErrorOr<File *> openfile(const char *path, const char *mode) {
     else
       open_flags |= O_RDONLY;
   }
+  return open_flags;
+}
+
+ErrorOr<File *> openfile(const char *path, const char *mode) {
+  auto modeflags = File::mode_flags(mode);
+  if (modeflags == 0) {
+    return Error(EINVAL);
+  }
+  int open_flags = mode_flags_to_open_flags(modeflags);
 
   // File created will have 0666 permissions.
   constexpr mode_t OPEN_MODE =
@@ -126,9 +133,9 @@ ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode) {
 
   using OpenMode = File::OpenMode;
   if (((fd_flags & O_ACCMODE) == O_RDONLY &&
-       !(modeflags & static_cast<ModeFlags>(OpenMode::READ))) ||
+       (modeflags & static_cast<ModeFlags>(OpenMode::WRITE))) ||
       ((fd_flags & O_ACCMODE) == O_WRONLY &&
-       !(modeflags & static_cast<ModeFlags>(OpenMode::WRITE)))) {
+       (modeflags & static_cast<ModeFlags>(OpenMode::READ)))) {
     return Error(EINVAL);
   }
 
@@ -174,4 +181,103 @@ int get_fileno(File *f) {
   return lf->get_fd();
 }
 
+int reopenfile(File *f, const char *path, const char *mode) {
+  auto modeflags = File::mode_flags(mode);
+  if (modeflags == 0)
+    return EINVAL;
+
+  auto *lf = reinterpret_cast<LinuxFile *>(f);
+
+  if (path != nullptr) {
+    int open_flags = mode_flags_to_open_flags(modeflags);
+
+    constexpr mode_t OPEN_MODE =
+        S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+
+    ErrorOr<int> new_fd = linux_syscalls::open(path, open_flags, OPEN_MODE);
+    int old_fd = lf->get_fd();
+
+    // If the new file fails to open, POSIX says we still have to close the old
+    // file.
+    if (!new_fd) {
+      if (old_fd >= 0) {
+        auto close_result = linux_syscalls::close(old_fd);
+        if (!close_result) {
+          f->reset_stream_state(modeflags);
+          return close_result.error();
+        }
+        lf->set_fd(-1);
+      }
+      f->reset_stream_state(modeflags);
+      return new_fd.error();
+    }
+
+    // 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);
+      if (!dup_result) {
+        f->reset_stream_state(modeflags);
+        return dup_result.error();
+      }
+      auto close_result = linux_syscalls::close(new_fd.value());
+      if (!close_result) {
+        f->reset_stream_state(modeflags);
+        return close_result.error();
+      }
+    } else {
+      lf->set_fd(new_fd.value());
+    }
+
+    f->reset_stream_state(modeflags);
+    return 0;
+  }
+
+  int fd = lf->get_fd();
+  if (fd < 0)
+    return EBADF;
+
+  auto result = internal::fcntl(fd, F_GETFL);
+  if (!result.has_value())
+    return EBADF;
+  int fd_flags = result.value();
+
+  using OpenMode = File::OpenMode;
+  using ModeFlags = File::ModeFlags;
+  if (((fd_flags & O_ACCMODE) == O_RDONLY &&
+       (modeflags & static_cast<ModeFlags>(OpenMode::WRITE))) ||
+      ((fd_flags & O_ACCMODE) == O_WRONLY &&
+       (modeflags & static_cast<ModeFlags>(OpenMode::READ)))) {
+    return EINVAL;
+  }
+
+  bool do_seek = false;
+  bool is_append = modeflags & static_cast<ModeFlags>(OpenMode::APPEND);
+  bool has_append_flag = fd_flags & O_APPEND;
+
+  if (is_append && !has_append_flag) {
+    if (!internal::fcntl(fd, F_SETFL,
+                         reinterpret_cast<void *>(fd_flags | O_APPEND))
+             .has_value()) {
+      return EBADF;
+    }
+    do_seek = true;
+  } else if (!is_append && has_append_flag) {
+    if (!internal::fcntl(fd, F_SETFL,
+                         reinterpret_cast<void *>(fd_flags & ~O_APPEND))
+             .has_value()) {
+      return EBADF;
+    }
+  }
+
+  f->reset_stream_state(modeflags);
+
+  if (do_seek) {
+    auto seek_result = linux_file_seek(f, 0, SEEK_END);
+    if (!seek_result.has_value())
+      return seek_result.error();
+  }
+  return 0;
+}
+
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/File/linux/file.h b/libc/src/__support/File/linux/file.h
index 16d72a60f8638..37f8c6c0b5630 100644
--- a/libc/src/__support/File/linux/file.h
+++ b/libc/src/__support/File/linux/file.h
@@ -29,6 +29,7 @@ class LinuxFile : public File {
         fd(file_descriptor) {}
 
   int get_fd() const { return fd; }
+  void set_fd(int new_fd) { fd = new_fd; }
 };
 
 // Create a File object and associate it with a fd.
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 68ee57fae646d..3ef6e4f7b7d01 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -278,6 +278,7 @@ add_stdio_entrypoint_object(fflush)
 add_stdio_entrypoint_object(clearerr)
 add_stdio_entrypoint_object(clearerr_unlocked)
 add_stdio_entrypoint_object(fopen)
+add_stdio_entrypoint_object(freopen)
 add_stdio_entrypoint_object(fclose)
 add_stdio_entrypoint_object(fread_unlocked)
 add_stdio_entrypoint_object(fread)
diff --git a/libc/src/stdio/freopen.h b/libc/src/stdio/freopen.h
new file mode 100644
index 0000000000000..f94c4d7944abe
--- /dev/null
+++ b/libc/src/stdio/freopen.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 header for freopen.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_FREOPEN_H
+#define LLVM_LIBC_SRC_STDIO_FREOPEN_H
+
+#include "hdr/types/FILE.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+::FILE *freopen(const char *__restrict filename, const char *__restrict mode,
+                ::FILE *__restrict stream);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_FREOPEN_H
diff --git a/libc/src/stdio/generic/CMakeLists.txt b/libc/src/stdio/generic/CMakeLists.txt
index b6bb41b5053a3..43c18857aeec1 100644
--- a/libc/src/stdio/generic/CMakeLists.txt
+++ b/libc/src/stdio/generic/CMakeLists.txt
@@ -184,6 +184,19 @@ add_generic_entrypoint_object(
     libc.src.__support.File.platform_file
 )
 
+add_generic_entrypoint_object(
+  freopen
+  SRCS
+    freopen.cpp
+  HDRS
+    ../freopen.h
+  DEPENDS
+    libc.hdr.types.FILE
+    libc.src.errno.errno
+    libc.src.__support.File.file
+    libc.src.__support.File.platform_file
+)
+
 add_generic_entrypoint_object(
   fclose
   SRCS
diff --git a/libc/src/stdio/generic/freopen.cpp b/libc/src/stdio/generic/freopen.cpp
new file mode 100644
index 0000000000000..8b0025e7fe81a
--- /dev/null
+++ b/libc/src/stdio/generic/freopen.cpp
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 freopen.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/freopen.h"
+#include "src/__support/File/file.h"
+
+#include "hdr/types/FILE.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(::FILE *, freopen,
+                   (const char *__restrict filename,
+                    const char *__restrict mode, ::FILE *__restrict stream)) {
+  if (stream == nullptr) {
+    libc_errno = EINVAL;
+    return nullptr;
+  }
+
+  auto *file = reinterpret_cast<File *>(stream);
+  file->lock();
+  file->flush_unlocked();
+
+  int error = reopenfile(file, filename, mode);
+  file->unlock();
+
+  if (error != 0) {
+    libc_errno = error;
+    return nullptr;
+  }
+
+  return stream;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 6ec484129dee2..124f6fe092da6 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -1,5 +1,31 @@
 add_custom_target(libc_stdio_unittests)
 
+add_libc_test(
+  freopen_test
+  SUITE
+    libc_stdio_unittests
+  SRCS
+    freopen_test.cpp
+  DEPENDS
+    libc.include.stdio
+    libc.src.errno.errno
+    libc.src.fcntl.fcntl
+    libc.src.stdio.clearerr
+    libc.src.stdio.fclose
+    libc.src.stdio.feof
+    libc.src.stdio.ferror
+    libc.src.stdio.fflush
+    libc.src.stdio.fileno
+    libc.src.stdio.fopen
+    libc.src.stdio.fread
+    libc.src.stdio.freopen
+    libc.src.stdio.fwrite
+    libc.src.stdio.stdout
+    libc.src.unistd.close
+    libc.src.wchar.fwide
+    libc.test.UnitTest.ErrnoCheckingTest
+)
+
 add_libc_test(
   fileop_test
   SUITE
diff --git a/libc/test/src/stdio/freopen_test.cpp b/libc/test/src/stdio/freopen_test.cpp
new file mode 100644
index 0000000000000..4fb4f5f788540
--- /dev/null
+++ b/libc/test/src/stdio/freopen_test.cpp
@@ -0,0 +1,317 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Unittests for freopen.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/fcntl/fcntl.h"
+#include "src/stdio/clearerr.h"
+#include "src/stdio/fclose.h"
+#include "src/stdio/feof.h"
+#include "src/stdio/ferror.h"
+#include "src/stdio/fflush.h"
+#include "src/stdio/fileno.h"
+#include "src/stdio/fopen.h"
+#include "src/stdio/fread.h"
+#include "src/stdio/freopen.h"
+#include "src/stdio/fwrite.h"
+#include "src/stdio/stdout.h"
+#include "src/unistd/close.h"
+#include "src/wchar/fwide.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+#include "hdr/fcntl_macros.h"
+#include "hdr/stdio_macros.h"
+#include "src/__support/macros/properties/os.h"
+
+using LlvmLibcFreopenTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+TEST_F(LlvmLibcFreopenTest, ReopenFile) {
+  auto FILENAME_A =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_a.test"));
+  auto FILENAME_B =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_b.test"));
+
+  // Step 1: Open file A and write initial content.
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  constexpr char CONTENT_A[] = "File A Content";
+  ASSERT_EQ(sizeof(CONTENT_A) - 1,
+            LIBC_NAMESPACE::fwrite(CONTENT_A, 1, sizeof(CONTENT_A) - 1, file));
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+
+  // Step 2: Open file A for reading.
+  file = LIBC_NAMESPACE::fopen(FILENAME_A, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  // Step 3: Use freopen to redirect stream from file A to file B for writing.
+  ::FILE *reopened_file = LIBC_NAMESPACE::freopen(FILENAME_B, "w", file);
+  ASSERT_NE(reopened_file, static_cast<::FILE *>(nullptr));
+  ASSERT_EQ(reopened_file, file);
+
+  // Step 4: Write to reopened stream (file B).
+  constexpr char CONTENT_B[] = "File B Content Written via freopen";
+  ASSERT_EQ(sizeof(CONTENT_B) - 1,
+            LIBC_NAMESPACE::fwrite(CONTENT_B, 1, sizeof(CONTENT_B) - 1,
+                                   reopened_file));
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened_file));
+
+  // Step 5: Verify file B content.
+  file = LIBC_NAMESPACE::fopen(FILENAME_B, "r");
+  ASSERT_FALSE(file == nullptr);
+  char read_buf[sizeof(CONTENT_B)] = {0};
+  ASSERT_EQ(sizeof(CONTENT_B) - 1,
+            LIBC_NAMESPACE::fread(read_buf, 1, sizeof(CONTENT_B) - 1, file));
+  ASSERT_STREQ(read_buf, CONTENT_B);
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+}
+
+TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_filename.test"));
+
+  // Step 1: Open file with write-update mode.
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
+  ASSERT_FALSE(file == nullptr);
+
+  constexpr char INITIAL_CONTENT[] = "Initial Data ";
+  ASSERT_EQ(sizeof(INITIAL_CONTENT) - 1,
+            LIBC_NAMESPACE::fwrite(INITIAL_CONTENT, 1,
+                                   sizeof(INITIAL_CONTENT) - 1, file));
+
+  // Step 2: Change mode with filename == nullptr to append.
+  ::FILE *reopened = LIBC_NAMESPACE::freopen(nullptr, "a", file);
+  ASSERT_NE(reopened, static_cast<::FILE *>(nullptr));
+  ASSERT_EQ(reopened, file);
+
+  // Step 3: Write appended content.
+  constexpr char APPENDED_CONTENT[] = "Appended Data";
+  ASSERT_EQ(sizeof(APPENDED_CONTENT) - 1,
+            LIBC_NAMESPACE::fwrite(APPENDED_CONTENT, 1,
+                                   sizeof(APPENDED_CONTENT) - 1, reopened));
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened));
+
+  // Step 4: Verify combined content.
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+  char read_buf[64] = {0};
+  size_t read_bytes =
+      LIBC_NAMESPACE::fread(read_buf, 1, sizeof(read_buf) - 1, file);
+  read_buf[read_bytes] = '\0';
+  ASSERT_STREQ(read_buf, "Initial Data Appended Data");
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+}
+
+TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
+  auto FILENAME = libc_make_test_file_path(
+      APPEND_LIBC_TEST("freopen_invalid_mode_change.test"));
+
+  // Open file read-only.
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
+  // Attempt incompatible mode change (r to w with filename == nullptr).
+  ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "w", file),
+              Fails(EINVAL, static_cast<void *>(nullptr)));
+
+  // Attempt incompatible mode change (r to w+ with filename == nullptr).
+  ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "w+", file),
+              Fails(EINVAL, static_cast<void *>(nullptr)));
+
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+}
+
+TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_stream.test"));
+
+  ASSERT_THAT(LIBC_NAMESPACE::freopen(FILENAME, "r", nullptr),
+              Fails(EINVAL, static_cast<void *>(nullptr)));
+}
+
+TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_invalid_mode.test"));
+
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  ASSERT_THAT(LIBC_NAMESPACE::freopen(FILENAME, "invalid_mode_str", file),
+              Fails(EINVAL, static_cast<void *>(nullptr)));
+
+  // Per spec, original stream fd was closed on filename != nullptr freopen
+  // attempt.
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+}
+
+#if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
+TEST_F(LlvmLibcFreopenTest, NonExistentFileFailure) {
+  auto EXISTING_FILE =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_existing.test"));
+  auto NON_EXISTENT_FILE =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_does_not_exist.test"));
+
+  ::FILE *file = LIBC_NAMESPACE::fopen(EXISTING_FILE, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  int old_fd = LIBC_NAMESPACE::fileno(file);
+  ASSERT_GT(old_fd, 0);
+
+  // Attempt to freopen a non-existent file in read mode.
+  ASSERT_THAT(LIBC_NAMESPACE::freopen(NON_EXISTENT_FILE, "r", file),
+              Fails(ENOENT, static_cast<void *>(nullptr)));
+
+  // Per POSIX spec: The original stream fd is closed even if open fails.
+  ASSERT_EQ(-1, LIBC_NAMESPACE::fcntl(old_fd, F_GETFL));
+  ASSERT_ERRNO_EQ(EBADF);
+
+  // Clean up stream object to avoid memory leaks.
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+}
+#endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
+
+TEST_F(LlvmLibcFreopenTest, FlushBeforeReopenTest) {
+  auto FILENAME_A =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flush_a.test"));
+  auto FILENAME_B =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flush_b.test"));
+
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  constexpr char DIRTY_DATA[] = "Buffered data before freopen";
+  ASSERT_EQ(
+      sizeof(DIRTY_DATA) - 1,
+      LIBC_NAMESPACE::fwrite(DIRTY_DATA, 1, sizeof(DIRTY_DATA) - 1, file));
+
+  // freopen must flush unwritten buffered data to FILENAME_A before reopening
+  ::FILE *reopened = LIBC_NAMESPACE::freopen(FILENAME_B, "w", file);
+  ASSERT_NE(reopened, static_cast<::FILE *>(nullptr));
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened));
+
+  // Verify FILENAME_A received the flushed dirty buffer
+  file = LIBC_NAMESPACE::fopen(FILENAME_A, "r");
+  ASSERT_FALSE(file == nullptr);
+  char read_buf[sizeof(DIRTY_DATA)] = {0};
+  ASSERT_EQ(sizeof(DIRTY_DATA) - 1,
+            LIBC_NAMESPACE::fread(read_buf, 1, sizeof(DIRTY_DATA) - 1, file));
+  ASSERT_STREQ(read_buf, DIRTY_DATA);
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+}
+
+TEST_F(LlvmLibcFreopenTest, ClearFlagsTest) {
+  auto FILENAME_A =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flags_a.test"));
+  auto FILENAME_B =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flags_b.test"));
+
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
+  ASSERT_FALSE(file == nullptr);
+  constexpr char SHORT_DATA[] = "X";
+  ASSERT_EQ(size_t(1), LIBC_NAMESPACE::fwrite(SHORT_DATA, 1, 1, file));
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+
+  // Trigger EOF on file
+  file = LIBC_NAMESPACE::fopen(FILENAME_A, "r");
+  ASSERT_FALSE(file == nullptr);
+  char buf[4];
+  LIBC_NAMESPACE::fread(buf, 1, sizeof(buf), file);
+  ASSERT_NE(0, LIBC_NAMESPACE::feof(file));
+
+  // freopen must clear EOF and error indicators
+  ::FILE *reopened = LIBC_NAMESPACE::freopen(FILENAME_B, "w", file);
+  ASSERT_NE(reopened, static_cast<::FILE *>(nullptr));
+  ASSERT_EQ(0, LIBC_NAMESPACE::feof(reopened));
+  ASSERT_EQ(0, LIBC_NAMESPACE::ferror(reopened));
+
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened));
+}
+
+#if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
+TEST_F(LlvmLibcFreopenTest, NullFilenameBadFdTest) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_bad_fd.test"));
+
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  int fd = LIBC_NAMESPACE::fileno(file);
+  ASSERT_GT(fd, 0);
+  // Manually close underlying fd to simulate bad file descriptor state
+  ASSERT_EQ(0, LIBC_NAMESPACE::close(fd));
+
+  // freopen with filename == nullptr on invalid fd should return nullptr +
+  // EBADF
+  ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "a", file),
+              Fails(EBADF, static_cast<void *>(nullptr)));
+}
+#endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
+
+TEST_F(LlvmLibcFreopenTest, ResetOrientationTest) {
+  auto FILENAME_A =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_orient_a.test"));
+  auto FILENAME_B =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_orient_b.test"));
+
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
+  ASSERT_FALSE(file == nullptr);
+
+  // Set wide orientation
+  ASSERT_GT(LIBC_NAMESPACE::fwide(file, 1), 0);
+
+  // freopen must reset orientation to 0 (unoriented)
+  ::FILE *reopened = LIBC_NAMESPACE::freopen(FILENAME_B, "w", file);
+  ASSERT_NE(reopened, static_cast<::FILE *>(nullptr));
+  ASSERT_EQ(0, LIBC_NAMESPACE::fwide(reopened, 0));
+
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened));
+}
+
+#if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
+TEST_F(LlvmLibcFreopenTest, StdoutRedirectionTest) {
+  auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_stdout.test"));
+
+  int stdout_fd = LIBC_NAMESPACE::fileno(LIBC_NAMESPACE::stdout);
+  ASSERT_EQ(stdout_fd, 1);
+
+  // Redirect stdout to FILENAME
+  ::FILE *reopened =
+      LIBC_NAMESPACE::freopen(FILENAME, "w", LIBC_NAMESPACE::stdout);
+  ASSERT_NE(reopened, static_cast<::FILE *>(nullptr));
+  ASSERT_EQ(reopened, static_cast<::FILE *>(LIBC_NAMESPACE::stdout));
+
+  // Verify fileno(stdout) is preserved as 1
+  ASSERT_EQ(1, LIBC_NAMESPACE::fileno(LIBC_NAMESPACE::stdout));
+
+  constexpr char MSG[] = "Redirected Stdout";
+  ASSERT_EQ(sizeof(MSG) - 1, LIBC_NAMESPACE::fwrite(MSG, 1, sizeof(MSG) - 1,
+                                                    LIBC_NAMESPACE::stdout));
+  ASSERT_EQ(0, LIBC_NAMESPACE::fflush(LIBC_NAMESPACE::stdout));
+
+  // Verify file content
+  ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+  char read_buf[sizeof(MSG)] = {0};
+  ASSERT_EQ(sizeof(MSG) - 1,
+            LIBC_NAMESPACE::fread(read_buf, 1, sizeof(MSG) - 1, file));
+  ASSERT_STREQ(read_buf, MSG);
+  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+}
+#endif

>From ae07f31568d5c570b3119214c24b12794f73f936 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 8 Jul 2026 17:50:05 +0000
Subject: [PATCH 2/8] various cleanups, address comments

---
 libc/src/__support/File/linux/file.cpp | 50 +++++++++++++++--------
 libc/src/stdio/generic/CMakeLists.txt  |  2 +
 libc/src/stdio/generic/freopen.cpp     |  6 +--
 libc/test/src/stdio/CMakeLists.txt     |  4 ++
 libc/test/src/stdio/freopen_test.cpp   | 55 ++++++++++++++------------
 5 files changed, 71 insertions(+), 46 deletions(-)

diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index 1b4ce5e52bf9c..57e3417c234ab 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -54,13 +54,17 @@ ErrorOr<off_t> linux_file_seek(File *f, off_t offset, int whence) {
 int linux_file_close(File *f) {
   File::remove_file(f);
   auto *lf = reinterpret_cast<LinuxFile *>(f);
+  int retval = 0;
   if (lf->get_fd() >= 0) {
+    // Linux closes the file descriptor early in the syscall, so we assume it's
+    // always closed after the call. That means we should also delete the
+    // LinuxFile on error.
     auto result = linux_syscalls::close(lf->get_fd());
     if (!result)
-      return result.error();
+      retval = result.error();
   }
   delete lf;
-  return 0;
+  return retval;
 }
 
 static int mode_flags_to_open_flags(File::ModeFlags modeflags) {
@@ -132,10 +136,18 @@ ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode) {
   int fd_flags = result.value();
 
   using OpenMode = File::OpenMode;
-  if (((fd_flags & O_ACCMODE) == O_RDONLY &&
-       (modeflags & static_cast<ModeFlags>(OpenMode::WRITE))) ||
-      ((fd_flags & O_ACCMODE) == O_WRONLY &&
-       (modeflags & static_cast<ModeFlags>(OpenMode::READ)))) {
+  using ModeFlags = File::ModeFlags;
+
+  constexpr ModeFlags REQUIRES_WRITE =
+      static_cast<ModeFlags>(OpenMode::WRITE) |
+      static_cast<ModeFlags>(OpenMode::APPEND) |
+      static_cast<ModeFlags>(OpenMode::PLUS);
+
+  constexpr ModeFlags REQUIRES_READ = static_cast<ModeFlags>(OpenMode::READ) |
+                                      static_cast<ModeFlags>(OpenMode::PLUS);
+
+  if (((fd_flags & O_ACCMODE) == O_RDONLY && (modeflags & REQUIRES_WRITE)) ||
+      ((fd_flags & O_ACCMODE) == O_WRONLY && (modeflags & REQUIRES_READ))) {
     return Error(EINVAL);
   }
 
@@ -201,11 +213,10 @@ int reopenfile(File *f, const char *path, const char *mode) {
     // file.
     if (!new_fd) {
       if (old_fd >= 0) {
-        auto close_result = linux_syscalls::close(old_fd);
-        if (!close_result) {
-          f->reset_stream_state(modeflags);
-          return close_result.error();
-        }
+        // POSIX: "Failure to close the file descriptor successfully shall be
+        // ignored"
+        linux_syscalls::close(old_fd);
+
         lf->set_fd(-1);
       }
       f->reset_stream_state(modeflags);
@@ -244,11 +255,18 @@ int reopenfile(File *f, const char *path, const char *mode) {
 
   using OpenMode = File::OpenMode;
   using ModeFlags = File::ModeFlags;
-  if (((fd_flags & O_ACCMODE) == O_RDONLY &&
-       (modeflags & static_cast<ModeFlags>(OpenMode::WRITE))) ||
-      ((fd_flags & O_ACCMODE) == O_WRONLY &&
-       (modeflags & static_cast<ModeFlags>(OpenMode::READ)))) {
-    return EINVAL;
+
+  constexpr ModeFlags REQUIRES_WRITE =
+      static_cast<ModeFlags>(OpenMode::WRITE) |
+      static_cast<ModeFlags>(OpenMode::APPEND) |
+      static_cast<ModeFlags>(OpenMode::PLUS);
+
+  constexpr ModeFlags REQUIRES_READ = static_cast<ModeFlags>(OpenMode::READ) |
+                                      static_cast<ModeFlags>(OpenMode::PLUS);
+
+  if (((fd_flags & O_ACCMODE) == O_RDONLY && (modeflags & REQUIRES_WRITE)) ||
+      ((fd_flags & O_ACCMODE) == O_WRONLY && (modeflags & REQUIRES_READ))) {
+    return EBADF;
   }
 
   bool do_seek = false;
diff --git a/libc/src/stdio/generic/CMakeLists.txt b/libc/src/stdio/generic/CMakeLists.txt
index 43c18857aeec1..461a70b009839 100644
--- a/libc/src/stdio/generic/CMakeLists.txt
+++ b/libc/src/stdio/generic/CMakeLists.txt
@@ -195,6 +195,8 @@ add_generic_entrypoint_object(
     libc.src.errno.errno
     libc.src.__support.File.file
     libc.src.__support.File.platform_file
+    libc.src.__support.common
+    libc.src.__support.macros.config
 )
 
 add_generic_entrypoint_object(
diff --git a/libc/src/stdio/generic/freopen.cpp b/libc/src/stdio/generic/freopen.cpp
index 8b0025e7fe81a..afdc098ee6849 100644
--- a/libc/src/stdio/generic/freopen.cpp
+++ b/libc/src/stdio/generic/freopen.cpp
@@ -18,16 +18,14 @@
 #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(::FILE *, freopen,
                    (const char *__restrict filename,
                     const char *__restrict mode, ::FILE *__restrict stream)) {
-  if (stream == nullptr) {
-    libc_errno = EINVAL;
-    return nullptr;
-  }
+  LIBC_CRASH_ON_NULLPTR(stream);
 
   auto *file = reinterpret_cast<File *>(stream);
   file->lock();
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 124f6fe092da6..87c96f535771a 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -8,6 +8,9 @@ add_libc_test(
     freopen_test.cpp
   DEPENDS
     libc.include.stdio
+    libc.hdr.fcntl_macros
+    libc.hdr.stdio_macros
+    libc.src.__support.macros.properties.os
     libc.src.errno.errno
     libc.src.fcntl.fcntl
     libc.src.stdio.clearerr
@@ -24,6 +27,7 @@ add_libc_test(
     libc.src.unistd.close
     libc.src.wchar.fwide
     libc.test.UnitTest.ErrnoCheckingTest
+    libc.test.UnitTest.ErrnoSetterMatcher
 )
 
 add_libc_test(
diff --git a/libc/test/src/stdio/freopen_test.cpp b/libc/test/src/stdio/freopen_test.cpp
index 4fb4f5f788540..4d7fb12766925 100644
--- a/libc/test/src/stdio/freopen_test.cpp
+++ b/libc/test/src/stdio/freopen_test.cpp
@@ -38,9 +38,9 @@ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
 
 TEST_F(LlvmLibcFreopenTest, ReopenFile) {
-  auto FILENAME_A =
+  const auto FILENAME_A =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_a.test"));
-  auto FILENAME_B =
+  const auto FILENAME_B =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_b.test"));
 
   // Step 1: Open file A and write initial content.
@@ -79,7 +79,7 @@ TEST_F(LlvmLibcFreopenTest, ReopenFile) {
 }
 
 TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_filename.test"));
 
   // Step 1: Open file with write-update mode.
@@ -115,7 +115,7 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
 }
 
 TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
-  auto FILENAME = libc_make_test_file_path(
+  const auto FILENAME = libc_make_test_file_path(
       APPEND_LIBC_TEST("freopen_invalid_mode_change.test"));
 
   // Open file read-only.
@@ -128,25 +128,17 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
 
   // Attempt incompatible mode change (r to w with filename == nullptr).
   ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "w", file),
-              Fails(EINVAL, static_cast<void *>(nullptr)));
+              Fails(EBADF, static_cast<void *>(nullptr)));
 
   // Attempt incompatible mode change (r to w+ with filename == nullptr).
   ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "w+", file),
-              Fails(EINVAL, static_cast<void *>(nullptr)));
+              Fails(EBADF, static_cast<void *>(nullptr)));
 
   ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 }
 
-TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
-  auto FILENAME =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_stream.test"));
-
-  ASSERT_THAT(LIBC_NAMESPACE::freopen(FILENAME, "r", nullptr),
-              Fails(EINVAL, static_cast<void *>(nullptr)));
-}
-
 TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_invalid_mode.test"));
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
@@ -155,8 +147,9 @@ TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
   ASSERT_THAT(LIBC_NAMESPACE::freopen(FILENAME, "invalid_mode_str", file),
               Fails(EINVAL, static_cast<void *>(nullptr)));
 
-  // Per spec, original stream fd was closed on filename != nullptr freopen
-  // attempt.
+  // TODO: POSIX says "The original stream shall be closed regardless of whether
+  // the subsequent open succeeds." so this should not be valid. Correct this
+  // test.
   ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 }
 
@@ -187,9 +180,9 @@ TEST_F(LlvmLibcFreopenTest, NonExistentFileFailure) {
 #endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
 
 TEST_F(LlvmLibcFreopenTest, FlushBeforeReopenTest) {
-  auto FILENAME_A =
+  const auto FILENAME_A =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flush_a.test"));
-  auto FILENAME_B =
+  const auto FILENAME_B =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flush_b.test"));
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
@@ -216,15 +209,16 @@ TEST_F(LlvmLibcFreopenTest, FlushBeforeReopenTest) {
 }
 
 TEST_F(LlvmLibcFreopenTest, ClearFlagsTest) {
-  auto FILENAME_A =
+  const auto FILENAME_A =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flags_a.test"));
-  auto FILENAME_B =
+  const auto FILENAME_B =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flags_b.test"));
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
   ASSERT_FALSE(file == nullptr);
   constexpr char SHORT_DATA[] = "X";
-  ASSERT_EQ(size_t(1), LIBC_NAMESPACE::fwrite(SHORT_DATA, 1, 1, file));
+  ASSERT_EQ(static_cast<size_t>(1),
+            LIBC_NAMESPACE::fwrite(SHORT_DATA, 1, 1, file));
   ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 
   // Trigger EOF on file
@@ -245,7 +239,7 @@ TEST_F(LlvmLibcFreopenTest, ClearFlagsTest) {
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
 TEST_F(LlvmLibcFreopenTest, NullFilenameBadFdTest) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_bad_fd.test"));
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
@@ -264,9 +258,9 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameBadFdTest) {
 #endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
 
 TEST_F(LlvmLibcFreopenTest, ResetOrientationTest) {
-  auto FILENAME_A =
+  const auto FILENAME_A =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_orient_a.test"));
-  auto FILENAME_B =
+  const auto FILENAME_B =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_orient_b.test"));
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
@@ -285,7 +279,7 @@ TEST_F(LlvmLibcFreopenTest, ResetOrientationTest) {
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
 TEST_F(LlvmLibcFreopenTest, StdoutRedirectionTest) {
-  auto FILENAME =
+  const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_stdout.test"));
 
   int stdout_fd = LIBC_NAMESPACE::fileno(LIBC_NAMESPACE::stdout);
@@ -315,3 +309,12 @@ TEST_F(LlvmLibcFreopenTest, StdoutRedirectionTest) {
   ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 }
 #endif
+
+// TODO: update to death test since this crashes now.
+//  TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
+//    const auto FILENAME =
+//        libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_stream.test"));
+
+//   ASSERT_THAT(LIBC_NAMESPACE::freopen(FILENAME, "r", nullptr),
+//               Fails(EINVAL, static_cast<void *>(nullptr)));
+// }

>From a6fa62c6e36901a688fd695e8b42a704e5ba3830 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Wed, 22 Jul 2026 21:02:41 +0000
Subject: [PATCH 3/8] address comments

---
 libc/src/__support/File/linux/file.cpp | 33 ++++++++++++++++---------
 libc/src/stdio/generic/freopen.cpp     |  3 +--
 libc/test/src/stdio/freopen_test.cpp   | 34 ++++++++++++++++++--------
 3 files changed, 47 insertions(+), 23 deletions(-)

diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index 57e3417c234ab..ffdd47112791c 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -15,9 +15,8 @@
 #include "src/__support/CPP/new.h"
 #include "src/__support/File/file.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
-#include "src/__support/OSUtil/linux/syscall_wrappers/fcntl.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/fcntl.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/lseek.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
@@ -194,20 +193,29 @@ int get_fileno(File *f) {
 }
 
 int reopenfile(File *f, const char *path, const char *mode) {
-  auto modeflags = File::mode_flags(mode);
-  if (modeflags == 0)
-    return EINVAL;
+  f->flush_unlocked();
 
+  auto modeflags = File::mode_flags(mode);
   auto *lf = reinterpret_cast<LinuxFile *>(f);
 
   if (path != nullptr) {
+    int old_fd = lf->get_fd();
+
+    if (modeflags == 0) {
+      if (old_fd >= 0) {
+        linux_syscalls::close(old_fd);
+        lf->set_fd(-1);
+      }
+      f->reset_stream_state(modeflags);
+      return EINVAL;
+    }
+
     int open_flags = mode_flags_to_open_flags(modeflags);
 
     constexpr mode_t OPEN_MODE =
         S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
 
     ErrorOr<int> new_fd = linux_syscalls::open(path, open_flags, OPEN_MODE);
-    int old_fd = lf->get_fd();
 
     // If the new file fails to open, POSIX says we still have to close the old
     // file.
@@ -244,11 +252,14 @@ int reopenfile(File *f, const char *path, const char *mode) {
     return 0;
   }
 
+  if (modeflags == 0)
+    return EINVAL;
+
   int fd = lf->get_fd();
   if (fd < 0)
     return EBADF;
 
-  auto result = internal::fcntl(fd, F_GETFL);
+  auto result = linux_syscalls::fcntl(fd, F_GETFL);
   if (!result.has_value())
     return EBADF;
   int fd_flags = result.value();
@@ -274,15 +285,15 @@ int reopenfile(File *f, const char *path, const char *mode) {
   bool has_append_flag = fd_flags & O_APPEND;
 
   if (is_append && !has_append_flag) {
-    if (!internal::fcntl(fd, F_SETFL,
-                         reinterpret_cast<void *>(fd_flags | O_APPEND))
+    if (!linux_syscalls::fcntl(fd, F_SETFL,
+                               reinterpret_cast<void *>(fd_flags | O_APPEND))
              .has_value()) {
       return EBADF;
     }
     do_seek = true;
   } else if (!is_append && has_append_flag) {
-    if (!internal::fcntl(fd, F_SETFL,
-                         reinterpret_cast<void *>(fd_flags & ~O_APPEND))
+    if (!linux_syscalls::fcntl(fd, F_SETFL,
+                               reinterpret_cast<void *>(fd_flags & ~O_APPEND))
              .has_value()) {
       return EBADF;
     }
diff --git a/libc/src/stdio/generic/freopen.cpp b/libc/src/stdio/generic/freopen.cpp
index afdc098ee6849..a82a4560d554d 100644
--- a/libc/src/stdio/generic/freopen.cpp
+++ b/libc/src/stdio/generic/freopen.cpp
@@ -28,9 +28,8 @@ LLVM_LIBC_FUNCTION(::FILE *, freopen,
   LIBC_CRASH_ON_NULLPTR(stream);
 
   auto *file = reinterpret_cast<File *>(stream);
-  file->lock();
-  file->flush_unlocked();
 
+  file->lock();
   int error = reopenfile(file, filename, mode);
   file->unlock();
 
diff --git a/libc/test/src/stdio/freopen_test.cpp b/libc/test/src/stdio/freopen_test.cpp
index 4d7fb12766925..8b5cdb41ef3a4 100644
--- a/libc/test/src/stdio/freopen_test.cpp
+++ b/libc/test/src/stdio/freopen_test.cpp
@@ -137,6 +137,7 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
   ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 }
 
+#if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
 TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
   const auto FILENAME =
       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_invalid_mode.test"));
@@ -144,14 +145,21 @@ TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
 
+  int old_fd = LIBC_NAMESPACE::fileno(file);
+  ASSERT_GT(old_fd, 0);
+
   ASSERT_THAT(LIBC_NAMESPACE::freopen(FILENAME, "invalid_mode_str", file),
               Fails(EINVAL, static_cast<void *>(nullptr)));
 
-  // TODO: POSIX says "The original stream shall be closed regardless of whether
-  // the subsequent open succeeds." so this should not be valid. Correct this
-  // test.
+  // Per POSIX spec, original stream fd was closed on filename != nullptr
+  // freopen attempt.
+  ASSERT_EQ(-1, LIBC_NAMESPACE::fcntl(old_fd, F_GETFL));
+  ASSERT_ERRNO_EQ(EBADF);
+
+  // Clean up stream object to avoid memory leaks.
   ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 }
+#endif
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
 TEST_F(LlvmLibcFreopenTest, NonExistentFileFailure) {
@@ -310,11 +318,17 @@ TEST_F(LlvmLibcFreopenTest, StdoutRedirectionTest) {
 }
 #endif
 
-// TODO: update to death test since this crashes now.
-//  TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
-//    const auto FILENAME =
-//        libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_stream.test"));
+#define UNIT 1
+#define HERMETIC 2
+#if LIBC_TEST == UNIT
+TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
+  const auto FILENAME =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_stream.test"));
+  const char *fn = FILENAME;
 
-//   ASSERT_THAT(LIBC_NAMESPACE::freopen(FILENAME, "r", nullptr),
-//               Fails(EINVAL, static_cast<void *>(nullptr)));
-// }
+  EXPECT_DEATH([=] { LIBC_NAMESPACE::freopen(fn, "r", nullptr); },
+               WITH_SIGNAL(-1));
+}
+#endif
+#undef UNIT
+#undef HERMETIC

>From a5a0c45dae71cc0feee61686862b74d3267e97f6 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Sat, 1 Aug 2026 00:01:57 +0000
Subject: [PATCH 4/8] new null check, add _unlocked, close on failure

---
 libc/include/stdio.yaml                |   6 +-
 libc/src/__support/File/linux/file.cpp |  16 ++-
 libc/src/stdio/generic/CMakeLists.txt  |   1 +
 libc/src/stdio/generic/fopen.cpp       |   6 +-
 libc/src/stdio/generic/freopen.cpp     |   3 +-
 libc/test/src/stdio/freopen_test.cpp   | 138 ++++++++++++-------------
 6 files changed, 93 insertions(+), 77 deletions(-)

diff --git a/libc/include/stdio.yaml b/libc/include/stdio.yaml
index 10fd623511d48..ef725dd4fe619 100644
--- a/libc/include/stdio.yaml
+++ b/libc/include/stdio.yaml
@@ -151,9 +151,9 @@ functions:
       - stdc
     return_type: FILE *
     arguments:
-      - type: const char *
-      - type: const char *
-      - type: FILE *
+      - type: const char *__restrict
+      - type: const char *__restrict
+      - type: FILE *__restrict
   - name: fopencookie
     standards:
       - gnu
diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index ffdd47112791c..b071c24b64982 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -61,6 +61,8 @@ int linux_file_close(File *f) {
     auto result = linux_syscalls::close(lf->get_fd());
     if (!result)
       retval = result.error();
+  } else {
+    retval = EBADF;
   }
   delete lf;
   return retval;
@@ -192,7 +194,7 @@ int get_fileno(File *f) {
   return lf->get_fd();
 }
 
-int reopenfile(File *f, const char *path, const char *mode) {
+int reopenfile_unlocked(File *f, const char *path, const char *mode) {
   f->flush_unlocked();
 
   auto modeflags = File::mode_flags(mode);
@@ -309,4 +311,16 @@ int reopenfile(File *f, const char *path, const char *mode) {
   return 0;
 }
 
+int reopenfile(File *f, const char *path, const char *mode) {
+  f->lock();
+  int ret = reopenfile_unlocked(f, path, mode);
+  f->unlock();
+
+  if (ret != 0) {
+    f->close();
+  }
+
+  return ret;
+}
+
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/generic/CMakeLists.txt b/libc/src/stdio/generic/CMakeLists.txt
index 461a70b009839..5f45d15ac8f26 100644
--- a/libc/src/stdio/generic/CMakeLists.txt
+++ b/libc/src/stdio/generic/CMakeLists.txt
@@ -197,6 +197,7 @@ add_generic_entrypoint_object(
     libc.src.__support.File.platform_file
     libc.src.__support.common
     libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
 )
 
 add_generic_entrypoint_object(
diff --git a/libc/src/stdio/generic/fopen.cpp b/libc/src/stdio/generic/fopen.cpp
index 57c85c2e54e16..88678ae72790c 100644
--- a/libc/src/stdio/generic/fopen.cpp
+++ b/libc/src/stdio/generic/fopen.cpp
@@ -7,16 +7,18 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/stdio/fopen.h"
-#include "src/__support/File/file.h"
-
 #include "hdr/types/FILE.h"
+#include "src/__support/File/file.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(::FILE *, fopen,
                    (const char *__restrict name, const char *__restrict mode)) {
+  LIBC_CRASH_ON_NULLPTR(name);
+  LIBC_CRASH_ON_NULLPTR(mode);
   auto result = LIBC_NAMESPACE::openfile(name, mode);
   if (!result.has_value()) {
     libc_errno = result.error();
diff --git a/libc/src/stdio/generic/freopen.cpp b/libc/src/stdio/generic/freopen.cpp
index a82a4560d554d..82514d43194f7 100644
--- a/libc/src/stdio/generic/freopen.cpp
+++ b/libc/src/stdio/generic/freopen.cpp
@@ -26,12 +26,11 @@ LLVM_LIBC_FUNCTION(::FILE *, freopen,
                    (const char *__restrict filename,
                     const char *__restrict mode, ::FILE *__restrict stream)) {
   LIBC_CRASH_ON_NULLPTR(stream);
+  LIBC_CRASH_ON_NULLPTR(mode);
 
   auto *file = reinterpret_cast<File *>(stream);
 
-  file->lock();
   int error = reopenfile(file, filename, mode);
-  file->unlock();
 
   if (error != 0) {
     libc_errno = error;
diff --git a/libc/test/src/stdio/freopen_test.cpp b/libc/test/src/stdio/freopen_test.cpp
index 8b5cdb41ef3a4..646a47aab8d0f 100644
--- a/libc/test/src/stdio/freopen_test.cpp
+++ b/libc/test/src/stdio/freopen_test.cpp
@@ -48,9 +48,9 @@ TEST_F(LlvmLibcFreopenTest, ReopenFile) {
   ASSERT_FALSE(file == nullptr);
 
   constexpr char CONTENT_A[] = "File A Content";
-  ASSERT_EQ(sizeof(CONTENT_A) - 1,
-            LIBC_NAMESPACE::fwrite(CONTENT_A, 1, sizeof(CONTENT_A) - 1, file));
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT_A, 1, sizeof(CONTENT_A) - 1, file),
+            sizeof(CONTENT_A) - 1);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 
   // Step 2: Open file A for reading.
   file = LIBC_NAMESPACE::fopen(FILENAME_A, "r");
@@ -63,19 +63,19 @@ TEST_F(LlvmLibcFreopenTest, ReopenFile) {
 
   // Step 4: Write to reopened stream (file B).
   constexpr char CONTENT_B[] = "File B Content Written via freopen";
-  ASSERT_EQ(sizeof(CONTENT_B) - 1,
-            LIBC_NAMESPACE::fwrite(CONTENT_B, 1, sizeof(CONTENT_B) - 1,
-                                   reopened_file));
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened_file));
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(CONTENT_B, 1, sizeof(CONTENT_B) - 1,
+                                   reopened_file),
+            sizeof(CONTENT_B) - 1);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(reopened_file), 0);
 
   // Step 5: Verify file B content.
   file = LIBC_NAMESPACE::fopen(FILENAME_B, "r");
   ASSERT_FALSE(file == nullptr);
   char read_buf[sizeof(CONTENT_B)] = {0};
-  ASSERT_EQ(sizeof(CONTENT_B) - 1,
-            LIBC_NAMESPACE::fread(read_buf, 1, sizeof(CONTENT_B) - 1, file));
+  ASSERT_EQ(LIBC_NAMESPACE::fread(read_buf, 1, sizeof(CONTENT_B) - 1, file),
+            sizeof(CONTENT_B) - 1);
   ASSERT_STREQ(read_buf, CONTENT_B);
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 }
 
 TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
@@ -87,9 +87,9 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
   ASSERT_FALSE(file == nullptr);
 
   constexpr char INITIAL_CONTENT[] = "Initial Data ";
-  ASSERT_EQ(sizeof(INITIAL_CONTENT) - 1,
-            LIBC_NAMESPACE::fwrite(INITIAL_CONTENT, 1,
-                                   sizeof(INITIAL_CONTENT) - 1, file));
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(INITIAL_CONTENT, 1,
+                                   sizeof(INITIAL_CONTENT) - 1, file),
+            sizeof(INITIAL_CONTENT) - 1);
 
   // Step 2: Change mode with filename == nullptr to append.
   ::FILE *reopened = LIBC_NAMESPACE::freopen(nullptr, "a", file);
@@ -98,10 +98,10 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
 
   // Step 3: Write appended content.
   constexpr char APPENDED_CONTENT[] = "Appended Data";
-  ASSERT_EQ(sizeof(APPENDED_CONTENT) - 1,
-            LIBC_NAMESPACE::fwrite(APPENDED_CONTENT, 1,
-                                   sizeof(APPENDED_CONTENT) - 1, reopened));
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened));
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(APPENDED_CONTENT, 1,
+                                   sizeof(APPENDED_CONTENT) - 1, reopened),
+            sizeof(APPENDED_CONTENT) - 1);
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(reopened), 0);
 
   // Step 4: Verify combined content.
   file = LIBC_NAMESPACE::fopen(FILENAME, "r");
@@ -111,7 +111,7 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
       LIBC_NAMESPACE::fread(read_buf, 1, sizeof(read_buf) - 1, file);
   read_buf[read_bytes] = '\0';
   ASSERT_STREQ(read_buf, "Initial Data Appended Data");
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 }
 
 TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
@@ -121,20 +121,23 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
   // Open file read-only.
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 
   file = LIBC_NAMESPACE::fopen(FILENAME, "r");
   ASSERT_FALSE(file == nullptr);
 
   // Attempt incompatible mode change (r to w with filename == nullptr).
+  // This failing freopen deallocates the stream.
   ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "w", file),
               Fails(EBADF, static_cast<void *>(nullptr)));
 
+  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
+  ASSERT_FALSE(file == nullptr);
+
   // Attempt incompatible mode change (r to w+ with filename == nullptr).
+  // This failing freopen also deallocates the stream.
   ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "w+", file),
               Fails(EBADF, static_cast<void *>(nullptr)));
-
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 }
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
@@ -152,12 +155,9 @@ TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
               Fails(EINVAL, static_cast<void *>(nullptr)));
 
   // Per POSIX spec, original stream fd was closed on filename != nullptr
-  // freopen attempt.
-  ASSERT_EQ(-1, LIBC_NAMESPACE::fcntl(old_fd, F_GETFL));
+  // freopen attempt, and the stream object was deallocated.
+  ASSERT_EQ(LIBC_NAMESPACE::fcntl(old_fd, F_GETFL), -1);
   ASSERT_ERRNO_EQ(EBADF);
-
-  // Clean up stream object to avoid memory leaks.
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 }
 #endif
 
@@ -178,12 +178,10 @@ TEST_F(LlvmLibcFreopenTest, NonExistentFileFailure) {
   ASSERT_THAT(LIBC_NAMESPACE::freopen(NON_EXISTENT_FILE, "r", file),
               Fails(ENOENT, static_cast<void *>(nullptr)));
 
-  // Per POSIX spec: The original stream fd is closed even if open fails.
-  ASSERT_EQ(-1, LIBC_NAMESPACE::fcntl(old_fd, F_GETFL));
+  // Per POSIX spec: The original stream fd is closed even if open fails,
+  // and the stream object is deallocated.
+  ASSERT_EQ(LIBC_NAMESPACE::fcntl(old_fd, F_GETFL), -1);
   ASSERT_ERRNO_EQ(EBADF);
-
-  // Clean up stream object to avoid memory leaks.
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
 }
 #endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
 
@@ -197,23 +195,22 @@ TEST_F(LlvmLibcFreopenTest, FlushBeforeReopenTest) {
   ASSERT_FALSE(file == nullptr);
 
   constexpr char DIRTY_DATA[] = "Buffered data before freopen";
-  ASSERT_EQ(
-      sizeof(DIRTY_DATA) - 1,
-      LIBC_NAMESPACE::fwrite(DIRTY_DATA, 1, sizeof(DIRTY_DATA) - 1, file));
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(DIRTY_DATA, 1, sizeof(DIRTY_DATA) - 1, file),
+            sizeof(DIRTY_DATA) - 1);
 
   // freopen must flush unwritten buffered data to FILENAME_A before reopening
   ::FILE *reopened = LIBC_NAMESPACE::freopen(FILENAME_B, "w", file);
   ASSERT_NE(reopened, static_cast<::FILE *>(nullptr));
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(reopened), 0);
 
   // Verify FILENAME_A received the flushed dirty buffer
   file = LIBC_NAMESPACE::fopen(FILENAME_A, "r");
   ASSERT_FALSE(file == nullptr);
   char read_buf[sizeof(DIRTY_DATA)] = {0};
-  ASSERT_EQ(sizeof(DIRTY_DATA) - 1,
-            LIBC_NAMESPACE::fread(read_buf, 1, sizeof(DIRTY_DATA) - 1, file));
+  ASSERT_EQ(LIBC_NAMESPACE::fread(read_buf, 1, sizeof(DIRTY_DATA) - 1, file),
+            sizeof(DIRTY_DATA) - 1);
   ASSERT_STREQ(read_buf, DIRTY_DATA);
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 }
 
 TEST_F(LlvmLibcFreopenTest, ClearFlagsTest) {
@@ -225,24 +222,24 @@ TEST_F(LlvmLibcFreopenTest, ClearFlagsTest) {
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
   ASSERT_FALSE(file == nullptr);
   constexpr char SHORT_DATA[] = "X";
-  ASSERT_EQ(static_cast<size_t>(1),
-            LIBC_NAMESPACE::fwrite(SHORT_DATA, 1, 1, file));
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+  ASSERT_EQ(LIBC_NAMESPACE::fwrite(SHORT_DATA, 1, 1, file),
+            static_cast<size_t>(1));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 
   // Trigger EOF on file
   file = LIBC_NAMESPACE::fopen(FILENAME_A, "r");
   ASSERT_FALSE(file == nullptr);
   char buf[4];
   LIBC_NAMESPACE::fread(buf, 1, sizeof(buf), file);
-  ASSERT_NE(0, LIBC_NAMESPACE::feof(file));
+  ASSERT_NE(LIBC_NAMESPACE::feof(file), 0);
 
   // freopen must clear EOF and error indicators
   ::FILE *reopened = LIBC_NAMESPACE::freopen(FILENAME_B, "w", file);
   ASSERT_NE(reopened, static_cast<::FILE *>(nullptr));
-  ASSERT_EQ(0, LIBC_NAMESPACE::feof(reopened));
-  ASSERT_EQ(0, LIBC_NAMESPACE::ferror(reopened));
+  ASSERT_EQ(LIBC_NAMESPACE::feof(reopened), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::ferror(reopened), 0);
 
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(reopened), 0);
 }
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
@@ -256,7 +253,7 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameBadFdTest) {
   int fd = LIBC_NAMESPACE::fileno(file);
   ASSERT_GT(fd, 0);
   // Manually close underlying fd to simulate bad file descriptor state
-  ASSERT_EQ(0, LIBC_NAMESPACE::close(fd));
+  ASSERT_EQ(LIBC_NAMESPACE::close(fd), 0);
 
   // freopen with filename == nullptr on invalid fd should return nullptr +
   // EBADF
@@ -280,9 +277,9 @@ TEST_F(LlvmLibcFreopenTest, ResetOrientationTest) {
   // freopen must reset orientation to 0 (unoriented)
   ::FILE *reopened = LIBC_NAMESPACE::freopen(FILENAME_B, "w", file);
   ASSERT_NE(reopened, static_cast<::FILE *>(nullptr));
-  ASSERT_EQ(0, LIBC_NAMESPACE::fwide(reopened, 0));
+  ASSERT_EQ(LIBC_NAMESPACE::fwide(reopened, 0), 0);
 
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(reopened));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(reopened), 0);
 }
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
@@ -300,35 +297,38 @@ TEST_F(LlvmLibcFreopenTest, StdoutRedirectionTest) {
   ASSERT_EQ(reopened, static_cast<::FILE *>(LIBC_NAMESPACE::stdout));
 
   // Verify fileno(stdout) is preserved as 1
-  ASSERT_EQ(1, LIBC_NAMESPACE::fileno(LIBC_NAMESPACE::stdout));
+  ASSERT_EQ(LIBC_NAMESPACE::fileno(LIBC_NAMESPACE::stdout), 1);
 
   constexpr char MSG[] = "Redirected Stdout";
-  ASSERT_EQ(sizeof(MSG) - 1, LIBC_NAMESPACE::fwrite(MSG, 1, sizeof(MSG) - 1,
-                                                    LIBC_NAMESPACE::stdout));
-  ASSERT_EQ(0, LIBC_NAMESPACE::fflush(LIBC_NAMESPACE::stdout));
+  ASSERT_EQ(
+      LIBC_NAMESPACE::fwrite(MSG, 1, sizeof(MSG) - 1, LIBC_NAMESPACE::stdout),
+      sizeof(MSG) - 1);
+  ASSERT_EQ(LIBC_NAMESPACE::fflush(LIBC_NAMESPACE::stdout), 0);
 
   // Verify file content
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "r");
   ASSERT_FALSE(file == nullptr);
   char read_buf[sizeof(MSG)] = {0};
-  ASSERT_EQ(sizeof(MSG) - 1,
-            LIBC_NAMESPACE::fread(read_buf, 1, sizeof(MSG) - 1, file));
+  ASSERT_EQ(LIBC_NAMESPACE::fread(read_buf, 1, sizeof(MSG) - 1, file),
+            sizeof(MSG) - 1);
   ASSERT_STREQ(read_buf, MSG);
-  ASSERT_EQ(0, LIBC_NAMESPACE::fclose(file));
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 }
 #endif
 
-#define UNIT 1
-#define HERMETIC 2
-#if LIBC_TEST == UNIT
-TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
-  const auto FILENAME =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_stream.test"));
-  const char *fn = FILENAME;
-
-  EXPECT_DEATH([=] { LIBC_NAMESPACE::freopen(fn, "r", nullptr); },
-               WITH_SIGNAL(-1));
-}
-#endif
-#undef UNIT
-#undef HERMETIC
+// TODO: hermetic tests are failing with this error:
+/*
+/usr/bin/x86_64-linux-gnu-ld.bfd:
+libc/test/src/stdio/libc.test.src.stdio.freopen_test.__hermetic__.__build__:
+hidden symbol
+`_ZN22__llvm_libc_23_0_0_git7testing4Test17testProcessKilledEPNS_9testutils14FunctionCallerEiPKcS6_NS0_8internal8LocationE'
+isn't defined /usr/bin/x86_64-linux-gnu-ld.bfd: final link failed: bad value
+*/
+// TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
+//   const auto FILENAME =
+//       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_stream.test"));
+//   const char *fn = FILENAME;
+
+//   EXPECT_DEATH([=] { LIBC_NAMESPACE::freopen(fn, "r", nullptr); },
+//                WITH_SIGNAL(-1));
+// }

>From 35b87c8390161ca893963d50ac823e5350750b46 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 18 Aug 2026 00:14:59 +0000
Subject: [PATCH 5/8] address comments: No delete on error; close on failed dup

---
 libc/src/__support/File/file.h         |  5 ++++
 libc/src/__support/File/linux/file.cpp | 14 +++++++----
 libc/test/src/stdio/freopen_test.cpp   | 33 +++++++++++++++++++-------
 3 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/libc/src/__support/File/file.h b/libc/src/__support/File/file.h
index bd0e8a18e9aa9..0fe1f5ac34afa 100644
--- a/libc/src/__support/File/file.h
+++ b/libc/src/__support/File/file.h
@@ -397,6 +397,11 @@ class File {
 // The implementation of this function is provided by the platform_file
 // library.
 ErrorOr<File *> openfile(const char *path, const char *mode);
+// Reopens a file stream.
+// Note: On failure, `reopenfile` will place the file stream in an invalid state
+// (closing the underlying file descriptor) but will not deallocate the `File`
+// object itself, ensuring static streams like stdin/stdout/stderr are
+// preserved.
 int reopenfile(File *f, const char *path, const char *mode);
 
 // The platform_file library should implement it if it relevant for that
diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index b071c24b64982..b5fc97fbcc2d0 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -194,6 +194,10 @@ int get_fileno(File *f) {
   return lf->get_fd();
 }
 
+// Assumes `f` is already locked by caller.
+// Note: On failure, `reopenfile_unlocked` places the stream in an invalid state
+// (closing the underlying file descriptor) but does not deallocate the `File`
+// structure.
 int reopenfile_unlocked(File *f, const char *path, const char *mode) {
   f->flush_unlocked();
 
@@ -238,6 +242,7 @@ int reopenfile_unlocked(File *f, const char *path, const char *mode) {
     if (old_fd >= 0) {
       auto dup_result = linux_syscalls::dup2(new_fd.value(), old_fd);
       if (!dup_result) {
+        linux_syscalls::close(new_fd.value());
         f->reset_stream_state(modeflags);
         return dup_result.error();
       }
@@ -311,15 +316,16 @@ int reopenfile_unlocked(File *f, const char *path, const char *mode) {
   return 0;
 }
 
+// Reopens a file stream.
+// Note: On failure, `reopenfile` places the stream in an invalid state (closing
+// the underlying file descriptor) but does not deallocate the `File` structure,
+// ensuring global static streams (such as stdin, stdout, and stderr) are not
+// freed.
 int reopenfile(File *f, const char *path, const char *mode) {
   f->lock();
   int ret = reopenfile_unlocked(f, path, mode);
   f->unlock();
 
-  if (ret != 0) {
-    f->close();
-  }
-
   return ret;
 }
 
diff --git a/libc/test/src/stdio/freopen_test.cpp b/libc/test/src/stdio/freopen_test.cpp
index 646a47aab8d0f..3f61c3978afd5 100644
--- a/libc/test/src/stdio/freopen_test.cpp
+++ b/libc/test/src/stdio/freopen_test.cpp
@@ -127,17 +127,14 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
   ASSERT_FALSE(file == nullptr);
 
   // Attempt incompatible mode change (r to w with filename == nullptr).
-  // This failing freopen deallocates the stream.
   ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "w", file),
               Fails(EBADF, static_cast<void *>(nullptr)));
 
-  file = LIBC_NAMESPACE::fopen(FILENAME, "r");
-  ASSERT_FALSE(file == nullptr);
-
   // Attempt incompatible mode change (r to w+ with filename == nullptr).
-  // This failing freopen also deallocates the stream.
   ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "w+", file),
               Fails(EBADF, static_cast<void *>(nullptr)));
+
+  ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 }
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
@@ -155,9 +152,12 @@ TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
               Fails(EINVAL, static_cast<void *>(nullptr)));
 
   // Per POSIX spec, original stream fd was closed on filename != nullptr
-  // freopen attempt, and the stream object was deallocated.
+  // freopen attempt.
   ASSERT_EQ(LIBC_NAMESPACE::fcntl(old_fd, F_GETFL), -1);
   ASSERT_ERRNO_EQ(EBADF);
+
+  // Clean up stream object to avoid memory leaks.
+  ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Fails(EBADF, EOF));
 }
 #endif
 
@@ -178,10 +178,12 @@ TEST_F(LlvmLibcFreopenTest, NonExistentFileFailure) {
   ASSERT_THAT(LIBC_NAMESPACE::freopen(NON_EXISTENT_FILE, "r", file),
               Fails(ENOENT, static_cast<void *>(nullptr)));
 
-  // Per POSIX spec: The original stream fd is closed even if open fails,
-  // and the stream object is deallocated.
+  // Per POSIX spec: The original stream fd is closed even if open fails.
   ASSERT_EQ(LIBC_NAMESPACE::fcntl(old_fd, F_GETFL), -1);
   ASSERT_ERRNO_EQ(EBADF);
+
+  // Clean up stream object to avoid memory leaks.
+  ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Fails(EBADF, EOF));
 }
 #endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
 
@@ -259,6 +261,9 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameBadFdTest) {
   // EBADF
   ASSERT_THAT(LIBC_NAMESPACE::freopen(nullptr, "a", file),
               Fails(EBADF, static_cast<void *>(nullptr)));
+
+  // Clean up stream object to avoid memory leaks.
+  ASSERT_THAT(LIBC_NAMESPACE::fclose(file), Fails(EBADF, EOF));
 }
 #endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
 
@@ -314,6 +319,18 @@ TEST_F(LlvmLibcFreopenTest, StdoutRedirectionTest) {
   ASSERT_STREQ(read_buf, MSG);
   ASSERT_EQ(LIBC_NAMESPACE::fclose(file), 0);
 }
+
+TEST_F(LlvmLibcFreopenTest, StdoutFailureTest) {
+  auto NON_EXISTENT_FILE =
+      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_stdout_fail.test"));
+
+  // Attempt to freopen non-existent file on stdout.
+  // This must return nullptr + ENOENT without attempting to free the static
+  // stdout object.
+  ASSERT_THAT(
+      LIBC_NAMESPACE::freopen(NON_EXISTENT_FILE, "r", LIBC_NAMESPACE::stdout),
+      Fails(ENOENT, static_cast<void *>(nullptr)));
+}
 #endif
 
 // TODO: hermetic tests are failing with this error:

>From e81fd61d5ab98901b86aa9b4baa77143dfe292a2 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 18 Aug 2026 17:02:05 +0000
Subject: [PATCH 6/8] address comments, reorganize platform code

---
 libc/src/__support/File/file.h         | 27 +++++++------
 libc/src/__support/File/linux/file.cpp | 52 ++++++++++++--------------
 libc/src/__support/File/linux/file.h   |  2 +
 3 files changed, 40 insertions(+), 41 deletions(-)

diff --git a/libc/src/__support/File/file.h b/libc/src/__support/File/file.h
index 0fe1f5ac34afa..67879e6c9933e 100644
--- a/libc/src/__support/File/file.h
+++ b/libc/src/__support/File/file.h
@@ -165,6 +165,18 @@ class File {
                    static_cast<ModeFlags>(OpenMode::PLUS));
   }
 
+  void reset_stream_state_unlocked(ModeFlags new_mode) {
+    mode = new_mode;
+    pos = 0;
+    prev_op = FileOp::NONE;
+    read_limit = 0;
+    eof = false;
+    err = false;
+    orientation = Orientation::UNORIENTED;
+    mbstate = internal::mbstate();
+    adjust_buf();
+  }
+
 public:
   // We want this constructor to be constexpr so that global file objects
   // like stdout do not require invocation of the constructor which can
@@ -344,18 +356,6 @@ class File {
   // OpenMode, ContentType and CreateType.
   static ModeFlags mode_flags(const char *mode);
 
-  void reset_stream_state(ModeFlags new_mode) {
-    mode = new_mode;
-    pos = 0;
-    prev_op = FileOp::NONE;
-    read_limit = 0;
-    eof = false;
-    err = false;
-    orientation = Orientation::UNORIENTED;
-    mbstate = internal::mbstate();
-    adjust_buf();
-  }
-
 private:
   FileIOResult write_unlocked_impl(const void *data, size_t len);
   FileIOResult read_unlocked_impl(void *data, size_t len);
@@ -403,6 +403,9 @@ ErrorOr<File *> openfile(const char *path, const char *mode);
 // object itself, ensuring static streams like stdin/stdout/stderr are
 // preserved.
 int reopenfile(File *f, const char *path, const char *mode);
+// Expected to be implemented by the platform file, will be called after
+// locking.
+int reopenfile_unlocked(File *f, const char *path, const char *mode);
 
 // The platform_file library should implement it if it relevant for that
 // platform.
diff --git a/libc/src/__support/File/linux/file.cpp b/libc/src/__support/File/linux/file.cpp
index b5fc97fbcc2d0..67b69b9f026ba 100644
--- a/libc/src/__support/File/linux/file.cpp
+++ b/libc/src/__support/File/linux/file.cpp
@@ -189,30 +189,20 @@ ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode) {
   return file;
 }
 
-int get_fileno(File *f) {
-  auto *lf = reinterpret_cast<LinuxFile *>(f);
-  return lf->get_fd();
-}
-
-// Assumes `f` is already locked by caller.
-// Note: On failure, `reopenfile_unlocked` places the stream in an invalid state
-// (closing the underlying file descriptor) but does not deallocate the `File`
-// structure.
-int reopenfile_unlocked(File *f, const char *path, const char *mode) {
-  f->flush_unlocked();
+int LinuxFile::reopen_unlocked(const char *path, const char *mode) {
+  flush_unlocked();
 
   auto modeflags = File::mode_flags(mode);
-  auto *lf = reinterpret_cast<LinuxFile *>(f);
 
   if (path != nullptr) {
-    int old_fd = lf->get_fd();
+    int old_fd = get_fd();
 
     if (modeflags == 0) {
       if (old_fd >= 0) {
         linux_syscalls::close(old_fd);
-        lf->set_fd(-1);
+        set_fd(-1);
       }
-      f->reset_stream_state(modeflags);
+      reset_stream_state_unlocked(modeflags);
       return EINVAL;
     }
 
@@ -231,9 +221,9 @@ int reopenfile_unlocked(File *f, const char *path, const char *mode) {
         // ignored"
         linux_syscalls::close(old_fd);
 
-        lf->set_fd(-1);
+        set_fd(-1);
       }
-      f->reset_stream_state(modeflags);
+      reset_stream_state_unlocked(modeflags);
       return new_fd.error();
     }
 
@@ -243,26 +233,25 @@ int reopenfile_unlocked(File *f, const char *path, const char *mode) {
       auto dup_result = linux_syscalls::dup2(new_fd.value(), old_fd);
       if (!dup_result) {
         linux_syscalls::close(new_fd.value());
-        f->reset_stream_state(modeflags);
+        reset_stream_state_unlocked(modeflags);
         return dup_result.error();
       }
       auto close_result = linux_syscalls::close(new_fd.value());
       if (!close_result) {
-        f->reset_stream_state(modeflags);
+        reset_stream_state_unlocked(modeflags);
         return close_result.error();
       }
     } else {
-      lf->set_fd(new_fd.value());
+      set_fd(new_fd.value());
     }
 
-    f->reset_stream_state(modeflags);
+    reset_stream_state_unlocked(modeflags);
     return 0;
   }
 
   if (modeflags == 0)
     return EINVAL;
 
-  int fd = lf->get_fd();
   if (fd < 0)
     return EBADF;
 
@@ -306,21 +295,21 @@ int reopenfile_unlocked(File *f, const char *path, const char *mode) {
     }
   }
 
-  f->reset_stream_state(modeflags);
+  reset_stream_state_unlocked(modeflags);
 
   if (do_seek) {
-    auto seek_result = linux_file_seek(f, 0, SEEK_END);
+    auto seek_result = linux_file_seek(this, 0, SEEK_END);
     if (!seek_result.has_value())
       return seek_result.error();
   }
   return 0;
 }
 
-// Reopens a file stream.
-// Note: On failure, `reopenfile` places the stream in an invalid state (closing
-// the underlying file descriptor) but does not deallocate the `File` structure,
-// ensuring global static streams (such as stdin, stdout, and stderr) are not
-// freed.
+int get_fileno(File *f) {
+  auto *lf = reinterpret_cast<LinuxFile *>(f);
+  return lf->get_fd();
+}
+
 int reopenfile(File *f, const char *path, const char *mode) {
   f->lock();
   int ret = reopenfile_unlocked(f, path, mode);
@@ -329,4 +318,9 @@ int reopenfile(File *f, const char *path, const char *mode) {
   return ret;
 }
 
+int reopenfile_unlocked(File *f, const char *path, const char *mode) {
+  auto *lf = reinterpret_cast<LinuxFile *>(f);
+  return lf->reopen_unlocked(path, mode);
+}
+
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/File/linux/file.h b/libc/src/__support/File/linux/file.h
index 37f8c6c0b5630..700db415156ae 100644
--- a/libc/src/__support/File/linux/file.h
+++ b/libc/src/__support/File/linux/file.h
@@ -30,6 +30,8 @@ class LinuxFile : public File {
 
   int get_fd() const { return fd; }
   void set_fd(int new_fd) { fd = new_fd; }
+
+  int reopen_unlocked(const char *path, const char *mode);
 };
 
 // Create a File object and associate it with a fd.

>From 13fd7bed6d53a5e55d00f6e8ee37c5088b71f04d Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 18 Aug 2026 17:10:01 +0000
Subject: [PATCH 7/8] and update the tests after rebasing

---
 libc/test/src/stdio/freopen_test.cpp | 72 ++++++++++------------------
 1 file changed, 25 insertions(+), 47 deletions(-)

diff --git a/libc/test/src/stdio/freopen_test.cpp b/libc/test/src/stdio/freopen_test.cpp
index 3f61c3978afd5..258c468dca0d2 100644
--- a/libc/test/src/stdio/freopen_test.cpp
+++ b/libc/test/src/stdio/freopen_test.cpp
@@ -38,10 +38,8 @@ using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
 using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
 
 TEST_F(LlvmLibcFreopenTest, ReopenFile) {
-  const auto FILENAME_A =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_a.test"));
-  const auto FILENAME_B =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_b.test"));
+  const auto FILENAME_A = libc_make_test_file_path("freopen_a.test");
+  const auto FILENAME_B = libc_make_test_file_path("freopen_b.test");
 
   // Step 1: Open file A and write initial content.
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
@@ -79,8 +77,7 @@ TEST_F(LlvmLibcFreopenTest, ReopenFile) {
 }
 
 TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
-  const auto FILENAME =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_filename.test"));
+  const auto FILENAME = libc_make_test_file_path("freopen_null_filename.test");
 
   // Step 1: Open file with write-update mode.
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w+");
@@ -115,8 +112,8 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameModeChange) {
 }
 
 TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
-  const auto FILENAME = libc_make_test_file_path(
-      APPEND_LIBC_TEST("freopen_invalid_mode_change.test"));
+  const auto FILENAME =
+      libc_make_test_file_path("freopen_invalid_mode_change.test");
 
   // Open file read-only.
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
@@ -139,8 +136,7 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameInvalidModeChange) {
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
 TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
-  const auto FILENAME =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_invalid_mode.test"));
+  const auto FILENAME = libc_make_test_file_path("freopen_invalid_mode.test");
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
@@ -163,10 +159,9 @@ TEST_F(LlvmLibcFreopenTest, InvalidModeFailure) {
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
 TEST_F(LlvmLibcFreopenTest, NonExistentFileFailure) {
-  auto EXISTING_FILE =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_existing.test"));
+  auto EXISTING_FILE = libc_make_test_file_path("freopen_existing.test");
   auto NON_EXISTENT_FILE =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_does_not_exist.test"));
+      libc_make_test_file_path("freopen_does_not_exist.test");
 
   ::FILE *file = LIBC_NAMESPACE::fopen(EXISTING_FILE, "w");
   ASSERT_FALSE(file == nullptr);
@@ -188,10 +183,8 @@ TEST_F(LlvmLibcFreopenTest, NonExistentFileFailure) {
 #endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
 
 TEST_F(LlvmLibcFreopenTest, FlushBeforeReopenTest) {
-  const auto FILENAME_A =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flush_a.test"));
-  const auto FILENAME_B =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flush_b.test"));
+  const auto FILENAME_A = libc_make_test_file_path("freopen_flush_a.test");
+  const auto FILENAME_B = libc_make_test_file_path("freopen_flush_b.test");
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
   ASSERT_FALSE(file == nullptr);
@@ -216,10 +209,8 @@ TEST_F(LlvmLibcFreopenTest, FlushBeforeReopenTest) {
 }
 
 TEST_F(LlvmLibcFreopenTest, ClearFlagsTest) {
-  const auto FILENAME_A =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flags_a.test"));
-  const auto FILENAME_B =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_flags_b.test"));
+  const auto FILENAME_A = libc_make_test_file_path("freopen_flags_a.test");
+  const auto FILENAME_B = libc_make_test_file_path("freopen_flags_b.test");
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
   ASSERT_FALSE(file == nullptr);
@@ -246,8 +237,7 @@ TEST_F(LlvmLibcFreopenTest, ClearFlagsTest) {
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
 TEST_F(LlvmLibcFreopenTest, NullFilenameBadFdTest) {
-  const auto FILENAME =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_bad_fd.test"));
+  const auto FILENAME = libc_make_test_file_path("freopen_bad_fd.test");
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME, "w");
   ASSERT_FALSE(file == nullptr);
@@ -268,10 +258,8 @@ TEST_F(LlvmLibcFreopenTest, NullFilenameBadFdTest) {
 #endif // LIBC_TARGET_OS_IS_POSIX || LIBC_TARGET_OS_IS_LINUX
 
 TEST_F(LlvmLibcFreopenTest, ResetOrientationTest) {
-  const auto FILENAME_A =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_orient_a.test"));
-  const auto FILENAME_B =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_orient_b.test"));
+  const auto FILENAME_A = libc_make_test_file_path("freopen_orient_a.test");
+  const auto FILENAME_B = libc_make_test_file_path("freopen_orient_b.test");
 
   ::FILE *file = LIBC_NAMESPACE::fopen(FILENAME_A, "w");
   ASSERT_FALSE(file == nullptr);
@@ -289,8 +277,7 @@ TEST_F(LlvmLibcFreopenTest, ResetOrientationTest) {
 
 #if defined(LIBC_TARGET_OS_IS_POSIX) || defined(LIBC_TARGET_OS_IS_LINUX)
 TEST_F(LlvmLibcFreopenTest, StdoutRedirectionTest) {
-  const auto FILENAME =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_stdout.test"));
+  const auto FILENAME = libc_make_test_file_path("freopen_stdout.test");
 
   int stdout_fd = LIBC_NAMESPACE::fileno(LIBC_NAMESPACE::stdout);
   ASSERT_EQ(stdout_fd, 1);
@@ -321,8 +308,7 @@ TEST_F(LlvmLibcFreopenTest, StdoutRedirectionTest) {
 }
 
 TEST_F(LlvmLibcFreopenTest, StdoutFailureTest) {
-  auto NON_EXISTENT_FILE =
-      libc_make_test_file_path(APPEND_LIBC_TEST("freopen_stdout_fail.test"));
+  auto NON_EXISTENT_FILE = libc_make_test_file_path("freopen_stdout_fail.test");
 
   // Attempt to freopen non-existent file on stdout.
   // This must return nullptr + ENOENT without attempting to free the static
@@ -333,19 +319,11 @@ TEST_F(LlvmLibcFreopenTest, StdoutFailureTest) {
 }
 #endif
 
-// TODO: hermetic tests are failing with this error:
-/*
-/usr/bin/x86_64-linux-gnu-ld.bfd:
-libc/test/src/stdio/libc.test.src.stdio.freopen_test.__hermetic__.__build__:
-hidden symbol
-`_ZN22__llvm_libc_23_0_0_git7testing4Test17testProcessKilledEPNS_9testutils14FunctionCallerEiPKcS6_NS0_8internal8LocationE'
-isn't defined /usr/bin/x86_64-linux-gnu-ld.bfd: final link failed: bad value
-*/
-// TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
-//   const auto FILENAME =
-//       libc_make_test_file_path(APPEND_LIBC_TEST("freopen_null_stream.test"));
-//   const char *fn = FILENAME;
-
-//   EXPECT_DEATH([=] { LIBC_NAMESPACE::freopen(fn, "r", nullptr); },
-//                WITH_SIGNAL(-1));
-// }
+TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
+  const auto FILENAME =
+      libc_make_test_file_path("freopen_null_stream.test");
+  const char *fn = FILENAME;
+
+  EXPECT_DEATH([=] { LIBC_NAMESPACE::freopen(fn, "r", nullptr); },
+               WITH_SIGNAL(-1));
+}

>From d83e0a596ea7427a5533b0483d9061f96aeca072 Mon Sep 17 00:00:00 2001
From: Michael Jones <michaelrj at google.com>
Date: Tue, 18 Aug 2026 17:13:23 +0000
Subject: [PATCH 8/8] fix format

---
 libc/test/src/stdio/freopen_test.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libc/test/src/stdio/freopen_test.cpp b/libc/test/src/stdio/freopen_test.cpp
index 258c468dca0d2..aa44c7e1c75fc 100644
--- a/libc/test/src/stdio/freopen_test.cpp
+++ b/libc/test/src/stdio/freopen_test.cpp
@@ -320,8 +320,7 @@ TEST_F(LlvmLibcFreopenTest, StdoutFailureTest) {
 #endif
 
 TEST_F(LlvmLibcFreopenTest, NullStreamFailure) {
-  const auto FILENAME =
-      libc_make_test_file_path("freopen_null_stream.test");
+  const auto FILENAME = libc_make_test_file_path("freopen_null_stream.test");
   const char *fn = FILENAME;
 
   EXPECT_DEATH([=] { LIBC_NAMESPACE::freopen(fn, "r", nullptr); },



More information about the libc-commits mailing list