Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/llvm/llvm-project.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/libc
diff options
context:
space:
mode:
authorSiva Chandra Reddy <sivachandra@google.com>2022-01-25 22:50:00 +0300
committerSiva Chandra Reddy <sivachandra@google.com>2022-02-15 08:34:29 +0300
commit4ef02da0947238ea1ef026d01c80b9b45b892fd4 (patch)
tree6c62330642677c343a1a09960591c13e0ff40031 /libc
parent0f29319e56459eb26a3bf7fb86e45e63f43a168e (diff)
[libc] Add a platform independent buffered file IO data structure.
Reviewed By: lntue Differential Revision: https://reviews.llvm.org/D119458
Diffstat (limited to 'libc')
-rw-r--r--libc/include/CMakeLists.txt1
-rw-r--r--libc/include/llvm-libc-macros/CMakeLists.txt6
-rw-r--r--libc/include/llvm-libc-macros/stdio-macros.h16
-rw-r--r--libc/include/stdio.h.def1
-rw-r--r--libc/src/__support/CMakeLists.txt1
-rw-r--r--libc/src/__support/File/CMakeLists.txt7
-rw-r--r--libc/src/__support/File/file.cpp242
-rw-r--r--libc/src/__support/File/file.h193
-rw-r--r--libc/test/src/__support/CMakeLists.txt1
-rw-r--r--libc/test/src/__support/File/CMakeLists.txt16
-rw-r--r--libc/test/src/__support/File/file_test.cpp321
11 files changed, 805 insertions, 0 deletions
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index bb713815ffbc..6198e90e3fd0 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 d6b59dbbb8e2..9ef039e499bf 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 000000000000..af75193c1ecf
--- /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 712053940e9b..d3b4cfaed7a9 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 c46370a45991..c1fa9c5661b2 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 000000000000..d08f5e7c0647
--- /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 000000000000..6d1d3b54f25d
--- /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 000000000000..521d89402b6a
--- /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 different 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 923db635c8a5..2bb125bc7c6a 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 000000000000..485c8e8a38ab
--- /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 000000000000..44712fcb6fa3
--- /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);
+}