jcs
/subtext
/amendments
/44
bile: Add initial binary file API
This will eventually replace resource files for the backend which are
not well-suited for writing new/modified records
jcs made amendment 44 over 2 years ago
--- bile.c Mon Jan 3 22:04:09 2022
+++ bile.c Mon Jan 3 22:21:19 2022
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include "bile.h"
+#include "util.h"
+
+void bile_write_map(struct bile *bile);
+void bile_sort_objects(struct bile *bile);
+void bile_free(struct bile *bile, unsigned long id);
+
+struct bile *
+bile_create(Str255 filename, OSType creator)
+{
+ struct bile *bile;
+ size_t len;
+ char *magic;
+ short error, fh;
+
+ /* create file */
+ error = Create(filename, 0, creator, BILE_FILE_TYPE);
+ if (error)
+ panic("bile_create: failed to create %s: %d", PtoCstr(filename),
+ error);
+
+ error = FSOpen(filename, 0, &fh);
+ if (error)
+ panic("bile_create: failed to open %s: %d", PtoCstr(filename),
+ error);
+
+ bile = xmalloczero(sizeof(struct bile));
+ bile->vrefid = fh;
+ memcpy(bile->filename, filename, sizeof(bile->filename));
+
+ /* write magic */
+ len = BILE_MAGIC_LEN;
+ magic = xstrdup(BILE_MAGIC);
+ error = FSWrite(bile->vrefid, &len, magic);
+ free(magic);
+ if (error)
+ goto write_error;
+
+ /* write empty map */
+ len = sizeof(long);
+ error = FSWrite(bile->vrefid, &len, &bile->map_pos);
+ if (error)
+ goto write_error;
+
+ len = sizeof(long);
+ error = FSWrite(bile->vrefid, &len, &bile->map_size);
+ if (error)
+ goto write_error;
+
+ GetFPos(fh, &bile->file_size);
+
+ if (bile->file_size != BILE_HEADER_LEN)
+ panic("bile_create: incorrect length after writing: %ld (!= %ld)",
+ bile->file_size, (long)BILE_HEADER_LEN);
+
+ return bile;
+
+write_error:
+ panic("bile_create: failed to write to %s: %d", PtoCstr(filename),
+ error);
+}
+
+struct bile *
+bile_open(Str255 filename)
+{
+ struct bile *bile;
+ size_t file_size, map_size, size;
+ short error, fh;
+ char magic[BILE_MAGIC_LEN + 1];
+
+ /* open file */
+ error = FSOpen(filename, 0, &fh);
+ if (error)
+ panic("bile_open: failed to open %s: %d", PtoCstr(filename),
+ error);
+
+ error = SetFPos(fh, fsFromLEOF, 0);
+ if (error)
+ panic("bile_open: SetFPos: %d", error);
+ GetFPos(fh, &file_size);
+
+ SetFPos(fh, fsFromStart, 0);
+
+ /* verify magic */
+ size = BILE_MAGIC_LEN;
+ error = FSRead(fh, &size, &magic);
+ if (error)
+ panic("bile_open: failed reading from %s: %d", PtoCstr(filename),
+ error);
+
+ if (strncmp(magic, BILE_MAGIC, BILE_MAGIC_LEN) != 0)
+ panic("bile_open: bad magic in %s", PtoCstr(filename));
+
+ bile = xmalloczero(sizeof(struct bile));
+ bile->vrefid = fh;
+ memcpy(bile->filename, filename, sizeof(filename));
+ bile->file_size = file_size;
+
+ /* load map */
+ size = sizeof(long);
+ error = FSRead(fh, &size, &bile->map_pos);
+ if (error)
+ goto read_error;
+
+ size = sizeof(long);
+ error = FSRead(fh, &size, &bile->map_size);
+ if (error)
+ goto read_error;
+
+ if (bile->map_pos + bile->map_size > file_size)
+ panic("bile_open: map points to %lu + %lu, but file is only %lu",
+ bile->map_pos, bile->map_size, file_size);
+
+ if (bile->map_size) {
+ error = SetFPos(bile->vrefid, fsFromStart, bile->map_pos +
+ BILE_OBJECT_SIZE);
+ if (error)
+ goto read_error;
+
+ /* read size */
+ size = bile->map_size;
+ bile->map = xmalloczero(bile->map_size);
+ error = FSRead(fh, &size, bile->map);
+ if (error)
+ goto read_error;
+ bile->nobjects = bile->map_size / BILE_OBJECT_SIZE;
+ }
+
+ return bile;
+
+read_error:
+ panic("bile_open: failed reading from %s: %d", PtoCstr(filename),
+ error);
+}
+
+void
+bile_close(struct bile *bile)
+{
+ FSClose(bile->vrefid);
+ free(bile->map);
+}
+
+/* XXX: these become invalid once we re-sort, should we return a copy? */
+struct bile_object *
+bile_find(struct bile *bile, unsigned long id)
+{
+ struct bile_object *o;
+ unsigned long n;
+
+ for (n = 0; n < bile->nobjects; n++) {
+ o = &bile->map[n];
+ if (o->id == id)
+ return o;
+ }
+
+ return NULL;
+}
+
+struct bile_object *
+bile_alloc(struct bile *bile, size_t size)
+{
+ struct bile_object *new, *o;
+ size_t last_pos = BILE_HEADER_LEN;
+ short n;
+
+ bile->map = xreallocarray(bile->map, bile->nobjects + 1,
+ BILE_OBJECT_SIZE);
+
+ /* find a last_pos we can use */
+ for (n = 0; n < bile->nobjects; n++) {
+ if (bile->map[n].pos - last_pos >= (size + BILE_OBJECT_SIZE))
+ break;
+ last_pos = bile->map[n].pos + BILE_OBJECT_SIZE + bile->map[n].size;
+ }
+
+ new = &bile->map[bile->nobjects];
+ bile->nobjects++;
+ new->pos = last_pos;
+ new->size = size;
+
+ bile_sort_objects(bile);
+
+ /* find our new object pointer after sorting */
+ for (n = 0; n < bile->nobjects; n++) {
+ o = &bile->map[n];
+ if (o->pos == last_pos)
+ return o;
+ }
+
+ panic("bile_alloc: couldn't find newly added object?");
+}
+
+void
+bile_sort_objects(struct bile *bile)
+{
+ short i, j;
+ struct bile_object o;
+
+ /* sort maps by position */
+ for (i = 0; i < bile->nobjects; i++) {
+ for (j = 0; j < bile->nobjects - i - 1; j++) {
+ if (bile->map[j].pos > bile->map[j + 1].pos) {
+ o = bile->map[j];
+ bile->map[j] = bile->map[j + 1];
+ bile->map[j + 1] = o;
+ }
+ }
+ }
+}
+
+void
+bile_free(struct bile *bile, unsigned long id)
+{
+ /* TODO */
+}
+
+size_t
+bile_read(struct bile *bile, unsigned long id, char **data)
+{
+ struct bile_object *o = bile_find(bile, id);
+ size_t rsize;
+ short error;
+
+ if (o == NULL)
+ return -1;
+
+ if (*data == NULL)
+ *data = xmalloc(o->size);
+
+ if (o->pos + BILE_OBJECT_SIZE + o->size > bile->file_size)
+ panic("bile_read: object %ld pos %ld size %ld > file size %ld",
+ id, o->pos, o->size, bile->file_size);
+
+ error = SetFPos(bile->vrefid, fsFromStart, o->pos + BILE_OBJECT_SIZE);
+ if (error)
+ goto read_error;
+
+ /* TODO: read object header and verify size and id */
+
+ rsize = o->size;
+ error = FSRead(bile->vrefid, &rsize, *data);
+ if (error)
+ goto read_error;
+ if (rsize != o->size)
+ panic("bile_open: needed to read %ld, read %ld", o->size, rsize);
+
+ return o->size;
+
+read_error:
+ panic("bile_open: failed reading from %s: %d", PtoCstr(bile->filename),
+ error);
+}
+
+size_t
+bile_write(struct bile *bile, unsigned long id, char *data, size_t size)
+{
+ struct bile_object *old, *new_obj;
+ size_t ret, wsize;
+ short error;
+
+ new_obj = bile_alloc(bile, size);
+ /* we must find after alloc because it re-sorts */
+ old = bile_find(bile, id);
+ new_obj->id = id;
+
+ if (new_obj->pos >= bile->file_size)
+ error = SetFPos(bile->vrefid, fsFromLEOF, 0);
+ else
+ error = SetFPos(bile->vrefid, fsFromStart, new_obj->pos);
+ if (error)
+ goto write_error;
+
+ wsize = BILE_OBJECT_SIZE;
+ error = FSWrite(bile->vrefid, &wsize, new_obj);
+ if (error)
+ goto write_error;
+
+ wsize = size;
+ error = FSWrite(bile->vrefid, &wsize, data);
+ if (error)
+ goto write_error;
+ if (wsize != size)
+ panic("bile_open: needed to write %ld, wrote %ld", size, wsize);
+
+ error = SetFPos(bile->vrefid, fsFromLEOF, 0);
+ if (error)
+ goto write_error;
+ GetFPos(bile->vrefid, &bile->file_size);
+
+ if (old)
+ old->id = BILE_PURGE_ID;
+
+ bile_write_map(bile);
+
+ return ret;
+
+write_error:
+ panic("bile_write: failed writing %ld to %s: %d", size,
+ PtoCstr(bile->filename), error);
+}
+
+void
+bile_write_map(struct bile *bile)
+{
+ struct bile_object *obj, *new_map_obj, *new_map;
+ size_t new_map_size, new_nobjects;
+ size_t size, n, new_n;
+ short error;
+ char *magic;
+
+ new_nobjects = 1; /* for the new map */
+ for (n = 0; n < bile->nobjects; n++) {
+ obj = &bile->map[n];
+
+ if (obj->pos == bile->map_pos || obj->id == BILE_PURGE_ID)
+ /* ignore old map and purgable objects */
+ continue;
+
+ new_nobjects++;
+ }
+
+ new_map_size = BILE_OBJECT_SIZE * new_nobjects;
+ new_map_obj = bile_alloc(bile, new_map_size);
+ new_map_obj->id = BILE_MAP_ID;
+
+ /* write new map object header */
+ if (new_map_obj->pos == bile->file_size)
+ error = SetFPos(bile->vrefid, fsFromLEOF, 0);
+ else
+ error = SetFPos(bile->vrefid, fsFromStart, new_map_obj->pos);
+ if (error)
+ goto write_error;
+
+ size = BILE_OBJECT_SIZE;
+ error = FSWrite(bile->vrefid, &size, &new_map_obj);
+ if (error)
+ goto write_error;
+
+ new_map = xmallocarray(BILE_OBJECT_SIZE, new_nobjects);
+ new_n = 0;
+ for (n = 0; n < bile->nobjects; n++) {
+ obj = &bile->map[n];
+
+ if (obj->pos == bile->map_pos || obj->id == BILE_PURGE_ID)
+ /* ignore old map and purgable objects */
+ continue;
+
+ new_map[new_n++] = *obj;
+ }
+
+ size = new_map_size;
+ error = FSWrite(bile->vrefid, &size, new_map);
+ if (error)
+ goto write_error;
+
+ error = SetFPos(bile->vrefid, fsFromLEOF, 0);
+ if (error)
+ goto write_error;
+ GetFPos(bile->vrefid, &bile->file_size);
+
+ /* successfully wrote new map, switch over */
+ free(bile->map);
+ bile->map = new_map;
+ bile->map_pos = new_map_obj->pos;
+ bile->map_size = new_map_obj->size;
+
+ /* write new header to point at new map object */
+ magic = xstrdup(BILE_MAGIC);
+ size = BILE_MAGIC_LEN;
+ error = SetFPos(bile->vrefid, fsFromStart, 0);
+ if (error)
+ goto write_error;
+ error = FSWrite(bile->vrefid, &size, magic);
+ free(magic);
+ if (error)
+ goto write_error;
+ size = sizeof(long);
+ error = FSWrite(bile->vrefid, &size, &bile->map_pos);
+ if (error)
+ goto write_error;
+ error = FSWrite(bile->vrefid, &size, &bile->map_size);
+ if (error)
+ goto write_error;
+
+ /* TODO: flush? */
+
+ return;
+
+write_error:
+ panic("bile_write_map: failed writing to %s: %d",
+ PtoCstr(bile->filename), error);
+
+}
--- bile.h Mon Jan 3 21:18:20 2022
+++ bile.h Mon Jan 3 22:22:07 2022
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "util.h"
+
+/*
+ * File format:
+ * [ bile header - BILE_HEADER_LEN ]
+ * [ BILE_MAGIC - BILE_MAGIC_LEN ]
+ * [ map position - long ]
+ * [ map size - long ]
+ * [ map contents - (map_size) ]
+ * [ object[0] position - long ]
+ * [ object[0] size - long ]
+ * [ object[0] id - long ]
+ * [ ... ]
+ * [ object[0] start (map points to this as its position) ]
+ * [ object[0] position - long ]
+ * [ object[0] size - long ]
+ * [ object[0] id - long ]
+ * [ object[1] start ]
+ * [ .. ]
+ */
+#define BILE_MAGIC "BILE1"
+#define BILE_MAGIC_LEN 5
+#define BILE_MAP_ID (ULONG_MAX)
+#define BILE_PURGE_ID (ULONG_MAX - 1)
+#define BILE_FILE_TYPE 'BILE'
+#define BILE_HEADER_LEN (BILE_MAGIC_LEN + (sizeof(long) + sizeof(long)))
+
+struct bile_object {
+ unsigned long pos;
+ unsigned long size;
+ unsigned long id;
+};
+#define BILE_OBJECT_SIZE (sizeof(struct bile_object))
+
+struct bile {
+ unsigned long map_pos;
+ unsigned long map_size;
+
+ short vrefid;
+ Str255 filename;
+ size_t file_size;
+ struct bile_object *map;
+ size_t nobjects;
+};
+
+struct bile * bile_create(Str255 filename, OSType creator);
+struct bile * bile_open(Str255 filename);
+void bile_close(struct bile *bile);
+struct bile_object * bile_find(struct bile *bile, unsigned long id);
+struct bile_object * bile_alloc(struct bile *bile, size_t size);
+size_t bile_read(struct bile *bile, unsigned long id, char **data);
+size_t bile_write(struct bile *bile, unsigned long id, char *data,
+ size_t size);