Version 0.8

This commit is contained in:
2026-02-19 15:27:47 +01:00
commit 868312edf8
37 changed files with 12782 additions and 0 deletions
+223
View File
@@ -0,0 +1,223 @@
#ifndef QUAKEY_INCLUDED
#define QUAKEY_INCLUDED
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <direct.h>
#else
#include <time.h>
#include <poll.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <dirent.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#endif
typedef struct {} Quakey;
// Function pointers to a simulated program's code
typedef int (*QuakeyInitFunc)(void *state, int argc, char **argv, void **ctxs, struct pollfd *pdata, int pcap, int *pnum, int *timeout);
typedef int (*QuakeyTickFunc)(void *state, void **ctxs, struct pollfd *pdata, int pcap, int *pnum, int *timeout);
typedef int (*QuakeyFreeFunc)(void *state);
typedef enum {
QUAKEY_LINUX,
QUAKEY_WINDOWS,
} QuakeyPlatform;
typedef struct {
// Label associated to the process for debugging
// The string must have global lifetime
char *name;
// Size of the opaque state struct
int state_size;
// Pointers to program code
QuakeyInitFunc init_func;
QuakeyTickFunc tick_func;
QuakeyFreeFunc free_func;
// Network addresses enabled on the process
char **addrs;
int num_addrs;
// Disk size for the process
int disk_size;
// Platform used by this program
QuakeyPlatform platform;
} QuakeySpawn;
typedef unsigned long long QuakeyUInt64;
// Start a simulation
int quakey_init(Quakey **quakey, QuakeyUInt64 seed);
// Stop a simulation
void quakey_free(Quakey *quakey);
typedef unsigned long long QuakeyNode;
// Add a program to the simulation
QuakeyNode quakey_spawn(Quakey *quakey, QuakeySpawn config, char *arg);
void *quakey_node_state(QuakeyNode node);
// Schedule and executes one program until it would block, then returns
int quakey_schedule_one(Quakey *quakey);
// Generate a random u64
QuakeyUInt64 quakey_random(void);
typedef struct {
char name[32];
int name_len;
} QuakeySignal;
void quakey_signal(char *name);
int quakey_get_signal(Quakey *quakey, QuakeySignal *signal);
// Access spawned host information
int quakey_num_hosts(Quakey *quakey);
void *quakey_host_state(Quakey *quakey, int idx); // Returns NULL if host is dead
int quakey_host_is_dead(Quakey *quakey, int idx);
const char *quakey_host_name(Quakey *quakey, int idx);
int *mock_errno_ptr(void);
#ifdef _WIN32
BOOL mock_QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);
BOOL mock_QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
HANDLE mock_CreateFileW(WCHAR *lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
BOOL mock_CloseHandle(HANDLE handle);
BOOL mock_ReadFile(HANDLE handle, char *dst, DWORD len, DWORD *num, OVERLAPPED *ov);
BOOL mock_WriteFile(HANDLE handle, char *src, DWORD len, DWORD *num, OVERLAPPED *ov);
BOOL mock_GetFileSizeEx(HANDLE handle, LARGE_INTEGER *buf);
DWORD mock_SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod);
BOOL mock_LockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh);
BOOL mock_UnlockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh);
BOOL mock_FlushFileBuffers(HANDLE handle);
BOOL mock_MoveFileExW(WCHAR *lpExistingFileName, WCHAR *lpNewFileName, DWORD dwFlags);
char* mock__fullpath(char *path, char *dst, int cap);
HANDLE mock_FindFirstFileA(char *lpFileName, WIN32_FIND_DATAA *lpFindFileData);
BOOL mock_FindNextFileA(HANDLE hFindFile, WIN32_FIND_DATAA *lpFindFileData);
BOOL mock_FindClose(HANDLE hFindFile);
int mock__mkdir(char *path);
#else
int mock_socket(int domain, int type, int protocol);
int mock_closesocket(int fd);
int mock_ioctlsocket(int fd, long cmd, unsigned long *argp);
int mock_bind(int fd, void *addr, unsigned long addr_len);
int mock_connect(int fd, void *addr, unsigned long addr_len);
int mock_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen);
int mock_listen(int fd, int backlog);
int mock_accept(int fd, void *addr, socklen_t *addr_len);
int mock_pipe(int *fds);
int mock_recv(int fd, char *dst, int len, int flags);
int mock_send(int fd, char *src, int len, int flags);
int mock_clock_gettime(clockid_t clockid, struct timespec *tp);
int mock_open(char *path, int flags, int mode);
int mock_fcntl(int fd, int cmd, int flags);
int mock_close(int fd);
int mock_ftruncate(int fd, size_t new_size);
int mock_fstat(int fd, struct stat *buf);
int mock_read(int fd, char *dst, int len);
int mock_write(int fd, char *src, int len);
off_t mock_lseek(int fd, off_t offset, int whence);
int mock_flock(int fd, int op);
int mock_fsync(int fd);
int mock_mkstemp(char *path);
int mock_mkdir(char *path, mode_t mode);
int mock_remove(char *path);
int mock_rename(char *oldpath, char *newpath);
char* mock_realpath(char *path, char *dst);
DIR* mock_opendir(char *name);
struct dirent* mock_readdir(DIR *dirp);
int mock_closedir(DIR *dirp);
#endif
void *mock_malloc(size_t size);
void *mock_realloc(void *ptr, size_t size);
void mock_free(void *ptr);
#ifdef QUAKEY_ENABLE_MOCKS
#define QUAKEY_SIGNAL(name) quakey_signal(name)
#undef errno
#define errno (*mock_errno_ptr())
#define malloc mock_malloc
#define realloc mock_realloc
#define free mock_free
#define socket mock_socket
#define closesocket mock_closesocket
#define ioctlsocket mock_ioctlsocket
#define bind mock_bind
#define connect mock_connect
#define getsockopt mock_getsockopt
#define listen mock_listen
#define accept mock_accept
#define pipe mock_pipe
#define recv mock_recv
#define send mock_send
#define clock_gettime mock_clock_gettime
#define QueryPerformanceCounter mock_QueryPerformanceCounter
#define QueryPerformanceFrequency mock_QueryPerformanceFrequency
#define open mock_open
#define fcntl mock_fcntl
#define close mock_close
#define ftruncate mock_ftruncate
#define CreateFileW mock_CreateFileW
#define CloseHandle mock_CloseHandle
#define ReadFile mock_ReadFile
#define WriteFile mock_WriteFile
#define read mock_read
#define write mock_write
#define fstat mock_fstat
#define GetFileSizeEx mock_GetFileSizeEx
#define lseek mock_lseek
#define SetFilePointer mock_SetFilePointer
#define flock mock_flock
#define LockFile mock_LockFile
#define UnlockFile mock_UnlockFile
#define fsync mock_fsync
#define FlushFileBuffers mock_FlushFileBuffers
#define mkstemp mock_mkstemp
#define mkdir mock_mkdir
#define _mkdir mock__mkdir
#define remove mock_remove
#define rename mock_rename
#define MoveFileExW mock_MoveFileExW
#define realpath mock_realpath
#define _fullpath mock__fullpath
#define opendir mock_opendir
#define readdir mock_readdir
#define closedir mock_closedir
#define FindFirstFileA mock_FindFirstFileA
#define FindNextFileA mock_FindNextFileA
#define FindClose mock_FindClose
#else
#define QUAKEY_SIGNAL(name) ((void) (name))
#endif
#endif // QUAKEY_INCLUDED
+985
View File
@@ -0,0 +1,985 @@
#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 void byte_buffer_truncate(ByteBuffer *byte_buffer, int new_size, ByteChunk **free_list)
{
if (new_size == 0) {
byte_buffer_free(byte_buffer, free_list);
byte_buffer_init(byte_buffer);
return;
}
// Walk through chunks to find the one containing the new end
int remaining = new_size;
ByteChunk *chunk = byte_buffer->head;
while (chunk) {
int chunk_used = (chunk->next ? BYTE_CHUNK_SIZE : byte_buffer->tail_used);
if (remaining <= chunk_used) {
// This chunk becomes the new tail
// Free all subsequent chunks
ByteChunk *to_free = chunk->next;
if (to_free) {
// Find end of chain to free
ByteChunk *last = to_free;
while (last->next)
last = last->next;
last->next = *free_list;
*free_list = to_free;
}
chunk->next = NULL;
byte_buffer->tail = chunk;
byte_buffer->tail_used = remaining;
return;
}
remaining -= BYTE_CHUNK_SIZE;
chunk = chunk->next;
}
}
int mockfs_ftruncate(MockFS_OpenFile *open_file, int new_size)
{
if (new_size < 0)
return MOCKFS_ERRNO_INVAL;
if (!(open_file->flags & (MOCKFS_O_WRONLY | MOCKFS_O_RDWR)))
return MOCKFS_ERRNO_BADF;
ByteBuffer *bb = &open_file->entity->byte_buffer;
int current_size = byte_buffer_size(bb);
if (new_size < current_size) {
byte_buffer_truncate(bb, new_size, &open_file->mfs->chunk_free_list);
} else if (new_size > current_size) {
int ret = byte_buffer_extend(bb, new_size, open_file->mfs);
if (ret < 0)
return ret;
}
return 0;
}
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;
}
+110
View File
@@ -0,0 +1,110 @@
#ifndef MOCKFS_INCLUDED
#define MOCKFS_INCLUDED
#include <stdbool.h>
#define MOCKFS_NAME_SIZE (1<<7)
enum {
MOCKFS_ERRNO_SUCCESS = 0,
MOCKFS_ERRNO_NOENT = -1, // No such file or directory
MOCKFS_ERRNO_PERM = -2, // Operation not permitted
MOCKFS_ERRNO_NOMEM = -3, // Out of memory
MOCKFS_ERRNO_NOTDIR = -4, // Not a directory
MOCKFS_ERRNO_ISDIR = -5, // Is a directory
MOCKFS_ERRNO_INVAL = -6, // Invalid argument
MOCKFS_ERRNO_NOTEMPTY = -7, // Directory not empty
MOCKFS_ERRNO_NOSPC = -8, // No space left on device
MOCKFS_ERRNO_EXIST = -9, // File exists
MOCKFS_ERRNO_BUSY = -10,
MOCKFS_ERRNO_BADF = -11,
};
enum {
MOCKFS_O_RDONLY = 0x00,
MOCKFS_O_WRONLY = 0x01,
MOCKFS_O_RDWR = 0x02,
MOCKFS_O_CREAT = 0x40,
MOCKFS_O_EXCL = 0x80,
MOCKFS_O_TRUNC = 0x200,
MOCKFS_O_APPEND = 0x400,
};
enum {
MOCKFS_SEEK_SET = 0,
MOCKFS_SEEK_CUR = 1,
MOCKFS_SEEK_END = 2,
};
#define BYTE_CHUNK_SIZE 128
typedef struct ByteChunk ByteChunk;
struct ByteChunk {
ByteChunk *next;
char data[BYTE_CHUNK_SIZE];
};
typedef struct {
int used;
int tail_used;
ByteChunk *head;
ByteChunk *tail;
} ByteBuffer;
typedef struct MockFS_Entity MockFS_Entity;
typedef struct {
char *mem;
int len;
int off;
MockFS_Entity *root;
MockFS_Entity *entity_free_list;
ByteChunk *chunk_free_list;
} MockFS;
typedef struct {
MockFS* mfs;
MockFS_Entity* entity;
int offset;
int flags;
} MockFS_OpenFile;
typedef struct {
MockFS* mfs;
MockFS_Entity* entity;
MockFS_Entity* child;
int idx;
} MockFS_OpenDir;
typedef struct {
char name[MOCKFS_NAME_SIZE];
int name_len;
bool is_dir;
} MockFS_Dirent;
int mockfs_init(MockFS **mfs, char *mem, int len);
void mockfs_free(MockFS *mfs);
int mockfs_open(MockFS *mfs, char *path, int path_len, int flags, MockFS_OpenFile *open_file);
int mockfs_open_dir(MockFS *mfs, char *path, int path_len, MockFS_OpenDir *open_dir);
int mockfs_file_size(MockFS_OpenFile *open_file);
void mockfs_close_file(MockFS_OpenFile *open_file);
void mockfs_close_dir(MockFS_OpenDir *open_dir);
int mockfs_read(MockFS_OpenFile *open_file, char *dst, int len);
int mockfs_write(MockFS_OpenFile *open_file, char *src, int len);
int mockfs_read_dir(MockFS_OpenDir *open_dir, MockFS_Dirent *dirent);
int mockfs_sync(MockFS_OpenFile *open_file);
int mockfs_lseek(MockFS_OpenFile *open_file, int offset, int whence);
int mockfs_ftruncate(MockFS_OpenFile *open_file, int new_size);
int mockfs_remove(MockFS *mfs, char *path, int path_len, bool recursive);
int mockfs_mkdir(MockFS *mfs, char *path, int path_len);
int mockfs_rename(MockFS *mfs, char *old_path, int old_path_len, char *new_path, int new_path_len);
#endif // MOCKFS_INCLUDED
+4774
View File
File diff suppressed because it is too large Load Diff