The kernel now checks owner/group/other on open, create, unlink, rename, exec and path search; honours set-user-id and set-group-id; restricts kill, mount, reboot and mknod; keeps supplementary groups; and implements chown. devtmpfs nodes carry owners and modes that chmod/chown keep. Terminals run login, the image has root and schos, X starts as the ordinary user. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
256 lines
11 KiB
C
256 lines
11 KiB
C
struct file {
|
|
// FD_MEMFD: an anonymous shared memory object, the thing memfd_create(2)
|
|
// returns. It is a descriptor with no name and no inode behind it, which is
|
|
// the entire reason memfd exists -- two processes share memory by passing
|
|
// the descriptor, not by agreeing on a path or a System V key.
|
|
//
|
|
// FD_EVENT: an eventfd, timerfd, signalfd or epoll set -- see
|
|
// kernel/sys/sys_event.c, which is the only file that looks inside `ev`.
|
|
enum { FD_NONE, FD_PIPE, FD_INODE, FD_SOCKET, FD_MEMFD, FD_EVENT } 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;
|
|
// The shared segment behind an FD_MEMFD, as an index into the table in
|
|
// kernel/sys/sys_mem.c. Not a pointer, because the segment table is where
|
|
// the reference counting lives and handing out pointers into it would
|
|
// invite somebody to keep one.
|
|
int memfd_seg;
|
|
struct evobj *ev;
|
|
uint64 off;
|
|
// What /proc/<pid>/fd shows. `ino` names the open thing (one per socket,
|
|
// one per pipe); `path` is the absolute path an inode was opened by, as
|
|
// written -- symlinks along it are not resolved. kmalloc'd, or 0.
|
|
uint64 ino;
|
|
char *path;
|
|
struct file *fnext; // file.c's free list, while ref is 0
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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);
|
|
|
|
// The largest write this filesystem wants in one call, or 0 for "no limit".
|
|
//
|
|
// v1 needs one: a write has to fit in the on-disk log, so filewrite splits
|
|
// anything larger and each piece is its own transaction. That limit is a
|
|
// property of the journal, not of writing, and applying it to SchFS was
|
|
// actively harmful -- SchFS makes one transaction out of whatever it is
|
|
// handed, so chopping a 1 MB write into 6 KB pieces bought 170 commits and
|
|
// the flushes that go with them for nothing.
|
|
uint max_write;
|
|
|
|
// Object types this filesystem has no way to store, as a mask of 1 << T_*.
|
|
// Creating one fails with EPERM, which is what Linux answers for mknod or a
|
|
// symlink on vfat. 0 for "anything".
|
|
uint refuse_types;
|
|
// One name per file: link() fails with EPERM. Rename still works -- it is
|
|
// a second name for as long as it takes to remove the first.
|
|
int no_hardlinks;
|
|
};
|
|
|
|
// 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
|
|
struct inode *hnext; // fs.c: hash chain while held, free list after
|
|
struct inode *anext; // fs.c: every inode there is, never reordered
|
|
|
|
short type;
|
|
short major;
|
|
short minor;
|
|
// int, not short. SchFS stores a uint32 link count and this truncated it, so
|
|
// a file that passed 32767 names came back with a negative one -- and iput
|
|
// frees an object whose count reaches zero. Signed rather than unsigned on
|
|
// purpose: `nlink--` on a count that is somehow already zero should land
|
|
// somewhere a check can see it, not wrap to four billion.
|
|
int nlink;
|
|
uint64 size;
|
|
|
|
uint rwmode;
|
|
// POSIX permission bits from a filesystem that keeps them, or 0 for one
|
|
// that does not, in which case stat makes some up from the type. Bit 18 set
|
|
// alone means a mode of 0000, which chmod can ask for.
|
|
uint mode;
|
|
// Owner. Whatever the filesystem keeps: SchFS has stored them all along,
|
|
// FAT and the synthetic filesystems report root.
|
|
uint uid, gid;
|
|
uint supermode;
|
|
uint showmode;
|
|
uint atime;
|
|
uint mtime;
|
|
uint ctime;
|
|
uint addrs[NDIRECT+3];
|
|
};
|
|
#define I_VALID 0x2
|
|
|
|
// Device numbers for filesystems that are not a raw disk. Real disks are 0..3,
|
|
// and these are deliberately far from them so a stray value never looks like a
|
|
// SchFS inode. Nothing on any disk records them -- they are runtime identities
|
|
// for the inode cache, so the numbering can be rearranged freely.
|
|
//
|
|
// Mounted SchFS volumes: SCHFS_DEV through SCHFS_DEV + SCHFS_VOL_SLOTS - 1.
|
|
//
|
|
// This was a single number, because there was a single volume: kmount.c held
|
|
// one `kvol` and every write entry point named it directly, so `mount -t schfs
|
|
// /dev/hdb /mnt` ignored its source argument entirely and handed back whichever
|
|
// disk the kernel had found at boot. Slot 0 is that boot volume, which is why
|
|
// SCHFS_DEV alone still means "the root" everywhere that only ever wanted it.
|
|
// Sixteen, which fills 64..79 up to PROCFS_DEV.
|
|
#define SCHFS_VOL_SLOTS 16
|
|
#define SCHFS_DEV 64
|
|
#define SCHFS_IS_VOL_DEV(d) \
|
|
((d) >= SCHFS_DEV && (d) < SCHFS_DEV + SCHFS_VOL_SLOTS)
|
|
|
|
// 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.
|
|
//
|
|
// More 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. Not many
|
|
// because each is a device number and they are a fixed, small space.
|
|
#define SCHFS_SNAP_SLOTS 8 // 96..103, up to FAT_DEV
|
|
#define SCHFS_SNAP_DEV 96
|
|
#define SCHFS_IS_SNAP_DEV(d) \
|
|
((d) >= SCHFS_SNAP_DEV && (d) < SCHFS_SNAP_DEV + SCHFS_SNAP_SLOTS)
|
|
|
|
// Anything SchFS answers for, live volume or snapshot.
|
|
#define SCHFS_IS_ANY_DEV(d) (SCHFS_IS_VOL_DEV(d) || SCHFS_IS_SNAP_DEV(d))
|
|
|
|
// The synthetic /proc.
|
|
#define PROCFS_DEV 80
|
|
|
|
// The synthetic /dev. See kernel/fs/devfs.c.
|
|
#define DEVFS_DEV 81
|
|
|
|
// The synthetic /sys. See kernel/fs/sysfs.c.
|
|
#define SYSFS_DEV 82
|
|
// Graft it over `target`; 0 or a negative errno. In sysfs.c.
|
|
int sysfs_mount(const char *target);
|
|
|
|
// The buffer /proc renders a file into, and how big it is; one at a time, so
|
|
// every get is followed by a put. In kernel/dev/procfs.c.
|
|
char *proc_genbuf_get(int *cap);
|
|
void proc_genbuf_put(void);
|
|
|
|
// Mounted FAT volumes, FAT_DEV onward. See include/fs/fat.h.
|
|
|
|
// Linux's limits, for every filesystem behind the VFS. A path component can be
|
|
// NAME_MAX bytes and a whole path PATH_MAX including its NUL. Each filesystem
|
|
// may refuse less: SchFS and FAT take 255, the v1 format DIRSIZ.
|
|
//
|
|
// The VFS used to copy every component into a DIRSIZ buffer and every path
|
|
// into a MAXPATH one, which cut names at 29 bytes and paths at 127 without a
|
|
// word -- so a longer name opened whichever file shared its first 29 bytes.
|
|
#define VFS_NAME_MAX 255
|
|
#define VFS_PATH_MAX 4096 // one page: namex_from() kalloc()s it
|
|
|
|
// 1 if some inode on dev with this number is referenced. In fs.c.
|
|
int iheld(uint dev, uint64 inum);
|
|
// 1 if any inode on dev but `except` is referenced (0: no exception). In fs.c.
|
|
int ibusy(uint dev, uint64 except);
|
|
// How many in-memory inodes exist; the table grows past NINODE. In fs.c.
|
|
uint inode_slots(void);
|
|
// How many open file descriptions exist; the table grows past NFILE. file.c.
|
|
uint file_slots(void);
|
|
// Path lookup that follows a final symlink too; 0 with *err set on failure.
|
|
// `start` 0 means the cwd. In fs.c.
|
|
struct inode *nameifollow(struct inode *start, char *path, int *err);
|
|
// execve(2) with the argv and envp arrays still in the caller's memory. In
|
|
// exec.c; exec() is the same for arrays the kernel holds.
|
|
int exec_user(char *path, addr_t uargv, addr_t uenvp);
|
|
|
|
// table mapping major device number to
|
|
// device functions
|
|
struct devsw {
|
|
int (*read)(struct inode*, uint64, char*, int);
|
|
int (*write)(struct inode*, uint64, 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
|
|
// Major number of /dev/dsp. See include/dev/audio.h.
|
|
#define AUDIO_MAJOR 12
|