[libc-commits] [libc] 4ef02da - [libc] Add a platform independent buffered file IO data structure.

Siva Chandra Reddy via libc-commits libc-commits at lists.llvm.org
Mon Feb 14 21:34:37 PST 2022


Author: Siva Chandra Reddy
Date: 2022-02-15T05:34:29Z
New Revision: 4ef02da0947238ea1ef026d01c80b9b45b892fd4

URL: https://github.com/llvm/llvm-project/commit/4ef02da0947238ea1ef026d01c80b9b45b892fd4
DIFF: https://github.com/llvm/llvm-project/commit/4ef02da0947238ea1ef026d01c80b9b45b892fd4.diff

LOG: [libc] Add a platform independent buffered file IO data structure.

Reviewed By: lntue

Differential Revision: https://reviews.llvm.org/D119458

Added: 
    libc/include/llvm-libc-macros/stdio-macros.h
    libc/src/__support/File/CMakeLists.txt
    libc/src/__support/File/file.cpp
    libc/src/__support/File/file.h
    libc/test/src/__support/File/CMakeLists.txt
    libc/test/src/__support/File/file_test.cpp

Modified: 
    libc/include/CMakeLists.txt
    libc/include/llvm-libc-macros/CMakeLists.txt
    libc/include/stdio.h.def
    libc/src/__support/CMakeLists.txt
    libc/test/src/__support/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index bb713815ffbc9..6198e90e3fd0d 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -124,6 +124,7 @@ add_gen_header(
   GEN_HDR stdio.h
   DEPENDS
     .llvm_libc_common_h
+    .llvm-libc-macros.stdio_macros
     .llvm-libc-types.FILE
     .llvm-libc-types.size_t
 )

diff  --git a/libc/include/llvm-libc-macros/CMakeLists.txt b/libc/include/llvm-libc-macros/CMakeLists.txt
index d6b59dbbb8e2b..9ef039e499bfe 100644
--- a/libc/include/llvm-libc-macros/CMakeLists.txt
+++ b/libc/include/llvm-libc-macros/CMakeLists.txt
@@ -7,3 +7,9 @@ add_header(
   DEPENDS
     .linux.fcntl_macros
 )
+
+add_header(
+  stdio_macros
+  HDR
+    stdio-macros.h
+)

diff  --git a/libc/include/llvm-libc-macros/stdio-macros.h b/libc/include/llvm-libc-macros/stdio-macros.h
new file mode 100644
index 0000000000000..af75193c1ecfb
--- /dev/null
+++ b/libc/include/llvm-libc-macros/stdio-macros.h
@@ -0,0 +1,16 @@
+//===-- Definition of macros from stdio.h ---------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef __LLVM_LIBC_MACROS_STDIO_MACROS_H
+#define __LLVM_LIBC_MACROS_STDIO_MACROS_H
+
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#define SEEK_END 2
+
+#endif // __LLVM_LIBC_MACROS_STDIO_MACROS_H

diff  --git a/libc/include/stdio.h.def b/libc/include/stdio.h.def
index 712053940e9bf..d3b4cfaed7a95 100644
--- a/libc/include/stdio.h.def
+++ b/libc/include/stdio.h.def
@@ -10,6 +10,7 @@
 #define LLVM_LIBC_STDIO_H
 
 #include <__llvm-libc-common.h>
+#include <llvm-libc-macros/stdio-macros.h>
 
 %%public_api()
 

diff  --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt
index c46370a45991f..c1fa9c5661b22 100644
--- a/libc/src/__support/CMakeLists.txt
+++ b/libc/src/__support/CMakeLists.txt
@@ -53,5 +53,6 @@ add_header_library(
     integer_operations.h
 )
 
+add_subdirectory(File)
 add_subdirectory(FPUtil)
 add_subdirectory(OSUtil)

diff  --git a/libc/src/__support/File/CMakeLists.txt b/libc/src/__support/File/CMakeLists.txt
new file mode 100644
index 0000000000000..d08f5e7c06476
--- /dev/null
+++ b/libc/src/__support/File/CMakeLists.txt
@@ -0,0 +1,7 @@
+add_object_library(
+  file
+  SRCS
+    file.cpp
+  HDRS
+    file.h
+)

