Update Arlib

This commit is contained in:
Alcaro
2016-12-20 16:19:01 +01:00
parent dba9647f31
commit ea39e0a2d8
6 changed files with 250 additions and 207 deletions

View File

@@ -9,6 +9,8 @@
//WARNING: Arlib comes with zero stability guarantees. It can and will change in arbitrary ways, for any reason and at any time.
#pragma once
#include "global.h"
#include <utility> // std::move
#include "bml.h"
#include "containers.h"
#include "endian.h"

View File

@@ -202,6 +202,7 @@ public:
T& operator[](size_t n) { resize_grow(n+1); return this->items[n]; }
void resize(size_t len) { resize_to(len); }
void reserve(size_t len) { resize_grow(len); }
void append(const T& item) { size_t pos = this->count; resize_grow(pos+1); this->items[pos] = item; }
void reset() { resize_shrink(0); }

View File

@@ -1,55 +0,0 @@
#include "file.h"
#include "os.h"
namespace {
class file_mem : public filewrite {
public:
array<byte> datawr;
arrayview<byte> datard;
file_mem(arrayview<byte> data) : filewrite("", data.size()), datard(data) {}
file_mem(array<byte> data) : filewrite("", data.size()), datawr(data), datard(datawr) {}
size_t read(arrayvieww<byte> target, size_t start)
{
size_t bytes_dst = target.size();
size_t bytes_src = datard.size()-start;
size_t bytes = min(bytes_dst, bytes_src);
memcpy(target.ptr(), datard.ptr()+start, bytes);
return bytes;
}
arrayview<byte> mmap(size_t start, size_t len) { return datard.slice(start, len); }
void unmap(arrayview<byte> data) {}
bool resize(size_t newsize)
{
datawr.resize(newsize);
datard = datawr;
len = newsize;
return true;
}
bool write(arrayview<byte> data, size_t start)
{
size_t bytes_src = data.size();
size_t bytes_dst = datawr.size()-start;
size_t bytes = min(bytes_dst, bytes_src);
memcpy(datawr.ptr()+start, data.ptr(), bytes);
return true;
}
arrayvieww<byte> mmapw(size_t start, size_t len) { return datawr.slice(start, len); }
void unmapw(arrayvieww<byte> data) {}
};
}
file* file::create_mem_view(arrayview<byte> data)
{
return new file_mem(data);
}
filewrite* file::create_mem_copy(array<byte> data)
{
return new file_mem(data);
}

View File

