Version 0.8
This commit is contained in:
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user