(Untested, no logging).
"NURS"
],
"files.associations": {
- "coquet.h": "c"
+ "coquet.h": "c",
+ "superblock.h": "c"
}
}
\ No newline at end of file
LIBS =
CC = gcc
CFLAGS = -g -Wall --std=c99
-FILES = main.c posix.c util.c coquet.c
+FILES = main.c unix.c util.c coquet.c superblock.c sha2.c
.PHONY: default all clean
#define COQUET_FILE_OLD 1
#define COQUET_FILE_TMP 2
+/* remember to update messages */
#define COQUET_RET_OK 0
#define COQUET_RET_VFSERR 1
#define COQUET_RET_HEAPERR 2
#define COQUET_RET_LOCKED 3
-#define COQUET_UNUSED_ERROR 4
-#define COQUET_LAST_ERROR 4
+#define COQUET_RET_CORRUPT 4
+#define COQUET_RET_EXISTS 5
+#define COQUET_UNUSED_ERROR 6
+#define COQUET_LAST_ERROR 6
#define COQUET_CMODE_OPEN 0
#define COQUET_CMODE_CREATE 1
#define COQUET_CMODE_EITHER 2
-#define COQUET_LOCK_WRITE 0
-#define COQUET_LOCK_ALIVE 1
-#define COQUET_LOCK_NURS_A 2
-#define COQUET_LOCK_NURS_B 3
-#define COQUET_LOCK_LAST 3
+#define COQUET_LOCK_WRITE 0
+#define COQUET_LOCK_ALIVE 1
+#define COQUET_LOCK_NURS_A 2
+#define COQUET_LOCK_NURS_B 3
+#define COQUET_LOCK_SUPERBLOCK 4
+#define COQUET_LOCK_LAST 4
#define COQUET_LMODE_EXCL 0
#define COQUET_LMODE_SHARE 1
typedef int bool_t;
+struct coquet;
+typedef struct coquet coquet_t;
+
#endif
#include "coquet.h"
#include "util.h"
-extern vfs_t vfs_posix;
+extern vfs_t vfs_unix;
/* VFS null is used as a placeholder prior to initialisation, so that
* subsequent tidies can be clean.
static int init_vfs(coquet_t *cq, char *basename) {
int r;
- cq->vfs_funcs = vfs_posix;
+ cq->vfs_funcs = vfs_unix;
/* Allocate memory */
cq->vfs_data = (cq->vfs_funcs.make)();
/* COQUET_RET_VFS */ "Unknown VFS Error",
/* COQUET_RET_HEAPERR */ "Heap allocation failed",
/* COQUET_RET_LOCKED */ "Locked",
- /* COQUET_UNUSED_ERROR */ "No such error code"
+ /* COQUET_RET_CORRUPT */ "File corrupt",
+ /* COQUET_RET_EXISTS */ "File already exists",
+ /* COQUET_UNUSED_ERROR */ "No such error code",
+
};
/* Return error string for most recent error. Returned string is owned by
#define COQUET_H
#include "constants.h"
+#include "superblock.h"
#include "vfs.h"
typedef struct coquet {
-
+ /* superblock stuff */
+ uint64_t global_iv[GLOBAL_IV_LEN];
+
/* VFS */
vfs_t vfs_funcs;
void *vfs_data;
coquet *does* make changes to the database file other than at the end, making it *not* append-only. However, it is guaranteed that these changes are such that at any moment any other process can read or copy the database file, even without locking it first, and receive a valid database state at some moment in time.
-The non-append actions relate to the nursery-area, a journal to improve write efficiency in various ways.
+The non-append actions relate
+
+1. to the nursery-area, a journal to improve write efficiency in various ways;
+2. to the superblock where an admin may update the intended parameters for the file for the database to apply when convenient.
The nursery-area is at a fixed location near the start of the file. It is a write-ahead log, designed in such a way that reading its data, even *during* updates to the log, gives you the valid log state at some point in the near past, at least at the most recent commit which has been followed by a filesystem sync, perhaps also including unsynced commits -- and therefore always a valid database at that point (or later).
As a write-ahead log, the nursery does place a performance obligation on readers. Again, given our pessimistic approach to systems administration, the default implementation does not use exotic shared memory to speed this up, but takes the performance hit.
+The superblock is at the very start of the file with a pair of entries, of which exactly one is valid. Superblocks include a small, increasing integer denoting the currently valid superblock, and are each protected by an HMAC. Writes to update a superblock update the inactive one of the pair and issue a sync, making it active.
+
Filesystem Guarantees
=====================
1. No two writes can be live simultaneously.
2. An old database file cannot be deleted while operations are ongoing.
3. A nursery half is not deleted on flushing while being read by another process.
+4. A superblock lock, ensuring no two process are updating the superblock at once.
The *write lock* is exclusively acquired by a writer before writing.
Deadlocks are avoided by having a defined order of lock acquisition.
+The superblock is locked during update. The lock only stops simultaneous writes: reads may run concurrently, even coexisting with other writers.
+
File Structure
==============
#include <stdlib.h>
#include "coquet.h"
#include "vfs.h"
+#include "superblock.h"
void bail(coquet_t * cq, int error_code) {
char *msg;
int main() {
coquet_t cq;
- int i,r;
- char buf[10];
+ int r;
+ struct cq_super super;
r = coquet_init(&cq,"test");
bail(&cq,r);
- r = (cq.vfs_funcs.open)(cq.vfs_data,COQUET_FILE_MAIN,1);
+ r = (cq.vfs_funcs.open)(cq.vfs_data,COQUET_FILE_MAIN,COQUET_CMODE_EITHER);
bail(&cq,r);
- r = (cq.vfs_funcs.write)(cq.vfs_data,COQUET_FILE_MAIN,"hello",20,5);
- bail(&cq,r);
- r = (cq.vfs_funcs.read)(cq.vfs_data,COQUET_FILE_MAIN,buf,18,10);
- bail(&cq,r);
- r = (cq.vfs_funcs.lock)(cq.vfs_data,COQUET_LOCK_ALIVE,COQUET_LMODE_EXCL,1);
- bail(&cq,r);
- for(i=0;i<10;i++) {
- printf("%d %d\n",i,buf[i]);
- }
- r = (cq.vfs_funcs.lock)(cq.vfs_data,COQUET_LOCK_ALIVE,COQUET_LMODE_UN,1);
+ r = cq_super_load(&cq,&super,1);
bail(&cq,r);
+ cq_super_save(&cq,&super,1);
r = (cq.vfs_funcs.close)(cq.vfs_data,COQUET_FILE_MAIN);
bail(&cq,r);
- r = (cq.vfs_funcs.delete)(cq.vfs_data,COQUET_FILE_MAIN);
- bail(&cq,r);
r = coquet_finish(&cq);
bail(&cq,r);
return 0;
* m: only written by
* a. memcpy len=56 at pointer offset 1. Is 8*64-bits, so at offset 1
* there's 7*64-bits, = 56 bytes.
+ * b. at indexes 0 and 4 which are less than its size.
*
* (2) pad_length is at most SHA2_MAX_BLOCK_LEN. It is calculated by taking
* block_size and subtracting positives. If this is greater than zero,
--- /dev/null
+#include <stdint.h>
+#include <string.h>
+#include "coquet.h"
+#include "util.h"
+#include "sha2.h"
+#include "superblock.h"
+
+#define HASH_LEN 32
+
+/* cq_config: length = 2048
+ *
+ * offset length
+ * +-------------------+
+ * | sb_hash | 0 32
+ * | sb_serial | 32 8
+ * +-------------------+
+ * | unused | 40 984
+ * +-------------------+
+ * | current | 1024 512
+ * +-------------------+
+ * | desired | 1536 512
+ * +-------------------+
+ *
+ * cq_super_config: length = 512
+ *
+ * offset length
+ * +-------------------+
+ * | global_iv | 0 32
+ * +-------------------+
+ * | block_size | 32 1
+ * +-------------------+
+ * | unused | 33 478
+ * +-------------------+
+ * | reserved | 511 1
+ * +-------------------+
+ *
+ * The reserved byte is to allow locking to work via superblock page.
+ * Range must exist to write, hence lock code may write this byte at
+ * offset 0x00007FFF
+ */
+
+struct cq_super default_super = {
+ .current = {{0,},12},
+ .desired = {{0,},12},
+ .sb_serial = 0,
+ .sb_hash = {0,},
+ .from_b = 1
+};
+
+/* Blindly do a extraction. Note that we may not have any verification
+ * at this point, so don't trust the data yet.
+ */
+static void extract_config(uint8_t *data, struct cq_super_config *sc) {
+ memcpy(sc->global_iv,data,HASH_LEN);
+ sc->block_size = be_decode(data+32,1);
+}
+
+static int extract_half(uint8_t *data, struct cq_super *super) {
+ struct sha2_ctx_t sha2;
+ uint8_t hash[HASH_LEN],cmp[HASH_LEN];
+
+ /* extract */
+ memset(super->sb_hash,0,HASH_LEN);
+ super->sb_serial = be_decode(data+32,8);
+ extract_config(data+1024,&(super->current));
+ extract_config(data+1536,&(super->desired));
+
+ /* verify */
+ memcpy(hash,data,HASH_LEN);
+ memset(data,0,HASH_LEN);
+ sha2_init_hmac(&sha2, SHA2_VARIETY_512_256,
+ super->current.global_iv, GLOBAL_IV_LEN);
+ sha2_more(&sha2,data,HALF_BYTES);
+ sha2_finish(&sha2,cmp,HASH_LEN);
+ if(memcmp(hash,cmp,HASH_LEN)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int super_init(coquet_t *cq, struct cq_super *super) {
+ int r;
+
+ r = (cq->vfs_funcs.random)(cq->vfs_data,
+ super->current.global_iv, GLOBAL_IV_LEN);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+ memcpy(super->desired.global_iv,super->current.global_iv,GLOBAL_IV_LEN);
+ return COQUET_RET_OK;
+}
+
+/* Extract a superblock into the given structure and check it. We don't
+ * have a valid global_iv, we just have to trust the one in the block,
+ * reducing the HMAC to a simple hash in terms of guarantees. Requires the
+ * main file to be open.
+ */
+int cq_super_load(coquet_t *cq, struct cq_super *super, bool_t create) {
+ uint8_t super_bytes[SUPER_BYTES];
+ struct cq_super super_a, super_b;
+ int r,r2;
+ bool_t use_b;
+
+ r = (cq->vfs_funcs.read)
+ (cq->vfs_data,COQUET_FILE_MAIN,super_bytes,0,SUPER_BYTES);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+
+ r = extract_half(super_bytes,&super_a);
+ r2 = extract_half(super_bytes+HALF_BYTES,&super_b);
+ switch(r*2+r2) {
+ case 3: /* both valid */
+ use_b = (super_b.sb_serial > super_a.sb_serial);
+ break;
+ case 2: /* only A valid */
+ use_b = 0;
+ break;
+ case 1: /* only B valid */
+ use_b = 1;
+ break;
+ case 0: /* nothing valid */
+ if(create) {
+ use_b = 0;
+ super_a = default_super;
+ r = super_init(cq,&super_a);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+ } else {
+ return COQUET_RET_CORRUPT;
+ }
+ }
+ memcpy(super, use_b?&super_b:&super_a, sizeof(struct cq_super));
+ super->from_b = use_b;
+ return COQUET_RET_OK;
+}
+
+static int lock_super(coquet_t *cq, int mode, bool_t wait) {
+ return (cq->vfs_funcs.lock)
+ (cq->vfs_data,COQUET_LOCK_SUPERBLOCK,mode,wait);
+}
+
+static void intract_config(uint8_t *data,
+ struct cq_super_config *sc) {
+ memcpy(data,sc->global_iv,HASH_LEN);
+ be_encode(data+32,sc->block_size,1);
+}
+
+static void intract(uint8_t *data, struct cq_super *super, uint64_t serial) {
+ struct sha2_ctx_t sha2;
+
+ /* intract */
+ memset(data,0,HALF_BYTES);
+ be_encode(data+32,serial,8);
+ intract_config(data+1024,&(super->current));
+ intract_config(data+1536,&(super->desired));
+
+ /* set hash */
+ sha2_init_hmac(&sha2, SHA2_VARIETY_512_256,
+ super->current.global_iv, GLOBAL_IV_LEN);
+ sha2_more(&sha2,data,HALF_BYTES);
+ sha2_finish(&sha2,data,HASH_LEN);
+}
+
+static int super_write(coquet_t *cq, struct cq_super *super,
+ bool_t use_b, uint64_t serial) {
+ uint8_t half[HALF_BYTES];
+ int r;
+
+ intract(half,super,serial);
+ r = (cq->vfs_funcs.write)
+ (cq->vfs_data,COQUET_FILE_MAIN,half,
+ use_b?HALF_BYTES:0,HALF_BYTES);
+
+ return r;
+}
+
+int cq_super_save(coquet_t *cq, struct cq_super *super, bool_t wait) {
+ int r, ret;
+ struct cq_super old;
+
+ /* lock */
+ r = lock_super(cq,COQUET_LMODE_EXCL,wait);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+
+ /* load */
+ ret = cq_super_load(cq,&old,0);
+ if(ret == COQUET_RET_OK) {
+ ret = super_write(cq,super,!old.from_b,old.sb_serial+1);
+ } else if(ret == COQUET_RET_CORRUPT) {
+ // XXX log
+ ret = super_write(cq,super,0,1);
+ }
+
+ /* unlock */
+ r = lock_super(cq,COQUET_LMODE_UN,wait);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+
+ return ret;
+}
--- /dev/null
+#ifndef COQUET_SUPERBLOCK_H
+#define COQUET_SUPERBLOCK_H
+
+#include "constants.h"
+
+#define HALF_BYTES 2048
+#define SUPER_BYTES (HALF_BYTES*2)
+#define GLOBAL_IV_LEN 32
+#define SUPERBLOCK_LAST_OFFSET (SUPER_BYTES-1)
+
+struct cq_super_config {
+ uint8_t global_iv[GLOBAL_IV_LEN]; /* (0 : GLOBAL_IV_LEN) */
+ uint8_t block_size; /* log bits (GLOBAL_IV_LEN: 1) */
+};
+
+struct cq_super {
+ struct cq_super_config current, desired;
+ uint64_t sb_serial;
+ uint8_t sb_hash[32]; /* HMAC SHA512-256 using global_iv */
+ int from_b; /* true if loaded from b, false if from a */
+};
+
+#include "coquet.h"
+
+int cq_super_save(coquet_t *cq, struct cq_super *super, bool_t wait);
+int cq_super_load(coquet_t *cq, struct cq_super *super, bool_t create);
+
+#endif
\ No newline at end of file
#include "vfs.h"
#include "coquet.h"
#include "util.h"
+#include "superblock.h"
-struct posix_data {
+struct unix_data {
char * filename;
char * dirname_buf, * dirname;
int seen_error;
int main_fd, tmp_fd, old_fd;
};
-/* Set message to be returned by posix_get_error_text. Passed string
+/* Set message to be returned by unix_get_error_text. Passed string
* remains owned by caller.
*/
-static void set_error(struct posix_data * pd, char *error,
+static void set_error(struct unix_data * pd, char *error,
bool_t use_errno) {
pd->seen_error = 1;
if(pd->error_text != NULL) {
}
}
-static void * posix_make() {
- struct posix_data * pd;
+static void * unix_make() {
+ struct unix_data * pd;
- pd = malloc(sizeof(struct posix_data));
+ pd = malloc(sizeof(struct unix_data));
pd->seen_error = 0;
pd->error_text = NULL;
pd->filename = pd->dirname_buf = NULL;
return pd;
}
-static int posix_start(void * vfs_data, char *filename) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+static int unix_start(void * vfs_data, char *filename) {
+ struct unix_data * pd = (struct unix_data *)vfs_data;
pd->filename = strdup(filename);
if(pd->filename == NULL) {
return COQUET_RET_OK;
}
-static int posix_finish(void *vfs_data) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+static int unix_finish(void *vfs_data) {
+ struct unix_data * pd = (struct unix_data *)vfs_data;
+ if(pd->main_fd!=-1) {
+ close(pd->main_fd); /* ret delibertely ignored */
+ }
+ if(pd->tmp_fd!=-1) {
+ close(pd->tmp_fd); /* ret delibertely ignored */
+ }
+ if(pd->old_fd!=-1) {
+ close(pd->old_fd); /* ret delibertely ignored */
+ }
if(pd->error_text) {
free(pd->error_text);
}
return COQUET_RET_OK;
}
-static char * posix_get_error_text(void * vfs_data) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+static char * unix_get_error_text(void * vfs_data) {
+ struct unix_data * pd = (struct unix_data *)vfs_data;
if(!pd->seen_error) {
return strdup("No VFS error occurred");
return strdup(pd->error_text);
}
-static char * filename(struct posix_data *pd, int which_file) {
+static char * filename(struct unix_data *pd, int which_file) {
switch(which_file) {
case COQUET_FILE_MAIN:
return cq_message("%s.coquet",pd->filename);
}
}
-static int * file_fd(struct posix_data *pd, int which_file) {
+static int * file_fd(struct unix_data *pd, int which_file) {
switch(which_file) {
case COQUET_FILE_MAIN: return &pd->main_fd;
case COQUET_FILE_OLD: return &pd->old_fd;
}
}
-static int sync_dir(struct posix_data * pd) {
+static int sync_dir(struct unix_data * pd) {
int r,fd;
if(pd->dirname == NULL) {
return COQUET_RET_OK;
}
-static int posix_open(void * vfs_data, int which_file,
- bool_t allow_create) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+static int unix_open(void * vfs_data, int which_file,
+ int mode) {
+ struct unix_data * pd = (struct unix_data *)vfs_data;
char *path;
int r, fd, flags, *fd_field;
}
flags = O_RDWR;
- if(allow_create) {
- flags |= O_CREAT;
+ switch(mode) {
+ case COQUET_CMODE_CREATE:
+ flags |= O_EXCL|O_CREAT;
+ break;
+ case COQUET_CMODE_EITHER:
+ flags |= O_CREAT;
+ break;
+ default:
}
fd_field = file_fd(pd,which_file);
return COQUET_RET_OK;
}
-static int posix_close(void * vfs_data, int which_file) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+static int unix_close(void * vfs_data, int which_file) {
+ struct unix_data * pd = (struct unix_data *)vfs_data;
int *fd_field, r;
fd_field = file_fd(pd,which_file);
return COQUET_RET_OK;
}
-static int posix_write(void * vfs_data, int which_file, char * data,
+static int unix_write(void * vfs_data, int which_file, uint8_t * data,
off_t offset, uint64_t length) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+ struct unix_data * pd = (struct unix_data *)vfs_data;
int *fd, r;
off_t r_off;
return COQUET_RET_OK;
}
-static int posix_read(void * vfs_data, int which_file, char * data,
+static int unix_read(void * vfs_data, int which_file, uint8_t * data,
uint64_t offset, uint64_t length) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+ struct unix_data * pd = (struct unix_data *)vfs_data;
int *fd, r;
off_t r_off;
}
/* main_fd must be open */
-static int min_size(struct posix_data *pd, off_t size) {
+static int min_size(struct unix_data *pd, off_t offset) {
int r;
off_t r_off;
char buf[1] = {0};
- if(size == 0) {
+ if(offset == 0) {
return COQUET_RET_OK;
}
- r_off = lseek(pd->main_fd,size-1,SEEK_SET);
+ r_off = lseek(pd->main_fd,offset,SEEK_SET);
if(r_off == -1) {
set_error(pd,"seek failed",1);
return COQUET_RET_VFSERR;
return COQUET_RET_OK;
}
-#define LOCK_BLOCK 512
-static int posix_lock(void * vfs_data, int which_lock, int lock_mode,
+#define LOCK_BLOCK 128
+static int unix_lock(void * vfs_data, int which_lock, int lock_mode,
bool_t wait) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+ struct unix_data * pd = (struct unix_data *)vfs_data;
int r, op;
struct flock flk;
}
/* Lock region needs to exist. We can append zeroes.*/
- r = min_size(pd,(COQUET_LOCK_LAST+1)*LOCK_BLOCK);
+ r = min_size(pd,SUPERBLOCK_LAST_OFFSET);
if(r != COQUET_RET_OK) {
return r;
}
return COQUET_RET_OK;
}
-static int posix_delete(void *vfs_data, int which_file) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+static int unix_delete(void *vfs_data, int which_file) {
+ struct unix_data * pd = (struct unix_data *)vfs_data;
int *fd, r;
char *path;
return COQUET_RET_OK;
}
-static int posix_sync(void * vfs_data, int which_file, bool_t data_only) {
- struct posix_data * pd = (struct posix_data *)vfs_data;
+static int unix_sync(void * vfs_data, int which_file, bool_t data_only) {
+ struct unix_data * pd = (struct unix_data *)vfs_data;
int *fd, r;
fd = file_fd(pd,which_file);
return COQUET_RET_OK;
}
-vfs_t vfs_posix = {
- .make = posix_make,
- .start = posix_start,
- .get_error_text = posix_get_error_text,
- .lock = posix_lock,
- .open = posix_open,
- .close = posix_close,
- .write = posix_write,
- .read = posix_read,
- .finish = posix_finish,
- .delete = posix_delete,
- .sync = posix_sync
+static int unix_random(void * vfs_data, uint8_t *out, int len) {
+ struct unix_data * pd = (struct unix_data *)vfs_data;
+ int fd, r;
+
+ fd = open("/dev/urandom",O_RDONLY);
+ if(fd==-1) {
+ set_error(pd,"Failed to open /dev/urandom",1);
+ return COQUET_RET_VFSERR;
+ }
+ while(len>0) {
+ r = read(fd,out,len);
+ if(r==-1) {
+ set_error(pd,"read of /dev/urandom failed",1);
+ return COQUET_RET_VFSERR;
+ }
+ out += r;
+ len -= r;
+ if(r==0) {
+ /* EOF */
+ set_error(pd,"EOF in /dev/urandom",0);
+ return COQUET_RET_VFSERR;
+ }
+ }
+
+ r = close(fd);
+ if(r==-1) {
+ set_error(pd,"Failed to close /dev/random",1);
+ return COQUET_RET_VFSERR;
+ }
+
+ return COQUET_RET_OK;
+}
+
+vfs_t vfs_unix = {
+ .make = unix_make,
+ .start = unix_start,
+ .get_error_text = unix_get_error_text,
+ .lock = unix_lock,
+ .open = unix_open,
+ .close = unix_close,
+ .write = unix_write,
+ .read = unix_read,
+ .finish = unix_finish,
+ .delete = unix_delete,
+ .sync = unix_sync,
+ .random = unix_random
};
return p;
}
+
+uint64_t be_decode(uint8_t *data,int len) {
+ int i;
+ uint64_t out;
+
+ out = 0;
+ for(i=0;i<len;i++) {
+ out <<= 8;
+ out += *(data++);
+ }
+ return out;
+}
+
+void be_encode(uint8_t *where, uint64_t value, int len) {
+ int i;
+
+ for(i=len-1;i>=0;i--) {
+ *(where+i) = value;
+ value >>= 8;
+ }
+}
#define COQUET_UTIL_H
char * cq_message(const char *fmt, ...);
+uint64_t be_decode(uint8_t *data,int len);
+void be_encode(uint8_t *where, uint64_t value, int len);
#endif
\ No newline at end of file
bool_t wait);
/* Open the given file. which_file is drawn from COQUET_FILE_*.
- * If allow_create is called, the file is created if not present.
+ * Open mode gives whether a file must (or can) be created.
* The file is guaranteed not to be open when this function is called.
+ * Can also return COQUET_RET_EXISTS if file exists and
+ * COQUET_CMODE_CREATE is passed.
*/
- int (*open)(void * vfs_data, int which_file, bool_t allow_create);
+ int (*open)(void * vfs_data, int which_file, int open_mode);
/* Sync given file to disk. */
int (*sync)(void * vfs_data, int which_file, bool_t data_only);
* offset is beyond the end of the file, the file should be extended
* to accommodate the write.
*/
- int (*write)(void * vfs_data, int which_file, char * data,
+ int (*write)(void * vfs_data, int which_file, uint8_t * data,
off_t offset, uint64_t length);
/* Read given data of given length at offset from to indicated file.
* offset is beyond the end of the file, all zeroes must be returned.
* File will be open before call.
*/
- int (*read)(void * vfs_data, int which_file, char * data,
+ int (*read)(void * vfs_data, int which_file, uint8_t * data,
uint64_t offset, uint64_t length);
/* Finish VFS layer. Release OS resources (files etc), and free the
*/
int (*delete)(void * vfs_data, int which_file);
+ /* Return some good-quality randomness.
+ */
+ int (*random)(void * vfs_data, uint8_t *out, int len);
+
} vfs_t;
#endif