@@ -20,79 +20,78 @@
#endif
//criteria:
//- no funny symbols
//- implausible name, nothing of value must be lost by deleting it
//- no funny symbols in the name
//- implausible name, nothing of value may be lost by deleting it
#define WRITABLE_FILE "arlib-selftest.txt"
test("file reading")
{
autoptr<file> f = file::open(READONLY_FILE);
assert(f);
assert(f->len);
assert(f->len > strlen(READONLY_FILE_HEAD));
assert(f->len >= 66000);
array<byte> bytes = f->read();
assert(bytes.size() == f->len);
file f;
assert(f.open(READONLY_FILE));
assert(f.size());
assert(f.size() > strlen(READONLY_FILE_HEAD));
assert(f.size() >= 66000);
array<byte> bytes = f.read();
assert(bytes.size() == f.size());
assert(!memcmp(bytes.ptr(), READONLY_FILE_HEAD, strlen(READONLY_FILE_HEAD)));
arrayview<byte> map = f->mmap();
arrayview<byte> map = f.mmap();
assert(map.ptr());
assert(map.size() == f->len);
assert(map.size() == f.size());
assert(!memcmp(bytes.ptr(), map.ptr(), bytes.size()));
arrayview<byte> map2 = f->mmap();
arrayview<byte> map2 = f.mmap();
assert(map2.ptr());
assert(map2.size() == f->len);
assert(map2.size() == f.size());
assert(!memcmp(bytes.ptr(), map2.ptr(), bytes.size()));
f->unmap(map2);
f.unmap(map2);
const size_t t_start[] = { 0, 65536, 4096, 1, 1, 1, 65537, 65535 };
const size_t t_len[] = { 66000, 400, 400, 65535, 65536, 65999, 400, 2 };
for (size_t i=0;i<ARRAY_SIZE(t_start);i++)
{
arrayview<byte> map3 = f->mmap(t_start[i], t_len[i]);
arrayview<byte> map3 = f.mmap(t_start[i], t_len[i]);
assert(map3.ptr());
assert(map3.size() == t_len[i]);
assert(!memcmp(bytes.ptr()+t_start[i], map3.ptr(), t_len[i]));
f->unmap(map3);
f.unmap(map3);
}
f->unmap(map);
f.unmap(map);
}
test("file writing")
{
autoptr<filewrite> f;
file f;
assert(!filewrite::open(READONLY_FILE, filewrite::m_default));
assert(!filewrite::open(READONLY_FILE, filewrite::m_existing));
assert(!filewrite::open(READONLY_FILE, filewrite::m_replace));
assert(!filewrite::open(READONLY_FILE, filewrite::m_create_excl));
assert(!f.open(READONLY_FILE, file::m_write));
assert(!f.open(READONLY_FILE, file::m_wr_existing));
assert(!f.open(READONLY_FILE, file::m_replace));
assert(!f.open(READONLY_FILE, file::m_create_excl));
assert(filewrite::unlink(WRITABLE_FILE));
assert(file::unlink(WRITABLE_FILE));
assert(!file::open(WRITABLE_FILE));
assert(!f.open(WRITABLE_FILE));
f = filewrite::open(WRITABLE_FILE);
assert(f);
assert(f->replace("foo"));
assert(f.open(WRITABLE_FILE, file::m_write));
assert(f.replace("foo"));
assert_eq(string(file::read(WRITABLE_FILE)), "foo");
f->resize(8);
assert(f->len == 8);
f.resize(8);
assert(f.size() == 8);
byte expected[8]={'f','o','o',0,0,0,0,0};
array<byte> actual = file::read(WRITABLE_FILE);
assert(actual.ptr());
assert(actual.size()==8);
assert(!memcmp(actual.ptr(), expected, 8));
arrayvieww<byte> map = f->mmapw();
arrayvieww<byte> map = f.mmapw();
assert(map.ptr());
assert_eq(map.size(), 8);
assert(!memcmp(map.ptr(), expected, 8));
map[3]='t';
f->unmapw(map);
f.unmapw(map);
expected[3] = 't';
actual = file::read(WRITABLE_FILE);
@@ -100,27 +99,56 @@ test("file writing")
assert(actual.size()==8);
assert(!memcmp(actual.ptr(), expected, 8));
f = NULL;
//test the various creation modes
//file exists, these three should work
f=NULL; assert( (f=filewrite::open(WRITABLE_FILE, filewrite::m_default)));
f=NULL; assert( (f=filewrite::open(WRITABLE_FILE, filewrite::m_existing)));
assert_eq(f->len, 8);
f=NULL; assert( (f=filewrite::open(WRITABLE_FILE, filewrite::m_replace)));
assert_eq(f->len, 0);
f=NULL; assert(!(f=filewrite::open(WRITABLE_FILE, filewrite::m_create_excl)));//but this shouldn't
assert( (f.open(WRITABLE_FILE, file::m_write)));
assert( (f.open(WRITABLE_FILE, file::m_wr_existing)));
assert_eq(f.size(), 8);
assert( (f.open(WRITABLE_FILE, file::m_replace)));
assert_eq(f.size(), 0);
assert(!(f.open(WRITABLE_FILE, file::m_create_excl)));//but this shouldn't
f=NULL;
assert(filewrite::unlink(WRITABLE_FILE));
assert(!filewrite::open(WRITABLE_FILE, filewrite::m_existing)); // this should fail
f=NULL; assert(f=filewrite::open(WRITABLE_FILE, filewrite::m_create_excl)); // this should create
assert(filewrite::unlink(WRITABLE_FILE));
assert(file::unlink(WRITABLE_FILE));
assert(!f.open(WRITABLE_FILE, file::m_wr_existing)); // this should fail
assert(f.open(WRITABLE_FILE, file::m_create_excl)); // this should create
assert(file::unlink(WRITABLE_FILE));
f=NULL; assert(f=filewrite::open(WRITABLE_FILE, filewrite::m_replace)); // replacing a nonexistent file is fine
//opening a nonexistent file with m_default is tested at the start of this function
f=NULL;
assert(filewrite::unlink(WRITABLE_FILE));
assert(filewrite::unlink(WRITABLE_FILE)); // ensure it properly deals with unlinking a nonexistent file
assert(f.open(WRITABLE_FILE, file::m_replace)); // replacing a nonexistent file is fine
//opening a nonexistent file with m_write is tested at the start of this function
f.close();
assert(file::unlink(WRITABLE_FILE));
assert(file::unlink(WRITABLE_FILE)); // ensure it properly deals with unlinking a nonexistent file
}
test("in-memory files")
{
array<byte> bytes;
for (int i=0;i<8;i++) bytes[i]=i;
array<byte> bytes2;
bytes2.resize(4);
file f = file::mem(bytes.slice(0, 8));
assert(f);
assert_eq(f.size(), 8);
assert(f.read(bytes2, 1));
for (int i=0;i<4;i++) assert_eq(bytes2[i], i+1);
//readonly
assert(!f.write(bytes2, 6));
assert(!f.replace(bytes2));
assert(!f.mmapw());
f = file::mem(bytes);
assert(f);
assert_eq(f.size(), 8);
assert(f.write(bytes2, 6));
assert_eq(f.size(), 10);
for (int i=0;i<6;i++) assert_eq(bytes[i], i);
for (int i=0;i<4;i++) assert_eq(bytes[i+6], i+1);
assert(f.replace(bytes2));
for (int i=0;i<4;i++) assert_eq(bytes[i], i+1);
assert_eq(f.size(), 4);
}
#endif

