[libc-commits] [libc] 8ed9cad - [libc] Implement fmemopen (#221748)
via libc-commits
libc-commits at lists.llvm.org
Thu Sep 10 12:52:18 PDT 2026
Author: Jeong Jihyeon
Date: 2026-09-10T12:52:14-07:00
New Revision: 8ed9cadeb6224ca3818f4277935aca31a6c73609
URL: https://github.com/llvm/llvm-project/commit/8ed9cadeb6224ca3818f4277935aca31a6c73609
DIFF: https://github.com/llvm/llvm-project/commit/8ed9cadeb6224ca3818f4277935aca31a6c73609.diff
LOG: [libc] Implement fmemopen (#221748)
Implement the POSIX `fmemopen` function.
### Validation
- Ran `ninja -C build-libc -j8 libc_stdio_unittests`.
- Ran `ninja -C build-libc -j8 check-libc`.
Fixes #220762
Added:
libc/src/stdio/fmemopen.cpp
libc/src/stdio/fmemopen.h
libc/test/src/stdio/fmemopen_test.cpp
Modified:
libc/config/linux/aarch64/entrypoints.txt
libc/config/linux/riscv/entrypoints.txt
libc/config/linux/x86_64/entrypoints.txt
libc/include/stdio.yaml
libc/src/stdio/CMakeLists.txt
libc/test/src/stdio/CMakeLists.txt
Removed:
################################################################################
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 57210e3c79208..acf310d3593c2 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1242,6 +1242,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.fgets
libc.src.stdio.fileno
libc.src.stdio.flockfile
+ libc.src.stdio.fmemopen
libc.src.stdio.fopen
libc.src.stdio.freopen
libc.src.stdio.fopencookie
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index ff3500e272d82..f9315b6977e56 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1427,6 +1427,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.fgets
libc.src.stdio.fileno
libc.src.stdio.flockfile
+ libc.src.stdio.fmemopen
libc.src.stdio.fopen
libc.src.stdio.freopen
libc.src.stdio.fopencookie
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 0175b97b326d4..c67f51371de80 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1442,6 +1442,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.stdio.fgets
libc.src.stdio.fileno
libc.src.stdio.flockfile
+ libc.src.stdio.fmemopen
libc.src.stdio.fopen
libc.src.stdio.freopen
libc.src.stdio.fopencookie
diff --git a/libc/include/stdio.yaml b/libc/include/stdio.yaml
index ef725dd4fe619..bb28dadd29b3b 100644
--- a/libc/include/stdio.yaml
+++ b/libc/include/stdio.yaml
@@ -139,6 +139,14 @@ functions:
return_type: void
arguments:
- type: FILE *
+ - name: fmemopen
+ standards:
+ - posix
+ return_type: FILE *
+ arguments:
+ - type: void *__restrict
+ - type: size_t
+ - type: const char *__restrict
- name: fopen
standards:
- stdc
diff --git a/libc/src/stdio/CMakeLists.txt b/libc/src/stdio/CMakeLists.txt
index 3ef6e4f7b7d01..a0d4e86674b95 100644
--- a/libc/src/stdio/CMakeLists.txt
+++ b/libc/src/stdio/CMakeLists.txt
@@ -52,6 +52,28 @@ add_entrypoint_object(
libc.src.__support.File.platform_file
)
+add_entrypoint_object(
+ fmemopen
+ SRCS
+ fmemopen.cpp
+ HDRS
+ fmemopen.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.stdint_proxy
+ libc.hdr.stdio_macros
+ libc.hdr.types.FILE
+ libc.hdr.types.off_t
+ libc.hdr.types.size_t
+ libc.src.__support.CPP.algorithm
+ libc.src.__support.CPP.new
+ libc.src.__support.File.file
+ libc.src.__support.alloc_checker
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.null_check
+ libc.src.string.memory_utils.inline_memcpy
+)
+
add_entrypoint_object(
fopencookie
SRCS
diff --git a/libc/src/stdio/fmemopen.cpp b/libc/src/stdio/fmemopen.cpp
new file mode 100644
index 0000000000000..d88426452fd2a
--- /dev/null
+++ b/libc/src/stdio/fmemopen.cpp
@@ -0,0 +1,169 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 fmemopen, a POSIX function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/stdio/fmemopen.h"
+
+#include "hdr/errno_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/stdio_macros.h"
+#include "hdr/types/off_t.h"
+#include "src/__support/CPP/algorithm.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/File/file.h"
+#include "src/__support/alloc-checker.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace {
+
+class MemoryFile : public File {
+ // The stdio buffer is separate from the memory used as the file's contents.
+ // In particular, ungetc and setvbuf must not modify or free that memory.
+ uint8_t stream_buffer[DEFAULT_BUFFER_SIZE];
+ uint8_t *storage;
+ size_t capacity;
+ size_t position = 0;
+ size_t end = 0;
+ bool owns_storage;
+ bool append;
+
+ static FileIOResult memory_read(File *f, void *data, size_t size) {
+ auto *mf = reinterpret_cast<MemoryFile *>(f);
+ if (size == 0 || mf->position >= mf->end)
+ return 0;
+ size_t available = mf->end - mf->position;
+ size_t count = cpp::min(size, available);
+ inline_memcpy(data, mf->storage + mf->position, count);
+ mf->position += count;
+ return count;
+ }
+
+ static FileIOResult memory_write(File *f, const void *data, size_t size) {
+ auto *mf = reinterpret_cast<MemoryFile *>(f);
+ if (size == 0)
+ return 0;
+ size_t start = mf->append ? mf->end : mf->position;
+ size_t available = mf->capacity - start;
+ size_t count = cpp::min(size, available);
+ // POSIX fflush and fseek specify ENOSPC for a full fmemopen buffer.
+ if (count == 0)
+ return {0, ENOSPC};
+ inline_memcpy(mf->storage + start, data, count);
+ mf->position = start + count;
+ if (mf->position > mf->end) {
+ mf->end = mf->position;
+ // The terminator is not part of the file contents. All capacity bytes
+ // may hold data, in which case there is no room for a terminator.
+ if (mf->end < mf->capacity)
+ mf->storage[mf->end] = '\0';
+ }
+ return {count, count < size ? ENOSPC : 0};
+ }
+
+ static ErrorOr<off_t> memory_seek(File *f, off_t offset, int whence) {
+ auto *mf = reinterpret_cast<MemoryFile *>(f);
+ size_t base;
+ switch (whence) {
+ case SEEK_SET:
+ base = 0;
+ break;
+ case SEEK_CUR:
+ base = mf->position;
+ break;
+ case SEEK_END:
+ base = mf->end;
+ break;
+ default:
+ return Error(EINVAL);
+ }
+
+ // The bounds fit in off_t since no object is larger than PTRDIFF_MAX, so
+ // comparing in off_t needs no negation of offset or narrowing to size_t.
+ const off_t min_offset = -static_cast<off_t>(base);
+ const off_t max_offset = static_cast<off_t>(mf->capacity - base);
+ if (offset < min_offset || offset > max_offset)
+ return Error(EINVAL);
+ size_t next = base + static_cast<size_t>(offset);
+ mf->position = next;
+ return static_cast<off_t>(next);
+ }
+
+ static int memory_close(File *f) {
+ auto *mf = reinterpret_cast<MemoryFile *>(f);
+ File::remove_file(mf);
+ if (mf->owns_storage)
+ delete[] mf->storage;
+ delete mf;
+ return 0;
+ }
+
+public:
+ MemoryFile(uint8_t *storage, size_t capacity, bool owns_storage,
+ ModeFlags mode)
+ : File(&memory_write, &memory_read, &memory_seek, &memory_close,
+ stream_buffer, sizeof(stream_buffer), _IOFBF, false, mode),
+ storage(storage), capacity(capacity), owns_storage(owns_storage),
+ append(mode & static_cast<ModeFlags>(OpenMode::APPEND)) {
+ if (mode & static_cast<ModeFlags>(OpenMode::READ)) {
+ end = capacity;
+ } else if (mode & static_cast<ModeFlags>(OpenMode::WRITE)) {
+ if (capacity != 0)
+ storage[0] = '\0';
+ } else if (!owns_storage) {
+ while (end < capacity && storage[end] != '\0')
+ ++end;
+ position = end;
+ }
+ }
+};
+
+} // namespace
+
+LLVM_LIBC_FUNCTION(::FILE *, fmemopen,
+ (void *__restrict buf, size_t max_size,
+ const char *__restrict mode)) {
+ LIBC_CRASH_ON_NULLPTR(mode);
+ // Use the same mode parser as fopen. Binary mode has no special effect.
+ auto flags = File::mode_flags(mode);
+ if (flags == 0) {
+ libc_errno = EINVAL;
+ return nullptr;
+ }
+
+ auto *storage = static_cast<uint8_t *>(buf);
+ bool owns_storage = buf == nullptr;
+ if (owns_storage && max_size != 0) {
+ AllocChecker ac;
+ storage = new (ac) uint8_t[max_size];
+ if (!ac) {
+ libc_errno = ENOMEM;
+ return nullptr;
+ }
+ }
+
+ AllocChecker ac;
+ auto *file = new (ac) MemoryFile(storage, max_size, owns_storage, flags);
+ if (!ac) {
+ if (owns_storage)
+ delete[] storage;
+ libc_errno = ENOMEM;
+ return nullptr;
+ }
+ File::add_file(file);
+ return reinterpret_cast<::FILE *>(file);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdio/fmemopen.h b/libc/src/stdio/fmemopen.h
new file mode 100644
index 0000000000000..5fe2ad4536743
--- /dev/null
+++ b/libc/src/stdio/fmemopen.h
@@ -0,0 +1,28 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Declaration of fmemopen a POSIX function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_FMEMOPEN_H
+#define LLVM_LIBC_SRC_STDIO_FMEMOPEN_H
+
+#include "hdr/types/FILE.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+::FILE *fmemopen(void *__restrict buf, size_t max_size,
+ const char *__restrict mode);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_FMEMOPEN_H
diff --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index b25219ef3ca03..8e1e9ff056287 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -142,6 +142,37 @@ add_libc_test(
libc.test.UnitTest.ErrnoCheckingTest
)
+add_libc_test(
+ fmemopen_test
+ SUITE
+ libc_stdio_unittests
+ SRCS
+ fmemopen_test.cpp
+ DEPENDS
+ libc.include.stdio
+ libc.hdr.stdio_macros
+ libc.hdr.types.off_t
+ libc.src.__support.CPP.limits
+ libc.src.__support.CPP.scope
+ libc.src.errno.errno
+ libc.src.stdio.clearerr
+ libc.src.stdio.fclose
+ libc.src.stdio.feof
+ libc.src.stdio.ferror
+ libc.src.stdio.fflush
+ libc.src.stdio.fgetc
+ libc.src.stdio.fmemopen
+ libc.src.stdio.fread
+ libc.src.stdio.fseek
+ libc.src.stdio.fseeko
+ libc.src.stdio.ftell
+ libc.src.stdio.fwrite
+ libc.src.stdio.setvbuf
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+ libc.test.UnitTest.MemoryMatcher
+)
+
add_libc_test(
fopencookie_test
SUITE
diff --git a/libc/test/src/stdio/fmemopen_test.cpp b/libc/test/src/stdio/fmemopen_test.cpp
new file mode 100644
index 0000000000000..1736580914a45
--- /dev/null
+++ b/libc/test/src/stdio/fmemopen_test.cpp
@@ -0,0 +1,295 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Unit tests for fmemopen.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/stdio_macros.h"
+#include "hdr/types/off_t.h"
+#include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/scope.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/fgetc.h"
+#include "src/stdio/fmemopen.h"
+#include "src/stdio/fread.h"
+#include "src/stdio/fseek.h"
+#include "src/stdio/fseeko.h"
+#include "src/stdio/ftell.h"
+#include "src/stdio/fwrite.h"
+#include "src/stdio/setvbuf.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/MemoryMatcher.h"
+#include "test/UnitTest/Test.h"
+
+using LlvmLibcFMemOpenTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+using MemoryView = LIBC_NAMESPACE::testing::MemoryView;
+using LIBC_NAMESPACE::cpp::scope_exit;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Fails;
+using LIBC_NAMESPACE::testing::ErrnoSetterMatcher::Succeeds;
+
+TEST_F(LlvmLibcFMemOpenTest, InitialPositionAndEnd) {
+ struct Mode {
+ const char *name;
+ long position;
+ long end;
+ };
+ const Mode modes[] = {
+ {"r", 0, 8}, {"rb", 0, 8}, {"r+", 0, 8}, {"rb+", 0, 8}, {"r+b", 0, 8},
+ {"w", 0, 0}, {"wb", 0, 0}, {"w+", 0, 0}, {"wb+", 0, 0}, {"w+b", 0, 0},
+ {"a", 2, 2}, {"ab", 2, 2}, {"a+", 2, 2}, {"ab+", 2, 2}, {"a+b", 2, 2},
+ };
+ for (const auto &mode : modes) {
+ char storage[] = {'A', 'B', '\0', 'x', 'x', 'x', 'x', 'x'};
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), mode.name);
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(mode.position, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(mode.name[0] == 'w' ? '\0' : 'A', storage[0]);
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(mode.end, LIBC_NAMESPACE::ftell(f));
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, ReadIncludesNullAndStopsAtEnd) {
+ char storage[] = {'A', '\0', 'B', 'C'};
+ char output[] = {'?', '?', '?', '?', '?', '?'};
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "r");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fread(output, 1, sizeof(output), f));
+ const char expected[] = {'A', '\0', 'B', 'C', '?', '?'};
+ EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)),
+ MemoryView(output, sizeof(output)));
+ EXPECT_NE(0, LIBC_NAMESPACE::feof(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fread(output, 1, 1, f));
+ EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(0, LIBC_NAMESPACE::feof(f));
+ EXPECT_EQ(int('A'), LIBC_NAMESPACE::fgetc(f));
+}
+
+TEST_F(LlvmLibcFMemOpenTest, WriteAndOverwrite) {
+ char storage[8];
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(size_t(5), LIBC_NAMESPACE::fwrite("ABCDE", 1, 5, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_STREQ("ABCDE", storage);
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 1, SEEK_SET));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_STREQ("AXCDE", storage);
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("F", 1, 1, f));
+ // Seeking flushes output and allows an update stream to switch to reading.
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ char output[8];
+ EXPECT_EQ(size_t(6), LIBC_NAMESPACE::fread(output, 1, sizeof(output), f));
+ EXPECT_MEM_EQ(MemoryView("AXCDEF", 6), MemoryView(output, 6));
+}
+
+TEST_F(LlvmLibcFMemOpenTest, ExactCapacityAndClose) {
+ const int buffer_modes[] = {_IONBF, _IOFBF, _IOLBF};
+ for (int buffering : buffer_modes) {
+ char guarded[] = {'L', '?', '?', '?', '?', 'R'};
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(guarded + 1, 4, "w");
+ ASSERT_TRUE(f != nullptr);
+ EXPECT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, buffering, 16));
+ EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fwrite("ABCD", 1, 4, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f));
+ const char expected[] = {'L', 'A', 'B', 'C', 'D', 'R'};
+ EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)),
+ MemoryView(guarded, sizeof(guarded)));
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, CloseFlushesPendingOutput) {
+ char storage[8];
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w");
+ ASSERT_TRUE(f != nullptr);
+ EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f));
+ EXPECT_STREQ("ABC", storage);
+}
+
+TEST_F(LlvmLibcFMemOpenTest, AppendAfterSeekAndRead) {
+ const int buffer_modes[] = {_IONBF, _IOFBF};
+ for (int buffering : buffer_modes) {
+ char storage[8] = "AB";
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "a+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, buffering, 16));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(int('A'), LIBC_NAMESPACE::fgetc(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_CUR));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_EQ(3L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_STREQ("ABX", storage);
+ // Even a position beyond the current end must not create an append hole.
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 7, SEEK_SET));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("Y", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_STREQ("ABXY", storage);
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, AppendTracksEndAcrossNullBytes) {
+ char storage[8] = "";
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "a+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ const char first[] = {'A', '\0', 'B'};
+ EXPECT_EQ(sizeof(first), LIBC_NAMESPACE::fwrite(first, 1, sizeof(first), f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("C", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ const char expected[] = {'A', '\0', 'B', 'C', '\0'};
+ EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)),
+ MemoryView(storage, sizeof(expected)));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f));
+}
+
+TEST_F(LlvmLibcFMemOpenTest, SeekBoundsAndEnd) {
+ char storage[8];
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(storage, sizeof(storage), "w+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+
+ EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 8, SEEK_SET), Succeeds());
+ EXPECT_EQ(8L, LIBC_NAMESPACE::ftell(f));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 9, SEEK_SET), Fails(EINVAL));
+ EXPECT_EQ(8L, LIBC_NAMESPACE::ftell(f));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, -1, SEEK_SET), Fails(EINVAL));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_END), Succeeds());
+ EXPECT_EQ(3L, LIBC_NAMESPACE::ftell(f));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 2, SEEK_END), Succeeds());
+ EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f));
+
+ // Reading past the data end must not advance the memory stream's position.
+ EXPECT_EQ(EOF, LIBC_NAMESPACE::fgetc(f));
+ EXPECT_NE(0, LIBC_NAMESPACE::feof(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_CUR), Succeeds());
+ EXPECT_EQ(5L, LIBC_NAMESPACE::ftell(f));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, -1, SEEK_END), Succeeds());
+ EXPECT_EQ(2L, LIBC_NAMESPACE::ftell(f));
+
+ const off_t offsets[] = {LIBC_NAMESPACE::cpp::numeric_limits<off_t>::min(),
+ LIBC_NAMESPACE::cpp::numeric_limits<off_t>::max()};
+ for (off_t offset : offsets) {
+ ASSERT_THAT(LIBC_NAMESPACE::fseeko(f, offset, SEEK_CUR), Fails(EINVAL));
+ EXPECT_EQ(2L, LIBC_NAMESPACE::ftell(f));
+ }
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 6, SEEK_SET), Succeeds());
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("Z", 1, 1, f));
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, SEEK_END), Succeeds());
+ EXPECT_EQ(7L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ('Z', storage[6]);
+ EXPECT_EQ('\0', storage[7]);
+
+ ASSERT_THAT(LIBC_NAMESPACE::fseek(f, 0, -1), Fails(EINVAL));
+ // The contents of the gap between the old end and position 6 are unspecified.
+}
+
+TEST_F(LlvmLibcFMemOpenTest, InternalBuffer) {
+ const char *update_modes[] = {"w+", "a+", "r+"};
+ for (const char *mode : update_modes) {
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(nullptr, 16, mode);
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(mode[0] == 'r' ? 16L : 0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(size_t(3), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ char output[3];
+ EXPECT_EQ(sizeof(output),
+ LIBC_NAMESPACE::fread(output, 1, sizeof(output), f));
+ EXPECT_MEM_EQ(MemoryView("ABC", 3), MemoryView(output, 3));
+ }
+ // Non-update modes are accepted, just as with an externally supplied buffer.
+ const char *other_modes[] = {"r", "w", "a"};
+ for (const char *mode : other_modes) {
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(nullptr, 4, mode);
+ ASSERT_TRUE(f != nullptr);
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f));
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, ZeroCapacity) {
+ char guard = 'X';
+ void *buffers[] = {&guard, nullptr};
+ for (void *buf : buffers) {
+ const char *modes[] = {"r+", "w+", "a+"};
+ for (const char *mode : modes) {
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(buf, 0, mode);
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, _IOFBF, 1));
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(EOF, LIBC_NAMESPACE::fgetc(f));
+ EXPECT_NE(0, LIBC_NAMESPACE::feof(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fwrite("ABC", 1, 3, f));
+ ASSERT_ERRNO_EQ(ENOSPC);
+ EXPECT_NE(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_END));
+ EXPECT_EQ(0L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_NE(0, LIBC_NAMESPACE::fseek(f, 1, SEEK_SET));
+ ASSERT_ERRNO_EQ(EINVAL);
+ EXPECT_EQ('X', guard);
+ }
+ }
+}
+
+TEST_F(LlvmLibcFMemOpenTest, ShortWriteAndRecovery) {
+ char guarded[] = {'L', '?', '?', '?', '?', 'R'};
+ ::FILE *f = LIBC_NAMESPACE::fmemopen(guarded + 1, 4, "w+");
+ ASSERT_TRUE(f != nullptr);
+ scope_exit close([&] { EXPECT_EQ(0, LIBC_NAMESPACE::fclose(f)); });
+ ASSERT_EQ(0, LIBC_NAMESPACE::setvbuf(f, nullptr, _IOFBF, 1));
+ EXPECT_EQ(size_t(4), LIBC_NAMESPACE::fwrite("ABCDEF", 1, 6, f));
+ ASSERT_ERRNO_EQ(ENOSPC);
+ EXPECT_NE(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(4L, LIBC_NAMESPACE::ftell(f));
+ EXPECT_EQ(size_t(0), LIBC_NAMESPACE::fwrite("XYZ", 1, 3, f));
+ ASSERT_ERRNO_EQ(ENOSPC);
+ LIBC_NAMESPACE::clearerr(f);
+ EXPECT_EQ(0, LIBC_NAMESPACE::ferror(f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fseek(f, 0, SEEK_SET));
+ EXPECT_EQ(size_t(1), LIBC_NAMESPACE::fwrite("X", 1, 1, f));
+ EXPECT_EQ(0, LIBC_NAMESPACE::fflush(f));
+ const char expected[] = {'L', 'X', 'B', 'C', 'D', 'R'};
+ EXPECT_MEM_EQ(MemoryView(expected, sizeof(expected)),
+ MemoryView(guarded, sizeof(guarded)));
+}
More information about the libc-commits
mailing list