// SCHFS - File system implementation. // // Five layers: // + Blocks: allocator for raw disk blocks. // + Log: crash recovery for multi-step updates. // + Files: inode allocator, reading, writing, metadata. // + Directories: inode with special contents (list of other inodes!) // + Names: paths like /usr/rtm/schfs/fs.c for convenient naming. // // Derived from "New File System For XV6" by Qi Ma, Leyi Pan, Ao Sun and // Peiran Xu (MIT License), which itself extends xv6's fs.c. The HTree directory // index, the Ext4-style extents, the inode bitmap and the symlink support all // come from there. See NOTICE. // // Changes from the original xv6-based fs.c: // 1. CRC32c checksums on every inode and the superblock. // 2. Timestamps: atime/mtime/ctime written on alloc, read, write, trunc. // 3. Proper extent tree for T_EXTENT inodes: struct fsv1_extent packed into // addrs[], with an indirect extent block for larger files. // Replaces the lossy "addr*256|len" encoding that overflowed on large // physical block numbers. // 4. itrunc for T_EXTENT fixed (original leaked the indirect block and had // an off-by-one loop termination). // 5. readi/writei honour the user_dst/user_src flag via copyout/copyin. // 6. tree_search uses OVERFLOW_DIR_START/END instead of bare magic numbers. // 7. contiguous_block_allocation: removed the dangerous mid-transaction // end_op()/begin_op() calls. // 8. divesymlink: fixed one-byte buffer overrun in the "short read" check. // 9. updatesb: writes CRC32c checksum before flushing to disk. // 10. Dead stubs (check_log_full, check_free, cal_block_index, tree_add) // removed. // This FS is a joke lol, suck my dick chinese people lol #include "types.h" #include "x86.h" #include "defs.h" #include "param.h" #include "stat.h" #include "spinlock.h" #include "proc.h" #include "sleeplock.h" #include "fs.h" #include "buf.h" #include "file.h" extern uint uptime(void); #define min(a, b) ((a) < (b) ? (a) : (b)) // =========================================================================== // CRC32c (Castagnoli polynomial 0x82F63B78) // Must be byte-for-byte identical to the copy in mkfs.c. // =========================================================================== static uint crc32c_table[256]; static int crc32c_ready = 0; static void crc32c_init(void) { uint i, j, crc; for (i = 0; i < 256; i++) { crc = i; for (j = 0; j < 8; j++) crc = (crc >> 1) ^ (crc & 1 ? 0x82F63B78U : 0); crc32c_table[i] = crc; } crc32c_ready = 1; } static uint crc32c(uint crc, const void *buf, uint len) { const uchar *p = (const uchar *)buf; crc = ~crc; while (len--) crc = (crc >> 8) ^ crc32c_table[(crc ^ *p++) & 0xFF]; return ~crc; } // Compute CRC32c of a dinode. The checksum field itself is zeroed for the // computation so the result is stable regardless of what was stored there. static uint inode_checksum(struct dinode *dip) { uint saved = dip->checksum; dip->checksum = 0; uint ck = crc32c(0, dip, sizeof(*dip)); dip->checksum = saved; return ck; } // =========================================================================== // Superblock // =========================================================================== struct superblock sb; struct spinlock sblock; void readsb(int dev, struct superblock *sb) { struct buf *bp = bread(dev, 1); memmove(sb, bp->data, sizeof(*sb)); brelse(bp); } static void updatesb(int dev, struct superblock *sb) { // Recompute checksum with the checksum field zeroed. sb->checksum = 0; sb->checksum = crc32c(0, sb, sizeof(*sb)); struct buf *bp = bread(dev, 1); memmove(bp->data, sb, sizeof(*sb)); log_write(bp); brelse(bp); } void fsinit(int dev) { if (!crc32c_ready) crc32c_init(); initlock(&sblock, "superblock"); readsb(dev, &sb); if (sb.magic != FSMAGIC) panic("fsinit: invalid file system"); // Validate superblock checksum. A zero checksum means an old image that // predates this field; skip validation so we stay backward-compatible. if (sb.checksum != 0) { uint stored = sb.checksum; sb.checksum = 0; uint computed = crc32c(0, &sb, sizeof(sb)); sb.checksum = stored; if (computed != stored) panic("fsinit: superblock checksum mismatch"); } initlog(dev); } // =========================================================================== // Block allocator // =========================================================================== static void bzero(int dev, int bno) { struct buf *bp = bread(dev, bno); memset(bp->data, 0, BSIZE); // Direct write (not logged): the bitmap log_write in balloc is the commit // point. If we crash before commit, the bitmap reverts and this block stays // free — the zero content is harmless. Logging every new block would burn // most of LOGSIZE when tree_expand pre-allocates hash-tree blocks. bwrite(bp); brelse(bp); } static uint balloc(uint dev) { int b, bi, m; struct buf *bp = 0; for (b = 0; b < sb.size; b += BPB) { bp = bread(dev, BBLOCK(b, sb)); for (bi = 0; bi < BPB && b + bi < sb.size; bi++) { m = 1 << (bi % 8); if ((bp->data[bi / 8] & m) == 0) { bp->data[bi / 8] |= m; log_write(bp); brelse(bp); acquire(&sblock); sb.freeblocks--; release(&sblock); updatesb(dev, &sb); bzero(dev, b + bi); return b + bi; } } brelse(bp); } panic("balloc: out of blocks"); } static void bfree(int dev, uint b) { struct buf *bp; int bi, m; bp = bread(dev, BBLOCK(b, sb)); bi = b % BPB; m = 1 << (bi % 8); if ((bp->data[bi / 8] & m) == 0) panic("bfree: freeing free block"); bp->data[bi / 8] &= ~m; log_write(bp); brelse(bp); acquire(&sblock); sb.freeblocks++; release(&sblock); updatesb(dev, &sb); } // =========================================================================== // Inode table // =========================================================================== struct { struct spinlock lock; struct inode inode[NINODE]; } itable; void iinit(void) { initlock(&itable.lock, "itable"); for (int i = 0; i < NINODE; i++) initsleeplock(&itable.inode[i].lock, "inode"); } struct inode *iget(uint dev, uint64 inum); // =========================================================================== // Filesystem dispatch // // Every entry point below the syscall layer goes through ip->ops, which iget() // picks from the device number. The v1 implementations keep their original // bodies and are simply named v1_*; the public functions are the thin wrappers // at the bottom of this file. // // The one rule worth stating: a filesystem's hooks are only ever called on // inodes belonging to that filesystem, so none of them needs to check. // =========================================================================== static void v1_iload(struct inode *); static void v1_istore(struct inode *); static void v1_ifree(struct inode *); static int v1_read(struct inode *, int, uint64, uint64, uint); static int v1_write(struct inode *, int, uint64, uint64, uint); static void v1_truncate(struct inode *, uint64); static void v1_itrunc_all(struct inode *); static void v1_itrunc_to(struct inode *, uint64); static struct inode *v1_dirlookup(struct inode *, char *, uint *); static int v1_dirlink(struct inode *, char *, uint64); static int v1_dirunlink(struct inode *, char *, uint); static int v1_dirempty(struct inode *); static struct inode *v1_ialloc(uint, short); static int v1_getdents(struct inode *, char *, int, uint64 *); static const struct fs_ops v1_ops = { .name = "schfs-v1", .iload = v1_iload, .istore = v1_istore, .ifree = v1_ifree, .read = v1_read, .write = v1_write, .truncate = v1_truncate, .dirlookup = v1_dirlookup, .dirlink = v1_dirlink, .dirunlink = v1_dirunlink, .dirempty = v1_dirempty, .ialloc = v1_ialloc, .getdents = v1_getdents, }; // Defined in fs/schfs/vfs.c, which is only linked when SchFS is built in. extern const struct fs_ops schfs_ops; // Defined in fs/procfs.c -- the synthetic /proc. extern const struct fs_ops procfs_ops; static const struct fs_ops * ops_for(uint dev) { if (dev == SCHFS_DEV) return &schfs_ops; if (dev == PROCFS_DEV) return &procfs_ops; return &v1_ops; } // =========================================================================== // Inode bitmap helpers // =========================================================================== static int searchibmap(uint dev) { int b, bi, m; struct buf *bp = 0; for (b = 0; b < sb.ninodes; b += BPB) { bp = bread(dev, IBBLOCK(b, sb)); for (bi = 0; bi < BPB && b + bi < sb.ninodes; bi++) { m = 1 << (bi % 8); if ((bp->data[bi / 8] & m) == 0) { bp->data[bi / 8] |= m; log_write(bp); brelse(bp); acquire(&sblock); sb.freeinodes--; release(&sblock); updatesb(dev, &sb); return b + bi; } } brelse(bp); } panic("searchibmap: out of free inodes"); } static void ifree(int dev, int inum) { struct buf *bp; int bi, m; bp = bread(dev, IBBLOCK(inum, sb)); bi = inum % BPB; m = 1 << (bi % 8); if ((bp->data[bi / 8] & m) == 0) panic("ifree: freeing free inode"); bp->data[bi / 8] &= ~m; log_write(bp); brelse(bp); acquire(&sblock); sb.freeinodes++; release(&sblock); updatesb(dev, &sb); } // =========================================================================== // Timestamp helper // =========================================================================== // Returns the current time in seconds. uptime() is assumed to return ticks; // replace with a real wall-clock source if your kernel has one. static uint now_sec(void) { return (uint)uptime(); } // =========================================================================== // Inode allocation / update // =========================================================================== struct inode * v1_ialloc(uint dev, short type) { int inum = searchibmap(dev); struct buf *bp = bread(dev, IBLOCK(inum, sb)); struct dinode *dip = (struct dinode *)bp->data + inum % IPB; memset(dip, 0, sizeof(*dip)); dip->type = type; dip->rwmode = RW; dip->supermode = 0; dip->showmode = 1; uint ts = now_sec(); dip->atime = ts; dip->mtime = ts; dip->ctime = ts; dip->checksum = inode_checksum(dip); log_write(bp); brelse(bp); return iget(dev, inum); } void v1_istore(struct inode *ip) { struct buf *bp = bread(ip->dev, IBLOCK(ip->inum, sb)); struct dinode *dip = (struct dinode *)bp->data + ip->inum % IPB; dip->type = ip->type; dip->major = ip->major; dip->minor = ip->minor; dip->nlink = ip->nlink; dip->size = ip->size; dip->rwmode = ip->rwmode; dip->supermode = ip->supermode; dip->showmode = ip->showmode; dip->atime = ip->atime; dip->mtime = ip->mtime; dip->ctime = ip->ctime; memmove(dip->addrs, ip->addrs, sizeof(ip->addrs)); dip->checksum = inode_checksum(dip); log_write(bp); brelse(bp); } // =========================================================================== // iget / idup / ilock / iunlock / iput // =========================================================================== struct inode * iget(uint dev, uint64 inum) { struct inode *ip, *empty; acquire(&itable.lock); empty = 0; for (ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++) { if (ip->ref > 0 && ip->dev == dev && ip->inum == inum) { ip->ref++; release(&itable.lock); return ip; } if (empty == 0 && ip->ref == 0) empty = ip; } if (empty == 0) panic("iget: no inodes"); ip = empty; ip->dev = dev; ip->inum = inum; ip->ref = 1; ip->valid = 0; ip->ops = ops_for(dev); release(&itable.lock); return ip; } struct inode * idup(struct inode *ip) { acquire(&itable.lock); ip->ref++; release(&itable.lock); return ip; } static void v1_iload(struct inode *ip) { struct buf *bp = bread(ip->dev, IBLOCK(ip->inum, sb)); struct dinode *dip = (struct dinode *)bp->data + ip->inum % IPB; // Validate checksum. Zero means an old image without checksums; skip. if (dip->checksum != 0) { uint computed = inode_checksum(dip); if (computed != dip->checksum) panic("ilock: inode checksum mismatch"); } ip->type = dip->type; ip->major = dip->major; ip->minor = dip->minor; ip->nlink = dip->nlink; ip->size = dip->size; ip->rwmode = dip->rwmode; ip->supermode = dip->supermode; ip->showmode = dip->showmode; ip->atime = dip->atime; ip->mtime = dip->mtime; ip->ctime = dip->ctime; memmove(ip->addrs, dip->addrs, sizeof(ip->addrs)); brelse(bp); ip->valid = 1; if (ip->type == 0) panic("ilock: no type"); } void ilock(struct inode *ip) { if (ip == 0 || ip->ref < 1) panic("ilock"); acquiresleep(&ip->lock); if (ip->valid == 0) ip->ops->iload(ip); } void iunlock(struct inode *ip) { if (ip == 0 || !holdingsleep(&ip->lock) || ip->ref < 1) panic("iunlock"); releasesleep(&ip->lock); } void iput(struct inode *ip) { acquire(&itable.lock); if (ip->ref == 1 && ip->valid && ip->nlink == 0) { acquiresleep(&ip->lock); release(&itable.lock); ip->ops->ifree(ip); ip->valid = 0; releasesleep(&ip->lock); acquire(&itable.lock); } ip->ref--; release(&itable.lock); } void iunlockput(struct inode *ip) { iunlock(ip); iput(ip); } // =========================================================================== // Extent-tree block mapping (T_EXTENT inodes) // // Inline extents: addrs[0..NDIRECT-1] hold packed struct fsv1_extent. // MAX_INLINE_EXTENTS = NDIRECT*sizeof(uint)/sizeof(fsv1_extent) = 4. // Indirect extent block: addrs[NDIRECT] → physical block containing // BSIZE/sizeof(fsv1_extent) = 85 more extents. // addrs[NDIRECT+1] and addrs[NDIRECT+2] are unused for T_EXTENT (future use). // =========================================================================== // Find the physical address for logical block bn. // Allocates a new extent entry (and physical block) if alloc != 0. static uint bmap_extent(struct inode *ip, uint bn, int alloc) { struct fsv1_extent *ext = (struct fsv1_extent *)ip->addrs; int n = 0; // --- Search inline extents --- for (n = 0; n < (int)MAX_INLINE_EXTENTS; n++) { if (ext[n].len == 0) break; if (bn >= ext[n].start_block && bn < ext[n].start_block + ext[n].len) return ext[n].start_blkaddr + (bn - ext[n].start_block); } // --- Search indirect extent block --- if (ip->addrs[NDIRECT] != 0) { struct buf *bp = bread(ip->dev, ip->addrs[NDIRECT]); struct fsv1_extent *iext = (struct fsv1_extent *)bp->data; int nind = BSIZE / sizeof(struct fsv1_extent); for (int i = 0; i < nind; i++) { if (iext[i].len == 0) break; if (bn >= iext[i].start_block && bn < iext[i].start_block + iext[i].len) { uint addr = iext[i].start_blkaddr + (bn - iext[i].start_block); brelse(bp); return addr; } } brelse(bp); } if (!alloc) return 0; // --- Allocate a new physical block --- uint addr = balloc(ip->dev); // Try to extend the last inline extent if blocks are contiguous. if (n > 0) { struct fsv1_extent *last = &ext[n - 1]; if (bn == last->start_block + last->len && addr == last->start_blkaddr + last->len) { last->len++; return addr; } } // Fits in a new inline slot? if (n < (int)MAX_INLINE_EXTENTS) { ext[n].start_block = bn; ext[n].start_blkaddr = addr; ext[n].len = 1; return addr; } // Spill to indirect extent block. if (ip->addrs[NDIRECT] == 0) ip->addrs[NDIRECT] = balloc(ip->dev); struct buf *bp = bread(ip->dev, ip->addrs[NDIRECT]); struct fsv1_extent *iext = (struct fsv1_extent *)bp->data; int nind = BSIZE / sizeof(struct fsv1_extent); for (int i = 0; i < nind; i++) { if (iext[i].len == 0) { // Try contiguity with previous indirect extent. if (i > 0 && bn == iext[i-1].start_block + iext[i-1].len && addr == iext[i-1].start_blkaddr + iext[i-1].len) { iext[i-1].len++; } else { iext[i].start_block = bn; iext[i].start_blkaddr = addr; iext[i].len = 1; } log_write(bp); brelse(bp); return addr; } } brelse(bp); panic("bmap_extent: out of extent entries"); } // Free all data blocks referenced by a T_EXTENT inode. static void itrunc_extent(struct inode *ip) { struct fsv1_extent *ext = (struct fsv1_extent *)ip->addrs; // Free inline extents. for (int n = 0; n < (int)MAX_INLINE_EXTENTS; n++) { if (ext[n].len == 0) break; for (uint b = 0; b < ext[n].len; b++) bfree(ip->dev, ext[n].start_blkaddr + b); ext[n].start_block = 0; ext[n].start_blkaddr = 0; ext[n].len = 0; } // Free indirect extent block and its extents. if (ip->addrs[NDIRECT]) { struct buf *bp = bread(ip->dev, ip->addrs[NDIRECT]); struct fsv1_extent *iext = (struct fsv1_extent *)bp->data; int nind = BSIZE / sizeof(struct fsv1_extent); for (int i = 0; i < nind; i++) { if (iext[i].len == 0) break; for (uint b = 0; b < iext[i].len; b++) bfree(ip->dev, iext[i].start_blkaddr + b); } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT]); ip->addrs[NDIRECT] = 0; } } // =========================================================================== // bmap: logical block → physical block (dispatch by inode type) // =========================================================================== static uint bmap(struct inode *ip, uint bn) { uint addr, *a; struct buf *bp; if (ip->type == T_EXTENT) return bmap_extent(ip, bn, 1); // Classic direct / singly-indirect / doubly-indirect / triply-indirect. if (bn < NDIRECT) { if ((addr = ip->addrs[bn]) == 0) ip->addrs[bn] = addr = balloc(ip->dev); return addr; } bn -= NDIRECT; // Singly indirect if (bn < NINDIRECT) { if ((addr = ip->addrs[NDIRECT]) == 0) ip->addrs[NDIRECT] = addr = balloc(ip->dev); bp = bread(ip->dev, addr); a = (uint *)bp->data; if ((addr = a[bn]) == 0) { a[bn] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); return addr; } bn -= NINDIRECT; // Doubly indirect if (bn < NINDIRECT * NINDIRECT) { if ((addr = ip->addrs[NDIRECT + 1]) == 0) ip->addrs[NDIRECT + 1] = addr = balloc(ip->dev); bp = bread(ip->dev, addr); a = (uint *)bp->data; if ((addr = a[bn / NINDIRECT]) == 0) { a[bn / NINDIRECT] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); bp = bread(ip->dev, addr); a = (uint *)bp->data; if ((addr = a[bn % NINDIRECT]) == 0) { a[bn % NINDIRECT] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); return addr; } bn -= NINDIRECT * NINDIRECT; // Triply indirect if (bn < NINDIRECT * NINDIRECT * NINDIRECT) { if ((addr = ip->addrs[NDIRECT + 2]) == 0) ip->addrs[NDIRECT + 2] = addr = balloc(ip->dev); bp = bread(ip->dev, addr); a = (uint *)bp->data; uint idx1 = bn / (NINDIRECT * NINDIRECT); if ((addr = a[idx1]) == 0) { a[idx1] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); bp = bread(ip->dev, addr); a = (uint *)bp->data; uint idx2 = (bn / NINDIRECT) % NINDIRECT; if ((addr = a[idx2]) == 0) { a[idx2] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); bp = bread(ip->dev, addr); a = (uint *)bp->data; uint idx3 = bn % NINDIRECT; if ((addr = a[idx3]) == 0) { a[idx3] = addr = balloc(ip->dev); log_write(bp); } brelse(bp); return addr; } panic("bmap: out of range"); } // =========================================================================== // itrunc: free all data blocks // =========================================================================== void v1_itrunc_all(struct inode *ip) { int i, j; struct buf *bp; uint *a; if (ip->type == T_EXTENT) { itrunc_extent(ip); ip->size = 0; ip->ctime = now_sec(); iupdate(ip); return; } // Direct blocks for (i = 0; i < NDIRECT; i++) { if (ip->addrs[i]) { bfree(ip->dev, ip->addrs[i]); ip->addrs[i] = 0; } } // Singly indirect if (ip->addrs[NDIRECT]) { bp = bread(ip->dev, ip->addrs[NDIRECT]); a = (uint *)bp->data; for (j = 0; j < NINDIRECT; j++) if (a[j]) bfree(ip->dev, a[j]); brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT]); ip->addrs[NDIRECT] = 0; } // Doubly indirect if (ip->addrs[NDIRECT + 1]) { bp = bread(ip->dev, ip->addrs[NDIRECT + 1]); a = (uint *)bp->data; for (j = 0; j < NINDIRECT; j++) { if (a[j]) { struct buf *bp2 = bread(ip->dev, a[j]); uint *a2 = (uint *)bp2->data; for (int k = 0; k < NINDIRECT; k++) if (a2[k]) bfree(ip->dev, a2[k]); brelse(bp2); bfree(ip->dev, a[j]); } } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT + 1]); ip->addrs[NDIRECT + 1] = 0; } // Triply indirect if (ip->addrs[NDIRECT + 2]) { bp = bread(ip->dev, ip->addrs[NDIRECT + 2]); a = (uint *)bp->data; for (j = 0; j < NINDIRECT; j++) { if (!a[j]) continue; struct buf *bp2 = bread(ip->dev, a[j]); uint *a2 = (uint *)bp2->data; for (int k = 0; k < NINDIRECT; k++) { if (!a2[k]) continue; struct buf *bp3 = bread(ip->dev, a2[k]); uint *a3 = (uint *)bp3->data; for (int l = 0; l < NINDIRECT; l++) if (a3[l]) bfree(ip->dev, a3[l]); brelse(bp3); bfree(ip->dev, a2[k]); } brelse(bp2); bfree(ip->dev, a[j]); } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT + 2]); ip->addrs[NDIRECT + 2] = 0; } ip->size = 0; ip->ctime = now_sec(); iupdate(ip); } // =========================================================================== // itrunc_to: shrink inode to newsize, freeing blocks beyond it. // ip must be locked. newsize must be <= ip->size. // Triply-indirect partial truncate is not implemented; those blocks are // only fully freed when newsize falls below their range start. // =========================================================================== void v1_itrunc_to(struct inode *ip, uint64 newsize) { int i, j; struct buf *bp; uint *a; if (newsize >= ip->size) return; if (ip->type == T_EXTENT) { if (newsize == 0) itrunc_extent(ip); ip->size = newsize; ip->ctime = now_sec(); iupdate(ip); return; } // first block index that is no longer needed uint first_free = (newsize == 0) ? 0 : (newsize - 1) / BSIZE + 1; // Direct blocks for (i = first_free; i < NDIRECT; i++) { if (ip->addrs[i]) { bfree(ip->dev, ip->addrs[i]); ip->addrs[i] = 0; } } // Singly indirect (logical blocks NDIRECT .. NDIRECT+NINDIRECT-1) if (ip->addrs[NDIRECT]) { if (first_free <= NDIRECT) { bp = bread(ip->dev, ip->addrs[NDIRECT]); a = (uint *)bp->data; for (j = 0; j < NINDIRECT; j++) if (a[j]) bfree(ip->dev, a[j]); brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT]); ip->addrs[NDIRECT] = 0; } else { uint local = first_free - NDIRECT; if (local < NINDIRECT) { bp = bread(ip->dev, ip->addrs[NDIRECT]); a = (uint *)bp->data; int mod = 0; for (j = local; j < NINDIRECT; j++) if (a[j]) { bfree(ip->dev, a[j]); a[j] = 0; mod = 1; } if (mod) log_write(bp); brelse(bp); } } } // Doubly indirect (logical blocks NDIRECT+NINDIRECT .. +NINDIRECT^2-1) if (ip->addrs[NDIRECT + 1]) { uint di_start = NDIRECT + NINDIRECT; if (first_free <= di_start) { bp = bread(ip->dev, ip->addrs[NDIRECT + 1]); a = (uint *)bp->data; for (j = 0; j < NINDIRECT; j++) { if (!a[j]) continue; struct buf *bp2 = bread(ip->dev, a[j]); uint *a2 = (uint *)bp2->data; for (int k = 0; k < NINDIRECT; k++) if (a2[k]) bfree(ip->dev, a2[k]); brelse(bp2); bfree(ip->dev, a[j]); } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT + 1]); ip->addrs[NDIRECT + 1] = 0; } else { uint off = first_free - di_start; if (off < (uint)(NINDIRECT * NINDIRECT)) { uint idx1 = off / NINDIRECT; uint idx2 = off % NINDIRECT; bp = bread(ip->dev, ip->addrs[NDIRECT + 1]); a = (uint *)bp->data; int bp_mod = 0; for (j = idx1; j < NINDIRECT; j++) { if (!a[j]) continue; struct buf *bp2 = bread(ip->dev, a[j]); uint *a2 = (uint *)bp2->data; int bp2_mod = 0; int k_start = (j == (int)idx1) ? (int)idx2 : 0; for (int k = k_start; k < NINDIRECT; k++) if (a2[k]) { bfree(ip->dev, a2[k]); a2[k] = 0; bp2_mod = 1; } if (bp2_mod) log_write(bp2); brelse(bp2); if (k_start == 0) { bfree(ip->dev, a[j]); a[j] = 0; bp_mod = 1; } } if (bp_mod) log_write(bp); brelse(bp); } } } // Triply indirect: only fully freed when first_free is at or before its range if (ip->addrs[NDIRECT + 2]) { uint ti_start = NDIRECT + NINDIRECT + NINDIRECT * NINDIRECT; if (first_free <= ti_start) { bp = bread(ip->dev, ip->addrs[NDIRECT + 2]); a = (uint *)bp->data; for (j = 0; j < NINDIRECT; j++) { if (!a[j]) continue; struct buf *bp2 = bread(ip->dev, a[j]); uint *a2 = (uint *)bp2->data; for (int k = 0; k < NINDIRECT; k++) { if (!a2[k]) continue; struct buf *bp3 = bread(ip->dev, a2[k]); uint *a3 = (uint *)bp3->data; for (int l = 0; l < NINDIRECT; l++) if (a3[l]) bfree(ip->dev, a3[l]); brelse(bp3); bfree(ip->dev, a2[k]); } brelse(bp2); bfree(ip->dev, a[j]); } brelse(bp); bfree(ip->dev, ip->addrs[NDIRECT + 2]); ip->addrs[NDIRECT + 2] = 0; } } ip->size = newsize; ip->mtime = now_sec(); ip->ctime = now_sec(); iupdate(ip); } // =========================================================================== // stati // =========================================================================== void stati(struct inode *ip, struct stat *st) { struct stat kst; memset(&kst, 0, sizeof(kst)); kst.dev = ip->dev; kst.ino = ip->inum; kst.type = ip->type; kst.nlink = ip->nlink; kst.size = ip->size; kst.rwmode = ip->rwmode; kst.supermode = ip->supermode; kst.showmode = ip->showmode; kst.atime = ip->atime; kst.mtime = ip->mtime; kst.ctime = ip->ctime; for (int i = 0; i < NDIRECT + 3; i++) kst.addrs[i] = ip->addrs[i]; memmove(st, &kst, sizeof(kst)); } // =========================================================================== // readi / writei // =========================================================================== int v1_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n) { int tot = 0; struct buf *bp; if (off >= ip->size) return 0; if (off + n > ip->size) n = ip->size - off; while (tot < (int)n) { uint addr = bmap(ip, off / BSIZE); if (addr == 0) break; bp = bread(ip->dev, addr); if (bp == 0) break; int m = min((int)n - tot, (int)(BSIZE - off % BSIZE)); if (user_dst) { if (copyout(myproc()->pgdir, dst, bp->data + (off % BSIZE), m) < 0) { brelse(bp); break; } } else { memmove((void *)dst, bp->data + (off % BSIZE), m); } brelse(bp); tot += m; off += m; dst += m; } // Update access time. Caller is responsible for calling iupdate() if // the change needs to be persisted immediately (avoid calling it in hot // paths like readdir). ip->atime = now_sec(); return tot; } int v1_write(struct inode *ip, int user_src, uint64 src, uint64 off, uint n) { if (off > ip->size || off + n < off) return -1; if ((uint64)off + n > (uint64)MAXFILE * BSIZE) return -1; uint tot, m; struct buf *bp; for (tot = 0; tot < n; tot += m, off += m, src += m) { bp = bread(ip->dev, bmap(ip, off / BSIZE)); m = min(n - tot, BSIZE - off % BSIZE); if (user_src) { if (copyin(myproc()->pgdir, (addr_t)src, bp->data + (off % BSIZE), (uint64)m) < 0) { brelse(bp); break; } } else { memmove(bp->data + (off % BSIZE), (void *)src, m); } log_write(bp); brelse(bp); } if (off > ip->size) ip->size = off; ip->mtime = now_sec(); ip->ctime = now_sec(); iupdate(ip); return tot; } // =========================================================================== // Directory HTree // =========================================================================== int namecmp(const char *s, const char *t) { return strncmp(s, t, DIRSIZ); } // BKDR hash → bucket index in [16, 16+HASHRANGE). unsigned int BKDRHash(char *str) { unsigned int seed = 131; unsigned int hash = 0; while (*str) hash = hash * seed + (*str++); return (hash % HASHRANGE) + 16; } // --------------------------------------------------------------------------- // tree_expand: grow directory inode to cover byte offset `want`. // Allocates backing blocks via bmap and zero-fills any partial-block tail. // --------------------------------------------------------------------------- uint tree_expand(struct inode *dp, uint want) { if (want < dp->size) return want; // Ensure all blocks up to the one containing `want` exist. uint end_block = want / BSIZE; for (uint b = dp->size / BSIZE; b <= end_block; b++) bmap(dp, b); // Zero-fill the old partial tail of the current last block. if (dp->size > 0) { uint inner = dp->size % BSIZE; if (inner != 0) { uint fill_end = (dp->size / BSIZE == want / BSIZE) ? (want % BSIZE) + 1 : BSIZE; struct buf *bp = bread(dp->dev, bmap(dp, (dp->size - 1) / BSIZE)); memset(bp->data + inner, 0, fill_end - inner); log_write(bp); brelse(bp); } } dp->size = want + 1; dp->mtime = now_sec(); dp->ctime = now_sec(); iupdate(dp); return want; } // --------------------------------------------------------------------------- // tree_search: hash-table directory lookup / slot-allocation. // // `add == 0` → find entry matching `name` (de->inum = 0 if not found). // `add == 1` → find a free slot (expands the dir if needed). // // Primary bucket: [hash*16*sizeof(dirent), (hash+1)*16*sizeof(dirent)) // Overflow bucket: [OVERFLOW_DIR_START, OVERFLOW_DIR_END) // --------------------------------------------------------------------------- void tree_search(struct inode *dp, char *name, uint *poff, struct dirent *de, uint add) { unsigned int hash_value = BKDRHash(name); uint tree_node_off = (uint)(hash_value * DIRENT_BUCKET_BYTES); uint tree_next_node_off = (uint)((hash_value + 1) * DIRENT_BUCKET_BYTES); if (tree_node_off > MAXFSIZE) { cprintf("tree_search: name=%s hash=%u off=%u MAXFSIZE=%u\n", name, hash_value, tree_node_off, MAXFSIZE); panic("tree_search: bucket offset exceeds MAXFSIZE"); } // Primary bucket for (; tree_node_off < tree_next_node_off; tree_node_off += sizeof(struct dirent)) { if (tree_node_off >= dp->size) { if (add) tree_expand(dp, tree_next_node_off - 1); else { de->inum = 0; return; } } if (readi(dp, 0, (uint64)de, tree_node_off, sizeof(struct dirent)) != sizeof(struct dirent)) { cprintf("tree_search: readi failed: name=%s off=%u size=%u\n", name, tree_node_off, dp->size); panic("tree_search: readi"); } if (!add) { if (namecmp(de->name, name) == 0) { if (poff) *poff = tree_node_off; return; } } else { if (de->inum == 0) { if (poff) *poff = tree_node_off; return; } } } // Overflow bucket tree_node_off = OVERFLOW_DIR_START; tree_next_node_off = OVERFLOW_DIR_END; for (; tree_node_off < tree_next_node_off; tree_node_off += sizeof(struct dirent)) { if (tree_node_off >= dp->size) { if (add) tree_expand(dp, tree_next_node_off - 1); else { de->inum = 0; return; } } if (readi(dp, 0, (uint64)de, tree_node_off, sizeof(struct dirent)) != sizeof(struct dirent)) { cprintf("tree_search: readi failed (overflow): name=%s off=%u size=%u\n", name, tree_node_off, dp->size); panic("tree_search: readi overflow"); } if (!add) { if (namecmp(de->name, name) == 0) { if (poff) *poff = tree_node_off; return; } } else { if (de->inum == 0) { if (poff) *poff = tree_node_off; return; } } } de->inum = 0; } // =========================================================================== // dirlookup / dirlink / dirlookup_old / build_dir_tree // =========================================================================== struct inode * v1_dirlookup(struct inode *dp, char *name, uint *poff) { uint off, inum; struct dirent de; if (dp->type != T_DIR) panic("dirlookup: not a directory"); // "." and ".." are stored at fixed offsets 0 and sizeof(dirent). if (namecmp(name, ".") == 0 || namecmp(name, "..") == 0) { for (off = 0; off < 2 * sizeof(de); off += sizeof(de)) { if (readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) panic("dirlookup: read"); if (de.inum == 0) continue; if (namecmp(name, de.name) == 0) { if (poff) *poff = off; inum = de.inum; return iget(dp->dev, inum); } } return 0; } tree_search(dp, name, poff, &de, 0); if (de.inum) return iget(dp->dev, de.inum); return 0; } int v1_dirlink(struct inode *dp, char *name, uint64 inum) { uint off = 0; struct dirent de; struct inode *ip; if ((ip = dirlookup(dp, name, 0)) != 0) { iput(ip); return -1; // already exists } tree_search(dp, name, &off, &de, 1); strncpy(de.name, name, DIRSIZ); de.inum = inum; if (writei(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) panic("dirlink: writei"); return 0; } // Remove the entry `name`, which dirlookup reported lives at byte offset `off`. // v1 marks a slot free by zeroing it in place; the hash bucket keeps its size. static int v1_dirunlink(struct inode *dp, char *name, uint off) { struct dirent de; (void)name; memset(&de, 0, sizeof(de)); if (writei(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) return -1; return 0; } // 1 if the directory holds nothing but "." and "..". static int v1_dirempty(struct inode *dp) { struct dirent de; for (uint64 off = 2 * sizeof(de); off < dp->size; off += sizeof(de)) { if (readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) return -1; if (de.inum != 0) return 0; } return 1; } static void v1_ifree(struct inode *ip) { itrunc(ip); ifree(ip->dev, ip->inum); } static void v1_truncate(struct inode *ip, uint64 newsize) { // Truncating to zero has its own path: it releases the indirect blocks // wholesale rather than walking back from an offset, and it is by far the // common case (every O_TRUNC open). if (newsize == 0) v1_itrunc_all(ip); else v1_itrunc_to(ip, newsize); } // Read directory entries in getdents64 layout. // // Directories are hash-indexed (see tree_search/tree_expand), so dp->size can // be large and mostly empty even for a handful of real entries -- around 115 KB // for the ~55 files in /bin. Reading one 16-byte dirent at a time meant a // bmap()+bread() per slot, nearly all of them empty, which was slow enough to // look like a hang. A whole block at a time fixes that. static int v1_getdents(struct inode *dp, char *out, int cap, uint64 *poff) { struct dirent block[BSIZE / sizeof(struct dirent)]; int written = 0; while (*poff < dp->size) { uint64 blockoff = *poff - (*poff % BSIZE); int nread = readi(dp, 0, (uint64)block, blockoff, BSIZE); if (nread <= 0) break; int nslots = nread / sizeof(struct dirent); int slot = (int)((*poff - blockoff) / sizeof(struct dirent)); for (; slot < nslots; slot++) { uint64 entry_off = blockoff + (uint64)(slot + 1) * sizeof(struct dirent); struct dirent *xde = &block[slot]; if (xde->inum == 0) { *poff = entry_off; continue; } int namelen = 0; while (namelen < DIRSIZ && xde->name[namelen]) namelen++; int reclen = (8 + 8 + 2 + 1 + namelen + 1 + 7) & ~7; if (written + reclen > cap) return written; /* buffer full; resume from *poff */ *poff = entry_off; uint8 dtype = 0; /* DT_UNKNOWN */ if (xde->inum == dp->inum) { // "." and any other self-referential entry: iget() would hand back this // same, already-locked inode, and ilock() on it would self-deadlock on // the sleeplock the caller is still holding. We already know it is a // directory, so skip the relock entirely. dtype = 4; /* DT_DIR */ } else { struct inode *eip = iget(dp->dev, xde->inum); if (eip) { ilock(eip); if (eip->type == T_DIR) dtype = 4; /* DT_DIR */ else if (eip->type == T_DEV) dtype = 2; /* DT_CHR */ else dtype = 8; /* DT_REG */ iunlockput(eip); } } char *kptr = out + written; uint64 *p64 = (uint64 *)kptr; p64[0] = xde->inum; p64[1] = *poff; *(uint16 *)(kptr + 16) = (uint16)reclen; *(uint8 *)(kptr + 18) = dtype; memmove(kptr + 19, xde->name, namelen); kptr[19 + namelen] = '\0'; written += reclen; } } return written; } // Linear-scan fallback (for readdir iteration over the whole directory). struct inode * dirlookup_old(struct inode *dp, char *name, uint *poff) { uint off, inum; struct dirent de; if (dp->type != T_DIR) panic("dirlookup_old: not a directory"); for (off = 0; off < dp->size; off += sizeof(de)) { if (readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) panic("dirlookup_old: read"); if (de.inum == 0) continue; if (namecmp(name, de.name) == 0) { if (poff) *poff = off; inum = de.inum; return iget(dp->dev, inum); } } return 0; } void build_dir_tree(struct inode *dp, uint64 pinum) { dp->size = 2 * sizeof(struct dirent); iupdate(dp); struct dirent de; // Write "." entry at offset 0. if (readi(dp, 0, (uint64)&de, 0, sizeof(de)) != sizeof(de)) panic("build_dir_tree: readi ."); strncpy(de.name, ".", DIRSIZ); de.inum = dp->inum; if (writei(dp, 0, (uint64)&de, 0, sizeof(de)) != sizeof(de)) panic("build_dir_tree: writei ."); // Write ".." entry at offset sizeof(dirent). if (readi(dp, 0, (uint64)&de, sizeof(de), sizeof(de)) != sizeof(de)) panic("build_dir_tree: readi .."); strncpy(de.name, "..", DIRSIZ); de.inum = pinum; if (writei(dp, 0, (uint64)&de, sizeof(de), sizeof(de)) != sizeof(de)) panic("build_dir_tree: writei .."); } // =========================================================================== // Path lookup // =========================================================================== static char * skipelem(char *path, char *name) { char *s; int len; while (*path == '/') path++; if (*path == 0) return 0; s = path; while (*path != '/' && *path != 0) path++; len = path - s; if (len >= DIRSIZ) memmove(name, s, DIRSIZ); else { memmove(name, s, len); name[len] = 0; } while (*path == '/') path++; return path; } // =========================================================================== // Mount table // // A mount is a substitution made during path resolution: when the walk lands on // a directory that is a mount point, it continues from the mounted filesystem's // root instead. Nothing on either disk records it, so unmounting is forgetting. // // The reverse direction matters as much as the forward one. ".." at the root of // a mounted filesystem has to leave it, and the mounted filesystem has no idea // it is mounted anywhere -- SchFS would answer with its own root, which is how // a subtree becomes a place you can walk into and never out of. // =========================================================================== #define NMOUNT 8 static struct { struct spinlock lock; struct { int used; uint dev; // device holding the mount point directory uint64 inum; // the mount point directory itself uint fsdev; // device of the filesystem mounted on it uint64 root; // that filesystem's root inode } m[NMOUNT]; } mtable; void mountinit(void) { initlock(&mtable.lock, "mtable"); } // Attach `fsdev`:`root` over the directory `dev`:`inum`. int mount_add(uint dev, uint64 inum, uint fsdev, uint64 root) { acquire(&mtable.lock); for (int i = 0; i < NMOUNT; i++) if (mtable.m[i].used && mtable.m[i].dev == dev && mtable.m[i].inum == inum) { release(&mtable.lock); return -1; /* already mounted there */ } for (int i = 0; i < NMOUNT; i++) if (!mtable.m[i].used) { mtable.m[i].used = 1; mtable.m[i].dev = dev; mtable.m[i].inum = inum; mtable.m[i].fsdev = fsdev; mtable.m[i].root = root; release(&mtable.lock); return 0; } release(&mtable.lock); return -1; /* table full */ } // Detach by naming the *mounted* filesystem's root, which is what a path to // the mount point resolves to once the mount exists. Naming the covered // directory instead is not possible from userland: the whole point of the // mount is that the path no longer reaches it. int mount_del_by_root(uint fsdev, uint64 root) { acquire(&mtable.lock); for (int i = 0; i < NMOUNT; i++) if (mtable.m[i].used && mtable.m[i].fsdev == fsdev && mtable.m[i].root == root) { mtable.m[i].used = 0; release(&mtable.lock); return 0; } release(&mtable.lock); return -1; } // If dev:inum is a mount point, hand back what is mounted on it. static int mount_down(uint dev, uint64 inum, uint *fsdev, uint64 *root) { acquire(&mtable.lock); for (int i = 0; i < NMOUNT; i++) if (mtable.m[i].used && mtable.m[i].dev == dev && mtable.m[i].inum == inum) { *fsdev = mtable.m[i].fsdev; *root = mtable.m[i].root; release(&mtable.lock); return 1; } release(&mtable.lock); return 0; } // If dev:inum is a mounted filesystem's root, hand back the directory it // covers, so ".." can climb out of it. static int mount_up(uint dev, uint64 inum, uint *pdev, uint64 *pinum) { acquire(&mtable.lock); for (int i = 0; i < NMOUNT; i++) if (mtable.m[i].used && mtable.m[i].fsdev == dev && mtable.m[i].root == inum) { *pdev = mtable.m[i].dev; *pinum = mtable.m[i].inum; release(&mtable.lock); return 1; } release(&mtable.lock); return 0; } // Follow every mount stacked on this inode, releasing each one as we go. A // loop rather than a single step because nothing stops a second filesystem // being mounted on the first one's root. static struct inode * mount_cross(struct inode *ip) { uint fsdev; uint64 root; int hops = 0; while (mount_down(ip->dev, ip->inum, &fsdev, &root)) { if (++hops > NMOUNT) break; /* mounts stacked in a cycle */ struct inode *next = iget(fsdev, root); iput(ip); ip = next; } return ip; } // Resolves `path` starting from `start` (idup'd internally — caller keeps // ownership of its own reference) rather than always `myproc()->cwd`. This is // what makes a real (non-AT_FDCWD) dirfd argument to the *at() syscalls // actually work: an absolute path still resolves from root either way. static struct inode * namex_from(struct inode *start, char *path, int nameiparent, char *name) { struct inode *ip, *next; // Collapse consecutive slashes into a safe local copy. char safe[MAXPATH]; int i = 0, j = 0; while (path[i] != '\0' && j < MAXPATH - 1) { if (path[i] == '/') { safe[j++] = '/'; while (path[i] == '/') i++; } else { safe[j++] = path[i++]; } } safe[j] = '\0'; char *spath = safe; // Root is a SchFS object, not a v1 inode. ops_for(SCHFS_DEV) then routes // every operation on it through schfs_ops, which is the whole point: the v1 // format code in this file is no longer reachable from `/`. ip = (*spath == '/') ? iget(SCHFS_DEV, schfs_kroot()) : idup(start); // The root itself can be a mount point, and a relative walk can start on one. ip = mount_cross(ip); while ((spath = skipelem(spath, name)) != 0) { // ".." out of a mounted filesystem's root leaves it. The filesystem down // there does not know it is mounted anywhere and would answer with its own // root, which turns the mount into a subtree you can enter and never // leave. Step up to the covered directory first and resolve ".." there. if (name[0] == '.' && name[1] == '.' && name[2] == '\0') { uint pdev; uint64 pinum; if (mount_up(ip->dev, ip->inum, &pdev, &pinum)) { struct inode *cover = iget(pdev, pinum); iput(ip); ip = cover; } } ilock(ip); if (ip->type != T_DIR) { iunlockput(ip); return 0; } if (nameiparent && *spath == '\0') { iunlock(ip); return ip; } if ((next = dirlookup(ip, name, 0)) == 0) { iunlockput(ip); return 0; } iunlockput(ip); ip = mount_cross(next); } if (nameiparent) { iput(ip); return 0; } return ip; } static struct inode * namex(char *path, int nameiparent, char *name) { return namex_from(myproc()->cwd, path, nameiparent, name); } struct inode * namei(char *path) { char name[DIRSIZ]; return namex(path, 0, name); } struct inode * nameiparent(char *path, char *name) { return namex(path, 1, name); } // dirfd-relative counterparts, for the *at() syscalls (openat/fstatat/ // unlinkat/...) when given a real directory fd instead of AT_FDCWD. struct inode * nameifrom(struct inode *start, char *path) { char name[DIRSIZ]; return namex_from(start, path, 0, name); } struct inode * nameiparentfrom(struct inode *start, char *path, char *name) { return namex_from(start, path, 1, name); } // =========================================================================== // Symlink traversal // // Fixed from original: // - Buffer was char path[MAXPATH+1]. Original called readi(..., MAXPATH) // then a second readi(... path+len, len, MAXPATH+1) to assert nothing // more was there. The second read could write 1 byte past the buffer // if len == MAXPATH. // - Now: read at most MAXPATH-1 bytes, always NUL-terminate. // =========================================================================== struct inode * divesymlink(struct inode *isym) { struct inode *ip = isym; char path[MAXPATH]; int deep = 0; do { int len = readi(ip, 0, (uint64)path, 0, MAXPATH - 1); if (len < 0) len = 0; path[len] = '\0'; iunlockput(ip); if (++deep > 10) return 0; // symlink loop if ((ip = namei(path)) == 0) return 0; ilock(ip); } while (ip->type == T_SYMLINK); return ip; } // =========================================================================== // Public entry points // // Thin wrappers over ip->ops. Everything above the filesystem layer calls // these and never learns which filesystem it is talking to; adding another one // is a new fs_ops table and nothing else. // =========================================================================== void iupdate(struct inode *ip) { ip->ops->istore(ip); } int readi(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n) { return ip->ops->read(ip, user_dst, dst, off, n); } int writei(struct inode *ip, int user_src, uint64 src, uint64 off, uint n) { return ip->ops->write(ip, user_src, src, off, n); } void itrunc(struct inode *ip) { ip->ops->truncate(ip, 0); } void itrunc_to(struct inode *ip, uint64 newsize) { ip->ops->truncate(ip, newsize); } struct inode * dirlookup(struct inode *dp, char *name, uint *poff) { return dp->ops->dirlookup(dp, name, poff); } int dirlink(struct inode *dp, char *name, uint64 inum) { return dp->ops->dirlink(dp, name, inum); } int dirunlink(struct inode *dp, char *name, uint off) { return dp->ops->dirunlink(dp, name, off); } int dirempty(struct inode *dp) { return dp->ops->dirempty(dp); } int getdents(struct inode *dp, char *out, int cap, uint64 *poff) { return dp->ops->getdents(dp, out, cap, poff); } // The device decides the filesystem, so a caller that wants an object on a // particular volume names that volume rather than a filesystem. struct inode * ialloc(uint dev, short type) { return ops_for(dev)->ialloc(dev, type); }