// sbfs.h // The SplitBit Filesystem, version two. // // This is the host side's copy of the format. The other implementation is SplitBit // assembly running on the machine itself, so nothing can be shared between them except // the specification: the two have to be kept honest by a document rather than by a // header. Everything here follows that document exactly, and anything that changes here // has to change there in the same breath. // // All multi byte numbers are most significant byte first, the same as every other number // SplitBit stores: addresses, the SPBT boot image header, and the vector table. // // Written by Anachronaut #ifndef SBFS_H #define SBFS_H #include #define SBFS_MAGIC "SBFS" #define SBFS_MAGIC_BYTES 4 // ---- Versions ---- // // Version two adds directories, and adds them without moving a single byte that version // one had defined: the parent lives in two of the four bytes each entry already reserved, // and a directory is a flag bit in a byte that was using one of its eight. // // A VERSION ONE DISK IS ALREADY A VALID VERSION TWO DISK. The parent field is written as // the entry's index PLUS ONE, so that the zero a version one disk has in those reserved // bytes reads as "in the root" - which is exactly what every file on a flat disk is in. // There is nothing to convert and no tool to convert it with. // // Compatibility therefore runs one way, which is the ordinary shape of it: this reads // either version, and version one code reading a version two disk would list directories // as strange empty files. A disk is written back at the version it was read at, and is // only raised to two by the thing that makes the difference real - the first directory // created on it. #define SBFS_VERSION_FLAT 1 #define SBFS_VERSION_TREE 2 #define SBFS_VERSION SBFS_VERSION_TREE #define SBFS_BLOCK_BYTES 256 // ---- Block 0, the superblock ---- // // 0 4 "SBFS" // 4 1 Version // 5 1 Reserved // 6 2 Blocks on the disk // 8 2 First directory block // 10 2 Blocks the directory occupies // 12 2 Free blocks, a cache rather than the authority // 14 2 Blocks in each boot slot, or zero for a disk that cannot be booted // 16 1 Which boot slot is live, 0 or 1 // 17 Reserved to the end of the block #define SBFS_SUPER_VERSION 4 #define SBFS_SUPER_DISK 6 #define SBFS_SUPER_DIRSTART 8 #define SBFS_SUPER_DIRBLOCKS 10 #define SBFS_SUPER_FREE 12 #define SBFS_SUPER_BOOTBLOCKS 14 #define SBFS_SUPER_BOOTSLOT 16 // ---- The boot area ---- // // Blocks between the superblock and the directory, which the filesystem never allocates // and never sees. Nothing had to be added to make room for them: both implementations // work out the first usable data block as directoryStart + directoryBlocks, and // directoryStart is a field rather than a constant, so moving the directory up reserves // everything below it by arithmetic that was already there. // // A DISK MADE BEFORE THIS HAS ZERO HERE, which reads as "no boot area", which is true. // The same shape as the version two parent field: the value an older disk already holds // is the correct answer rather than something needing conversion. // // TWO SLOTS, ALWAYS, and the reason is that a boot slot is raw blocks. A file being // rewritten is protected by writing a temporary and renaming it, and there is no name // here to rename - so a machine interrupted while updating its only boot slot would not // boot at all, which is the one failure on this disk with no way back. Writing the slot // that is not live and then moving one byte turns that into a machine that boots the // version it had before. // // block 0 the superblock // 1 .. bootBlocks slot 0 // bootBlocks+1 .. 2*bootBlocks slot 1 // directoryStart .. the directory, and then files #define SBFS_BOOT_SLOTS 2 #define SBFS_FIRST_BOOT_BLOCK 1 // ---- Directory entries ---- // // 0 1 Flags // 1 2 First block of the file's data // 3 2 Whole blocks the file occupies // 5 1 Bytes in the trailing part block, or zero if there is not one // 6 22 Name, padded with zeroes // 28 2 Parent, as an entry index plus one. Zero is the root. (Version two.) // 30 2 Reserved // // Thirty two divides two hundred and fifty six, so an entry never straddles a block and // reading one never means handling a split. Version two did not change that, because it // spent bytes that were already inside the entry. // // A DIRECTORY IS AN ENTRY WITH NO BLOCKS. Its start, blocks and tail are all zero, and it // costs one entry and nothing else. That is what keeps the flat array of entries the // whole allocation map: with files laid down contiguously, every block is inside some // entry's range or it is not, and an entry with no range is in nobody's way. // // Because parents are entry indices, and because nothing ever compacts the directory, // those indices are stable for as long as an entry is in use. DELETING A DIRECTORY THAT // STILL HAS CHILDREN MUST BE REFUSED: the freed index would be handed to some unrelated // file later, and the orphans would reappear inside it. #define SBFS_ENTRY_BYTES 32 #define SBFS_ENTRIES_PER_BLOCK (SBFS_BLOCK_BYTES / SBFS_ENTRY_BYTES) #define SBFS_ENTRY_FLAGS 0 #define SBFS_ENTRY_START 1 #define SBFS_ENTRY_BLOCKS 3 #define SBFS_ENTRY_TAIL 5 #define SBFS_ENTRY_NAME 6 #define SBFS_NAME_BYTES 22 #define SBFS_ENTRY_PARENT 28 #define SBFS_FLAG_IN_USE 0x01 #define SBFS_FLAG_DIRECTORY 0x02 // A FILE BEING WRITTEN, WHICH IS NOT YET A FILE. Saving something that already exists is // done by writing a temporary, deleting the original and giving the temporary its name, // so that nothing is lost if the writing fails. The temporary has to be an ordinary entry // while that happens - it holds real blocks and needs a name - and the only thing that // distinguishes it from a finished file is that nobody has committed it yet. // // That is not a property of its contents. The same bytes become the real file the moment // the rename lands, so there is nothing to put inside it that would be true; it belongs // in the entry, which is the thing the commit changes. It was formerly told apart by // being called "sbfs.part" or "sbfs.out", and those are legal names a user may also // choose, so cleaning up by name could delete somebody's file. // // Cleared as part of committing. An entry still carrying it is the wreckage of a write // that stopped, and its blocks are spoken for until something clears it up. #define SBFS_FLAG_TEMPORARY 0x04 // The root is not an entry. It is the absence of a parent, written as zero, which is why // the field is an index plus one and why a freshly zeroed entry is already in the root. #define SBFS_PARENT_ROOT 0 #define SBFS_PARENT_OF(index) ((uint16_t)((index) + 1)) #define SBFS_PARENT_INDEX(parent) ((int)(parent) - 1) // ---- How big a directory may be ---- // // The parent is an index plus one in sixteen bits, so index 65535 has no representation: // adding one wraps to zero, and zero is the root. An entry that cannot be named as a // parent is a directory that cannot hold anything, and it does not fail by refusing. // // WHAT IT DOES INSTEAD IS WORSE THAN FAILING. Creating something inside it writes a // parent of zero, so the thing lands in the root while the tool reports the path it was // asked for. Looking in that directory afterwards finds nothing, because the search is // for a parent of 65536 and the entry says zero - so the same create succeeds again, and // again, piling up entries of one name in the root. Duplicate names in one directory are // the one thing rename refuses outright, on the grounds that a search answers with // whichever it meets first and the rest can never be reached; this manufactured them. // // Eight entries to a block, and 65535 entries is the most that leaves every index one // short of the wrap. 8191 blocks gives 65528 of them, which is the last whole block that // fits. Checked when formatting and again when reading, because a disk claiming more may // have been made by something that never checked at all. #define SBFS_MAX_DIRECTORY_BLOCKS 8191 #define SBFS_MAX_ENTRIES (SBFS_MAX_DIRECTORY_BLOCKS * SBFS_ENTRIES_PER_BLOCK) // Paths are separated by this, and a leading one means "from the root". A name may not // contain it, which is what makes a path unambiguous without any quoting. #define SBFS_SEPARATOR '/' // The directory begins at block 1 and is this many blocks unless told otherwise, which // is sixty four files. The superblock carries the real number, so this is only what a // freshly formatted disk gets. #define SBFS_FIRST_DIRECTORY_BLOCK 1 #define SBFS_DEFAULT_DIRECTORY_BLOCKS 8 // A file of n bytes occupies n / 256 whole blocks and, if anything is left over, one more // for the tail. Zero means zero in both, so an empty file occupies nothing at all. #define SBFS_WHOLE_BLOCKS(bytes) ((bytes) / SBFS_BLOCK_BYTES) #define SBFS_TAIL_BYTES(bytes) ((bytes) % SBFS_BLOCK_BYTES) #define SBFS_BLOCKS_USED(blocks, tail) ((blocks) + ((tail) ? 1 : 0)) #endif // SBFS_H