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. enum { FD_NONE, FD_PIPE, FD_INODE, FD_SOCKET, FD_MEMFD } 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; uint64 off; 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; 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