The filesystem every other system can read: a USB stick, an SD card, the EFI system partition. It is here first for the hardware report, which has to leave a machine on something that can be read on another one. mount -t vfat /dev/usb0p1 /mnt (msdos and fat work too; -o ro) Written from Microsoft's specification, and matched to Linux vfat where the specification leaves a choice: 8.3 names with the NT lowercase bits, long-name entries otherwise, Linux's alias rule (REPORTS, then BIGNUM~1), case-insensitive lookup, UTC times, the boot-sector dirty bit, every FAT copy and FSInfo kept up to date. It uses its own sector cache over the new blkdev_xfer, since a partition need not be a whole number of 4 KB blocks, and every operation that changes something reaches the disk before it returns. Inode numbers come from the entry position, or the first cluster for a directory, plus a small table for entries that moved. Around it: - /dev lists every disk and partition the block layer found (major 8, minor = block device), and raw reads and writes on them go by sector. - The ESP and MBR FAT partitions are registered, typed so the root probe passes over them. MBR entries with a bad status byte are skipped, so a superfloppy's boot code is not read as a partition table. - statfs, sync, fsync, umount (EBUSY while in use), shutdown, reboot and the power button all know about FAT. - create undoes and reports a failed ialloc or dirlink (ENOSPC, EINVAL, EPERM) instead of panicking; fs_ops gains refuse_types and no_hardlinks. - filewrite returns a short write instead of panicking on it. - utimensat is implemented. As a stub returning 0 it made toybox touch believe the file existed, so `touch newfile` created nothing on any filesystem. Tested in QEMU against mkfs.vfat images (FAT12 floppy, FAT16 on MBR, FAT32 superfloppy and GPT ESP) by writing, renaming, deleting, filling to ENOSPC and truncating there, then checking with fsck.fat -n and mtools on the host: all clean, contents match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
1955 lines
53 KiB
C
1955 lines
53 KiB
C
// 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 "fsstat.h"
|
|
#include "file.h"
|
|
#include "fat.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,
|
|
// A write has to fit in the on-disk log, with room for the inode block, the
|
|
// bitmap block and the log header. This is the constant filewrite used to
|
|
// apply to every filesystem, SchFS included.
|
|
.max_write = ((LOGSIZE-1-1-2) / 2) * 512,
|
|
};
|
|
|
|
// Defined in fs/schfs/vfs.c, which is only linked when SchFS is built in.
|
|
extern const struct fs_ops schfs_ops;
|
|
extern const struct fs_ops fat_ops;
|
|
// Defined in fs/procfs.c -- the synthetic /proc.
|
|
extern const struct fs_ops procfs_ops;
|
|
extern const struct fs_ops devfs_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 SCHFS_IS_SNAP_DEV(dev) || (FAT_IS_DEV(dev) && fat_readonly(dev));
|
|
}
|
|
|
|
static const struct fs_ops *
|
|
ops_for(uint dev)
|
|
{
|
|
// Every mounted SchFS volume, and every mounted snapshot. One table serves
|
|
// them all: the operations take an inode, an inode carries its device, and
|
|
// kmount.c turns that device into the right volume.
|
|
if (SCHFS_IS_ANY_DEV(dev)) return &schfs_ops;
|
|
if (dev == PROCFS_DEV) return &procfs_ops;
|
|
if (dev == DEVFS_DEV) return &devfs_ops;
|
|
if (FAT_IS_DEV(dev)) return &fat_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 since the epoch.
|
|
//
|
|
// It used to return uptime() under a comment saying to replace it with a real
|
|
// wall-clock source "if your kernel has one". It has one -- see wallclock() in
|
|
// kernel/core/time.c -- and the same fix went into SchFS's know(), where the
|
|
// symptom was visible: every file created after boot was stamped 1970, and the
|
|
// timestamps advanced a hundred times too fast because uptime() counts 10 ms
|
|
// ticks and they were being read as seconds.
|
|
static uint
|
|
now_sec(void)
|
|
{
|
|
uint64 sec;
|
|
wallclock(&sec, 0);
|
|
return (uint)sec;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// 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;
|
|
// v1's on-disk field is a short and cannot be widened without changing the
|
|
// format, so the narrowing is the format's, not an accident of this line.
|
|
// The in-memory count is an int now because SchFS needs the range.
|
|
dip->nlink = (short)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;
|
|
FSSTAT_TAKE(fsstat.inode_used, fsstat.inode_peak); // 0 -> 1
|
|
ip->valid = 0;
|
|
ip->ops = ops_for(dev);
|
|
release(&itable.lock);
|
|
return ip;
|
|
}
|
|
|
|
// Is (dev, inum) in the inode cache with a reference? For a filesystem that
|
|
// keeps its own table of inode numbers and wants to know which it can drop.
|
|
int
|
|
iheld(uint dev, uint64 inum)
|
|
{
|
|
int held = 0;
|
|
acquire(&itable.lock);
|
|
for (struct inode *ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++)
|
|
if (ip->ref > 0 && ip->dev == dev && ip->inum == inum) {
|
|
held = 1;
|
|
break;
|
|
}
|
|
release(&itable.lock);
|
|
return held;
|
|
}
|
|
|
|
// Is anything on dev held, other than `except` (a mounted root, which the
|
|
// mount itself holds)? For umount.
|
|
int
|
|
ibusy(uint dev, uint64 except)
|
|
{
|
|
int busy = 0;
|
|
acquire(&itable.lock);
|
|
for (struct inode *ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++)
|
|
if (ip->ref > 0 && ip->dev == dev && ip->inum != except) {
|
|
busy = 1;
|
|
break;
|
|
}
|
|
release(&itable.lock);
|
|
return busy;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (--ip->ref == 0)
|
|
FSSTAT_DROP(fsstat.inode_used);
|
|
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
|
|
// What userland called it. Resolution never needs these -- it works in
|
|
// (dev, inum) -- but /proc/mounts does, and a path cannot be recovered from
|
|
// an inode without walking the whole tree backwards. So the strings are
|
|
// kept at mount time, when the caller still has them.
|
|
char path[MAXPATH];
|
|
char source[MAXPATH];
|
|
char type[16];
|
|
} m[NMOUNT];
|
|
} mtable;
|
|
|
|
// Copy a string into a fixed field, always NUL-terminating.
|
|
static void
|
|
mstrcpy(char *dst, const char *src, int cap)
|
|
{
|
|
int i = 0;
|
|
if (src)
|
|
for (; i < cap - 1 && src[i]; i++)
|
|
dst[i] = src[i];
|
|
dst[i] = '\0';
|
|
}
|
|
|
|
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,
|
|
const char *path, const char *source, const char *type)
|
|
{
|
|
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;
|
|
mstrcpy(mtable.m[i].path, path, MAXPATH);
|
|
mstrcpy(mtable.m[i].source, source, MAXPATH);
|
|
mstrcpy(mtable.m[i].type, type, 16);
|
|
release(&mtable.lock);
|
|
return 0;
|
|
}
|
|
release(&mtable.lock);
|
|
return -1; /* table full */
|
|
}
|
|
|
|
// Walk the mount table for /proc/mounts. Returns 0 and fills the caller's
|
|
// buffers for entry `i`, or -1 when there is no such entry.
|
|
//
|
|
// An index rather than a callback because the caller formats into a growing
|
|
// string buffer and must not hold this lock while doing it.
|
|
int
|
|
mount_get(int i, char *path, char *source, char *type, uint *fsdev)
|
|
{
|
|
if (i < 0 || i >= NMOUNT)
|
|
return -1;
|
|
acquire(&mtable.lock);
|
|
if (!mtable.m[i].used) {
|
|
release(&mtable.lock);
|
|
return -1;
|
|
}
|
|
mstrcpy(path, mtable.m[i].path, MAXPATH);
|
|
mstrcpy(source, mtable.m[i].source, MAXPATH);
|
|
mstrcpy(type, mtable.m[i].type, 16);
|
|
if (fsdev)
|
|
*fsdev = mtable.m[i].fsdev;
|
|
release(&mtable.lock);
|
|
return 0;
|
|
}
|
|
|
|
// 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(SCHFS_DEV)) : 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);
|
|
}
|