// SCHFS - On-disk file system format. // Both the kernel and user programs use this header file. // // Changes from original: // - struct dinode gains atime, mtime, ctime (timestamps) and checksum // (CRC32c). Padding reduced from 44 to 28 bytes; struct stays 128 bytes. // - struct superblock gains checksum field. // - struct fsv1_extent added for T_EXTENT inode block mapping. // - OVERFLOW_DIR_START / OVERFLOW_DIR_END replace the bare "113 * BSIZE" // magic numbers used in fs.c and mkfs.c. #ifndef FS_H #define FS_H #define ROOTINO 1 // root i-number // Buffer-cache block size. Matches SCHFS_BLKSIZE deliberately. // // This was 1024, inherited from xv6, while a SchFS block is 4096 -- so every // SchFS block cost *four* bread() calls, and each bread() is one IDE request // with its own interrupt, sleep and wakeup. Loading the 800 KB toybox binary // meant ~800 round-trips, which is why every command took about two seconds: // `seq 1 200`, which touches no file and prints nothing, cost the same 2.0s as // `cat /bin/toybox`, because both are dominated by exec() reading the same // binary. `ls | wc -l` cost exactly double, loading it twice. // // At 4096 a SchFS block is one request. Keep the two equal. #define BSIZE 4096 // block size // Permission mode bits (rwmode field) #define RW 3 // 11 read+write #define W_ 1 // 01 write-only #define R_ 2 // 10 read-only #define NONE 0 // 00 no access // --------------------------------------------------------------------------- // Superblock // --------------------------------------------------------------------------- // Disk layout: // [ boot | super | log | inode-bitmap | inode-blocks | block-bitmap | data ] struct superblock { uint magic; // Must be FSMAGIC uint size; // Size of file system image (blocks) uint nblocks; // Number of data blocks uint ninodes; // Number of inodes uint nlog; // Number of log blocks uint logstart; // Block number of first log block uint inodestart; // Block number of first inode block uint bmapstart; // Block number of first free-block bitmap block uint ibmapstart; // Block number of first inode-bitmap block uint freeinodes; // Count of free inodes uint freeblocks; // Count of free blocks uint checksum; // CRC32c of this struct (field zeroed during computation) }; extern struct superblock sb; #define FSMAGIC 0x10203040 // --------------------------------------------------------------------------- // Block / inode geometry // --------------------------------------------------------------------------- #define NDIRECT 12 #define NINDIRECT (BSIZE / sizeof(uint)) // Maximum file size in blocks: direct + 1x + 2x + 3x indirect #define MAXFILE (NDIRECT \ + NINDIRECT \ + (NINDIRECT * NINDIRECT) \ + (NINDIRECT * NINDIRECT * NINDIRECT)) // --------------------------------------------------------------------------- // Extent descriptor (T_EXTENT inodes only) // // Up to MAX_INLINE_EXTENTS are packed into ip->addrs[0..NDIRECT-1]. // addrs[] = NDIRECT uints = 12*4 = 48 bytes // each fsv1_extent = 12 bytes → 48/12 = 4 inline extents // (addrs[NDIRECT], [NDIRECT+1], [NDIRECT+2] remain as indirect pointers) // // For files needing more extents, addrs[NDIRECT] points to an indirect block // of BSIZE/sizeof(struct fsv1_extent) = 85 additional extents. // --------------------------------------------------------------------------- // Renamed from "schfs_extent". The new format in include/schfs.h has its own, // completely different, struct of that name -- 64-bit fields and a refcount -- // and any file including both headers hit a redefinition error. Two structs // with one name and two layouts is a trap worth removing rather than working // around; this one belongs to the v1 format and dies with it. struct fsv1_extent { uint start_block; // logical block number in the file (first of run) uint start_blkaddr; // physical disk block address uint len; // number of contiguous blocks in this run }; // Inline extents packed into addrs[0 .. NDIRECT-1] only. #define MAX_INLINE_EXTENTS (NDIRECT * sizeof(uint) / sizeof(struct fsv1_extent)) // --------------------------------------------------------------------------- // On-disk inode (exactly 128 bytes; IPB = 8 inodes per 1024-byte block) // // Byte layout: // 0 short type 2 // 2 short major 2 // 4 short minor 2 // 6 short nlink 2 // 8 uint size 4 // 12 uint rwmode 4 // 16 uint supermode 4 // 20 uint addrs[15] 60 T_EXTENT: packed fsv1_extent[4] in [0..11], rest normal // 80 uint showmode 4 // 84 uint atime 4 ← last access time (Unix seconds) // 88 uint mtime 4 ← last modification time // 92 uint ctime 4 ← last metadata-change time // 96 uint checksum 4 ← CRC32c (zeroed during computation) // 100 uchar pad[28] 28 ← was 44; reduced by 16 for the 4 new fields // 128 bytes total // --------------------------------------------------------------------------- struct dinode { short type; // file type short major; // major device number (T_DEVICE only) short minor; // minor device number (T_DEVICE only) short nlink; // hard-link count uint size; // file size in bytes uint rwmode; // read/write permission flags uint supermode; // superuser-only flag uint addrs[NDIRECT + 3]; // block addresses / packed extent entries uint showmode; // visibility flag uint atime; // last access time (seconds since epoch) uint mtime; // last modification time uint ctime; // last metadata-change time uint checksum; // CRC32c of this dinode (field = 0 when hashing) uchar pad[28]; // reserved padding (struct = 128 bytes) }; // Inodes per block #define IPB (BSIZE / sizeof(struct dinode)) // Block containing inode i #define IBLOCK(i, sb) ((i) / IPB + (sb).inodestart) // Bitmap bits per block #define BPB (BSIZE * 8) // Block of free-block bitmap containing bit for block b #define BBLOCK(b, sb) ((b) / BPB + (sb).bmapstart) // Block of inode bitmap containing bit for inode i #define IBBLOCK(i, sb) ((i) / BPB + (sb).ibmapstart) // --------------------------------------------------------------------------- // Directory entry // --------------------------------------------------------------------------- // 30, not 14. // // 14 was xv6's, and the v1 filesystem truncated to it *silently*: a file called // JetBrainsMono-Regular.ttf was stored as "JetBrainsMono-", could not be opened // by its real name, and would have collided outright with // JetBrainsMono-Bold.ttf. Any two files sharing a 14-character prefix mapped to // one entry. That is data loss, not a display limit. // // The value is chosen so that `struct dirent` is exactly 32 bytes, because the // directory HTree below is laid out in *bytes* and a bucket must stay 256 bytes // (see DIRENT_BUCKET_BYTES). Going from 16-byte to 32-byte entries therefore // halves the slots per bucket from 16 to 8 and changes no offset anywhere else // -- HASHRANGE, the overflow region and MAXFSIZE all keep their values. The // cost is more overflow use in heavily collided directories, which is a far // better trade than silently mangling names. // // It is still not POSIX's 255. Getting there means reshaping the directory // format, which wants the on-disk version policy SchFS is getting first. #define DIRSIZ 30 struct dirent { ushort inum; char name[DIRSIZ]; }; // --------------------------------------------------------------------------- // Directory HTree constants // // BKDRHash(name) returns a value in [16, 16 + HASHRANGE). // Each bucket is 256 bytes: 8 dirent slots of 32 bytes. It was 16 slots of 16 // until DIRSIZ grew; the byte size is what the layout depends on, so it is the // thing held constant. // // With HASHRANGE=443: // max primary end byte = (16 + 443) * 256 = 117 504 (~114.75 blocks) // OVERFLOW_DIR_START = 113 * BSIZE = 115 712 // OVERFLOW_DIR_END = 115 * BSIZE = 117 760 // MAXFSIZE = 119 * BSIZE = 121 856 // --------------------------------------------------------------------------- #define DIRENT_NUM 7168 #define FIRST_LAYER_NODE_NUM 112 #define OVERFLOW_BLOCK (FIRST_LAYER_NODE_NUM + 1) #define NUM_DIRENT_PER_BLOCK (BSIZE / sizeof(struct dirent)) #define SECOND_LAYER_NODE_NUM NUM_DIRENT_PER_BLOCK #define HASHRANGE 443 // One hash bucket, in bytes. Every offset in the HTree is derived from this, so // it must not change when DIRSIZ does -- see the note on DIRSIZ. #define DIRENT_BUCKET_BYTES 256 #define DIRENT_PER_BUCKET (DIRENT_BUCKET_BYTES / sizeof(struct dirent)) #define MAXFSIZE (119 * BSIZE) // Overflow bucket: shared between fs.c and mkfs.c. // These replace the bare "113 * BSIZE" / "115 * BSIZE" literals. #define OVERFLOW_DIR_START (113 * BSIZE) #define OVERFLOW_DIR_END (115 * BSIZE) #endif // FS_H