SRCDIR := src
BINDIR := bin
-OBJDIR := obj
+OBJDIR := obj/main
+TESTOBJDIR := obj/test
+
+SRCFILES := unix.c util.c coquet.c superblock.c sha2.c testvfs.c
-TARGET := $(BINDIR)/coquet
-SRCFILES := main.c unix.c util.c coquet.c superblock.c sha2.c
SRC := $(addprefix $(SRCDIR)/,$(SRCFILES))
+DEPSRC := $(wildcard $(SRCDIR)/*.c)
+
+DEPS = $(DEPSRC:$(SRCDIR)/%.c=$(OBJDIR)/%.d)
OBJ := $(SRC:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
-DEPS := $(SRCFILES:%.c=$(OBJDIR)/%.d)
-# TARGET = coquet
+TESTDEPS = $(DEPSRC:$(SRCDIR)/%.c=$(TESTOBJDIR)/%.d)
+TESTOBJ := $(SRC:$(SRCDIR)/%.c=$(TESTOBJDIR)/%.o)
+
LIBS =
CC = gcc
CFLAGS = -g -Wall --std=c99
-.PHONY: default all clean
+.PHONY: default all clean test
-default: $(TARGET)
+default: $(BINDIR)/coquet-cli $(BINDIR)/coquet-test
all: default
+obj/test:
+ -mkdir obj/test
+
$(OBJDIR)/%.o: $(SRCDIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@
+$(TESTOBJDIR)/%.o: $(SRCDIR)/%.c obj/test
+ $(CC) $(CFLAGS) -DCOQUET_TEST -c $< -o $@
+
$(OBJDIR)/%.d: $(SRCDIR)/%.c
$(CC) -MM -MP -MT $(@:.d=.o) $< > $@
-$(TARGET): $(OBJ)
- $(CC) $(OBJ) -Wall $(LIBS) -o $@
+$(TESTOBJDIR)/%.d: $(SRCDIR)/%.c obj/test
+ $(CC) -DCOQUET_TEST -MM -MP -MT $(@:.d=.o) $< > $@
+
+$(BINDIR)/coquet-cli: $(OBJ) $(OBJDIR)/main.o
+ $(CC) $(OBJ) $(OBJDIR)/main.o -Wall $(LIBS) -o $@
+
+$(BINDIR)/coquet-test: $(TESTOBJ) $(TESTOBJDIR)/test.o
+ $(CC) $(TESTOBJ) $(TESTOBJDIR)/test.o -Wall $(LIBS) -o $@
# .PRECIOUS: $(TARGET) $(OBJECTS)
clean:
-rm -f $(OBJDIR)/*
- -rm -f $(TARGET)
+ -rm -f $(TESTOBJDIR)/*
+ -rm -f $(BINDIR)/*
--include $(SRC:$(SRCDIR)/%.c=$(OBJDIR)/%.d)
+-include $(DEPS)
+-include $(TESTDEPS)
#include "constants.h"
#include "coquet.h"
#include "util.h"
-
-extern vfs_t vfs_unix;
+#include "vfs.h"
/* VFS null is used as a placeholder prior to initialisation, so that
* subsequent tidies can be clean.
*/
vfs_t vfs_null = {
- .make = NULL,
.start = NULL,
.get_error_text = NULL,
.lock = NULL,
.finish = NULL
};
+/* Teardown and free vfs layer. */
+static int finish_vfs(coquet_t *cq) {
+ int r;
+
+ if(cq->vfs_funcs.finish != NULL) {
+ r = (cq->vfs_funcs.finish)(cq->vfs_data);
+ if(r != COQUET_RET_OK) {
+ return r;
+ }
+ cq->vfs_funcs.finish = NULL;
+ }
+
+ return COQUET_RET_OK;
+}
+
+#ifdef COQUET_TEST
+static int testvfs_wrap(coquet_t *cq) {
+ cq->vfs_data = testvfs_make(&cq->vfs_funcs,cq->vfs_data);
+ if(cq->vfs_data == NULL)
+ return COQUET_RET_HEAPERR;
+
+ cq->vfs_funcs = vfs_test;
+
+ return COQUET_RET_OK;
+}
+#endif
+
/* Allocate and initialise vfs layer. */
static int init_vfs(coquet_t *cq, char *basename) {
int r;
cq->vfs_funcs = vfs_unix;
-
- /* Allocate memory */
- cq->vfs_data = (cq->vfs_funcs.make)();
+ cq->vfs_data = unix_make();
if(cq->vfs_data == NULL) {
cq->vfs_funcs = vfs_null;
return COQUET_RET_HEAPERR;
}
+#ifdef COQUET_TEST
+ r = testvfs_wrap(cq);
+ if(r != COQUET_RET_OK) {
+ finish_vfs(cq);
+ return r;
+ }
+#endif
+
/* Initialise */
r = (cq->vfs_funcs.start)(cq->vfs_data,basename);
if(r != COQUET_RET_OK) {
return COQUET_RET_OK;
}
-/* Teardown and free vfs layer. */
-static int finish_vfs(coquet_t *cq) {
- int r;
-
- if(cq->vfs_funcs.make != NULL) {
- r = (cq->vfs_funcs.finish)(cq->vfs_data);
- if(r != COQUET_RET_OK) {
- return r;
- }
- cq->vfs_funcs.make = NULL;
- }
-
- return COQUET_RET_OK;
-}
-
/* Tear down whole library. Idempotent to allow crash recovery. */
int coquet_finish(coquet_t *cq) {
int r;
*/
char * coquet_error_string(coquet_t *cq, int error);
+#ifdef COQUET_TEST
+void test_bail(coquet_t * cq, int error_code);
+void test_unlink(char *path);
+#endif
+
#endif
* +-------------------+
* | magic | 0 8
* | version | 8 8
- * | sb_hash | 16 32
- * | sb_serial | 48 8
+ * | global_iv | 16 32
+ * | sb_hash | 48 32
+ * | sb_serial | 80 8
* +-------------------+
- * | unused | 56 968
+ * | unused | 88 936
* +-------------------+
* | current | 1024 512
* +-------------------+
*
* offset length
* +-------------------+
- * | global_iv | 0 32
- * | block_size | 32 1
- * | nursery_size | 33 1
+ * | block_size | 0 1
+ * | nursery_size | 1 1
* +-------------------+
- * | unused | 34 477
+ * | unused | 2 509
* +-------------------+
* | reserved | 511 1
* +-------------------+
*/
struct cq_super default_super = {
- .current = {{0,},12,10},
- .desired = {{0,},12,10},
+ .current = {12,10},
+ .desired = {12,10},
+ .global_iv = {0,},
.sb_serial = 0,
.sb_hash = {0,},
.version = COQUET_VERSION,
* 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);
- sc->nursery_size = be_decode(data+33,1);
+ sc->block_size = be_decode(data,1);
+ sc->nursery_size = be_decode(data+1,1);
}
static int extract_half(uint8_t *data, struct cq_super *super) {
return 0;
}
super->version = be_decode(data+8,8);
- /* All versions are fine for now. */
+ /* (all versions are fine for now). */
+
+ memcpy(super->global_iv,data+16,GLOBAL_IV_LEN);
memset(super->sb_hash,0,HASH_LEN);
- super->sb_serial = be_decode(data+48,8);
+ super->sb_serial = be_decode(data+80,8);
extract_config(data+1024,&(super->current));
extract_config(data+1536,&(super->desired));
/* verify */
- memcpy(hash,data+16,HASH_LEN);
- memset(data+16,0,HASH_LEN);
+ memcpy(hash,data+48,HASH_LEN);
+ memset(data+48,0,HASH_LEN);
sha2_init_hmac(&sha2, SHA2_VARIETY_512_256,
- super->current.global_iv, GLOBAL_IV_LEN);
+ super->global_iv, GLOBAL_IV_LEN);
sha2_more(&sha2,data,HALF_BYTES);
sha2_finish(&sha2,cmp,HASH_LEN);
if(memcmp(hash,cmp,HASH_LEN)) {
int r;
r = (cq->vfs_funcs.random)(cq->vfs_data,
- super->current.global_iv, GLOBAL_IV_LEN);
+ super->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;
}
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);
- be_encode(data+33,sc->nursery_size,1);
+ be_encode(data,sc->block_size,1);
+ be_encode(data+1,sc->nursery_size,1);
}
static void intract(uint8_t *data, struct cq_super *super, uint64_t serial) {
struct sha2_ctx_t sha2;
/* intract */
- memset(data,0,HASH_LEN);
+ memset(data,0,HALF_BYTES);
be_encode(data,COQUET_MAGIC,8);
be_encode(data+8,COQUET_VERSION,8);
- be_encode(data+48,serial,8);
+ memcpy(data+16,super->global_iv,GLOBAL_IV_LEN);
+ be_encode(data+80,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);
+ super->global_iv, GLOBAL_IV_LEN);
sha2_more(&sha2,data,HALF_BYTES);
- sha2_finish(&sha2,data+16,HASH_LEN);
+ sha2_finish(&sha2,data+48,HASH_LEN);
}
static int super_write(coquet_t *cq, struct cq_super *super,
return ret;
}
+
+#ifdef COQUET_TEST
+
+#include <stdio.h>
+
+void test_superblock() {
+ coquet_t cq;
+ int r;
+ struct cq_super super;
+
+ printf("testing superblock\n");
+ r = coquet_init(&cq,"tmp/test");
+ test_bail(&cq,r);
+
+ test_unlink("tmp/test.coquet");
+ r = (cq.vfs_funcs.open)(cq.vfs_data,COQUET_FILE_MAIN,COQUET_CMODE_EITHER);
+ test_bail(&cq,r);
+
+ testvfs_fakerandom(cq.vfs_data,0xA5);
+ r = cq_super_load(&cq,&super,1);
+ test_bail(&cq,r);
+
+ cq_super_save(&cq,&super,1);
+ test_bail(&cq,r);
+ testvfs_fakerandom(cq.vfs_data,-1);
+
+ r = (cq.vfs_funcs.close)(cq.vfs_data,COQUET_FILE_MAIN);
+ test_bail(&cq,r);
+
+ r = coquet_finish(&cq);
+ test_bail(&cq,r);
+}
+
+#endif
#define SUPERBLOCK_LAST_OFFSET (SUPER_BYTES-1)
struct cq_super_config {
- uint8_t global_iv[GLOBAL_IV_LEN];
uint8_t block_size; /* log2 bytes */
uint8_t nursery_size; /* log2 blocks */
};
struct cq_super_config current, desired;
uint64_t version;
uint64_t sb_serial;
+ uint8_t global_iv[GLOBAL_IV_LEN];
uint8_t sb_hash[32]; /* HMAC SHA512-256 using global_iv */
int from_b; /* true if loaded from b, false if from a */
};
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);
+#ifdef COQUET_TEST
+void test_superblock();
+#endif
+
#endif
\ No newline at end of file
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include "superblock.h"
+
+void test_unlink(char *path) {
+ int r;
+
+ r = unlink(path);
+ if(r && errno != ENOENT) {
+ fprintf(stderr,"Couldn't unlink '%s': %s\n",path,strerror(errno));
+ exit(1);
+ }
+}
+
+void test_bail(coquet_t * cq, int error_code) {
+ char *msg;
+
+ if(error_code == COQUET_RET_OK) {
+ return;
+ }
+ msg = coquet_error_string(cq,error_code);
+ if(!msg) {
+ fprintf(stderr,"Heap exhausted");
+ exit(1);
+ }
+ fprintf(stderr,"%s\n",msg);
+ free(msg);
+ coquet_finish(cq);
+ exit(1);
+}
+
+int main() {
+ printf("testing\n");
+ test_superblock();
+ return 0;
+}
--- /dev/null
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "vfs.h"
+#include "coquet.h"
+
+struct test_data {
+ int fake_random;
+ vfs_t vfs_funcs;
+ void *vfs_data;
+};
+
+void * testvfs_make(vfs_t *vfs_funcs, void *vfs_data) {
+ struct test_data *td;
+
+ td = malloc(sizeof(struct test_data));
+ if(td == NULL)
+ return NULL;
+ td->fake_random = -1;
+ td->vfs_funcs = *vfs_funcs;
+ td->vfs_data = vfs_data;
+ return td;
+}
+
+static int test_start(void * vfs_data, char *filename) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ printf("Using test VFS\n");
+ return (td->vfs_funcs.start)(td->vfs_data,filename);
+}
+
+static int test_finish(void *vfs_data) {
+ struct test_data * td = (struct test_data *)vfs_data;
+ int r;
+
+ r = (td->vfs_funcs.finish)(td->vfs_data);
+ if(r != COQUET_RET_OK)
+ return r;
+ free(td);
+ return COQUET_RET_OK;
+}
+
+static char * test_get_error_text(void * vfs_data) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ return (td->vfs_funcs.get_error_text)(td->vfs_data);
+}
+
+static int test_open(void * vfs_data, int which_file,
+ int mode) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ return (td->vfs_funcs.open)(td->vfs_data,which_file,mode);
+}
+
+static int test_close(void * vfs_data, int which_file) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ return (td->vfs_funcs.close)(td->vfs_data,which_file);
+}
+
+static int test_write(void * vfs_data, int which_file, uint8_t * data,
+ off_t offset, uint64_t length) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ return (td->vfs_funcs.write)(td->vfs_data,
+ which_file, data, offset, length);
+}
+
+static int test_read(void * vfs_data, int which_file, uint8_t * data,
+ uint64_t offset, uint64_t length) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ return (td->vfs_funcs.read)(td->vfs_data,
+ which_file, data, offset, length);
+}
+
+static int test_lock(void * vfs_data, int which_lock, int lock_mode,
+ bool_t wait) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ return (td->vfs_funcs.lock)(td->vfs_data,
+ which_lock, lock_mode, wait);
+}
+
+static int test_delete(void *vfs_data, int which_file) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ return (td->vfs_funcs.delete)(td->vfs_data, which_file);
+}
+
+static int test_sync(void * vfs_data, int which_file, bool_t data_only) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ return (td->vfs_funcs.sync)(td->vfs_data, which_file, data_only);
+}
+
+static int test_random(void * vfs_data, uint8_t *out, int len) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ if(td->fake_random < 0) {
+ return (td->vfs_funcs.random)(td->vfs_data, out, len);
+ } else {
+ memset(out,td->fake_random,len);
+ return COQUET_RET_OK;
+ }
+}
+
+void testvfs_fakerandom(void * vfs_data, int setting) {
+ struct test_data * td = (struct test_data *)vfs_data;
+
+ td->fake_random = setting;
+}
+
+vfs_t vfs_test = {
+ .start = test_start,
+ .get_error_text = test_get_error_text,
+ .lock = test_lock,
+ .open = test_open,
+ .close = test_close,
+ .write = test_write,
+ .read = test_read,
+ .finish = test_finish,
+ .delete = test_delete,
+ .sync = test_sync,
+ .random = test_random
+};
#define _GNU_SOURCE
-#include <stdio.h> /* During testing */
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
}
}
-static void * unix_make() {
+void * unix_make() {
struct unix_data * pd;
pd = malloc(sizeof(struct unix_data));
+ if(pd == NULL)
+ return NULL;
pd->seen_error = 0;
pd->error_text = NULL;
pd->filename = pd->dirname_buf = NULL;
}
vfs_t vfs_unix = {
- .make = unix_make,
.start = unix_start,
.get_error_text = unix_get_error_text,
.lock = unix_lock,
* COQUET_RET_VFS or COQUET_RET_HEAPERR unless docs here indicate otherwise.
*/
typedef struct vfs {
-
- /* Returning vfs_data payload which will then be
- * owned by caller until passed to finish on shutdown. Initialise
- * is then called. This is split in two to allow errors to be reported
- * in the regular way by init. May be null if heap allocation failed.
- */
- void * (*make)();
-
/* Initialise VFS layer. Use basename as filepath in all calls.
* basename = a/b/foo => a/b/foo.coquet, a/b/foo.coquet.tmp, etc.
* basename is still owned by caller.
} vfs_t;
+extern vfs_t vfs_unix;
+
+void * unix_make();
+
+#ifdef COQUET_TEST
+
+extern vfs_t vfs_test;
+
+void * testvfs_make(vfs_t *vfs_funcs, void *vfs_data);
+void testvfs_fakerandom(void * vfs_data, int setting);
+
+#endif /* COQUET_TEST */
+
#endif