SchOS/include/file.h
Nexoniarz b34779111a schfs: several snapshots at once, and unmount gives the slot back
Two loose ends from the mount-by-name commit, both closed.

A slot is a device number, so supporting more than one snapshot is a
small array rather than a redesign: SCHFS_SNAP_DEV through
SCHFS_SNAP_DEV + SCHFS_SNAP_SLOTS - 1, each with its own volume and its
own scratch pages, allocated on first use. Four of them -- "one at a
time" is an odd thing to have to explain, and the cost is two pages per
slot. Not many, because each one is a device number and those are a
small fixed space.

The leak mattered more. umount removed the mount table entry and left the
slot marked in use, so a snapshot could be unmounted and then never
mounted again until reboot. sys_umount2 releases it now, keyed off the
device it already has in hand.

Verified in the running system: two snapshots mounted at once, both
readable, both unmounted, and a third mount afterwards succeeds and reads
correctly -- which is the case that failed before.

schfs-check passes; bootcheck 108 [ok].

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:30:28 +02:00

148 lines
5.7 KiB
C

struct file {
enum { FD_NONE, FD_PIPE, FD_INODE, FD_SOCKET } type;
int ref; // reference count
char readable;
char writable;
// O_NONBLOCK. A property of the open file description, not of the
// descriptor, which is why it lives here and not in myproc()->ofile_flags --
// dup() shares it and F_SETFL on either fd changes it for both, exactly as
// POSIX says.
char nonblock;
struct pipe *pipe;
struct inode *ip;
struct socket *sock;
uint64 off;
};
// ---------------------------------------------------------------------------
// Filesystem operations
//
// One table per filesystem, chosen by iget() from the device number and hung
// off the inode. Everything above this line -- the syscall layer, exec, the
// page cache -- calls the wrappers in fs.c and never learns which filesystem it
// is talking to.
//
// A vtable rather than a chain of `if (dev == SCHFS_DEV)` because there are two
// filesystems today and there will be more: the whole point of the exercise is
// that adding one is a new table, not another branch in fifteen functions.
//
// Every hook takes an inode that is already locked, except ialloc (which has no
// inode yet) and iload (which is what makes one valid in the first place).
// ---------------------------------------------------------------------------
struct stat;
struct fs_ops {
const char *name;
// Fill in the type/size/link fields of a freshly iget()'d inode.
void (*iload)(struct inode *);
// Write those fields back to disk.
void (*istore)(struct inode *);
// The last link is gone and nobody has it open: release the object.
void (*ifree)(struct inode *);
int (*read)(struct inode *, int user_dst, uint64 dst, uint64 off, uint n);
int (*write)(struct inode *, int user_src, uint64 src, uint64 off, uint n);
// Set the length. Growing is allowed only where the filesystem can represent
// the gap; v1 cannot and ignores it.
void (*truncate)(struct inode *, uint64 newsize);
// Directory operations. `poff` is an opaque cookie identifying where the
// entry lives, meaningful only to the same filesystem's dirunlink.
struct inode *(*dirlookup)(struct inode *dp, char *name, uint *poff);
int (*dirlink)(struct inode *dp, char *name, uint64 inum);
int (*dirunlink)(struct inode *dp, char *name, uint off);
int (*dirempty)(struct inode *dp);
// Create an object with no name yet. The caller sets nlink and calls
// dirlink; until it does, the object is unreachable.
struct inode *(*ialloc)(uint dev, short type);
// Read directory entries into a getdents64-shaped stream. `off` is an opaque
// position that starts at 0 and is echoed back; returns bytes written, 0 at
// the end of the directory, or negative on error.
int (*getdents)(struct inode *dp, char *out, int cap, uint64 *off);
};
// In-memory inode.
//
// `inum` and `size` are 64-bit because the filesystem underneath may be: SchFS
// object ids and file sizes are both uint64 on disk, and a 32-bit copy here
// would silently truncate them into a different file. The v1 filesystem uses a
// small fraction of the range and is unaffected.
struct inode {
uint dev;
uint64 inum;
int ref;
struct sleeplock lock;
int valid;
const struct fs_ops *ops; // chosen by iget from dev; never null once held
short type;
short major;
short minor;
short nlink;
uint64 size;
uint rwmode;
uint supermode;
uint showmode;
uint atime;
uint mtime;
uint ctime;
uint addrs[NDIRECT+3];
};
#define I_VALID 0x2
// Device number that means "the mounted SchFS volume". Real disks are 0 and 1,
// and this is deliberately far from them so a stray value never looks like a
// SchFS inode.
#define SCHFS_DEV 64
// Device number that means "the synthetic /proc". Like SCHFS_DEV, it is
// deliberately nowhere near a real disk's 0 or 1.
// Mounted snapshots. Same block device, an older tree root, read-only.
//
// Each needs its own device number because an inode is identified by
// (dev, inum) and a snapshot's object ids are the *same numbers* as the live
// tree's -- one number would make the views alias in the inode cache and hand
// a reader whichever was cached first. So a slot is a device: SCHFS_SNAP_DEV
// through SCHFS_SNAP_DEV + SCHFS_SNAP_SLOTS - 1.
//
// Four rather than one because "one at a time" is an odd thing to explain and
// the cost is two scratch pages per slot, allocated on first use. Four rather
// than many because each is a device number and they are a fixed, small space.
#define SCHFS_SNAP_SLOTS 4
#define SCHFS_SNAP_DEV 66
#define SCHFS_IS_SNAP_DEV(d) \
((d) >= SCHFS_SNAP_DEV && (d) < SCHFS_SNAP_DEV + SCHFS_SNAP_SLOTS)
#define PROCFS_DEV 65
// table mapping major device number to
// device functions
struct devsw {
int (*read)(struct inode*, uint, char*, int);
int (*write)(struct inode*, uint, char*, int);
// Optional. Returns 0 when a read would block, which is the only thing
// fileread needs to know to honour O_NONBLOCK without every device driver
// growing a flags argument. A device that omits this is treated as always
// ready, which is the right answer for a disk and for /dev/zero.
int (*ready)(struct inode*);
// Optional. The last descriptor for this open file is going away. A device
// that hands a program exclusive-feeling control of the hardware needs this
// to put the hardware back -- /dev/fb0 uses it to give the text console its
// screen back when whatever was drawing on it exits.
void (*close)(struct inode*);
// Optional. A descriptor has just been opened on this device. /dev/input
// uses it to discard events that arrived while nobody was listening -- see
// inputopen().
void (*open)(struct inode*);
};
extern struct devsw devsw[];
#define CONSOLE 1
// Major number of /dev/input/eventN. The minor is the device index.
#define INPUT_MAJOR 7