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

Reviewed By: lntue

Differential Revision: https://reviews.llvm.org/D119458
This commit is contained in:
Siva Chandra Reddy 2022-01-25 19:50:00 +00:00
parent 0f29319e56
commit 4ef02da094
11 changed files with 805 additions and 0 deletions

View File

@ -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
)

View File

@ -7,3 +7,9 @@ add_header(
DEPENDS
.linux.fcntl_macros
)
add_header(
stdio_macros
HDR
stdio-macros.h
)

View File

@ -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

View File

@ -10,6 +10,7 @@
#define LLVM_LIBC_STDIO_H
#include <__llvm-libc-common.h>
#include <llvm-libc-macros/stdio-macros.h>
%%public_api()

View File

@ -53,5 +53,6 @@ add_header_library(
integer_operations.h
)
add_subdirectory(File)
add_subdirectory(FPUtil)
add_subdirectory(OSUtil)

View File

@ -0,0 +1,7 @@
add_object_library(
file
SRCS
file.cpp
HDRS
file.h
)

View File

@ -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

View File

@ -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

View File

@ -50,4 +50,5 @@ add_custom_command(TARGET libc_str_to_float_comparison_test
VERBATIM)
add_subdirectory(CPP)
add_subdirectory(File)
add_subdirectory(OSUtil)

View File

@ -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
)

View File

@ -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);
}