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.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
277
kernel/fs/schfs/schfs_int.h
Normal file
277
kernel/fs/schfs/schfs_int.h
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
// 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 */
|
||||
Loading…
Reference in a new issue