[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