SchOS/kernel/fs/schfs/schfs_int.h
Nexoniarz d0f7acf9eb Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
2026-08-09 13:47:01 +02:00

277 lines
13 KiB
C

// SPDX-License-Identifier: Apache-2.0
//
// SchFS internals: the volume (uberblock ring, block allocation) and the object
// layer (inodes, directories, file data) that sit on top of the B-tree.
//
// Split three ways so the kernel links only what it needs:
//
// vol.c open / format / commit — the uberblock ring
// obj.c read paths — kernel needs these in phase 2
// objw.c write paths — host tools only, until phase 3
//
// Everything here is portable C compiled both hosted (mkfs, fsck) and freestanding
// (kernel), so a bug is found on the host, where a test takes a second rather
// than a boot.
#ifndef SCHFS_INT_H
#define SCHFS_INT_H
#include "btree.h"
// ---------------------------------------------------------------------------
// space.c — the allocation bitmap
//
// One bit per block, held in RAM and derived from the committed tree at mount
// rather than stored on disk. See space.c for why.
//
// The bitmap is a vector of pages because the kernel allocates a page at a time
// and gives no promise that two are adjacent. That vector is fixed-size, and
// its length is therefore the hard ceiling on how large a volume this build can
// mount: SCHFS_SPACE_PAGES pages x SCHFS_SPACE_BITS_PER_PAGE blocks each.
// ---------------------------------------------------------------------------
#define SCHFS_SPACE_BITS_PER_PAGE ((uint64_t)SCHFS_BLKSIZE * 8) /* 32768 */
#define SCHFS_SPACE_PAGES 512
#define SCHFS_MAX_BLOCKS \
(SCHFS_SPACE_BITS_PER_PAGE * (uint64_t)SCHFS_SPACE_PAGES) /* 64 GiB */
struct schfs_space {
uint64_t *inuse[SCHFS_SPACE_PAGES]; // 1 = allocated
uint64_t *pend [SCHFS_SPACE_PAGES]; // 1 = freed, but not yet reusable
int npages;
uint64_t nblocks;
uint64_t free_blocks;
uint64_t pending; // bits set in pend[], counted
uint64_t hint; // rotating search start
};
// A mounted (or under-construction) volume.
//
// NOT copyable: tree.dev points at this struct's own dev field, so a copy
// leaves the tree talking to whichever struct it was built in. Open into the
// storage it will live in.
struct schfs_vol {
struct schfs_bdev dev;
struct schfs_btree tree;
// The three block buffers every mutation borrows, handed in at open or
// format and owned by the caller for the life of the volume. Nothing in
// SchFS may put a SCHFS_BLKSIZE buffer on the stack: a block is 4096 bytes
// and so is the whole kernel stack.
//
// The object layer also uses ctx.a as a cursor buffer, which is safe on the
// one condition that no cursor is still live when a tree mutation runs.
// Every write path here reads what it needs out of the cursor first.
struct schfs_ctx ctx;
struct schfs_space space;
uint64_t total_blocks;
uint64_t free_blocks;
uint64_t next_objectid;
uint64_t root_objectid;
uint64_t generation; // last *committed* transaction group
int uber_slot; // ring slot the last commit used
int readonly;
char label[SCHFS_LABEL_MAX];
uint8_t uuid[16];
// Staging for a directory's packed dirent list. Deliberately not one of the
// ctx buffers: a split reuses ctx.b and ctx.c while the value being
// inserted is still being read from, so the value needs storage of its own.
char dirbuf[SCHFS_MAX_INLINE];
};
// ---------------------------------------------------------------------------
// vol.c
// ---------------------------------------------------------------------------
// Scan the whole uberblock ring and adopt the highest generation that both
// checksums and points at a readable root. This *is* the crash recovery story:
// there is no journal to replay, because a torn update simply leaves an older
// slot as the newest valid one. Returns 0, -1 if no valid uberblock exists
// (not a SchFS volume), -2 on I/O error.
//
// `x` supplies the volume's three scratch blocks; they are kept for its whole
// lifetime, so they must outlive it and must not alias one another. `palloc`
// yields pages for the allocation bitmap and may be NULL for a read-only mount,
// which then cannot allocate at all.
int schfs_vol_open(struct schfs_vol *v, struct schfs_bdev *dev,
struct schfs_ctx *x, void *(*palloc)(void));
// Lay down an empty volume: one empty tree and one uberblock.
int schfs_vol_format(struct schfs_vol *v, struct schfs_bdev *dev,
uint64_t total_blocks, const char *label,
struct schfs_ctx *x, void *(*palloc)(void));
// Publish the current tree by writing the next slot in the ring. Until this
// returns, everything written since the last commit is invisible and a crash
// discards it — which is exactly what makes the format crash-safe.
int schfs_vol_commit(struct schfs_vol *v);
// Block allocation. Returns 0 when the volume is full.
//
// Not themselves schfs_bdev callbacks: a bdev has one ctx shared by all of its
// hooks, and the caller's block I/O needs that slot for its own state (a file
// handle, a device number). Callers wrap these in a one-line adapter instead.
uint64_t schfs_vol_alloc(struct schfs_vol *v);
uint64_t schfs_vol_alloc_run(struct schfs_vol *v, uint64_t n);
void schfs_vol_free(struct schfs_vol *v, uint64_t blk);
// ---------------------------------------------------------------------------
// space.c
// ---------------------------------------------------------------------------
// Build an empty map covering `nblocks`, with everything below
// SCHFS_FIRST_BLOCK already reserved. `palloc` yields SCHFS_BLKSIZE-sized
// pages; the map keeps them for the volume's lifetime. Returns 0, -3 if the
// volume is too small to hold anything, -4 if it is larger than this build can
// map (see SCHFS_MAX_BLOCKS), -5 if palloc ran out.
int schfs_space_init(struct schfs_space *s, uint64_t nblocks,
void *(*palloc)(void));
// Record a run as allocated. Idempotent, because an extent range may be shared
// by several files.
void schfs_space_mark(struct schfs_space *s, uint64_t blk, uint64_t n);
uint64_t schfs_space_alloc(struct schfs_space *s);
uint64_t schfs_space_alloc_run(struct schfs_space *s, uint64_t n);
// Release a block — but only for the *next* transaction group. Until the
// commit that supersedes it, the last committed tree still points here and a
// crash would land on it.
void schfs_space_free(struct schfs_space *s, uint64_t blk);
// Fold this group's deferred frees back into the free map. Called by
// schfs_vol_commit once the new uberblock is on the disk, never before.
void schfs_space_commit(struct schfs_space *s);
// Walk the committed tree and mark every block it reaches. This is what makes
// the map correct without storing it. Returns 0 or -2 on an unreadable tree.
int schfs_space_scan(struct schfs_vol *v);
// ---------------------------------------------------------------------------
// obj.c — read side
// ---------------------------------------------------------------------------
uint64_t schfs_namehash(const char *name, unsigned len);
// Test-only knob; see obj.c. Narrowing this forces directory-hash collisions
// so the packed-dirent path actually runs. Never changed outside tools/.
extern uint64_t schfs_hash_mask;
void schfs_mkkey(struct schfs_key *k, uint64_t objectid, uint8_t type,
uint64_t offset);
// Fetch an inode record. Returns 0, -1 if there is no such object, -2 corrupt.
int schfs_inode_get(struct schfs_vol *v, struct schfs_cursor *c,
uint64_t ino, struct schfs_inode *out);
// Resolve one name inside a directory. Returns 0 and fills *out_ino/*out_type,
// -1 if absent, -2 corrupt.
int schfs_dir_lookup(struct schfs_vol *v, struct schfs_cursor *c,
uint64_t dir, const char *name, unsigned namelen,
uint64_t *out_ino, uint16_t *out_type);
// Iterate a directory's real entries in hash order. "." and ".." are not
// stored and are not reported; callers that need them synthesise them.
// A non-zero return from fn stops the walk and is passed back.
int schfs_dir_iterate(struct schfs_vol *v, struct schfs_cursor *c, uint64_t dir,
int (*fn)(uint64_t ino, uint16_t type, const char *name,
unsigned namelen, void *arg),
void *arg);
// Read from a file. Short reads at end-of-file are normal; returns the byte
// count, or -1 on a missing object, -2 on corruption or a failed data checksum.
// `dblk` is a caller-owned SCHFS_BLKSIZE scratch buffer for data blocks.
long schfs_file_read(struct schfs_vol *v, struct schfs_cursor *c, uint64_t ino,
uint64_t off, void *buf, unsigned n, void *dblk);
// Walk an absolute path. Returns the object id, or 0 if any component is
// missing or is not a directory.
uint64_t schfs_path_lookup(struct schfs_vol *v, struct schfs_cursor *c,
const char *path);
// ---------------------------------------------------------------------------
// objw.c — write side
//
// Shared error codes throughout: 0 success, -1 absent or not permitted,
// -2 corruption or I/O failure, -3 out of space, -4 a directory hash-collision
// list that will not fit in one item, -5 directory not empty.
//
// None of it is visible to a reader until schfs_vol_commit publishes the tree,
// which is what lets a failure part way through a multi-record operation be
// abandoned rather than unwound.
// ---------------------------------------------------------------------------
uint64_t schfs_alloc_objectid(struct schfs_vol *v);
int schfs_inode_put(struct schfs_vol *v, uint64_t ino,
const struct schfs_inode *in);
// Add one entry, appending to the packed list if another name already hashes
// the same. -1 if the name is already present.
int schfs_dir_add(struct schfs_vol *v, uint64_t dir, const char *name,
unsigned namelen, uint64_t child, uint16_t type);
// Remove one entry. -1 if there is no such name.
int schfs_dir_del(struct schfs_vol *v, uint64_t dir, const char *name,
unsigned namelen);
// 1 if the directory holds no entries, 0 if it holds some, -2 on corruption.
int schfs_dir_empty(struct schfs_vol *v, uint64_t dir);
// Claim a contiguous run of data blocks for a file whose final size is already
// known, and record the whole run as ONE extent. Worth calling before a bulk
// write: without it, data and tree blocks interleave, and only the merging in
// schfs_file_write keeps the extent count down.
int schfs_file_reserve(struct schfs_vol *v, uint64_t ino, uint64_t nblocks);
// Write at an arbitrary offset, allocating blocks, extent records and data
// checksums, copying any block that a committed tree can still reach, and
// splitting the extent record around it. Writing past the end extends the file;
// a gap left behind stays a hole and reads back as zeros. Returns the byte
// count written, or a negative error.
long schfs_file_write(struct schfs_vol *v, uint64_t ino, uint64_t off,
const void *buf, unsigned n, void *dblk);
// Append at the current end of file. A convenience over schfs_file_write, kept
// because image builders write nothing else.
int schfs_file_append(struct schfs_vol *v, uint64_t ino, const void *buf,
unsigned n, void *dblk);
// Set a file's length, releasing whole blocks past the new end and zeroing what
// is left of the block the new end falls inside — truncating down and growing
// again has to read back zeros. Growing alone costs nothing: a range with no
// extent records is a hole. `dblk` is a caller-owned SCHFS_BLKSIZE buffer.
int schfs_file_truncate(struct schfs_vol *v, uint64_t ino, uint64_t size,
void *dblk);
// Release an object's data, checksums and inode record. The caller must already
// have removed every name pointing at it.
int schfs_object_free(struct schfs_vol *v, uint64_t ino);
// Create an object and the entry naming it. `now` is the timestamp to stamp all
// three times with — SchFS has no clock of its own.
int schfs_create(struct schfs_vol *v, uint64_t dir, const char *name,
unsigned namelen, uint16_t type, uint32_t mode,
uint16_t rdev_major, uint16_t rdev_minor, uint64_t now,
uint64_t *out_ino);
// A second name for an existing object. -1 for a directory: hard-linking one
// makes cycles possible, and a cycle turns every recursive walk infinite.
int schfs_link(struct schfs_vol *v, uint64_t dir, const char *name,
unsigned namelen, uint64_t ino);
// Remove a name, and the object with it once the last name is gone. Handles
// directories too, refusing with -5 if the directory still has entries.
int schfs_unlink(struct schfs_vol *v, uint64_t dir, const char *name,
unsigned namelen);
// Move a name, replacing the target if one exists. Refuses to move a directory
// into its own subtree, which would detach that subtree from the root.
int schfs_rename(struct schfs_vol *v, uint64_t olddir, const char *oldname,
unsigned oldlen, uint64_t newdir, const char *newname,
unsigned newlen);
#endif /* SCHFS_INT_H */