diff  --git a/libc/src/__support/File/file.cpp b/libc/src/__support/File/file.cpp
new file mode 100644
index 0000000000000..6d1d3b54f25d2
--- /dev/null
+++ b/libc/src/__support/File/file.cpp
@@ -0,0 +1,242 @@
+//===--- Implementation of a platform independent file data structure -----===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "file.h"
+
+#include "src/__support/CPP/ArrayRef.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+namespace __llvm_libc {
+
+size_t File::write(const void *data, size_t len) {
+  FileLock lock(this);
+
+  if (!write_allowed()) {
+    errno = EBADF;
+    err = true;
+    return 0;
+  }
+
+  prev_op = FileOp::WRITE;
+
+  cpp::ArrayRef<uint8_t> dataref(data, len);
+  cpp::MutableArrayRef<uint8_t> bufref(buf, bufsize);
+
+  const size_t used = pos;
+  const size_t bufspace = bufsize - pos;
+  const size_t write_size = bufspace > len ? len : bufspace;
+  // TODO: Replace the for loop below with a call to internal memcpy.
+  for (size_t i = 0; i < write_size; ++i)
+    bufref[pos + i] = dataref[i];
+  pos += write_size;
+  if (len < bufspace)
+    return len;
+
+  // If the control reaches beyond this point, it means that |data|
+  // is more than what can be accomodated in the buffer. So, we first
+  // flush out the buffer.
+  size_t bytes_written = platform_write(this, buf, bufsize);
+  pos = 0; // Buffer is now empty so reset pos to the beginning.
+  if (bytes_written < bufsize) {
+    err = true;
+    // If less bytes were written than expected, then there are two
+    // possibilities.
+    // 1. None of the bytes from |data| were flushed out.
+    if (bytes_written <= used)
+      return 0;
+    // 2. Some of the bytes from |data| were written
+    return bytes_written - used;
+  }
+
+  // If the remaining bytes from |data| can fit in the buffer, write
+  // into it. Else, write it directly to the platform stream.
+  size_t remaining = len - write_size;
+  if (remaining <= len) {
+    // TODO: Replace the for loop below with a call to internal memcpy.
+    for (size_t i = 0; i < remaining; ++i)
+      bufref[i] = dataref[i];
+    pos += remaining;
+    return len;
+  }
+
+  size_t transferred =
+      platform_write(this, dataref.data() + write_size, remaining);
+  if (transferred < remaining) {
+    err = true;
+    return write_size + transferred;
+  }
+  return len;
+}
+
+size_t File::read(void *data, size_t len) {
+  FileLock lock(this);
+
+  if (!read_allowed()) {
+    errno = EBADF;
+    err = true;
+    return 0;
+  }
+
+  prev_op = FileOp::READ;
+
+  cpp::MutableArrayRef<uint8_t> bufref(buf, bufsize);
+  cpp::MutableArrayRef<uint8_t> dataref(data, len);
+
+  // Because read_limit is always greater than equal to pos,
+  // available_data is never a wrapped around value.
+  size_t available_data = read_limit - pos;
+  if (len <= available_data) {
+    // TODO: Replace the for loop below with a call to internal memcpy.
+    for (size_t i = 0; i < len; ++i)
+      dataref[i] = bufref[i + pos];
+    pos += len;
+    return len;
+  }
+
+  // Copy all of the available data.
+  // TODO: Replace the for loop with a call to internal memcpy.
+  for (size_t i = 0; i < available_data; ++i)
+    dataref[i] = bufref[i + pos];
+  read_limit = pos = 0; // Reset the pointers.
+
+  size_t to_fetch = len - available_data;
+  if (to_fetch > bufsize) {
+    size_t fetched_size = platform_read(this, data, to_fetch);
+    if (fetched_size < to_fetch) {
+      if (errno == 0)
+        eof = true;
+      else
+        err = true;
+      return available_data + fetched_size;
+    }
+    return len;
+  }
+
+  // Fetch and buffer another buffer worth of data.
+  size_t fetched_size = platform_read(this, buf, bufsize);
+  read_limit += fetched_size;
+  size_t transfer_size = fetched_size >= to_fetch ? to_fetch : fetched_size;
+  for (size_t i = 0; i < transfer_size; ++i)
+    dataref[i] = bufref[i];
+  pos += transfer_size;
+  if (fetched_size < to_fetch) {
+    if (errno == 0)
+      eof = true;
+    else
+      err = true;
+  }
+  return transfer_size + available_data;
+}
+
+int File::seek(long offset, int whence) {
+  FileLock lock(this);
+  if (prev_op == FileOp::WRITE && pos > 0) {
+    size_t transferred_size = platform_write(this, buf, pos);
+    if (transferred_size < pos) {
+      err = true;
+      return -1;
+    }
+  }
+  pos = read_limit = 0;
+  prev_op = FileOp::SEEK;
+  // Reset the eof flag as a seek might move the file positon to some place
+  // readable.
+  eof = false;
+  return platform_seek(this, offset, whence);
+}
+
+int File::flush() {
+  FileLock lock(this);
+  if (prev_op == FileOp::WRITE && pos > 0) {
+    size_t transferred_size = platform_write(this, buf, pos);
+    if (transferred_size < pos) {
+      err = true;
+      return -1;
+    }
+    pos = 0;
+    return platform_flush(this);
+  }
+  return 0;
+}
+
+int File::close() {
+  {
+    FileLock lock(this);
+    if (prev_op == FileOp::WRITE && pos > 0) {
+      size_t transferred_size = platform_write(this, buf, pos);
+      if (transferred_size < pos) {
+        err = true;
+        return -1;
+      }
+    }
+    if (platform_close(this) != 0)
+      return -1;
+    if (own_buf)
+      free(buf);
+  }
+  free(this);
+  return 0;
+}
+
+void File::set_buffer(void *buffer, size_t size, bool owned) {
+  if (own_buf)
+    free(buf);
+  buf = buffer;
+  bufsize = size;
+  own_buf = owned;
+}
+
+File::ModeFlags File::mode_flags(const char *mode) {
+  // First character in |mode| should be 'a', 'r' or 'w'.
+  if (*mode != 'a' && *mode != 'r' && *mode != 'w')
+    return 0;
+
+  // There should be exaclty one main mode ('a', 'r' or 'w') character.
+  // If there are more than one main mode characters listed, then
+  // we will consider |mode| as incorrect and return 0;
+  int main_mode_count = 0;
+
+  ModeFlags flags = 0;
+  for (; *mode != '\0'; ++mode) {
+    switch (*mode) {
+    case 'r':
+      flags |= static_cast<ModeFlags>(OpenMode::READ);
+      ++main_mode_count;
+      break;
+    case 'w':
+      flags |= static_cast<ModeFlags>(OpenMode::WRITE);
+      ++main_mode_count;
+      break;
+    case '+':
+      flags |= (static_cast<ModeFlags>(OpenMode::WRITE) |
+                static_cast<ModeFlags>(OpenMode::READ));
+      break;
+    case 'b':
+      flags |= static_cast<ModeFlags>(ContentType::BINARY);
+      break;
+    case 'a':
+      flags |= static_cast<ModeFlags>(OpenMode::APPEND);
+      ++main_mode_count;
+      break;
+    case 'x':
+      flags |= static_cast<ModeFlags>(CreateType::EXCLUSIVE);
+      break;
+    default:
+      return 0;
+    }
+  }
+
+  if (main_mode_count != 1)
+    return 0;
+
+  return flags;
+}
+
+} // namespace __llvm_libc

