],
"files.associations": {
"coquet.h": "c",
- "superblock.h": "c"
+ "superblock.h": "c",
+ "page.h": "c"
},
"C_Cpp.dimInactiveRegions": false
}
\ No newline at end of file
OBJDIR := obj/main
TESTOBJDIR := obj/test
-SRCFILES := unix.c util.c coquet.c superblock.c sha2.c testvfs.c
+SRCFILES := unix.c util.c coquet.c superblock.c sha2.c testvfs.c page.c
SRC := $(addprefix $(SRCDIR)/,$(SRCFILES))
#define COQUET_FILE_NUM 3
/* 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_RET_CORRUPT 4
-#define COQUET_RET_EXISTS 5
-#define COQUET_UNUSED_ERROR 6
-#define COQUET_LAST_ERROR 6
+#define COQUET_RET_OK 0
+#define COQUET_RET_VFSERR 1
+#define COQUET_RET_HEAPERR 2
+#define COQUET_RET_LOCKED 3
+#define COQUET_RET_CORRUPT 4
+#define COQUET_RET_EXISTS 5
+#define COQUET_RET_TRSMERROR 6
+#define COQUET_RET_BADARGS 7
+#define COQUET_UNUSED_ERROR 8
+#define COQUET_LAST_ERROR 8
#define COQUET_CMODE_OPEN 0
#define COQUET_CMODE_CREATE 1
#include "coquet.h"
#include "util.h"
#include "vfs.h"
+#include "page.h"
/* VFS null is used as a placeholder prior to initialisation, so that
* subsequent tidies can be clean.
}
#endif
+static void choose_vfs(coquet_t *cq) {
+ cq->vfs_funcs = vfs_unix;
+ cq->vfs_data = unix_make();
+}
+
/* Allocate and initialise vfs layer. */
static int init_vfs(coquet_t *cq, char *basename) {
int r;
- cq->vfs_funcs = vfs_unix;
- cq->vfs_data = unix_make();
+ choose_vfs(cq);
if(cq->vfs_data == NULL) {
cq->vfs_funcs = vfs_null;
return COQUET_RET_HEAPERR;
* content.
*/
int coquet_init(coquet_t *cq, char * basename) {
- int r;
+ int i,r;
r = init_vfs(cq,basename);
+ cq->flags = 0;
+ for(i=0;i<COQUET_FILE_NUM;i++) {
+ cq->filemeta[i].which_file = i;
+ }
+
if(r != COQUET_RET_OK) {
return r;
}
/* COQUET_RET_LOCKED */ "Locked",
/* COQUET_RET_CORRUPT */ "File corrupt",
/* COQUET_RET_EXISTS */ "File already exists",
+ /* COQUET_RET_TRSMERROR */"Transaction state machine error",
+ /* COQUET_RET_BADARGS */ "Internal error: bad arguments",
/* COQUET_UNUSED_ERROR */ "No such error code",
-
};
/* Return error string for most recent error. Returned string is owned by
return strdup(error_strings[error]);
}
}
+
+/* find last block */
+/* TODO: this is not correct, but we can't do it correclty yet! */
+static int find_last_main_block(coquet_t *cq, struct cq_filemeta *fm) {
+ int r;
+ off_t offset;
+ int64_t main_size;
+
+ r = (cq->vfs_funcs.size)(cq->vfs_data,fm->which_file,&offset);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+
+ /* subtract header */
+ main_size = offset - cq_offset_main_page(cq,fm->which_file,1);
+ if(main_size < 0)
+ main_size = 0;
+
+ /* reduce to pages */
+ main_size = main_size / (1<<(fm->super.current.block_size));
+
+ /* TODO: check validity, etc */
+
+ fm->last_main_block = main_size;
+
+ return COQUET_RET_OK;
+}
+
+static int filemeta_setup_write(coquet_t *cq, struct cq_filemeta *fm) {
+ int r;
+
+ fm->flags = 0;
+
+ /* load superblock */
+ r = cq_super_load(cq,fm->which_file,&(fm->super),1); // TODO "create mode"
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+
+ /* find last block */
+ r = find_last_main_block(cq,fm);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+
+ fm->flags |= COQUET_FM_FLAG_WRITE;
+ return COQUET_RET_OK;
+}
+
+bool cq_writing(coquet_t *cq, int which_file) {
+ return ((cq->flags & COQUET_CQ_FILE_VALID(which_file)) &&
+ (cq->filemeta[which_file].flags & COQUET_FM_FLAG_WRITE));
+}
+
+int coquet_write_start(coquet_t *cq, int which_file, bool wait) {
+ int r;
+
+ if(cq_writing(cq,which_file)) {
+ return COQUET_RET_TRSMERROR;
+ }
+
+ /* lock */
+ r = (cq->vfs_funcs.lock)(cq->vfs_data, which_file,
+ COQUET_LOCK_WRITE,COQUET_LMODE_EXCL,wait);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+
+ /* setup */
+ r = filemeta_setup_write(cq,&(cq->filemeta[which_file]));
+ if(r != COQUET_RET_OK) {
+ (cq->vfs_funcs.lock)(cq->vfs_data, which_file,
+ COQUET_LOCK_WRITE,COQUET_LMODE_UN,wait);
+ return r;
+ }
+ cq->flags |= COQUET_CQ_FILE_VALID(which_file);
+
+ return COQUET_RET_OK;
+}
+
+int coquet_write_end(coquet_t *cq, int which_file) {
+ int r;
+
+ if(!cq_writing(cq,which_file)) {
+ return COQUET_RET_TRSMERROR;
+ }
+
+ /* unlock */
+ r = (cq->vfs_funcs.lock)(cq->vfs_data, which_file,
+ COQUET_LOCK_WRITE,COQUET_LMODE_UN,1);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+
+ /* mark stale */
+ cq->flags &=~ COQUET_CQ_FILE_VALID(which_file);
+
+ return COQUET_RET_OK;
+}
#include "superblock.h"
#include "vfs.h"
+#define COQUET_CQ_FLAG_MAINVALID 0x00000001
+#define COQUET_CQ_FLAG_OLDVALID 0x00000002
+#define COQUET_CQ_FLAG_TMPVALID 0x00000004
+
+#define COQUET_CQ_FILE_VALID(file) (COQUET_CQ_FLAG_MAINVALID<<(file))
+
+#define COQUET_FM_FLAG_WRITE 0x01
+
+struct cq_filemeta {
+ int which_file;
+ struct cq_super super;
+ uint8_t flags;
+ uint64_t last_main_block;
+};
+
typedef struct coquet {
- /* superblock stuff */
- uint64_t global_iv[GLOBAL_IV_LEN];
+ uint32_t flags;
+ struct cq_filemeta filemeta[COQUET_FILE_NUM];
/* VFS */
vfs_t vfs_funcs;
void *vfs_data;
-
} coquet_t;
/* Initialise whole library. Returns NULL on malloc failure. cq must
*/
char * coquet_error_string(coquet_t *cq, int error);
+/* Prepare for a write transaction: locking, and general setup.
+ */
+int coquet_write_start(coquet_t *cq, int which_file, bool wait);
+
+/* Write has fnished, tear down. */
+int coquet_write_end(coquet_t *cq, int which_file);
+
+/* test if we're open for writing */
+bool cq_writing(coquet_t *cq, int which_file);
+
#endif
bail(&cq,r);
r = (cq.vfs_funcs.open)(cq.vfs_data,COQUET_FILE_MAIN,COQUET_CMODE_EITHER);
bail(&cq,r);
- r = cq_super_load(&cq,&super,1);
+ r = cq_super_load(&cq,COQUET_FILE_MAIN,&super,1);
bail(&cq,r);
- cq_super_save(&cq,&super,1);
+ cq_super_save(&cq,COQUET_FILE_MAIN,&super,1);
r = (cq.vfs_funcs.close)(cq.vfs_data,COQUET_FILE_MAIN);
bail(&cq,r);
r = coquet_finish(&cq);
--- /dev/null
+#include "page.h"
+
+off_t cq_offset_main_page(coquet_t *cq, int which_file, u_int64_t pgid) {
+ struct cq_filemeta *fm;
+ off_t header;
+ off_t nursery;
+
+ fm = &(cq->filemeta[which_file]);
+ nursery = (2<<(fm->super.current.nursery_size + fm->super.current.block_size));
+ header = 2*SUPER_BYTES + nursery;
+
+ return header + (pgid-1) * (1<<fm->super.current.block_size);
+}
+
+int cq_load_page(coquet_t *cq, int which_file, uint64_t pgid, uint64_t **buffer, int *len) {
+ /* TODO nursery */
+
+ return COQUET_RET_OK;
+}
+
+int cq_save_page(coquet_t *cq, int which_file, uint64_t *pgid, uint8_t *buffer, int len) {
+ int r;
+ off_t offset;
+ struct cq_filemeta *fm;
+
+ /* TODO nursery */
+
+ fm = &(cq->filemeta[which_file]);
+
+ /* check we're open for writing */
+ if(!cq_writing(cq,which_file)) {
+ return COQUET_RET_TRSMERROR;
+ }
+
+ /* check we've got the right amount of data */
+ if(len != 1 << fm->super.current.block_size) {
+ return COQUET_RET_BADARGS;
+ }
+
+ /* write to the end of main (always, for now) */
+ offset = cq_offset_main_page(cq,which_file,fm->last_main_block+1);
+
+ /* write */
+ r = (cq->vfs_funcs.write)(cq->vfs_data,which_file,buffer,offset,len);
+ if(r != COQUET_RET_OK)
+ return r;
+
+ /* update metadata */
+ fm->last_main_block += 1;
+ *pgid = fm->last_main_block;
+
+ return COQUET_RET_OK;
+}
+
+#ifdef COQUET_TEST
+
+#include <stdio.h>
+#include <string.h>
+#include "test.h"
+
+void test_page() {
+ uint64_t pgid,pgid2;
+ uint8_t buffer[4096],buffer2[4096];
+ coquet_t cq;
+ struct cq_super super;
+ int r;
+
+ /* write two pages, one of all 0xC3, and one of all 0x3C */
+ /* TODO, when we have proper end detection, this needs to become a
+ * plausible root page.
+ */
+ memset(buffer,0xC3,4096);
+
+ r = coquet_init(&cq,"tmp/pagetest");
+ test_bail(&cq,r);
+
+ r = (cq.vfs_funcs.delete(cq.vfs_data,COQUET_FILE_MAIN));
+ test_bail(&cq,r);
+
+ /* first page */
+ r = (cq.vfs_funcs.open)(cq.vfs_data,COQUET_FILE_MAIN,COQUET_CMODE_CREATE);
+ test_bail(&cq,r);
+
+ cq_super_load(&cq,COQUET_FILE_MAIN,&super,1);
+ cq_super_save(&cq,COQUET_FILE_MAIN,&super,1);
+
+ r = coquet_write_start(&cq,COQUET_FILE_MAIN,1);
+ test_bail(&cq,r);
+
+ r = cq_save_page(&cq,COQUET_FILE_MAIN,&pgid,buffer,4096);
+ test_bail(&cq,r);
+
+ /* close and reopen to make it work it all out again */
+ r = coquet_write_end(&cq,COQUET_FILE_MAIN);
+ test_bail(&cq,r);
+
+ r = (cq.vfs_funcs.close)(cq.vfs_data,COQUET_FILE_MAIN);
+ test_bail(&cq,r);
+
+ r = (cq.vfs_funcs.open)(cq.vfs_data,COQUET_FILE_MAIN,COQUET_CMODE_OPEN);
+ test_bail(&cq,r);
+
+ r = coquet_write_start(&cq,COQUET_FILE_MAIN,1);
+ test_bail(&cq,r);
+
+ /* second page */
+ memset(buffer,0x3C,4096);
+
+ r = cq_save_page(&cq,COQUET_FILE_MAIN,&pgid2,buffer,4096);
+ test_bail(&cq,r);
+
+ test_eq_int(pgid,1,"pgid1");
+ test_eq_int(pgid2,2,"pgid2");
+
+ r = coquet_write_end(&cq,COQUET_FILE_MAIN);
+ test_bail(&cq,r);
+
+ /* nursery is (2 sets of 2^12 lots of 10-bit blocks = 2 * 2^22 bytes)
+ *
+ * File should look like this
+ * 0x00000000 - 0x00000FFF superblock
+ * 0x00001000 - 0x00002FFF lock region
+ * 0x00002000 - 0x00401FFF nursery A
+ * 0x00402000 - 0x00801FFF nursery B
+ * 0x00802000 - 0x00802FFF pgid = 1 value = 0xC3
+ * 0x00803000 - 0x00803FFF pgid = 2 value = 0x3C
+ */
+ r = (cq.vfs_funcs.read)(cq.vfs_data,COQUET_FILE_MAIN,buffer2,0x00802000,4096);
+ test_bail(&cq,r);
+ memset(buffer,0xC3,4096);
+ test_assert(!memcmp(buffer,buffer2,4096),"pgid1 data");
+
+ r = (cq.vfs_funcs.read)(cq.vfs_data,COQUET_FILE_MAIN,buffer2,0x00803000,4096);
+ test_bail(&cq,r);
+ memset(buffer,0x3C,4096);
+ test_assert(!memcmp(buffer,buffer2,4096),"pgid2 data");
+
+ r = coquet_finish(&cq);
+ test_bail(&cq,r);
+}
+
+#endif
--- /dev/null
+#ifndef COQUET_PAGE_H
+#define COQUET_PAGE_H
+
+#include <stdint.h>
+#include "coquet.h"
+
+/* byte offset of given main page. Note pgids are 1-based */
+off_t cq_offset_main_page(coquet_t *cq, int which_file, u_int64_t pgid);
+
+/* Load/Save page with given pgid. Translation to nursery addresses (if
+ * needed) occurs immediately INSIDE these functions.
+ */
+int cq_load_page(coquet_t *cq, int which_file, uint64_t pgid, uint64_t **buffer, int *len);
+int cq_save_page(coquet_t *cq, int which_file, uint64_t *pgid, uint8_t *buffer, int len);
+
+#ifdef COQUET_TEST
+void test_page();
+#endif
+
+#endif
* 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 create) {
+int cq_super_load(coquet_t *cq, int which_file, struct cq_super *super, bool create) {
uint8_t super_bytes[SUPER_BYTES];
struct cq_super super_a, super_b;
int r,r2;
bool use_b;
r = (cq->vfs_funcs.read)
- (cq->vfs_data,COQUET_FILE_MAIN,super_bytes,0,SUPER_BYTES);
+ (cq->vfs_data,which_file,super_bytes,0,SUPER_BYTES);
if(r != COQUET_RET_OK) {
return r;
}
return COQUET_RET_OK;
}
-static int lock_super(coquet_t *cq, int mode, bool wait) {
+static int lock_super(coquet_t *cq, int which_file, int mode, bool wait) {
return (cq->vfs_funcs.lock)
- (cq->vfs_data,COQUET_LOCK_SUPERBLOCK,mode,wait);
+ (cq->vfs_data,which_file,COQUET_LOCK_SUPERBLOCK,mode,wait);
}
static void intract_config(uint8_t *data,
return r;
}
-int cq_super_save(coquet_t *cq, struct cq_super *super, bool wait) {
+int cq_super_save(coquet_t *cq, int which_file, struct cq_super *super, bool wait) {
int r, ret;
struct cq_super old;
/* lock */
- r = lock_super(cq,COQUET_LMODE_EXCL,wait);
+ r = lock_super(cq,which_file,COQUET_LMODE_EXCL,wait);
if(r != COQUET_RET_OK) {
return r;
}
/* load */
- ret = cq_super_load(cq,&old,0);
+ ret = cq_super_load(cq,which_file,&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) {
ret = (cq->vfs_funcs.sync)(cq->vfs_data,COQUET_FILE_MAIN,1);
/* unlock */
- r = lock_super(cq,COQUET_LMODE_UN,wait);
+ r = lock_super(cq,which_file,COQUET_LMODE_UN,wait);
if(r != COQUET_RET_OK) {
return r;
}
test_open(&cq);
- r = cq_super_load(&cq,&super,1);
+ r = cq_super_load(&cq,COQUET_FILE_MAIN,&super,1);
test_bail(&cq,r);
- cq_super_save(&cq,&super,1);
+ cq_super_save(&cq,COQUET_FILE_MAIN,&super,1);
test_bail(&cq,r);
super.desired.nursery_size = 11; /* change something for B */
cq_super_set_desc(&super,"test file");
- cq_super_save(&cq,&super,1);
+ cq_super_save(&cq,COQUET_FILE_MAIN,&super,1);
test_bail(&cq,r);
test_close(&cq);
/* reopen to check the marshalling */
test_open(&cq);
- r = cq_super_load(&cq,&super,1);
+ r = cq_super_load(&cq,COQUET_FILE_MAIN,&super,1);
test_bail(&cq,r);
test_assert(super.version == COQUET_VERSION,"version");
/* check updated in file successfully */
test_open(&cq);
- cq_super_save(&cq,&super,1);
+ cq_super_save(&cq,COQUET_FILE_MAIN,&super,1);
test_close(&cq);
test_open(&cq);
- r = cq_super_load(&cq,&super,1);
+ r = cq_super_load(&cq,COQUET_FILE_MAIN,&super,1);
test_bail(&cq,r);
desc = cq_super_get_desc(&super);
test_bail(cq,r);
test_open(cq);
- r = cq_super_load(cq,&super,1);
+ r = cq_super_load(cq,COQUET_FILE_MAIN,&super,1);
super.current.nursery_size = 12;
- cq_super_save(cq,&super,1);
+ cq_super_save(cq,COQUET_FILE_MAIN,&super,1);
if(also_b) {
super.current.nursery_size = 11;
- cq_super_save(cq,&super,1);
+ cq_super_save(cq,COQUET_FILE_MAIN,&super,1);
}
if(corrupt>-1) {
r = (cq->vfs_funcs.read)(cq->vfs_data,COQUET_FILE_MAIN,&d,corrupt,1);
/* check */
test_open(cq);
- r = cq_super_load(cq,&super,1);
+ r = cq_super_load(cq,COQUET_FILE_MAIN,&super,1);
test_bail(cq,r);
test_eq_int(super.current.nursery_size,check,"c1");
test_close(cq);
creation
a/b choice
-magic
*/
#include "coquet.h"
-int cq_super_save(coquet_t *cq, struct cq_super *super, bool wait);
-int cq_super_load(coquet_t *cq, struct cq_super *super, bool create);
+int cq_super_save(coquet_t *cq, int which_file, struct cq_super *super, bool wait);
+int cq_super_load(coquet_t *cq, int which_file, struct cq_super *super, bool create);
void cq_super_set_desc(struct cq_super *super, char *desc);
char * cq_super_get_desc(struct cq_super *super);
#include <fcntl.h>
#include "sha2.h"
#include "superblock.h"
+#include "page.h"
#include "test.h"
void test_assert(int is_true, char *msg) {
printf("testing\n");
test_sha2();
test_superblock();
+ test_page();
return 0;
}
which_file, data, offset, length);
}
-static int test_lock(void * vfs_data, int which_lock, int lock_mode,
+static int test_lock(void * vfs_data, int which_file, int which_lock, int lock_mode,
bool wait) {
struct test_data * td = (struct test_data *)vfs_data;
if(td->virtual)
return COQUET_RET_OK;
- return (td->vfs_funcs.lock)(td->vfs_data,
+ return (td->vfs_funcs.lock)(td->vfs_data, which_file,
which_lock, lock_mode, wait);
}
}
/* main_fd must be open */
-static int min_size(struct unix_data *pd, off_t offset) {
+static int min_size(struct unix_data *pd, int fd, off_t offset) {
int r;
off_t r_off;
char buf[1] = {0};
return COQUET_RET_OK;
}
- r_off = lseek(pd->main_fd,offset,SEEK_SET);
+ r_off = lseek(fd,offset,SEEK_SET);
if(r_off == -1) {
set_error(pd,"seek failed",1);
return COQUET_RET_VFSERR;
}
- r = write(pd->main_fd,buf,1);
+ r = write(fd,buf,1);
if(r == -1) {
set_error(pd,"write failed",1);
return COQUET_RET_VFSERR;
return COQUET_RET_OK;
}
-#define LOCK_BLOCK 128
-static int unix_lock(void * vfs_data, int which_lock, int lock_mode,
+#define LOCK_BLOCK (SUPER_BYTES/NUM_LOCKS)
+static int unix_lock(void * vfs_data, int which_file, int which_lock, int lock_mode,
bool wait) {
struct unix_data * pd = (struct unix_data *)vfs_data;
- int r, op;
+ int r, op, *fd;
struct flock flk;
- if(pd->main_fd==-1) {
+ fd = file_fd(pd,which_file);
+ if(*fd==-1) {
set_error(pd,"main file closed during locking",0);
return COQUET_RET_VFSERR;
}
/* Lock region needs to exist. We can append zeroes.*/
- r = min_size(pd,SUPER_BYTES+LOCK_BLOCK*NUM_LOCKS);
+ r = min_size(pd,*fd,SUPER_BYTES+LOCK_BLOCK*NUM_LOCKS);
if(r != COQUET_RET_OK) {
return r;
}
op = F_OFD_SETLK;
}
- r = fcntl(pd->main_fd,op,&flk);
+ r = fcntl(*fd,op,&flk);
if(r == -1) {
set_error(pd,"flock failed",1);
return COQUET_RET_VFSERR;
* purpose. COQUET_FILE_MAIN will be open when called. If wait is true,
* function will wait, otherwise return COQUET_RET_LOCKED
*/
- int (*lock)(void * vfs_data, int which_lock, int lock_mode,
+ int (*lock)(void * vfs_data, int which_file, int which_lock, int lock_mode,
bool wait);
/* Open the given file. which_file is drawn from COQUET_FILE_*.