Symlinks are followed in namex_from() now, in the middle of a path as well as at the end, and a relative target resolves from the directory that holds the link rather than from the caller's cwd. `ln -s f d1/s; cat d1/s` said "Symbolic link loop" because it looked for ./f. A loop is ELOOP after 40 links, a dangling link ENOENT, and open(O_CREAT) through a link to a file opens the file. divesymlink() is gone. mknod from a Linux program passes (mode, dev) where the native call passes (major, minor), and was read as the native one: `mknod n c 1 2` made node 432,258, and mkfifo made a character device. Both are decoded now; a FIFO is EPERM, and an existing name is EEXIST. /proc/mounts listed devtmpfs twice: once from a fixed line and once from the mount table. The fixed line is gone, and mount_add() refuses a filesystem mounted on its own root, which is where a second devtmpfs mount lands. A read-only FAT mount says ro. /proc/partitions exists, in Linux's format, and /proc/filesystems lists vfat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
1975 lines
54 KiB
C
1975 lines
54 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"
|
|
#include "errno.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)
|
|
{
|
|
// The lookup that found the mount point already crossed any mount on it,
|
|
// so mounting devtmpfs on /dev a second time lands on devtmpfs's own root.
|
|
// That stacked it on itself and listed it twice in /proc/mounts.
|
|
if (dev == fsdev && inum == root)
|
|
return -1;
|
|
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.
|
|
//
|
|
// Symlinks are followed the way Linux follows them. One in the middle of the
|
|
// path always is; the last component only when `follow` says so, since
|
|
// lstat(), readlink(), unlink() and friends want the link itself. A relative
|
|
// target is resolved from the directory that holds the link, not from the
|
|
// caller's cwd, and more than MAXSYMLINKS of them in one lookup is ELOOP.
|
|
//
|
|
// The link is read with no directory locked: the walk only ever holds a
|
|
// reference to the directory it is in, so re-entering it costs nothing.
|
|
#define MAXSYMLINKS 40
|
|
|
|
static struct inode *
|
|
namex_from(struct inode *start, char *path, int nameiparent, char *name,
|
|
int follow, int *err)
|
|
{
|
|
struct inode *ip, *next;
|
|
int links = 0;
|
|
|
|
// A private copy, because following a link rewrites what is left of it.
|
|
char safe[MAXPATH];
|
|
safestrcpy(safe, path, MAXPATH);
|
|
char *spath = safe;
|
|
if (err)
|
|
*err = ENOENT;
|
|
|
|
// 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);
|
|
if (err)
|
|
*err = ENOTDIR;
|
|
return 0;
|
|
}
|
|
if (nameiparent && *spath == '\0') {
|
|
iunlock(ip);
|
|
return ip;
|
|
}
|
|
if ((next = dirlookup(ip, name, 0)) == 0) {
|
|
iunlockput(ip);
|
|
return 0;
|
|
}
|
|
iunlock(ip);
|
|
next = mount_cross(next);
|
|
|
|
ilock(next);
|
|
if (next->type != T_SYMLINK || (*spath == '\0' && (!follow || nameiparent))) {
|
|
iunlock(next);
|
|
iput(ip);
|
|
ip = next;
|
|
continue;
|
|
}
|
|
|
|
// A link: put its target in front of what is left of the path and carry
|
|
// on from the directory it was found in, or from root.
|
|
char tgt[MAXPATH];
|
|
int len = (++links > MAXSYMLINKS) ? -1
|
|
: readi(next, 0, (uint64)tgt, 0, MAXPATH - 1);
|
|
iunlockput(next);
|
|
int rest = strlen(spath);
|
|
if (len <= 0 || len + 1 + rest >= MAXPATH) {
|
|
iput(ip);
|
|
if (err)
|
|
*err = len < 0 ? ELOOP : len == 0 ? ENOENT : ENAMETOOLONG;
|
|
return 0;
|
|
}
|
|
tgt[len] = '\0';
|
|
if (rest) {
|
|
tgt[len] = '/';
|
|
memmove(tgt + len + 1, spath, rest + 1);
|
|
}
|
|
memmove(safe, tgt, strlen(tgt) + 1);
|
|
spath = safe;
|
|
if (*spath == '/') {
|
|
iput(ip);
|
|
ip = mount_cross(iget(SCHFS_DEV, schfs_kroot(SCHFS_DEV)));
|
|
}
|
|
}
|
|
|
|
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, 0, 0);
|
|
}
|
|
|
|
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, 0, 0);
|
|
}
|
|
|
|
struct inode *
|
|
nameiparentfrom(struct inode *start, char *path, char *name)
|
|
{
|
|
return namex_from(start, path, 1, name, 0, 0);
|
|
}
|
|
|
|
// The lookup open(), stat(), chdir() and exec() want: a final symlink is
|
|
// followed too, and on failure `*err` says why (ENOENT, ENOTDIR, ELOOP,
|
|
// ENAMETOOLONG), so a loop is not reported as a missing file.
|
|
struct inode *
|
|
nameifollow(struct inode *start, char *path, int *err)
|
|
{
|
|
char name[DIRSIZ];
|
|
return namex_from(start ? start : myproc()->cwd, path, 0, name, 1, err);
|
|
}
|
|
|
|
// ===========================================================================
|
|
// 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);
|
|
}
|