schfs: mount a snapshot by name, read-only

`mount -t schfs @golden /mnt` mounts a snapshot. The source argument
finally means something: sys_mount ignored it outright, because there was
one volume and nothing to choose between. `@name` rather than a path
because a snapshot is not a device -- there is no node in /dev for it and
there should not be, since it lives inside the volume already mounted.

A second volume rather than a parameter threaded through all 23 schfs_k*
entry points. Only the read side can ever address it, so the write path
keeps talking to the live volume and needed no change; the four read
entry points grew _dev variants and pick the volume from the device the
inode already carries. One fs_ops table serves both, because every entry
takes an inode and dispatches on it -- a second table would be the same
pointers under a different name.

It needs its own device number: a snapshot's object ids are the *same
numbers* as the live tree's, so one device would make the two views alias
in the inode cache and hand a reader whichever was cached first.

Three things this turned up, none of them new but all of them reachable
for the first time:

  - sys_mount refused with `dev == SCHFS_DEV`, meaning "do not mount SchFS
    onto SchFS". A snapshot's mount point is necessarily on the live root,
    so that refused the entire feature. The real condition is mounting a
    device onto *itself*, which is `dev == fsdev`.
  - createat allocates an inode and then panics if dirlink fails --
    reasonable about a writable filesystem, a crash on a read-only one.
    The refusal now happens before anything is allocated. Unlink and
    rename already handled a refusal properly.
  - a refused create reported ENOENT, so writing to a snapshot said "No
    such file or directory" -- the sentence a shell prints for every
    create failure. It reports EROFS now.

Verified in the running system against an image built with
--snapshot golden: the mount succeeds, ls lists the snapshot's tree,
/mnt2/etc/motd reads back through it, and a write is refused with
"Read-only file system".

One at a time, and no umount path for it yet: mount_del_by_root removes
the mount table entry, but ksnap_unmount is only called on the failure
paths. schfs-check passes; bootcheck 108 [ok].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-10 21:10:59 +02:00
commit 3919f1446e
6 changed files with 345 additions and 18 deletions

View file

@ -275,10 +275,25 @@ extern const struct fs_ops schfs_ops;
// Defined in fs/procfs.c -- the synthetic /proc.
extern const struct fs_ops procfs_ops;
// Is this filesystem read-only?
//
// A mounted snapshot is. Callers must ask *before* they start mutating: the
// create path allocates an inode and then panics if the dirlink fails, which is
// a reasonable assumption about a writable filesystem and a crash on a
// read-only one.
int
fs_readonly(uint dev)
{
return dev == SCHFS_SNAP_DEV;
}
static const struct fs_ops *
ops_for(uint dev)
{
if (dev == SCHFS_DEV) return &schfs_ops;
// A mounted snapshot is the same filesystem seen through an older root, so it
// is the same operations; they dispatch on the device themselves.
if (dev == SCHFS_SNAP_DEV) return &schfs_ops;
if (dev == PROCFS_DEV) return &procfs_ops;
return &v1_ops;
}