use io; // BFS state export type bfs = struct { device: io::handle, bootsector: *bootsector, }; // Open a BFS from a device export fn open(device: io::handle) (*bfs | error | io::error) = { // Allocate bootsector object const bootsector = alloc(bootsector { ... }); // Allocate BFS object const bfs = alloc(bfs { device = device, bootsector = bootsector, }); // Pull the data match (pull(bfs)) { case let e: io::error => free(bootsector); free(bfs); return e; case => void; }; return bfs; }; // Pull the bootsector from the device export fn pull(filesystem: *bfs) (void | io::error) = { pread(filesystem.device, filesystem.bootsector: *[512]u8, 0)?; }; // Commit the bootsector to the device export fn commit(filesystem: *bfs) (void | io::error) = { pwrite(filesystem.device, filesystem.bootsector: *[512]u8, 0)?; }; // Close the BFS export fn close(filesystem: *bfs) (void | io::error) = { // Commit before closing commit(filesystem)?; // Freeing the underlying objects free(filesystem.bootsector); free(filesystem); }; // Allocate a block export fn allocblock(self: *bfs) (u64 | io::error) = { if (self.bootsector.lastblock == 0) { const blockid = self.bootsector.freeblock; self.bootsector.freeblock += 1; commit(self)?; return blockid; }; const blockid = self.bootsector.lastblock; let freeblock = free_block { ... }; getfreeblock(self, blockid, &freeblock)?; if (freeblock.prevblock == 0) self.bootsector.nextblock = 0; self.bootsector.lastblock = freeblock.prevblock; commit(self)?; return blockid; }; // Free a block given a block ID export fn freeblock(self: *bfs, blockid: u64) (void | io::error) = { if (self.bootsector.nextblock != 0) { const freeblock = free_block { prevblock = blockid, }; setfreeblock(self, self.bootsector.nextblock, &freeblock)?; } else self.bootsector.lastblock = blockid; const freeblock = free_block { ... }; setfreeblock(self, blockid, &freeblock)?; self.bootsector.nextblock = blockid; commit(self)?; }; // Clears an inode fn zeroinode(self: *bfs, inid: u64) (void | io::error) = { const in = inode { ... }; setinode(self, inid, &in)?; }; // Allocate a new inode export fn allocinode(self: *bfs) (u64 | io::error) = { // If no currently free inode block, if (self.bootsector.nextinblk == 0) { // Allocate new inode block const inblockid = allocblock(self)?; // Initialize it const inid = (inblockid << self.bootsector.blocksize) / size(inode): u64 + 1; const inblock = inode_block { freeinode = inid + 1, freecount = (1: u64 << self.bootsector.blocksize) / size(inode): u64 - 2, ... }; // Insert it insert_inodeblock(self, inblockid, &inblock)?; // Write to it setinblock(self, inblockid, &inblock)?; // Zero the inode zeroinode(self, inid)?; return inid; }; const inblockid = self.bootsector.nextinblk; // Get inode block let inblock = inode_block { ... }; getinblock(self, inblockid, &inblock)?; // Check if inode in inode freelist let inid = inblock.nextinode; if (inblock.nextinode == 0) { // No inode in freelist, bump freeinode inid = inblock.freeinode; inblock.freeinode += 1; } else { // Get the free inode's next inode (stored in block field) let in = inode { ... }; getinode(self, inblock.nextinode, &in)?; // Update the inode block first inode to it inblock.nextinode = in.block; }; // Decrement the inode block's freecount inblock.freecount -= 1; // If no free inode in the inode block, remove the inode block // from the freelist if (inblock.freecount == 0) remove_inodeblock(self, inblockid, &inblock)?; // Finally, set the block and zero the inode setinblock(self, inblockid, &inblock)?; zeroinode(self, inid)?; return inid; }; // Free an inode given its inode ID export fn freeinode(self: *bfs, inid: u64) (void | io::error) = { const inblockid = inid >> (self.bootsector.blocksize - 6); let inblock = inode_block { ... }; getinblock(self, inblockid, &inblock)?; let in = inode { ... }; getinode(self, inid, &in)?; in.block = inblock.nextinode; inblock.nextinode = inid; inblock.freecount += 1; if (inblock.freecount == ((1: u64 << self.bootsector.blocksize) / size(inode): u64 - 1)) { remove_inodeblock(self, inblockid, &inblock)?; freeblock(self, inblockid)?; return; }; if (inblock.freecount == 1) insert_inodeblock(self, inblockid, &inblock)?; setinode(self, inid, &in)?; setinblock(self, inblockid, &inblock)?; }; fn insert_inodeblock(self: *bfs, inblockid: u64, inblock: *inode_block) (void | io::error) = { inblock.prevblock = 0; if (self.bootsector.nextinblk != 0) { let nextinblock = inode_block { ... }; getinblock(self, self.bootsector.nextinblk, &nextinblock)?; nextinblock.prevblock = inblockid; setinblock(self, self.bootsector.nextinblk, &nextinblock)?; }; inblock.nextblock = self.bootsector.nextinblk; self.bootsector.nextinblk = inblockid; commit(self)?; }; fn remove_inodeblock(self: *bfs, inblockid: u64, inblock: *inode_block) (void | io::error) = { if (inblock.prevblock != 0) { let previnblock = inode_block { ... }; getinblock(self, inblock.prevblock, &previnblock)?; previnblock.nextblock = inblock.nextblock; setinblock(self, inblock.prevblock, &previnblock)?; } else { self.bootsector.nextinblk = inblock.nextblock; commit(self)?; }; if (inblock.nextblock != 0) { let nextinblock = inode_block { ... }; getinblock(self, inblock.nextblock, &nextinblock)?; nextinblock.prevblock = inblock.prevblock; setinblock(self, inblock.nextblock, &nextinblock)?; }; inblock.prevblock = 0; inblock.nextblock = 0; }; export fn open_file(self: *bfs, inid: u64) (*file | io::error) = { let in = alloc(inode { ... }); match (getinode(self, inid, in)) { case let e: io::error => free(in); return e; case => void; }; return alloc(file { stream_trait = &file_impl, fs = self, inid = inid, in = in, ... }); }; export fn create_file(self: *bfs) (*file | io::error) = { const inid = allocinode(self)?; let file = match (open_file(self, inid)) { case let e: io::error => freeinode(self, inid)!; return e; case let f: *file => yield f; }; file.in.block = match (allocblock(self)) { case let e: io::error => io::close(file)!; freeinode(self, inid)!; return e; case let b: u64 => yield b; }; return file; }; export fn root(self: *bfs) (*file | io::error) = { if (self.bootsector.rootinode == 0) { const file = create_file(self)?; self.bootsector.rootinode == file.inid; commit(self)!; return file; }; return open_file(self, self.bootsector.rootinode)?; };