View File

@@ -129,13 +129,19 @@
static long pagesize;
namespace {
class file_fs : public filewrite {
class file_unix : public file::impl {
public:
int fd;
file_fs(cstring filename, int fd) : filewrite(filename), fd(fd)
file_unix(int fd) : fd(fd) {}
size_t size()
{
len = lseek(fd, 0, SEEK_END);
return lseek(fd, 0, SEEK_END);
}
bool resize(size_t newsize)
{
return (ftruncate(this->fd, newsize)==0);
}
size_t read(arrayvieww<byte> target, size_t start)
@@ -144,14 +150,6 @@ namespace {
if (ret<0) return 0;
else return ret;
}
bool resize(size_t newsize)
{
bool ret = (ftruncate(this->fd, newsize)==0);
len = lseek(fd, 0, SEEK_END);
return ret;
}
bool write(arrayview<byte> data, size_t start)
{
size_t ret = pwrite(fd, data.ptr(), data.size(), start);
@@ -159,10 +157,10 @@ namespace {
else return ret;
}
/*private*/ arrayvieww<byte> mmap(bool write, size_t start, size_t len)
/*private*/ arrayvieww<byte> mmap(bool writable, size_t start, size_t len)
{
size_t offset = start % pagesize;
void* data=::mmap(NULL, len+offset, write ? PROT_WRITE|PROT_READ : PROT_READ, MAP_SHARED, this->fd, start-offset);
void* data=::mmap(NULL, len+offset, writable ? PROT_WRITE|PROT_READ : PROT_READ, MAP_SHARED, this->fd, start-offset);
if (data==MAP_FAILED) return NULL;
return arrayvieww<byte>((uint8_t*)data+offset, len);
}
@@ -177,43 +175,31 @@ namespace {
arrayvieww<byte> mmapw(size_t start, size_t len) { return mmap(true, start, len); }
void unmapw(arrayvieww<byte> data) { unmap(data); }
~file_fs() { close(fd); }
~file_unix() { close(fd); }
};
}
file* file::open_fs(cstring filename)
file::impl* file::open_impl_fs(cstring filename, mode m)
{
int fd = ::open(filename, O_RDONLY);
if (fd<0) return NULL;
return new file_fs(filename, fd);
}
filewrite* filewrite::open_fs(cstring filename, mode m)
{
int flags[] = { O_RDWR|O_CREAT, O_RDWR, O_RDWR|O_CREAT|O_TRUNC, O_RDWR|O_CREAT|O_EXCL };
int flags[] = { O_RDONLY, O_RDWR|O_CREAT, O_RDWR, O_RDWR|O_CREAT|O_TRUNC, O_RDWR|O_CREAT|O_EXCL };
int fd = ::open(filename, flags[m], 0666);
if (fd<0) return NULL;
return new file_fs(filename, fd);
return new file_unix(fd);
}
bool filewrite::unlink_fs(cstring filename)
bool file::unlink_fs(cstring filename)
{
int ret = ::unlink(filename);
return ret==0 || (ret==-1 && errno==ENOENT);
}
//#ifdef ARGUI_NONE
file* file::open(cstring filename)
file::impl* file::open_impl(cstring filename, mode m)
{
return open_fs(filename);
return open_impl_fs(filename, m);
}
filewrite* filewrite::open(cstring filename, mode m)
{
return open_fs(filename, m);
}
bool filewrite::unlink(cstring filename)
bool file::unlink(cstring filename)
{
return unlink_fs(filename);
}

View File

@@ -1,106 +1,187 @@
#pragma once
#include "global.h"
#include "string.h"
#include "array.h"
class filewrite;
class file : nocopy {
file(){}
protected:
file(cstring filename) : path(filename) {}
file(cstring filename, size_t len) : path(filename), len(len) {}
public:
class impl : nocopy {
friend class file;
protected:
virtual size_t size() = 0;
virtual bool resize(size_t newsize) = 0;
virtual size_t read(arrayvieww<byte> target, size_t start) = 0;
virtual bool write(arrayview<byte> data, size_t start = 0) = 0;
virtual bool replace(arrayview<byte> data) { return resize(data.size()) && write(data); }
virtual arrayview<byte> mmap(size_t start, size_t len) = 0;
virtual void unmap(arrayview<byte> data) = 0;
virtual arrayvieww<byte> mmapw(size_t start, size_t len) = 0;
virtual void unmapw(arrayvieww<byte> data) = 0;
virtual ~impl() {}
};
class implrd : impl {
friend class file;
protected:
virtual size_t size() = 0;
bool resize(size_t newsize) { return false; }
virtual size_t read(arrayvieww<byte> target, size_t start) = 0;
bool write(arrayview<byte> data, size_t start = 0) { return false; }
bool replace(arrayview<byte> data) { return false; }
virtual arrayview<byte> mmap(size_t start, size_t len) = 0;
virtual void unmap(arrayview<byte> data) = 0;
virtual arrayvieww<byte> mmapw(size_t start, size_t len) { return NULL; }
virtual void unmapw(arrayvieww<byte> data) {}
};
private:
impl* core;
public:
enum mode {
m_read,
m_write, // If the file exists, opens it. If it doesn't, creates a new file.
m_wr_existing, // Fails if the file doesn't exist.
m_replace, // If the file exists, it's either deleted and recreated, or truncated.
m_create_excl, // Fails if the file does exist.
};
file() : core(NULL) {}
file(file&& f) { core=f.core; f.core=NULL; }
file& operator=(file&& f) { delete core; core=f.core; f.core=NULL; return *this; }
file(impl* core) : core(core) {}
file(cstring filename, mode m = m_read) : core(NULL) { open(filename, m); }
bool open(cstring filename, mode m = m_read)
{
delete core;
core = open_impl(filename, m);
return core;
}
void close()
{
delete core;
core = NULL;
}
private:
//This one will create the file from the filesystem.
//create() can simply return create_fs(filename), or can additionally support stuff like gvfs.
static file* open_fs(cstring filename);
class mem;
static impl* open_impl_fs(cstring filename, mode m);
//A path refers to a directory if it ends with a slash, and file otherwise. Directories may not be open()ed.
static impl* open_impl(cstring filename, mode m);
public:
//While this object may look thread-safe, it isn't. One thread at the time only.
//Interacting with mmap() results doesn't count as interaction.
//A path refers to a directory if it ends with a slash, and file otherwise. Directories may not be open()ed, though listdir() is valid.
static file* open(cstring filename);
//Returns all items in the given directory path, as absolute paths.
static array<string> listdir(cstring path);
//If the input path is a directory, the basename is the last component after the final slash.
static string dirname(cstring path);
static string basename(cstring path);
//Changing these two is undefined behavior. Only the implementation may do that.
string path;
size_t len;
operator bool() const { return core; }
//Reading outside the file will return partial results.
virtual size_t read(arrayvieww<byte> target, size_t start) = 0;
array<byte> read()
size_t size() const { return core->size(); }
size_t read(arrayvieww<byte> target, size_t start) const { return core->read(target, start); }
array<byte> read() const
{
array<byte> ret;
ret.resize(this->len);
ret.resize(this->size());
size_t actual = this->read(ret, 0);
ret.resize(actual);
return ret;
}
static array<byte> read(cstring path)
{
autoptr<file> f = file::open(path);
if (f) return f->read();
file f(path);
if (f) return f.read();
else return NULL;
}
// May only be used if there are no mappings alive, not even read-only.
bool resize(size_t newsize) { return core->resize(newsize); }
//Writes outside the file will extend it. If the write starts after the current size, it's zero extended. Includes mmapw.
bool write(arrayview<byte> data, size_t start = 0) { return core->write(data, start); }
bool replace(arrayview<byte> data) { return core->replace(data); }
bool replace(cstring data) { return replace(data.bytes()); }
bool write(cstring data) { return write(data.bytes()); }
//Mappings must be deallocated before deleting the file object.
//If the underlying file is changed, it's undefined whether the mappings update. To force an update, delete and recreate the mapping.
//Mapping outside the file is undefined behavior.
virtual arrayview<byte> mmap(size_t start, size_t len) = 0;
arrayview<byte> mmap() { return this->mmap(0, this->len); }
virtual void unmap(arrayview<byte> data) = 0;
arrayview<byte> mmap(size_t start, size_t len) const { return core->mmap(start, len); }
arrayview<byte> mmap() const { return this->mmap(0, this->size()); }
void unmap(arrayview<byte> data) const { return core->unmap(data); }
virtual ~file() {}
arrayvieww<byte> mmapw(size_t start, size_t len) { return core->mmapw(start, len); }
arrayvieww<byte> mmapw() { return this->mmapw(0, this->size()); }
void unmapw(arrayvieww<byte> data) { return core->unmapw(data); }
//Mostly usable for debug purposes.
static file* create_mem_view(arrayview<byte> data);
static filewrite* create_mem_copy(array<byte> data);
};
class filewrite : public file {
protected:
filewrite(cstring filename) : file(filename) {}
filewrite(cstring filename, size_t len) : file(filename, len) {}
~file() { delete core; }
public:
enum mode {
m_default, // If the file exists, opens it. If it doesn't, creates a new file. (O_CREAT) (OPEN_ALWAYS)
m_existing, // Fails if the file doesn't exist. (0) (OPEN_EXISTING)
m_replace, // If the file exists, it's either deleted and recreated, or truncated. (O_CREAT|O_TRUNC) (CREATE_ALWAYS)
m_create_excl, // Fails if the file does exist. (O_CREAT|O_EXCL) (CREATE_NEW)
};
protected:
//These refer to the physical file system. The public versions can forward to these, or can additionally support stuff like gvfs.
static filewrite* open_fs(cstring filename, mode m = m_default);
static bool unlink_fs(cstring filename);
public:
static filewrite* open(cstring filename, mode m = m_default);
static bool unlink(cstring filename); // Returns whether the file is now gone. If the file didn't exist, returns true.
virtual bool resize(size_t newsize) = 0; // May only be used if there are no mappings alive, not even read-only.
//Writes outside the file will extend it. If the write starts after the current size, it's zero extended. Includes mmapw.
virtual bool write(arrayview<byte> data, size_t start = 0) = 0;
virtual bool replace(arrayview<byte> data) { return resize(data.size()) && write(data); }
bool replace(cstring data) { return replace(data.bytes()); }
bool write(cstring data) { return write(data.bytes()); }
static bool write(cstring path, arrayview<byte> data)
static file mem(arrayview<byte> data)
{
autoptr<filewrite> f = filewrite::open(path, m_replace);
return f->write(data);
return file(new file::memimpl(data));
}
static file mem(array<byte>& data)
{
return file(new file::memimpl(&data));
}
private:
class memimpl : public file::implrd {
public:
arrayview<byte> datard;
array<byte>* datawr; // this object does not own the array
memimpl(arrayview<byte> data) : datard(data), datawr(NULL) {}
memimpl(array<byte>* data) : datard(*data), datawr(data) {}
size_t size() { return datard.size(); }
bool resize(size_t newsize)
{
if (!datawr) return false;
datawr->resize(newsize);
datard=*datawr;
return true;
}
size_t read(arrayvieww<byte> target, size_t start)
{
size_t nbyte = min(target.size(), datard.size()-start);
memcpy(target.ptr(), datard.slice(start, nbyte).ptr(), nbyte);
return nbyte;
}
virtual bool write(arrayview<byte> newdata, size_t start = 0)
{
if (!datawr) return false;
size_t nbyte = newdata.size();
datawr->reserve(start+nbyte);
memcpy(datawr->slice(start, nbyte).ptr(), newdata.ptr(), nbyte);
datard=*datawr;
return true;
}
virtual bool replace(arrayview<byte> newdata)
{
if (!datawr) return false;
*datawr = newdata;
datard = *datawr;
return true;
}
arrayview<byte> mmap(size_t start, size_t len) { return datard.slice(start, len); }
arrayvieww<byte> mmapw(size_t start, size_t len) { if (!datawr) return NULL; return datawr->slice(start, len); }
void unmap(arrayview<byte> data) {}
void unmapw(arrayvieww<byte> data) {}
};
public:
//The only allowed method on a file object that has an existing writable mapping is unmapw.
//Fails if it goes outside the file; use resize().
virtual arrayvieww<byte> mmapw(size_t start, size_t len) = 0;
arrayvieww<byte> mmapw() { return this->mmapw(0, this->len); }
virtual void unmapw(arrayvieww<byte> data) = 0;
//Returns all items in the given directory path, as absolute paths.
static array<string> listdir(cstring path);
static bool unlink(cstring filename); // Returns whether the file is now gone. If the file didn't exist, returns true.
//If the input path is a directory, the basename is blank.
static string dirname(cstring path);
static string basename(cstring path);
private:
static bool unlink_fs(cstring filename);
};
void _window_init_file();