Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
623
kernel/fs/schfs/kmount.c
Normal file
623
kernel/fs/schfs/kmount.c
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// SchFS in the kernel: block device, mount, and the transaction bracket.
|
||||
//
|
||||
// The filesystem code itself (btree.c, space.c, vol.c, obj.c, objw.c) is the
|
||||
// same portable C the host tools compile, so nothing here re-implements the
|
||||
// format. This file is only the three things that cannot be shared: a block
|
||||
// device backed by the buffer cache, the boot-time mount, and the rule about
|
||||
// when a group of changes becomes durable.
|
||||
//
|
||||
// The running root filesystem is still the v1 code in kernel/fs/fs.c, so a
|
||||
// SchFS volume that fails to mount costs nothing — the system boots exactly as
|
||||
// before and says so on the console.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transactions
|
||||
//
|
||||
// Every entry point below that changes anything is one transaction group: it
|
||||
// takes the volume lock, does its work, and commits before returning. That
|
||||
// makes an operation durable the moment it returns, and — because a commit is a
|
||||
// single uberblock write that either lands or does not — indivisible against a
|
||||
// crash. There is no journal to replay and nothing to undo, because none of the
|
||||
// work is reachable until that one block is written.
|
||||
//
|
||||
// The cost is one uberblock write per operation. Batching several operations
|
||||
// into a group would amortise it, and the structure here is deliberately shaped
|
||||
// so that becomes a change to where commit is called rather than a redesign;
|
||||
// but a filesystem that loses the last few operations on a crash is a much
|
||||
// worse default than a slow one.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "buf.h"
|
||||
|
||||
#include "schfs_int.h"
|
||||
|
||||
// A SchFS block is 4096 bytes and a buffer-cache block is BSIZE (1024), so each
|
||||
// filesystem block spans SCHFS_BPB consecutive buffers. Keeping the buffer
|
||||
// cache at its own block size avoids disturbing the v1 filesystem, the log and
|
||||
// the IDE driver, all of which assume BSIZE.
|
||||
#define SCHFS_BPB (SCHFS_BLKSIZE / BSIZE)
|
||||
|
||||
struct schfs_kdev {
|
||||
uint dev;
|
||||
};
|
||||
|
||||
static struct schfs_kdev kdev;
|
||||
static struct schfs_vol kvol;
|
||||
static int kmounted;
|
||||
|
||||
// Block buffers, allocated once at mount.
|
||||
//
|
||||
// Nothing in SchFS may put a block on the stack: a cursor needs a whole
|
||||
// SCHFS_BLKSIZE buffer and the kernel stack is one 4096-byte page (KSTACKSIZE),
|
||||
// so a single such local overflows it. kalloc returns exactly one page, which
|
||||
// is exactly one SchFS block, so each of these fits with nothing to spare.
|
||||
//
|
||||
// kcurblk and kdatablk serve the read paths; kctx* are the three the volume
|
||||
// keeps for mutations. They are separate pages because a write path runs a
|
||||
// cursor of its own inside the volume's buffers while the caller's cursor is
|
||||
// still positioned in kcurblk.
|
||||
static char *kcurblk;
|
||||
static char *kdatablk;
|
||||
static char *kctxa, *kctxb, *kctxc;
|
||||
|
||||
// Those buffers are shared by every caller, so every entry point below holds
|
||||
// this lock for the whole operation. A sleeplock rather than a spinlock because
|
||||
// bread() sleeps waiting on the disk.
|
||||
//
|
||||
// It also serialises transactions, which is not incidental: a group being built
|
||||
// rewrites its own blocks in place, so two operations interleaving inside one
|
||||
// group would each see the other's half-finished tree.
|
||||
static struct sleeplock schfs_lock;
|
||||
|
||||
// Seconds, for inode timestamps. The kernel has no wall clock wired up, so this
|
||||
// matches what fs.c does for the v1 filesystem rather than inventing a second,
|
||||
// differently-wrong answer.
|
||||
static uint64_t
|
||||
know(void)
|
||||
{
|
||||
return (uint64_t)uptime();
|
||||
}
|
||||
|
||||
// Start and finish one transaction. `changed` says whether anything was
|
||||
// written, so a read does not pay for an uberblock write.
|
||||
static void
|
||||
ktx_begin(void)
|
||||
{
|
||||
acquiresleep(&schfs_lock);
|
||||
}
|
||||
|
||||
static int
|
||||
ktx_end(int changed, int r)
|
||||
{
|
||||
if (changed && r >= 0) {
|
||||
int c = schfs_vol_commit(&kvol);
|
||||
if (c != 0)
|
||||
r = c;
|
||||
}
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
kd_read(void *ctx, uint64_t blk, void *out)
|
||||
{
|
||||
struct schfs_kdev *d = ctx;
|
||||
|
||||
if (blk == 0)
|
||||
return -1;
|
||||
// The buffer cache addresses blocks with a uint, which caps the volume long
|
||||
// before the on-disk format does. Refusing out here beats truncating and
|
||||
// reading some unrelated sector, which would surface as corruption.
|
||||
if (blk > 0xffffffffULL / SCHFS_BPB)
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i < SCHFS_BPB; i++) {
|
||||
struct buf *b = bread(d->dev, (uint)(blk * SCHFS_BPB + i));
|
||||
memmove((char *)out + i * BSIZE, b->data, BSIZE);
|
||||
brelse(b);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
kd_write(void *ctx, uint64_t blk, const void *in)
|
||||
{
|
||||
struct schfs_kdev *d = ctx;
|
||||
|
||||
if (blk == 0)
|
||||
return -1;
|
||||
if (blk > 0xffffffffULL / SCHFS_BPB)
|
||||
return -1;
|
||||
|
||||
// Straight through the buffer cache, with no log. The xv6 log exists to make
|
||||
// multi-block updates atomic; copy-on-write makes them atomic by never
|
||||
// modifying a block the committed tree can reach, so logging every write
|
||||
// would double the I/O to re-solve a problem the format has already solved.
|
||||
//
|
||||
// bread before the overwrite because the cache has no "give me this block
|
||||
// without reading it" entry point. It costs a read the first time a block is
|
||||
// touched and nothing afterwards.
|
||||
for (int i = 0; i < SCHFS_BPB; i++) {
|
||||
struct buf *b = bread(d->dev, (uint)(blk * SCHFS_BPB + i));
|
||||
memmove(b->data, (const char *)in + i * BSIZE, BSIZE);
|
||||
bwrite(b);
|
||||
brelse(b);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocation goes through the volume's space manager. These take no ctx of
|
||||
// their own because a schfs_bdev has one ctx shared by every hook and the block
|
||||
// I/O above needs it for the device number.
|
||||
static uint64_t
|
||||
kd_alloc(void *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
return schfs_vol_alloc(&kvol);
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
kd_alloc_run(void *ctx, uint64_t n)
|
||||
{
|
||||
(void)ctx;
|
||||
return schfs_vol_alloc_run(&kvol, n);
|
||||
}
|
||||
|
||||
static void
|
||||
kd_free(void *ctx, uint64_t b)
|
||||
{
|
||||
(void)ctx;
|
||||
schfs_vol_free(&kvol, b);
|
||||
}
|
||||
|
||||
// Pages for the allocation bitmap: one per 32768 blocks of volume, taken once
|
||||
// at mount and never given back.
|
||||
static void *
|
||||
kd_palloc(void)
|
||||
{
|
||||
return kalloc();
|
||||
}
|
||||
|
||||
// Try to mount a SchFS volume from `dev`. Never panics: a disk that is not
|
||||
// SchFS, or is an unreadable SchFS, simply leaves the volume unmounted.
|
||||
void
|
||||
schfs_kmount(uint dev)
|
||||
{
|
||||
if (kmounted)
|
||||
return;
|
||||
|
||||
initsleeplock(&schfs_lock, "schfs");
|
||||
|
||||
if (kcurblk == 0) kcurblk = kalloc();
|
||||
if (kdatablk == 0) kdatablk = kalloc();
|
||||
if (kctxa == 0) kctxa = kalloc();
|
||||
if (kctxb == 0) kctxb = kalloc();
|
||||
if (kctxc == 0) kctxc = kalloc();
|
||||
if (kcurblk == 0 || kdatablk == 0 ||
|
||||
kctxa == 0 || kctxb == 0 || kctxc == 0) {
|
||||
cprintf("schfs: no memory for block buffers\n");
|
||||
return;
|
||||
}
|
||||
|
||||
kdev.dev = dev;
|
||||
|
||||
struct schfs_bdev bd;
|
||||
bd.ctx = &kdev;
|
||||
bd.bread = kd_read;
|
||||
bd.bwrite = kd_write;
|
||||
bd.balloc = kd_alloc;
|
||||
bd.bfree = kd_free;
|
||||
bd.balloc_run = kd_alloc_run;
|
||||
|
||||
struct schfs_ctx cx;
|
||||
cx.a = kctxa;
|
||||
cx.b = kctxb;
|
||||
cx.c = kctxc;
|
||||
|
||||
// The uberblock scan doubles as the probe: no valid uberblock means this is
|
||||
// simply not a SchFS volume, which is not an error worth reporting loudly.
|
||||
// Opening with a page allocator also builds the allocation bitmap, by walking
|
||||
// the tree — the only mount-time cost SchFS has, and what buys it a mount
|
||||
// that cannot disagree with the filesystem it just adopted.
|
||||
int r = schfs_vol_open(&kvol, &bd, &cx, kd_palloc);
|
||||
if (r == -1)
|
||||
return;
|
||||
if (r == -4) {
|
||||
cprintf("schfs: device %d is larger than %d GB and cannot be mapped\n",
|
||||
dev, (int)(SCHFS_MAX_BLOCKS * SCHFS_BLKSIZE / (1024 * 1024 * 1024)));
|
||||
return;
|
||||
}
|
||||
if (r == -5) {
|
||||
cprintf("schfs: no memory for the allocation map of device %d\n", dev);
|
||||
return;
|
||||
}
|
||||
if (r != 0) {
|
||||
cprintf("schfs: device %d has a SchFS uberblock but an unreadable tree\n",
|
||||
dev);
|
||||
return;
|
||||
}
|
||||
|
||||
kmounted = 1;
|
||||
cprintf("schfs: mounted '%s' on device %d, %d MB, generation %d\n",
|
||||
kvol.label, dev,
|
||||
(int)(kvol.total_blocks * SCHFS_BLKSIZE / (1024 * 1024)),
|
||||
(int)kvol.generation);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kmounted(void)
|
||||
{
|
||||
return kmounted;
|
||||
}
|
||||
|
||||
// Resolve a path on the mounted volume. Returns the object id, or 0.
|
||||
uint64_t
|
||||
schfs_klookup(const char *path)
|
||||
{
|
||||
if (!kmounted)
|
||||
return 0;
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
uint64_t r = schfs_path_lookup(&kvol, &c, path);
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kstat(uint64_t ino, struct schfs_inode *out)
|
||||
{
|
||||
if (!kmounted)
|
||||
return -1;
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
int r = schfs_inode_get(&kvol, &c, ino, out);
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
long
|
||||
schfs_kread(uint64_t ino, uint64_t off, void *buf, unsigned n)
|
||||
{
|
||||
if (!kmounted)
|
||||
return -1;
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
long r = schfs_file_read(&kvol, &c, ino, off, buf, n, kdatablk);
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kreaddir(uint64_t dir,
|
||||
int (*fn)(uint64_t, uint16_t, const char *, unsigned, void *),
|
||||
void *arg)
|
||||
{
|
||||
if (!kmounted)
|
||||
return -1;
|
||||
// NOTE: fn must not call back into schfs_k* -- the lock is not recursive and
|
||||
// the cursor buffer is shared. Callers that need per-entry detail collect the
|
||||
// entries first, then look them up after the walk.
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
int r = schfs_dir_iterate(&kvol, &c, dir, fn, arg);
|
||||
releasesleep(&schfs_lock);
|
||||
return r;
|
||||
}
|
||||
|
||||
struct schfs_vol *
|
||||
schfs_kvol(void)
|
||||
{
|
||||
return kmounted ? &kvol : 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Write side */
|
||||
/* */
|
||||
/* Each of these is one transaction: everything it does is invisible */
|
||||
/* until it commits, and a crash part way through leaves the volume at */
|
||||
/* the state before the call. That is the entire failure story — there */
|
||||
/* is no partial state to detect and nothing to roll back. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int
|
||||
schfs_kcreate(uint64_t dir, const char *name, unsigned namelen, uint16_t type,
|
||||
uint32_t mode, uint16_t major, uint16_t minor, uint64_t *out)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_create(&kvol, dir, name, namelen, type, mode, major, minor,
|
||||
know(), out);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
long
|
||||
schfs_kwrite(uint64_t ino, uint64_t off, const void *buf, unsigned n)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
long r = schfs_file_write(&kvol, ino, off, buf, n, kdatablk);
|
||||
// ktx_end speaks in error codes, and a write speaks in byte counts. Passing
|
||||
// the count straight through told it the transaction had failed with status
|
||||
// 17, which it duly reported -- and a short write is a panic one layer up.
|
||||
int e = ktx_end(1, r < 0 ? (int)r : 0);
|
||||
return e == 0 ? r : (long)e;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_ktruncate(uint64_t ino, uint64_t size)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_file_truncate(&kvol, ino, size, kdatablk);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kunlink(uint64_t dir, const char *name, unsigned namelen)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_unlink(&kvol, dir, name, namelen);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_klink(uint64_t dir, const char *name, unsigned namelen, uint64_t ino)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_link(&kvol, dir, name, namelen, ino);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_krename(uint64_t olddir, const char *oldname, unsigned oldlen,
|
||||
uint64_t newdir, const char *newname, unsigned newlen)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_rename(&kvol, olddir, oldname, oldlen,
|
||||
newdir, newname, newlen);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
// Write back an inode record the caller has modified — the one way to change
|
||||
// mode, ownership or timestamps. The caller reads it with schfs_kstat, edits
|
||||
// the struct, and hands it back.
|
||||
int
|
||||
schfs_ksetattr(uint64_t ino, const struct schfs_inode *in)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_inode_put(&kvol, ino, in);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
// Flush without changing anything. Nothing needs this yet — every operation
|
||||
// above commits already — but sync(2) has to mean something.
|
||||
int
|
||||
schfs_ksync(void)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return 0;
|
||||
ktx_begin();
|
||||
return ktx_end(1, 0);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Pieces the VFS needs */
|
||||
/* */
|
||||
/* The VFS keeps its own link count and decides when an object dies, */
|
||||
/* so it needs the directory entry and the object as separate */
|
||||
/* operations rather than the combined create/unlink above. Two layers */
|
||||
/* counting links independently is exactly how a filesystem loses a */
|
||||
/* file that something still has open. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
// Resolve one name inside a directory. 0 if there is no such entry.
|
||||
uint64_t
|
||||
schfs_klookup_at(uint64_t dir, const char *name, unsigned namelen)
|
||||
{
|
||||
if (!kmounted)
|
||||
return 0;
|
||||
acquiresleep(&schfs_lock);
|
||||
struct schfs_cursor c;
|
||||
schfs_cursor_init(&c, &kvol.tree, kcurblk);
|
||||
uint64_t ino = 0;
|
||||
if (schfs_dir_lookup(&kvol, &c, dir, name, namelen, &ino, 0) != 0)
|
||||
ino = 0;
|
||||
releasesleep(&schfs_lock);
|
||||
return ino;
|
||||
}
|
||||
|
||||
// 1 if the directory holds no entries, 0 if it does, -1 on error.
|
||||
int
|
||||
schfs_kdirempty(uint64_t dir)
|
||||
{
|
||||
if (!kmounted)
|
||||
return -1;
|
||||
acquiresleep(&schfs_lock);
|
||||
int r = schfs_dir_empty(&kvol, dir);
|
||||
releasesleep(&schfs_lock);
|
||||
return r == -2 ? -1 : r;
|
||||
}
|
||||
|
||||
// An object with no name and no links yet. Nothing can reach it until the
|
||||
// caller links it, and nothing reaches the disk until the transaction commits,
|
||||
// so a caller that gives up in between leaves nothing behind.
|
||||
int
|
||||
schfs_kalloc_object(uint16_t type, uint32_t mode, uint64_t *out)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
|
||||
uint64_t ino = schfs_alloc_objectid(&kvol);
|
||||
struct schfs_inode in;
|
||||
memset(&in, 0, sizeof(in));
|
||||
in.type = type;
|
||||
in.mode = mode;
|
||||
in.nlink = 0;
|
||||
in.atime = in.mtime = in.ctime = know();
|
||||
in.gen = kvol.tree.generation;
|
||||
|
||||
int r = schfs_inode_put(&kvol, ino, &in);
|
||||
if (r == 0 && out)
|
||||
*out = ino;
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kdiradd(uint64_t dir, const char *name, unsigned namelen, uint64_t ino,
|
||||
uint16_t type)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_dir_add(&kvol, dir, name, namelen, ino, type);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
// Remove one directory entry, leaving the object alone. The VFS decrements the
|
||||
// link count itself and calls schfs_kfree when it reaches zero.
|
||||
int
|
||||
schfs_kdirdel(uint64_t dir, const char *name, unsigned namelen)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_dir_del(&kvol, dir, name, namelen);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
// Release an object's data, checksums and inode record. Called from iput once
|
||||
// the last name is gone and nothing has it open.
|
||||
int
|
||||
schfs_kfree(uint64_t ino)
|
||||
{
|
||||
if (!kmounted || kvol.readonly)
|
||||
return -1;
|
||||
ktx_begin();
|
||||
int r = schfs_object_free(&kvol, ino);
|
||||
return ktx_end(1, r);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
schfs_kroot(void)
|
||||
{
|
||||
return kmounted ? kvol.root_objectid : 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* /proc/schfs */
|
||||
/* */
|
||||
/* Phase 2 has no VFS integration -- SchFS files are not reachable */
|
||||
/* through open(2) yet -- so this is how the mount is observable from */
|
||||
/* userland at all. It reports the volume and lists the root directory, */
|
||||
/* which is enough to confirm from inside a running Schunix that the */
|
||||
/* kernel really did parse an image mkschfs wrote. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
kp_puts(char *buf, int cap, int *len, const char *s)
|
||||
{
|
||||
while (*s && *len < cap - 1)
|
||||
buf[(*len)++] = *s++;
|
||||
}
|
||||
|
||||
static void
|
||||
kp_putu(char *buf, int cap, int *len, uint64 v)
|
||||
{
|
||||
char tmp[24];
|
||||
int i = 0;
|
||||
if (v == 0) { if (*len < cap - 1) buf[(*len)++] = '0'; return; }
|
||||
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
|
||||
while (i--) { if (*len < cap - 1) buf[(*len)++] = tmp[i]; }
|
||||
}
|
||||
|
||||
struct kp_ctx {
|
||||
char *buf;
|
||||
int cap;
|
||||
int *len;
|
||||
int count;
|
||||
};
|
||||
|
||||
static int
|
||||
kp_entry(uint64_t ino, uint16_t type, const char *name, unsigned namelen,
|
||||
void *arg)
|
||||
{
|
||||
struct kp_ctx *c = arg;
|
||||
c->count++;
|
||||
// Only the first handful, and no per-entry stat: schfs_kreaddir holds the
|
||||
// lock across the walk, so calling schfs_kstat from here would deadlock.
|
||||
if (c->count > 16)
|
||||
return 0;
|
||||
kp_puts(c->buf, c->cap, c->len, " ");
|
||||
kp_puts(c->buf, c->cap, c->len,
|
||||
type == SCHFS_T_DIR ? "dir " :
|
||||
type == SCHFS_T_SYMLINK ? "symlink " :
|
||||
type == SCHFS_T_DEV ? "dev " : "file ");
|
||||
for (unsigned i = 0; i < namelen && *c->len < c->cap - 1; i++)
|
||||
c->buf[(*c->len)++] = name[i];
|
||||
kp_puts(c->buf, c->cap, c->len, " #");
|
||||
kp_putu(c->buf, c->cap, c->len, ino);
|
||||
kp_puts(c->buf, c->cap, c->len, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
schfs_kinfo(char *buf, int cap)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!kmounted) {
|
||||
kp_puts(buf, cap, &len, "no SchFS volume mounted\n");
|
||||
return len;
|
||||
}
|
||||
|
||||
kp_puts(buf, cap, &len, "label: ");
|
||||
kp_puts(buf, cap, &len, kvol.label);
|
||||
kp_puts(buf, cap, &len, "\nblocksize: ");
|
||||
kp_putu(buf, cap, &len, SCHFS_BLKSIZE);
|
||||
kp_puts(buf, cap, &len, "\ntotal_blocks: ");
|
||||
kp_putu(buf, cap, &len, kvol.total_blocks);
|
||||
kp_puts(buf, cap, &len, "\nfree_blocks: ");
|
||||
kp_putu(buf, cap, &len, kvol.free_blocks);
|
||||
kp_puts(buf, cap, &len, "\ngeneration: ");
|
||||
kp_putu(buf, cap, &len, kvol.generation);
|
||||
kp_puts(buf, cap, &len, "\ntree_root: ");
|
||||
kp_putu(buf, cap, &len, kvol.tree.root);
|
||||
kp_puts(buf, cap, &len, "\nroot_objectid: ");
|
||||
kp_putu(buf, cap, &len, kvol.root_objectid);
|
||||
kp_puts(buf, cap, &len, "\n\nroot directory:\n");
|
||||
|
||||
struct kp_ctx c = { buf, cap, &len, 0 };
|
||||
if (schfs_kreaddir(kvol.root_objectid, kp_entry, &c) != 0) {
|
||||
kp_puts(buf, cap, &len, " (directory read failed)\n");
|
||||
return len;
|
||||
}
|
||||
if (c.count > 16) {
|
||||
kp_puts(buf, cap, &len, " ... ");
|
||||
kp_putu(buf, cap, &len, (uint64)(c.count - 16));
|
||||
kp_puts(buf, cap, &len, " more\n");
|
||||
}
|
||||
return len;
|
||||
}
|
||||
Loading…
Reference in a new issue