diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -124,6 +124,7 @@ 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 --- a/libc/include/llvm-libc-macros/CMakeLists.txt +++ b/libc/include/llvm-libc-macros/CMakeLists.txt @@ -7,3 +7,9 @@ DEPENDS .linux.fcntl_macros ) + +add_header( + stdio_macros + HDR + stdio-macros.h +) diff --git a/libc/include/stdio.h.def b/libc/include/llvm-libc-macros/stdio-macros.h copy from libc/include/stdio.h.def copy to libc/include/llvm-libc-macros/stdio-macros.h --- a/libc/include/stdio.h.def +++ b/libc/include/llvm-libc-macros/stdio-macros.h @@ -1,4 +1,4 @@ -//===-- C standard library header stdio.h ---------------------------------===// +//===-- 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. @@ -6,11 +6,11 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_LIBC_STDIO_H -#define LLVM_LIBC_STDIO_H +#ifndef __LLVM_LIBC_MACROS_STDIO_MACROS_H +#define __LLVM_LIBC_MACROS_STDIO_MACROS_H -#include <__llvm-libc-common.h> +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 -%%public_api() - -#endif // LLVM_LIBC_STDIO_H +#endif // __LLVM_LIBC_MACROS_STDIO_MACROS_H diff --git a/libc/include/stdio.h.def b/libc/include/stdio.h.def --- 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 %%public_api() diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt --- a/libc/src/__support/CMakeLists.txt +++ b/libc/src/__support/CMakeLists.txt @@ -53,5 +53,6 @@ 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 --- /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.h b/libc/src/__support/File/file.h new file mode 100644 --- /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 +#include + +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(OpenMode::WRITE) | + static_cast(OpenMode::APPEND)); + } + + bool read_allowed() const { + return mode & static_cast(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(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/src/__support/File/file.cpp b/libc/src/__support/File/file.cpp new file mode 100644 --- /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 +#include + +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 dataref(data, len); + cpp::MutableArrayRef 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 bufref(buf, bufsize); + cpp::MutableArrayRef 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(OpenMode::READ); + ++main_mode_count; + break; + case 'w': + flags |= static_cast(OpenMode::WRITE); + ++main_mode_count; + break; + case '+': + flags |= (static_cast(OpenMode::WRITE) | + static_cast(OpenMode::READ)); + break; + case 'b': + flags |= static_cast(ContentType::BINARY); + break; + case 'a': + flags |= static_cast(OpenMode::APPEND); + ++main_mode_count; + break; + case 'x': + flags |= static_cast(CreateType::EXCLUSIVE); + break; + default: + return 0; + } + } + + if (main_mode_count != 1) + return 0; + + return flags; +} + +} // namespace __llvm_libc diff --git a/libc/test/src/__support/CMakeLists.txt b/libc/test/src/__support/CMakeLists.txt --- a/libc/test/src/__support/CMakeLists.txt +++ b/libc/test/src/__support/CMakeLists.txt @@ -50,4 +50,5 @@ 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 --- /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 --- /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 +#include +#include + +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(__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(__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(f); + if (sf->pos >= sf->eof_marker) + return 0; + size_t i = 0; + for (i = 0; i < len; ++i) + reinterpret_cast(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(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(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(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(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); +}