Replace LittleFS with MockFS
This commit is contained in:
@@ -0,0 +1,928 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mockfs.h"
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
int len;
|
||||
} Slice;
|
||||
|
||||
#define S(X) (Slice) { (X), (int) sizeof(X)-1 }
|
||||
|
||||
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||
|
||||
#define MOCKFS_COMP_LIMIT 32
|
||||
|
||||
struct MockFS_Entity {
|
||||
MockFS_Entity *parent;
|
||||
MockFS_Entity *prev;
|
||||
MockFS_Entity *next;
|
||||
char name[MOCKFS_NAME_SIZE];
|
||||
int name_len;
|
||||
bool is_dir;
|
||||
int refcount; // Number of open file handles pointing to this entity
|
||||
union {
|
||||
struct {
|
||||
MockFS_Entity *head_child;
|
||||
MockFS_Entity *tail_child;
|
||||
};
|
||||
ByteBuffer byte_buffer;
|
||||
};
|
||||
};
|
||||
|
||||
static bool slice_eq(Slice s1, Slice s2)
|
||||
{
|
||||
if (s1.len != s2.len)
|
||||
return false;
|
||||
return !memcmp(s1.ptr, s2.ptr, s1.len);
|
||||
}
|
||||
|
||||
static void *alloc(MockFS *mfs, int len, int align)
|
||||
{
|
||||
int pad = -(unsigned long long) (mfs->mem + mfs->off) & (align - 1);
|
||||
if (mfs->len - mfs->off < pad + len)
|
||||
return NULL;
|
||||
void *p = mfs->mem + mfs->off + pad;
|
||||
mfs->off += pad + len;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void byte_buffer_init(ByteBuffer *byte_buffer)
|
||||
{
|
||||
byte_buffer->used = 0;
|
||||
byte_buffer->tail_used = 0;
|
||||
byte_buffer->head = NULL;
|
||||
byte_buffer->tail = NULL;
|
||||
}
|
||||
|
||||
static void byte_buffer_free(ByteBuffer *byte_buffer, ByteChunk **free_list)
|
||||
{
|
||||
if (byte_buffer->head) {
|
||||
byte_buffer->tail->next = *free_list;
|
||||
*free_list = byte_buffer->head;
|
||||
}
|
||||
}
|
||||
|
||||
static int convert_offset(ByteBuffer *byte_buffer, int off, ByteChunk **pchunk, int *poffset)
|
||||
{
|
||||
assert(off > -1);
|
||||
|
||||
int skipped = 0;
|
||||
ByteChunk *chunk = byte_buffer->head;
|
||||
while (chunk) {
|
||||
int chunk_used = (chunk->next ? BYTE_CHUNK_SIZE : byte_buffer->tail_used);
|
||||
if (off >= skipped && off < skipped + chunk_used) {
|
||||
*pchunk = chunk;
|
||||
*poffset = off - skipped;
|
||||
return 1;
|
||||
}
|
||||
chunk = chunk->next;
|
||||
skipped += chunk_used;
|
||||
}
|
||||
|
||||
if (off == skipped) {
|
||||
*pchunk = byte_buffer->tail;
|
||||
*poffset = byte_buffer->tail ? byte_buffer->tail_used : 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int byte_buffer_read(ByteBuffer *byte_buffer, int off, char *dst, int cap)
|
||||
{
|
||||
int rel_off;
|
||||
ByteChunk *chunk;
|
||||
convert_offset(byte_buffer, off, &chunk, &rel_off);
|
||||
|
||||
int copied = 0;
|
||||
while (copied < cap) {
|
||||
if (chunk == byte_buffer->tail) {
|
||||
if (rel_off == byte_buffer->tail_used)
|
||||
break;
|
||||
int cpy = MIN(byte_buffer->tail_used - rel_off, cap - copied);
|
||||
memcpy(dst + copied, chunk->data + rel_off, cpy);
|
||||
copied += cpy;
|
||||
break; // No more chunks after tail
|
||||
} else {
|
||||
int cpy = MIN(BYTE_CHUNK_SIZE - rel_off, cap - copied);
|
||||
memcpy(dst + copied, chunk->data + rel_off, cpy);
|
||||
copied += cpy;
|
||||
chunk = chunk->next;
|
||||
rel_off = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
// Calculate total used bytes in the buffer
|
||||
static int byte_buffer_size(ByteBuffer *byte_buffer)
|
||||
{
|
||||
int size = 0;
|
||||
ByteChunk *chunk = byte_buffer->head;
|
||||
while (chunk) {
|
||||
if (chunk->next) {
|
||||
size += BYTE_CHUNK_SIZE;
|
||||
} else {
|
||||
size += byte_buffer->tail_used;
|
||||
}
|
||||
chunk = chunk->next;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
// Extend buffer to given size by filling with zeros
|
||||
static int byte_buffer_extend(ByteBuffer *byte_buffer, int target_size, MockFS *mfs)
|
||||
{
|
||||
int current_size = byte_buffer_size(byte_buffer);
|
||||
int to_write = target_size - current_size;
|
||||
|
||||
while (to_write > 0) {
|
||||
// Get or create tail chunk
|
||||
if (byte_buffer->tail == NULL || byte_buffer->tail_used == BYTE_CHUNK_SIZE) {
|
||||
ByteChunk *tmp = mfs->chunk_free_list;
|
||||
if (tmp == NULL) {
|
||||
tmp = alloc(mfs, sizeof(ByteChunk), _Alignof(ByteChunk));
|
||||
if (tmp == NULL)
|
||||
return MOCKFS_ERRNO_NOSPC;
|
||||
} else {
|
||||
mfs->chunk_free_list = tmp->next;
|
||||
}
|
||||
tmp->next = NULL;
|
||||
|
||||
if (byte_buffer->head == NULL) {
|
||||
byte_buffer->head = tmp;
|
||||
} else {
|
||||
byte_buffer->tail->next = tmp;
|
||||
}
|
||||
byte_buffer->tail = tmp;
|
||||
byte_buffer->tail_used = 0;
|
||||
}
|
||||
|
||||
// Fill remaining space in tail chunk with zeros
|
||||
int space = BYTE_CHUNK_SIZE - byte_buffer->tail_used;
|
||||
int fill = MIN(space, to_write);
|
||||
memset(byte_buffer->tail->data + byte_buffer->tail_used, 0, fill);
|
||||
byte_buffer->tail_used += fill;
|
||||
to_write -= fill;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int byte_buffer_write(ByteBuffer *byte_buffer, int off, char *src, int len, MockFS *mfs)
|
||||
{
|
||||
int rel_off;
|
||||
ByteChunk *chunk;
|
||||
if (!convert_offset(byte_buffer, off, &chunk, &rel_off)) {
|
||||
// Offset is beyond end of buffer - extend with zeros
|
||||
int ret = byte_buffer_extend(byte_buffer, off, mfs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
convert_offset(byte_buffer, off, &chunk, &rel_off);
|
||||
}
|
||||
|
||||
int copied = 0;
|
||||
while (copied < len) {
|
||||
|
||||
if (chunk == byte_buffer->tail) {
|
||||
if (chunk == NULL || rel_off == BYTE_CHUNK_SIZE) {
|
||||
|
||||
ByteChunk *tmp = mfs->chunk_free_list;
|
||||
if (tmp == NULL) {
|
||||
tmp = alloc(mfs, sizeof(ByteChunk), _Alignof(ByteChunk));
|
||||
if (tmp == NULL)
|
||||
return MOCKFS_ERRNO_NOSPC;
|
||||
} else {
|
||||
mfs->chunk_free_list = tmp->next;
|
||||
}
|
||||
tmp->next = NULL;
|
||||
|
||||
if (byte_buffer->head == NULL) {
|
||||
byte_buffer->head = tmp;
|
||||
} else {
|
||||
byte_buffer->tail->next = tmp;
|
||||
}
|
||||
byte_buffer->tail = tmp;
|
||||
byte_buffer->tail_used = 0;
|
||||
|
||||
rel_off = 0;
|
||||
|
||||
int cpy = MIN(BYTE_CHUNK_SIZE, len - copied);
|
||||
assert(cpy > 0);
|
||||
|
||||
memcpy(tmp->data, src + copied, cpy);
|
||||
copied += cpy;
|
||||
chunk = tmp;
|
||||
rel_off += cpy;
|
||||
|
||||
byte_buffer->tail_used = cpy;
|
||||
|
||||
} else if (rel_off == byte_buffer->tail_used) {
|
||||
|
||||
assert(chunk);
|
||||
|
||||
int cpy = MIN(BYTE_CHUNK_SIZE - byte_buffer->tail_used, len - copied);
|
||||
assert(cpy > 0);
|
||||
|
||||
memcpy(chunk->data + byte_buffer->tail_used, src + copied, cpy);
|
||||
|
||||
copied += cpy;
|
||||
rel_off += cpy;
|
||||
byte_buffer->tail_used += cpy;
|
||||
|
||||
} else {
|
||||
|
||||
assert(rel_off < byte_buffer->tail_used);
|
||||
|
||||
assert(chunk);
|
||||
|
||||
int cpy = MIN(byte_buffer->tail_used - rel_off, len - copied);
|
||||
assert(cpy > 0);
|
||||
|
||||
memcpy(chunk->data + rel_off, src + copied, cpy);
|
||||
copied += cpy;
|
||||
rel_off += cpy;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
assert(chunk);
|
||||
|
||||
int cpy = MIN(BYTE_CHUNK_SIZE - rel_off, len - copied);
|
||||
assert(cpy > 0);
|
||||
|
||||
memcpy(chunk->data + rel_off, src + copied, cpy);
|
||||
copied += cpy;
|
||||
chunk = chunk->next;
|
||||
rel_off = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_path(char *src, int len, Slice *buf, int cap)
|
||||
{
|
||||
int cur = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (len > 0 && src[0] == '/')
|
||||
cur++;
|
||||
|
||||
for (;;) {
|
||||
|
||||
int off = cur;
|
||||
while (cur < len && src[cur] != '/')
|
||||
cur++;
|
||||
Slice s = { src + off, cur - off };
|
||||
|
||||
if (s.len > 0) {
|
||||
if (ret == cap)
|
||||
return -1; // TODO: proper error code
|
||||
buf[ret++] = s;
|
||||
}
|
||||
|
||||
if (cur == len)
|
||||
break;
|
||||
assert(src[cur] == '/');
|
||||
cur++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int resolve_path(MockFS *mfs, Slice *comps, int num_comps,
|
||||
MockFS_Entity **stack, int cap)
|
||||
{
|
||||
int ret = 0;
|
||||
stack[ret++] = mfs->root;
|
||||
|
||||
for (int i = 0; i < num_comps; i++) {
|
||||
|
||||
if (!stack[ret-1]->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
|
||||
if (slice_eq(comps[i], S(".."))) {
|
||||
ret--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (slice_eq(comps[i], S(".")))
|
||||
continue;
|
||||
|
||||
MockFS_Entity *child = stack[ret-1]->head_child;
|
||||
while (child) {
|
||||
if (slice_eq(comps[i], (Slice) { child->name, child->name_len }))
|
||||
break;
|
||||
child = child->next;
|
||||
}
|
||||
|
||||
if (child == NULL)
|
||||
return MOCKFS_ERRNO_NOENT;
|
||||
|
||||
if (ret == cap)
|
||||
return -1; // TODO: return proper error code
|
||||
stack[ret++] = child;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int entity_init(MockFS_Entity *entity, Slice name, bool is_dir)
|
||||
{
|
||||
entity->parent = NULL;
|
||||
entity->prev = NULL;
|
||||
entity->next = NULL;
|
||||
entity->is_dir = is_dir;
|
||||
entity->refcount = 0;
|
||||
entity->head_child = NULL;
|
||||
entity->tail_child = NULL;
|
||||
|
||||
if (name.len > (int) sizeof(entity->name))
|
||||
return -1; // TODO: proper error code
|
||||
memcpy(entity->name, name.ptr, name.len);
|
||||
entity->name_len = name.len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mockfs_init(MockFS **pmfs, char *mem, int len)
|
||||
{
|
||||
int off = -(unsigned long long) mem & (_Alignof(MockFS)-1);
|
||||
if (off + sizeof(MockFS) > (unsigned long long)len)
|
||||
return MOCKFS_ERRNO_NOMEM;
|
||||
MockFS *mfs = (MockFS *)(mem + off);
|
||||
|
||||
mfs->mem = mem;
|
||||
mfs->len = len;
|
||||
mfs->off = off + sizeof(MockFS);
|
||||
mfs->root = NULL;
|
||||
mfs->entity_free_list = NULL;
|
||||
mfs->chunk_free_list = NULL;
|
||||
|
||||
MockFS_Entity *entity = alloc(mfs, sizeof(MockFS_Entity), _Alignof(MockFS_Entity));
|
||||
if (entity == NULL)
|
||||
return MOCKFS_ERRNO_NOMEM;
|
||||
entity_init(entity, (Slice) { "", 0 }, true);
|
||||
mfs->root = entity;
|
||||
|
||||
*pmfs = mfs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mockfs_free(MockFS *mfs)
|
||||
{
|
||||
(void) mfs;
|
||||
}
|
||||
|
||||
static bool find_child(MockFS_Entity *entity, Slice child_name)
|
||||
{
|
||||
assert(entity->is_dir);
|
||||
MockFS_Entity *child = entity->head_child;
|
||||
while (child) {
|
||||
if (slice_eq(child_name, (Slice) { child->name, child->name_len }))
|
||||
return true;
|
||||
child = child->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int create_entity(MockFS *mfs, MockFS_Entity *parent, Slice name, bool is_dir)
|
||||
{
|
||||
MockFS_Entity *entity = mfs->entity_free_list;
|
||||
if (entity == NULL) {
|
||||
entity = alloc(mfs, sizeof(MockFS_Entity), _Alignof(MockFS_Entity));
|
||||
if (entity == NULL)
|
||||
return MOCKFS_ERRNO_NOMEM;
|
||||
} else {
|
||||
mfs->entity_free_list = entity->next;
|
||||
}
|
||||
entity->next = NULL;
|
||||
|
||||
int ret = entity_init(entity, name, is_dir);
|
||||
if (ret < 0) {
|
||||
entity->next = mfs->entity_free_list;
|
||||
mfs->entity_free_list = entity;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Initialize file-specific fields
|
||||
byte_buffer_init(&entity->byte_buffer);
|
||||
|
||||
// Link to parent directory
|
||||
entity->parent = parent;
|
||||
entity->next = NULL;
|
||||
entity->prev = parent->tail_child;
|
||||
|
||||
if (parent->tail_child) {
|
||||
parent->tail_child->next = entity;
|
||||
} else {
|
||||
parent->head_child = entity;
|
||||
}
|
||||
parent->tail_child = entity;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mockfs_open(MockFS *mfs, char *path, int path_len, int flags, MockFS_OpenFile *open_file)
|
||||
{
|
||||
Slice comps[MOCKFS_COMP_LIMIT];
|
||||
int ret = parse_path(path, path_len, comps, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
int num_comps = ret;
|
||||
|
||||
MockFS_Entity *stack[MOCKFS_COMP_LIMIT];
|
||||
bool file_was_created = false;
|
||||
ret = resolve_path(mfs, comps, num_comps, stack, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0) {
|
||||
// If file doesn't exist AND O_CREAT is specified, try to create it
|
||||
if (ret == MOCKFS_ERRNO_NOENT && (flags & MOCKFS_O_CREAT)) {
|
||||
// Resolve parent directory
|
||||
ret = resolve_path(mfs, comps, num_comps-1, stack, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
assert(ret > 0);
|
||||
MockFS_Entity *parent = stack[ret-1];
|
||||
|
||||
// Path ending with '/' implies directory, but you can't create
|
||||
// a directory with open() - that's what mkdir() is for
|
||||
if (path_len > 1 && path[path_len-1] == '/') {
|
||||
return MOCKFS_ERRNO_ISDIR;
|
||||
}
|
||||
|
||||
// Create the file
|
||||
ret = create_entity(mfs, parent, comps[num_comps-1], false);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
file_was_created = true;
|
||||
|
||||
// Retry full path resolution (now should succeed)
|
||||
ret = resolve_path(mfs, comps, num_comps, stack, MOCKFS_COMP_LIMIT);
|
||||
assert(ret > 0);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
assert(ret > 0);
|
||||
|
||||
// Check for trailing slash (but "/" alone is not a "trailing slash" case)
|
||||
bool has_trailing_slash = (path_len > 1 && path[path_len-1] == '/');
|
||||
|
||||
if ((flags & MOCKFS_O_CREAT) && has_trailing_slash)
|
||||
return MOCKFS_ERRNO_ISDIR;
|
||||
|
||||
// O_EXCL check: if file already existed and O_EXCL is set, fail
|
||||
if ((flags & MOCKFS_O_EXCL) && (flags & MOCKFS_O_CREAT) && !file_was_created)
|
||||
return MOCKFS_ERRNO_EXIST;
|
||||
|
||||
if (stack[ret-1]->is_dir)
|
||||
return MOCKFS_ERRNO_ISDIR;
|
||||
|
||||
if (has_trailing_slash) {
|
||||
// If target is not a directory, return ENOTDIR
|
||||
if (!stack[ret-1]->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
}
|
||||
|
||||
// O_TRUNC: truncate file to zero length if opened for writing
|
||||
if ((flags & MOCKFS_O_TRUNC) && (flags & (MOCKFS_O_WRONLY | MOCKFS_O_RDWR))) {
|
||||
byte_buffer_free(&stack[ret-1]->byte_buffer, &mfs->chunk_free_list);
|
||||
byte_buffer_init(&stack[ret-1]->byte_buffer);
|
||||
}
|
||||
|
||||
open_file->mfs = mfs;
|
||||
open_file->entity = stack[ret-1];
|
||||
open_file->offset = 0;
|
||||
open_file->flags = flags;
|
||||
open_file->entity->refcount++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mockfs_open_dir(MockFS *mfs, char *path, int path_len, MockFS_OpenDir *open_dir)
|
||||
{
|
||||
Slice comps[MOCKFS_COMP_LIMIT];
|
||||
int ret = parse_path(path, path_len, comps, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
int num_comps = ret;
|
||||
|
||||
MockFS_Entity *stack[MOCKFS_COMP_LIMIT];
|
||||
ret = resolve_path(mfs, comps, num_comps, stack, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
assert(ret > 0);
|
||||
|
||||
if (!stack[ret-1]->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
|
||||
open_dir->mfs = mfs;
|
||||
open_dir->entity = stack[ret-1];
|
||||
open_dir->child = stack[ret-1]->head_child;
|
||||
open_dir->idx = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mockfs_file_size(MockFS_OpenFile *open_file)
|
||||
{
|
||||
return byte_buffer_size(&open_file->entity->byte_buffer);
|
||||
}
|
||||
|
||||
void mockfs_close_file(MockFS_OpenFile *open_file)
|
||||
{
|
||||
MockFS_Entity *entity = open_file->entity;
|
||||
entity->refcount--;
|
||||
|
||||
// If refcount drops to 0 and entity was unlinked (removed while open),
|
||||
// now we can actually free it
|
||||
if (entity->refcount == 0 && entity->parent == NULL) {
|
||||
// Free the byte buffer chunks
|
||||
byte_buffer_free(&entity->byte_buffer, &open_file->mfs->chunk_free_list);
|
||||
|
||||
// Add entity to free list
|
||||
entity->next = open_file->mfs->entity_free_list;
|
||||
open_file->mfs->entity_free_list = entity;
|
||||
}
|
||||
}
|
||||
|
||||
void mockfs_close_dir(MockFS_OpenDir *open_dir)
|
||||
{
|
||||
(void) open_dir;
|
||||
}
|
||||
|
||||
int mockfs_read(MockFS_OpenFile *open_file, char *dst, int len)
|
||||
{
|
||||
if (open_file->flags & MOCKFS_O_WRONLY) {
|
||||
return MOCKFS_ERRNO_BADF;
|
||||
}
|
||||
|
||||
int copied = byte_buffer_read(&open_file->entity->byte_buffer, open_file->offset, dst, len);
|
||||
open_file->offset += copied;
|
||||
return copied;
|
||||
}
|
||||
|
||||
int mockfs_write(MockFS_OpenFile *open_file, char *src, int len)
|
||||
{
|
||||
if (!(open_file->flags & (MOCKFS_O_WRONLY | MOCKFS_O_RDWR))) {
|
||||
return MOCKFS_ERRNO_BADF;
|
||||
}
|
||||
|
||||
if ((open_file->flags & MOCKFS_O_WRONLY) && (open_file->flags & MOCKFS_O_RDWR)) {
|
||||
return MOCKFS_ERRNO_BADF;
|
||||
}
|
||||
|
||||
// If O_APPEND is set, seek to end before writing
|
||||
if (open_file->flags & MOCKFS_O_APPEND) {
|
||||
open_file->offset = byte_buffer_size(&open_file->entity->byte_buffer);
|
||||
}
|
||||
|
||||
int ret = byte_buffer_write(&open_file->entity->byte_buffer, open_file->offset, src, len, open_file->mfs);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
open_file->offset += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
int mockfs_read_dir(MockFS_OpenDir *open_dir, MockFS_Dirent *dirent)
|
||||
{
|
||||
if (open_dir->idx == 0) {
|
||||
if (sizeof(dirent->name) < 1)
|
||||
return -1; // TODO: proper error code
|
||||
dirent->name[0] = '.';
|
||||
dirent->name_len = 1;
|
||||
dirent->is_dir = true;
|
||||
open_dir->idx++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (open_dir->idx == 1) {
|
||||
if (sizeof(dirent->name) < 2)
|
||||
return -1; // TODO: proper error code
|
||||
dirent->name[0] = '.';
|
||||
dirent->name[1] = '.';
|
||||
dirent->name_len = 2;
|
||||
dirent->is_dir = true;
|
||||
open_dir->idx++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (open_dir->child == NULL)
|
||||
return MOCKFS_ERRNO_NOENT;
|
||||
|
||||
memcpy(dirent->name, open_dir->child->name, open_dir->child->name_len);
|
||||
dirent->name_len = open_dir->child->name_len;
|
||||
dirent->is_dir = open_dir->child->is_dir;
|
||||
|
||||
open_dir->child = open_dir->child->next;
|
||||
open_dir->idx++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mockfs_sync(MockFS_OpenFile *open_file)
|
||||
{
|
||||
// TODO
|
||||
(void) open_file;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mockfs_lseek(MockFS_OpenFile *open_file, int offset, int whence)
|
||||
{
|
||||
int new_offset;
|
||||
|
||||
switch (whence) {
|
||||
case MOCKFS_SEEK_SET:
|
||||
new_offset = offset;
|
||||
break;
|
||||
case MOCKFS_SEEK_CUR:
|
||||
new_offset = open_file->offset + offset;
|
||||
break;
|
||||
case MOCKFS_SEEK_END:
|
||||
new_offset = byte_buffer_size(&open_file->entity->byte_buffer) + offset;
|
||||
break;
|
||||
default:
|
||||
return MOCKFS_ERRNO_INVAL;
|
||||
}
|
||||
|
||||
if (new_offset < 0)
|
||||
return MOCKFS_ERRNO_INVAL;
|
||||
|
||||
open_file->offset = new_offset;
|
||||
return new_offset;
|
||||
}
|
||||
|
||||
static int remove_inner(MockFS *mfs, MockFS_Entity *entity, bool recursive)
|
||||
{
|
||||
if (entity->parent == NULL)
|
||||
return MOCKFS_ERRNO_BUSY;
|
||||
|
||||
if (entity->is_dir) {
|
||||
// Remove children
|
||||
if (entity->head_child) {
|
||||
if (!recursive)
|
||||
return MOCKFS_ERRNO_NOTEMPTY;
|
||||
MockFS_Entity *child = entity->head_child;
|
||||
while (child) {
|
||||
MockFS_Entity *next = child->next;
|
||||
remove_inner(mfs, child, true);
|
||||
child = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unlink entity node from parent
|
||||
if (entity->prev) {
|
||||
entity->prev->next = entity->next;
|
||||
} else {
|
||||
entity->parent->head_child = entity->next;
|
||||
}
|
||||
|
||||
if (entity->next) {
|
||||
entity->next->prev = entity->prev;
|
||||
} else {
|
||||
entity->parent->tail_child = entity->prev;
|
||||
}
|
||||
|
||||
// Mark as unlinked
|
||||
entity->parent = NULL;
|
||||
|
||||
// Only fully free the entity if no open handles
|
||||
if (entity->refcount == 0) {
|
||||
if (!entity->is_dir) {
|
||||
// Append chunks to the free list
|
||||
byte_buffer_free(&entity->byte_buffer, &mfs->chunk_free_list);
|
||||
}
|
||||
|
||||
// Append entity to the free list
|
||||
entity->next = mfs->entity_free_list;
|
||||
mfs->entity_free_list = entity;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mockfs_remove(MockFS *mfs, char *path, int path_len, bool recursive)
|
||||
{
|
||||
Slice comps[MOCKFS_COMP_LIMIT];
|
||||
int ret = parse_path(path, path_len, comps, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
int num_comps = ret;
|
||||
|
||||
if (num_comps > 0) {
|
||||
if (slice_eq(comps[num_comps-1], S(".")))
|
||||
return MOCKFS_ERRNO_INVAL;
|
||||
|
||||
if (slice_eq(comps[num_comps-1], S("..")))
|
||||
return MOCKFS_ERRNO_INVAL;
|
||||
}
|
||||
|
||||
MockFS_Entity *stack[MOCKFS_COMP_LIMIT];
|
||||
ret = resolve_path(mfs, comps, num_comps, stack, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
assert(ret > 0);
|
||||
|
||||
if (path_len > 0 && path[path_len-1] == '/') {
|
||||
if (!stack[ret-1]->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
}
|
||||
|
||||
return remove_inner(mfs, stack[ret-1], recursive);
|
||||
}
|
||||
|
||||
int mockfs_mkdir(MockFS *mfs, char *path, int path_len)
|
||||
{
|
||||
Slice comps[MOCKFS_COMP_LIMIT];
|
||||
int ret = parse_path(path, path_len, comps, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret == 0)
|
||||
return MOCKFS_ERRNO_EXIST;
|
||||
int num_comps = ret;
|
||||
|
||||
if (slice_eq(comps[num_comps-1], S(".")))
|
||||
return MOCKFS_ERRNO_EXIST;
|
||||
|
||||
if (slice_eq(comps[num_comps-1], S("..")))
|
||||
return MOCKFS_ERRNO_INVAL;
|
||||
|
||||
MockFS_Entity *stack[MOCKFS_COMP_LIMIT];
|
||||
ret = resolve_path(mfs, comps, num_comps-1, stack, MOCKFS_COMP_LIMIT);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
assert(ret > 0);
|
||||
MockFS_Entity *parent = stack[ret-1];
|
||||
|
||||
if (!parent->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
|
||||
if (find_child(parent, comps[num_comps-1]))
|
||||
return MOCKFS_ERRNO_EXIST;
|
||||
|
||||
MockFS_Entity *entity = mfs->entity_free_list;
|
||||
if (entity == NULL) {
|
||||
entity = alloc(mfs, sizeof(MockFS_Entity), _Alignof(MockFS_Entity));
|
||||
if (entity == NULL)
|
||||
return MOCKFS_ERRNO_NOMEM;
|
||||
} else {
|
||||
mfs->entity_free_list = entity->next;
|
||||
}
|
||||
entity->next = NULL;
|
||||
|
||||
ret = entity_init(entity, comps[num_comps-1], true);
|
||||
if (ret < 0) {
|
||||
entity->next = mfs->entity_free_list;
|
||||
mfs->entity_free_list = entity;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Initialize byte_buffer to clear all 24 bytes of the union, including
|
||||
// byte_buffer.tail (bytes 16-23) which would otherwise retain garbage
|
||||
// when reusing an entity from the free list
|
||||
byte_buffer_init(&entity->byte_buffer);
|
||||
|
||||
entity->parent = parent;
|
||||
entity->next = NULL;
|
||||
entity->prev = parent->tail_child;
|
||||
|
||||
if (parent->tail_child) {
|
||||
parent->tail_child->next = entity;
|
||||
} else {
|
||||
parent->head_child = entity;
|
||||
}
|
||||
parent->tail_child = entity;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mockfs_rename(MockFS *mfs, char *old_path, int old_path_len, char *new_path, int new_path_len)
|
||||
{
|
||||
Slice new_comps[MOCKFS_COMP_LIMIT];
|
||||
Slice old_comps[MOCKFS_COMP_LIMIT];
|
||||
int num_new_comps = parse_path(new_path, new_path_len, new_comps, MOCKFS_COMP_LIMIT);
|
||||
int num_old_comps = parse_path(old_path, old_path_len, old_comps, MOCKFS_COMP_LIMIT);
|
||||
|
||||
if (num_new_comps < 0) return num_new_comps;
|
||||
if (num_old_comps < 0) return num_old_comps;
|
||||
|
||||
MockFS_Entity *new_stack[MOCKFS_COMP_LIMIT];
|
||||
MockFS_Entity *old_stack[MOCKFS_COMP_LIMIT];
|
||||
int num_new_stack = resolve_path(mfs, new_comps, num_new_comps-1, new_stack, MOCKFS_COMP_LIMIT);
|
||||
int num_old_stack = resolve_path(mfs, old_comps, num_old_comps, old_stack, MOCKFS_COMP_LIMIT);
|
||||
|
||||
if (num_new_stack == MOCKFS_ERRNO_NOTDIR ||
|
||||
num_old_stack == MOCKFS_ERRNO_NOTDIR)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
|
||||
if (num_new_stack < 0)
|
||||
return num_new_stack;
|
||||
assert(num_new_stack > 0);
|
||||
|
||||
if (!new_stack[num_new_stack-1]->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
|
||||
if (num_old_stack < 0)
|
||||
return num_old_stack;
|
||||
|
||||
assert(num_old_stack > 0);
|
||||
MockFS_Entity *source = old_stack[num_old_stack-1];
|
||||
|
||||
if (source->parent == NULL)
|
||||
return MOCKFS_ERRNO_BUSY;
|
||||
|
||||
if (old_path_len > 0 && old_path[old_path_len-1] == '/') {
|
||||
if (!source->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
}
|
||||
|
||||
if (new_path_len > 0 && new_path[new_path_len-1] == '/') {
|
||||
if (!source->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
}
|
||||
|
||||
if (num_new_comps == 0)
|
||||
return MOCKFS_ERRNO_BUSY;
|
||||
|
||||
// Make sure the entity isn't being moved inside itself,
|
||||
// by checking that the last element of the old stack isn't
|
||||
// in the new stack.
|
||||
for (int i = 0; i < num_new_stack; i++)
|
||||
if (new_stack[i] == source)
|
||||
return MOCKFS_ERRNO_INVAL;
|
||||
|
||||
// Check if new path exists
|
||||
Slice new_name = new_comps[num_new_comps-1];
|
||||
MockFS_Entity *target = NULL;
|
||||
MockFS_Entity *child = new_stack[num_new_stack-1]->head_child;
|
||||
while (child) {
|
||||
if (slice_eq(new_name, (Slice) { child->name, child->name_len })) {
|
||||
target = child;
|
||||
break;
|
||||
}
|
||||
child = child->next;
|
||||
}
|
||||
|
||||
if (target) {
|
||||
|
||||
if (target == source)
|
||||
return 0;
|
||||
|
||||
if (target->is_dir) {
|
||||
|
||||
for (int i = 0; i < num_old_stack; i++)
|
||||
if (old_stack[i] == target)
|
||||
return MOCKFS_ERRNO_NOTEMPTY;
|
||||
|
||||
if (!source->is_dir)
|
||||
return MOCKFS_ERRNO_ISDIR;
|
||||
|
||||
if (target->head_child)
|
||||
return MOCKFS_ERRNO_NOTEMPTY;
|
||||
|
||||
} else {
|
||||
if (source->is_dir)
|
||||
return MOCKFS_ERRNO_NOTDIR;
|
||||
}
|
||||
|
||||
remove_inner(mfs, target, false);
|
||||
}
|
||||
|
||||
// Unlink source from old parent
|
||||
if (source->prev) {
|
||||
source->prev->next = source->next;
|
||||
} else {
|
||||
source->parent->head_child = source->next;
|
||||
}
|
||||
|
||||
if (source->next) {
|
||||
source->next->prev = source->prev;
|
||||
} else {
|
||||
source->parent->tail_child = source->prev;
|
||||
}
|
||||
|
||||
// Update source's name
|
||||
if (new_name.len > (int) sizeof(source->name))
|
||||
return MOCKFS_ERRNO_INVAL; // Name too long
|
||||
memcpy(source->name, new_name.ptr, new_name.len);
|
||||
source->name_len = new_name.len;
|
||||
|
||||
// Link source to new parent
|
||||
source->parent = new_stack[num_new_stack-1];
|
||||
source->next = NULL;
|
||||
source->prev = new_stack[num_new_stack-1]->tail_child;
|
||||
|
||||
if (new_stack[num_new_stack-1]->tail_child) {
|
||||
new_stack[num_new_stack-1]->tail_child->next = source;
|
||||
} else {
|
||||
new_stack[num_new_stack-1]->head_child = source;
|
||||
}
|
||||
new_stack[num_new_stack-1]->tail_child = source;
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user