Tree file: Generic: Breath-first stored, first tree, then data offsets and sizes are uint32 time_t are uint32 with base stored in header data stored in big endian non-string blocks padded to 32bit all key names and values are utf8, without zeros filenames are byte strings Detailed: magic file type version guint32 rotated # != 0 => new file has been written, changed at runtime guint32 random_tag offset to root offset to keywords gint64 time_t base (other time_ts stored as offsets) keywords: n_keywords array of offset to keywords, sorted by keyword string block for keywords root dirent: offset to name ("/") offset to children for root offset to root metadata time_t last_change for root metadata children: Each dir in breath-first order: int num_children children, array of: (sorted by name) offset name offset children offset metadata time_t last_change_metadata string block for names: zero terminated strings metadata: each metadata block: (in breath first order) int num_keys keys, array of: sorted by keyword guint32 keyword | high bit set => is_list offset value (pointer to string, or array of strings) block of string arrays for values for each directory, string block of values for metadata in dir ---------------------------------------- ------------- Journal ------------------ ---------------------------------------- Fixed size, rotated when full Array of operations, each with a checksum Readers handle only up to first non-ok checksum Writer periodically rewrites stable tree and creates new journal strings are stored as plain zero terminated c-strings Updates to stable: 1 block writes 2 write new stable to tmp file, w/ fsync 3 create new empty journal (name based on random_tag) 4 rename new stable over old 5 set rotated to true in old (via open fd) 6 sync old fd 7 remove old journal 8 re-enable writes When opening a stable file + journal there is a race where we can open the old tree, but then the old journal is removed before we read it. To handle this, on open you must always re-check "rotated" after the journal has been opened (or failed to open) and verified Journal file header: char[6] magic char[2] file type version guint32 random_tag guint32 file_size # Must be same as file size guint32 num_entries Journal entry: guint32 entry_size # Must verify wrt file size (includes entry_size, etc) guint32 crc32 # crc32 of following data, including padding and last size guint64 mtime byte operation type (set: 0, set_list: 1: unset: 2, move: 3, copy: 4) cstring path # target if copy/move set: cstring key cstring value set_list: cstring key guint32 n_values cstring value uset: cstring key copy: (overwrites all destination data) cstring source_path remove: guint32 entry_size_end # Must be same as entry_size, for reverse skipping