[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