The last three phases of the filesystem, which were written together and
touch the same files throughout -- kmount.c, objw.c and vfs.c are each
rewritten by all three -- so they land as one commit rather than three
that could not be built separately.
Durability. A transaction that reaches the platter out of order is worse
than one that never reached it: the uberblock can name a tree whose
blocks are still in the drive's write cache. bdirty/bflush give the
buffer layer an explicit barrier, ideflush issues ATA FLUSH CACHE and
usb_storage_flush the SCSI SYNCHRONIZE CACHE that means the same thing,
and a commit now waits for the data before it writes the root that
points at it. The uberblock groups are read newest-first with fallback,
so a volume whose newest roots are torn mounts at an older one and says
which generations were lost.
Reflink. Extents carry a refcount, so two objects can name the same
blocks and neither frees them while the other holds them. Exposed as
FICLONE -- Linux's ioctl value, so `cp --reflink` works unmodified --
and as cmd/reflink for the case where there is no cp to hand.
Attributes, and the one genuinely new idea here. attr.c stores named
values on an object, reachable through setxattr/getxattr/removexattr at
Linux's syscall numbers. On top of that, schfs_index declares an index
over an attribute name and schfs_query answers "which objects carry this
value" from the B-tree rather than by walking. A query against an
unindexed attribute is refused with an error, never silently answered by
a full scan: a query that quietly becomes a scan is worse than one that
admits it cannot help. cmd/schattr drives both.
Three bugs fixed on the way, each found by the numbers being wrong
rather than by inspection:
- statfs's f_fsid was declared int64[2]. Linux's fsid_t is two *ints*,
so every field after it landed one slot late: userland read f_frsize
out of f_namelen, got 255, and df reported a 32 MB root filesystem
as 1992 KB. It hid for as long as it did because the fields used to
be invented constants, so wrong numbers looked like the usual ones.
- struct inode's nlink was a short against SchFS's uint32, so a file
past 32767 names came back negative -- and iput frees an object
whose count reaches zero. It is an int now, signed on purpose, so an
underflow lands somewhere a check can see rather than wrapping.
- filewrite split every write to fit v1's log. That limit belongs to
the journal, not to writing, and SchFS makes one transaction of
whatever it is handed: a 1 MB write was becoming 170 commits and
170 flushes for nothing. fs_ops carries max_write now, and SchFS
sets no limit.
Also here: a real mount table, so `mount -t schfs /dev/hdb /mnt` shows
up in /proc/mounts -- df looks a path up there before it calls statfs at
all, so a mount missing from that list was invisible to it -- several
volumes mounted at once, snapshots mounted by name, and /proc/fsstat
with an rdtsc-based counter, which is the only instrument fine enough to
see inside a single syscall.
Verified: `make schfs-check` passes in full, including the sweep that
crashes the volume at each of 119 points mid-transaction and requires
every one to mount with exactly its committed data, and the case where
both newest uberblocks are rubbish and the volume still comes back. The
headless boot check passes with 27 [ok] and no failures, on a SchFS root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1907 lines
51 KiB
C
1907 lines
51 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 "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,
|
|
// 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;
|
|
// Defined in fs/procfs.c -- the synthetic /proc.
|
|
extern const struct fs_ops procfs_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);
|
|
}
|
|
|
|
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;
|
|
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;
|
|
// 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;
|
|
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
|
|
// 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);
|
|
}
|