diff  --git a/libc/src/__support/File/file.h b/libc/src/__support/File/file.h
new file mode 100644
index 0000000000000..521d89402b6a9
--- /dev/null
+++ b/libc/src/__support/File/file.h
@@ -0,0 +1,193 @@
+//===--- A platform independent file data structure -------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SUPPORT_OSUTIL_FILE_H
+#define LLVM_LIBC_SRC_SUPPORT_OSUTIL_FILE_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace __llvm_libc {
+
+// This a generic base class to encapsulate a platform independent file data
+// structure. Platform specific specializations should create a subclass as
+// suitable for their platform.
+class File {
+public:
+  using LockFunc = void(File *);
+  using UnlockFunc = void(File *);
+
+  using WriteFunc = size_t(File *, const void *, size_t);
+  using ReadFunc = size_t(File *, void *, size_t);
+  using SeekFunc = int(File *, long, int);
+  using CloseFunc = int(File *);
+  using FlushFunc = int(File *);
+
+  using ModeFlags = uint32_t;
+
+  // The three 
diff erent types of flags below are to be used with '|' operator.
+  // Their values correspond to mutually exclusive bits in a 32-bit unsigned
+  // integer value. A flag set can include both READ and WRITE if the file
+  // is opened in update mode (ie. if the file was opened with a '+' the mode
+  // string.)
+  enum class OpenMode : ModeFlags {
+    READ = 0x1,
+    WRITE = 0x2,
+    APPEND = 0x4,
+  };
+
+  // Denotes a file opened in binary mode (which is specified by including
+  // the 'b' character in teh mode string.)
+  enum class ContentType : ModeFlags {
+    BINARY = 0x10,
+  };
+
+  // Denotes a file to be created for writing.
+  enum class CreateType : ModeFlags {
+    EXCLUSIVE = 0x100,
+  };
+
+private:
+  enum class FileOp : uint8_t { NONE, READ, WRITE, SEEK };
+
+  // Platfrom specific functions which create new file objects should initialize
+  // these fields suitably via the constructor. Typically, they should be simple
+  // syscall wrappers for the corresponding functionality.
+  WriteFunc *platform_write;
+  ReadFunc *platform_read;
+  SeekFunc *platform_seek;
+  CloseFunc *platform_close;
+  FlushFunc *platform_flush;
+
+  // Platform specific functions to lock and unlock file for mutually exclusive
+  // access from threads in a multi-threaded application.
+  LockFunc *platform_lock;
+  UnlockFunc *platform_unlock;
+
+  void *buf;      // Pointer to the stream buffer for buffered streams
+  size_t bufsize; // Size of the buffer pointed to by |buf|.
+
+  // Buffering mode to used to buffer.
+  int bufmode;
+
+  // If own_buf is true, the |buf| is owned by the stream and will be
+  // free-ed when close method is called on the stream.
+  bool own_buf;
+
+  // The mode in which the file was opened.
+  ModeFlags mode;
+
+  // Current read or write pointer.
+  size_t pos;
+
+  // Represents the previous operation that was performed.
+  FileOp prev_op;
+
+  // When the buffer is used as a read buffer, read_limit is the upper limit
+  // of the index to which the buffer can be read until.
+  size_t read_limit;
+
+  bool eof;
+  bool err;
+
+protected:
+  bool write_allowed() const {
+    return mode & (static_cast<ModeFlags>(OpenMode::WRITE) |
+                   static_cast<ModeFlags>(OpenMode::APPEND));
+  }
+
+  bool read_allowed() const {
+    return mode & static_cast<ModeFlags>(OpenMode::READ);
+  }
+
+public:
+  // We want this constructor to be constexpr so that global file objects
+  // like stdout do not require invocation of the constructor which can
+  // potentially lead to static initialization order fiasco.
+  constexpr File(WriteFunc *wf, ReadFunc *rf, SeekFunc *sf, CloseFunc *cf,
+                 FlushFunc *ff, LockFunc *lf, UnlockFunc *ulf, void *buffer,
+                 size_t buffer_size, int buffer_mode, bool owned,
+                 ModeFlags modeflags)
+      : platform_write(wf), platform_read(rf), platform_seek(sf),
+        platform_close(cf), platform_flush(ff), platform_lock(lf),
+        platform_unlock(ulf), buf(buffer), bufsize(buffer_size),
+        bufmode(buffer_mode), own_buf(owned), mode(modeflags), pos(0),
+        prev_op(FileOp::NONE), read_limit(0), eof(false), err(false) {}
+
+  // This function helps initialize the various fields of the File data
+  // structure after a allocating memory for it via a call to malloc.
+  static void init(File *f, WriteFunc *wf, ReadFunc *rf, SeekFunc *sf,
+                   CloseFunc *cf, FlushFunc *ff, LockFunc *lf, UnlockFunc *ulf,
+                   void *buffer, size_t buffer_size, int buffer_mode,
+                   bool owned, ModeFlags modeflags) {
+    f->platform_write = wf;
+    f->platform_read = rf;
+    f->platform_seek = sf;
+    f->platform_close = cf;
+    f->platform_flush = ff;
+    f->platform_lock = lf;
+    f->platform_unlock = ulf;
+    f->buf = reinterpret_cast<uint8_t *>(buffer);
+    f->bufsize = buffer_size;
+    f->bufmode = buffer_mode;
+    f->own_buf = owned;
+    f->mode = modeflags;
+
+    f->prev_op = FileOp::NONE;
+    f->read_limit = f->pos = 0;
+    f->eof = f->err = false;
+  }
+
+  // Buffered write of |len| bytes from |data|.
+  size_t write(const void *data, size_t len);
+
+  // Buffered read of |len| bytes into |data|.
+  size_t read(void *data, size_t len);
+
+  int seek(long offset, int whence);
+
+  // If buffer has data written to it, flush it out. Does nothing if the
+  // buffer is currently being used as a read buffer.
+  int flush();
+
+  // Sets the internal buffer to |buffer| with buffering mode |mode|.
+  // |size| is the size of |buffer|. This new |buffer| is owned by the
+  // stream only if |owned| is true.
+  void set_buffer(void *buffer, size_t size, bool owned);
+
+  // Closes the file stream and frees up all resources owned by it.
+  int close();
+
+  void lock() { platform_lock(this); }
+  void unlock() { platform_unlock(this); }
+
+  bool error() const { return err; }
+  void clearerr() { err = false; }
+  bool iseof() const { return eof; }
+
+  // Returns an bit map of flags corresponding to enumerations of
+  // OpenMode, ContentType and CreateType.
+  static ModeFlags mode_flags(const char *mode);
+};
+
+// This is a convenience RAII class to lock and unlock file objects.
+class FileLock {
+  File *file;
+
+public:
+  explicit FileLock(File *f) : file(f) { file->lock(); }
+
+  ~FileLock() { file->unlock(); }
+
+  FileLock(const FileLock &) = delete;
+  FileLock(FileLock &&) = delete;
+};
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_SUPPORT_OSUTIL_FILE_H

