mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-28 04:36:07 +00:00
[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:
parent
0f29319e56
commit
4ef02da094
@ -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
|
||||
)
|
||||
|
@ -7,3 +7,9 @@ add_header(
|
||||
DEPENDS
|
||||
.linux.fcntl_macros
|
||||
)
|
||||
|
||||
add_header(
|
||||
stdio_macros
|
||||
HDR
|
||||
stdio-macros.h
|
||||
)
|
||||
|
16
libc/include/llvm-libc-macros/stdio-macros.h
Normal file
16
libc/include/llvm-libc-macros/stdio-macros.h
Normal 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
|
@ -10,6 +10,7 @@
|
||||
#define LLVM_LIBC_STDIO_H
|
||||
|
||||
#include <__llvm-libc-common.h>
|
||||
#include <llvm-libc-macros/stdio-macros.h>
|
||||
|
||||
%%public_api()
|
||||
|
||||
|
@ -53,5 +53,6 @@ add_header_library(
|
||||
integer_operations.h
|
||||
)
|
||||
|
||||
add_subdirectory(File)
|
||||
add_subdirectory(FPUtil)
|
||||
add_subdirectory(OSUtil)
|
||||
|
7
libc/src/__support/File/CMakeLists.txt
Normal file
7
libc/src/__support/File/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
add_object_library(
|
||||
file
|
||||
SRCS
|
||||
file.cpp
|
||||
HDRS
|
||||
file.h
|
||||
)
|
242
libc/src/__support/File/file.cpp
Normal file
242
libc/src/__support/File/file.cpp
Normal 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
|
193
libc/src/__support/File/file.h
Normal file
193
libc/src/__support/File/file.h
Normal 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
|
@ -50,4 +50,5 @@ add_custom_command(TARGET libc_str_to_float_comparison_test
|
||||
VERBATIM)
|
||||
|
||||
add_subdirectory(CPP)
|
||||
add_subdirectory(File)
|
||||
add_subdirectory(OSUtil)
|
||||
|
16
libc/test/src/__support/File/CMakeLists.txt
Normal file
16
libc/test/src/__support/File/CMakeLists.txt
Normal 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
|
||||
)
|
321
libc/test/src/__support/File/file_test.cpp
Normal file
321
libc/test/src/__support/File/file_test.cpp
Normal 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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user