SchOS/include/fs/fs.h
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

214 lines
9 KiB
C

// 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/fs/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