diff  --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt
index 923db635c8a59..2bb125bc7c6a2 100644
--- a/libc/test/src/__support/CMakeLists.txt
+++ b/libc/test/src/__support/CMakeLists.txt
@@ -50,4 +50,5 @@ add_custom_command(TARGET libc_str_to_float_comparison_test
                    VERBATIM)
 
 add_subdirectory(CPP)
+add_subdirectory(File)
 add_subdirectory(OSUtil)

diff  --git a/libc/test/src/__support/File/CMakeLists.txt b/libc/test/src/__support/File/CMakeLists.txt
new file mode 100644
index 0000000000000..485c8e8a38abe
--- /dev/null
+++ b/libc/test/src/__support/File/CMakeLists.txt
@@ -0,0 +1,16 @@
+
+add_libc_unittest(
+  file_test
+  SUITE
+    libc_support_unittests
+  SRCS
+    file_test.cpp
+  DEPENDS
+    libc.include.stdio
+    libc.include.stdlib
+    libc.src.__support.File.file
+)
+
+target_link_libraries(
+  libc.test.src.__support.File.file_test PRIVATE LibcMemoryHelpers
+)

diff  --git a/libc/test/src/__support/File/file_test.cpp b/libc/test/src/__support/File/file_test.cpp
new file mode 100644
index 0000000000000..44712fcb6fa39
--- /dev/null
+++ b/libc/test/src/__support/File/file_test.cpp
@@ -0,0 +1,321 @@
+//===-- Unittests for platform independent file class ---------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/File/file.h"
+#include "utils/UnitTest/MemoryMatcher.h"
+#include "utils/UnitTest/Test.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+using ModeFlags = __llvm_libc::File::ModeFlags;
+using MemoryView = __llvm_libc::memory::testing::MemoryView;
+
+class StringFile : public __llvm_libc::File {
+  static constexpr size_t SIZE = 512;
+  size_t pos;
+  char str[SIZE] = {0};
+  size_t eof_marker;
+  bool write_append;
+
+  static size_t str_read(__llvm_libc::File *f, void *data, size_t len);
+  static size_t str_write(__llvm_libc::File *f, const void *data, size_t len);
+  static int str_seek(__llvm_libc::File *f, long offset, int whence);
+  static int str_close(__llvm_libc::File *f) { return 0; }
+  static int str_flush(__llvm_libc::File *f) { return 0; }
+
+  // TODO: Add a proper locking system and tests which exercise that.
+  static void str_lock(__llvm_libc::File *f) {}
+  static void str_unlock(__llvm_libc::File *f) {}
+
+public:
+  explicit StringFile(char *buffer, size_t buflen, int bufmode, bool owned,
+                      ModeFlags modeflags)
+      : __llvm_libc::File(&str_write, &str_read, &str_seek, &str_close,
+                          &str_flush, &str_lock, &str_unlock, buffer, buflen,
+                          bufmode, owned, modeflags),
+        pos(0), eof_marker(0), write_append(false) {
+    if (modeflags & static_cast<ModeFlags>(__llvm_libc::File::OpenMode::APPEND))
+      write_append = true;
+  }
+
+  void init(char *buffer, size_t buflen, int bufmode, bool owned,
+            ModeFlags modeflags) {
+    File::init(this, &str_write, &str_read, &str_seek, &str_close, &str_flush,
+               &str_lock, &str_unlock, buffer, buflen, bufmode, owned,
+               modeflags);
+    pos = eof_marker = 0;
+    if (modeflags & static_cast<ModeFlags>(__llvm_libc::File::OpenMode::APPEND))
+      write_append = true;
+    else
+      write_append = false;
+  }
+
+  void reset() { pos = 0; }
+  size_t get_pos() const { return pos; }
+  char *get_str() { return str; }
+
+  // Use this method to prefill the file.
+  void reset_and_fill(const char *data, size_t len) {
+    size_t i;
+    for (i = 0; i < len && i < SIZE; ++i) {
+      str[i] = data[i];
+    }
+    pos = 0;
+    eof_marker = i;
+  }
+};
+
+size_t StringFile::str_read(__llvm_libc::File *f, void *data, size_t len) {
+  StringFile *sf = static_cast<StringFile *>(f);
+  if (sf->pos >= sf->eof_marker)
+    return 0;
+  size_t i = 0;
+  for (i = 0; i < len; ++i)
+    reinterpret_cast<char *>(data)[i] = sf->str[sf->pos + i];
+  sf->pos += i;
+  return i;
+}
+
+size_t StringFile::str_write(__llvm_libc::File *f, const void *data,
+                             size_t len) {
+  StringFile *sf = static_cast<StringFile *>(f);
+  if (sf->write_append)
+    sf->pos = sf->eof_marker;
+  if (sf->pos >= SIZE)
+    return 0;
+  size_t i = 0;
+  for (i = 0; i < len && sf->pos < SIZE; ++i, ++sf->pos)
+    sf->str[sf->pos] = reinterpret_cast<const char *>(data)[i];
+  // Move the eof marker if the data was written beyond the current eof marker.
+  if (sf->pos > sf->eof_marker)
+    sf->eof_marker = sf->pos;
+  return i;
+}
+
+int StringFile::str_seek(__llvm_libc::File *f, long offset, int whence) {
+  StringFile *sf = static_cast<StringFile *>(f);
+  if (whence == SEEK_SET)
+    sf->pos = offset;
+  if (whence == SEEK_CUR)
+    sf->pos += offset;
+  if (whence == SEEK_END)
+    sf->pos = SIZE + offset;
+  return 0;
+}
+
+StringFile *new_string_file(char *buffer, size_t buflen, int bufmode,
+                            bool owned, const char *mode) {
+  StringFile *f = reinterpret_cast<StringFile *>(malloc(sizeof(StringFile)));
+  f->init(buffer, buflen, bufmode, owned, __llvm_libc::File::mode_flags(mode));
+  return f;
+}
+
+TEST(LlvmLibcFileTest, WriteOnly) {
+  const char data[] = "hello, file";
+  constexpr size_t FILE_BUFFER_SIZE = sizeof(data) * 3 / 2;
+  char file_buffer[FILE_BUFFER_SIZE];
+  StringFile *f = new_string_file(file_buffer, FILE_BUFFER_SIZE, 0, false, "w");
+
+  ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)));
+  EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream
+  ASSERT_EQ(f->flush(), 0);
+  EXPECT_EQ(f->get_pos(), sizeof(data)); // Data should now be available
+  EXPECT_STREQ(f->get_str(), data);
+
+  f->reset();
+  ASSERT_EQ(f->get_pos(), size_t(0));
+  ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)));
+  EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream
+  // The second write should trigger a buffer flush.
+  ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)));
+  EXPECT_GE(f->get_pos(), size_t(0));
+  ASSERT_EQ(f->flush(), 0);
+  EXPECT_EQ(f->get_pos(), 2 * sizeof(data));
+
+  char read_data[sizeof(data)];
+  // This is not a readable file.
+  EXPECT_EQ(f->read(read_data, sizeof(data)), size_t(0));
+  EXPECT_TRUE(f->error());
+  EXPECT_NE(errno, 0);
+  errno = 0;
+
+  ASSERT_EQ(f->close(), 0);
+}
+
+TEST(LlvmLibcFileTest, ReadOnly) {
+  const char initial_content[] = "1234567890987654321";
+  constexpr size_t FILE_BUFFER_SIZE = sizeof(initial_content);
+  char file_buffer[FILE_BUFFER_SIZE];
+  StringFile *f = new_string_file(file_buffer, FILE_BUFFER_SIZE, 0, false, "r");
+  f->reset_and_fill(initial_content, sizeof(initial_content));
+
+  constexpr size_t READ_SIZE = sizeof(initial_content) / 2;
+  char read_data[READ_SIZE];
+  ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE));
+  EXPECT_FALSE(f->iseof());
+  // Reading less than file buffer worth will still read one
+  // full buffer worth of data.
+  EXPECT_STREQ(file_buffer, initial_content);
+  EXPECT_STREQ(file_buffer, f->get_str());
+  EXPECT_EQ(FILE_BUFFER_SIZE, f->get_pos());
+  // The read data should match what was supposed to be read anyway.
+  MemoryView src1(initial_content, READ_SIZE), dst1(read_data, READ_SIZE);
+  EXPECT_MEM_EQ(src1, dst1);
+
+  // Reading another buffer worth should read out everything in
+  // the file.
+  ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE));
+  EXPECT_FALSE(f->iseof());
+  MemoryView src2(initial_content + READ_SIZE, READ_SIZE),
+      dst2(read_data, READ_SIZE);
+  EXPECT_MEM_EQ(src2, dst2);
+
+  // Another read should trigger an EOF.
+  ASSERT_GT(READ_SIZE, f->read(read_data, READ_SIZE));
+  EXPECT_TRUE(f->iseof());
+
+  // Reset the pos to the beginning of the file which should allow
+  // reading again.
+  for (size_t i = 0; i < READ_SIZE; ++i)
+    read_data[i] = 0;
+  f->seek(0, SEEK_SET);
+  ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE));
+  MemoryView src3(initial_content, READ_SIZE), dst3(read_data, READ_SIZE);
+  EXPECT_MEM_EQ(src3, dst3);
+
+  // This is not a writable file.
+  EXPECT_EQ(f->write(initial_content, sizeof(initial_content)), size_t(0));
+  EXPECT_TRUE(f->error());
+  EXPECT_NE(errno, 0);
+  errno = 0;
+
+  ASSERT_EQ(f->close(), 0);
+}
+
+TEST(LlvmLibcFileTest, AppendOnly) {
+  const char initial_content[] = "1234567890987654321";
+  const char write_data[] = "append";
+  constexpr size_t FILE_BUFFER_SIZE = sizeof(write_data) * 3 / 2;
+  char file_buffer[FILE_BUFFER_SIZE];
+  StringFile *f = new_string_file(file_buffer, FILE_BUFFER_SIZE, 0, false, "a");
+  f->reset_and_fill(initial_content, sizeof(initial_content));
+
+  constexpr size_t READ_SIZE = 5;
+  char read_data[READ_SIZE];
+  // This is not a readable file.
+  ASSERT_EQ(f->read(read_data, READ_SIZE), size_t(0));
+  EXPECT_TRUE(f->error());
+  EXPECT_NE(errno, 0);
+  errno = 0;
+
+  // Write should succeed but will be buffered in the file stream.
+  ASSERT_EQ(f->write(write_data, sizeof(write_data)), sizeof(write_data));
+  EXPECT_EQ(f->get_pos(), size_t(0));
+  // Flushing will write to the file.
+  EXPECT_EQ(f->flush(), int(0));
+  EXPECT_EQ(f->get_pos(), sizeof(write_data) + sizeof(initial_content));
+
+  ASSERT_EQ(f->close(), 0);
+}
+
+TEST(LlvmLibcFileTest, WriteUpdate) {
+  const char data[] = "hello, file";
+  constexpr size_t FILE_BUFFER_SIZE = sizeof(data) * 3 / 2;
+  char file_buffer[FILE_BUFFER_SIZE];
+  StringFile *f =
+      new_string_file(file_buffer, FILE_BUFFER_SIZE, 0, false, "w+");
+
+  ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)));
+  EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream
+
+  ASSERT_EQ(f->seek(0, SEEK_SET), 0);
+
+  // Seek flushes the stream buffer so we can read the previously written data.
+  char read_data[sizeof(data)];
+  ASSERT_EQ(f->read(read_data, sizeof(data)), sizeof(data));
+  EXPECT_STREQ(read_data, data);
+
+  ASSERT_EQ(f->close(), 0);
+}
+
+TEST(LlvmLibcFileTest, ReadUpdate) {
+  const char initial_content[] = "1234567890987654321";
+  constexpr size_t FILE_BUFFER_SIZE = sizeof(initial_content);
+  char file_buffer[FILE_BUFFER_SIZE];
+  StringFile *f =
+      new_string_file(file_buffer, FILE_BUFFER_SIZE, 0, false, "r+");
+  f->reset_and_fill(initial_content, sizeof(initial_content));
+
+  constexpr size_t READ_SIZE = sizeof(initial_content) / 2;
+  char read_data[READ_SIZE];
+  ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE));
+  EXPECT_FALSE(f->iseof());
+  // Reading less than file buffer worth will still read one
+  // full buffer worth of data.
+  EXPECT_STREQ(file_buffer, initial_content);
+  EXPECT_STREQ(file_buffer, f->get_str());
+  EXPECT_EQ(FILE_BUFFER_SIZE, f->get_pos());
+  // The read data should match what was supposed to be read anyway.
+  MemoryView src1(initial_content, READ_SIZE), dst1(read_data, READ_SIZE);
+  EXPECT_MEM_EQ(src1, dst1);
+
+  ASSERT_EQ(f->seek(0, SEEK_SET), 0);
+  const char write_data[] = "hello, file";
+  ASSERT_EQ(sizeof(write_data), f->write(write_data, sizeof(write_data)));
+  EXPECT_STREQ(file_buffer, write_data);
+  ASSERT_EQ(f->flush(), 0);
+  MemoryView dst2(f->get_str(), sizeof(write_data)),
+      src2(write_data, sizeof(write_data));
+  EXPECT_MEM_EQ(src2, dst2);
+
+  ASSERT_EQ(f->close(), 0);
+}
+
+TEST(LlvmLibcFileTest, AppendUpdate) {
+  const char initial_content[] = "1234567890987654321";
+  const char data[] = "hello, file";
+  constexpr size_t FILE_BUFFER_SIZE = sizeof(data) * 3 / 2;
+  char file_buffer[FILE_BUFFER_SIZE];
+  StringFile *f =
+      new_string_file(file_buffer, FILE_BUFFER_SIZE, 0, false, "a+");
+  f->reset_and_fill(initial_content, sizeof(initial_content));
+
+  ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)));
+  EXPECT_EQ(f->get_pos(), size_t(0)); // Data is buffered in the file stream
+  ASSERT_EQ(f->flush(), 0);
+  // The flush should write |data| to the endof the file.
+  EXPECT_EQ(f->get_pos(), sizeof(data) + sizeof(initial_content));
+
+  ASSERT_EQ(f->seek(0, SEEK_SET), 0);
+  // Seeking to the beginning of the file should not affect the place
+  // where write happens.
+  ASSERT_EQ(sizeof(data), f->write(data, sizeof(data)));
+  ASSERT_EQ(f->flush(), 0);
+  EXPECT_EQ(f->get_pos(), sizeof(data) * 2 + sizeof(initial_content));
+  MemoryView src1(initial_content, sizeof(initial_content)),
+      dst1(f->get_str(), sizeof(initial_content));
+  EXPECT_MEM_EQ(src1, dst1);
+  MemoryView src2(data, sizeof(data)),
+      dst2(f->get_str() + sizeof(initial_content), sizeof(data));
+  EXPECT_MEM_EQ(src2, dst2);
+  MemoryView src3(data, sizeof(data)),
+      dst3(f->get_str() + sizeof(initial_content) + sizeof(data), sizeof(data));
+  EXPECT_MEM_EQ(src3, dst3);
+
+  // Reads can happen from any point.
+  ASSERT_EQ(f->seek(0, SEEK_SET), 0);
+  constexpr size_t READ_SIZE = 10;
+  char read_data[READ_SIZE];
+  ASSERT_EQ(READ_SIZE, f->read(read_data, READ_SIZE));
+  MemoryView src4(initial_content, READ_SIZE), dst4(read_data, READ_SIZE);
+  EXPECT_MEM_EQ(src4, dst4);
+
+  ASSERT_EQ(f->close(), 0);
+}


        


More information about the libc-commits mailing list