| git.druid.rocks | index | druid520 | kaboom | src/ | kernel/ | virtfs.nsc |
src/kernel/virtfs.nsc
/*
* /proc and /int: a handful of names whose "file content" is
* generated live, at read time, instead of coming from real disk
* blocks -- the real thing, not a boot-time snapshot: a snapshot
* baked onto disk at mkfs time would go stale the moment anything it
* reports (heap usage, the process table, keyboard state, ...)
* changed post-boot, which is most of what makes any of it worth
* reading in the first place.
* real, empty placeholder files still exist on disk for each of these
* (mk/disk.pl creates them at build time) purely so kfs_resolve/
* ls/stat keep working completely unmodified -- virtfs_read is what
* fd_open (fd.nsc) calls FIRST, before ever falling through to
* kfs_read_file, so opening one of these always returns fresh content
* instead of whatever empty bytes were on disk at mkfs time.
*
* dispatch is "which known directory is the parent, and which exact
* name" -- a plain if-chain, same shape used everywhere else in this
* kernel that wanted a real lookup table and got an if-chain instead,
* because nsc has no arrays-of-structs or function pointers to build
* one with.
*
* /proc/self and /proc/ps (added once exec.nsc grew a real, if
* shallow, process table -- see that file's own top note) are the
* honest version of "per-process /proc entries": a genuine parent/
* child pair (sh, plus whatever single command it's running), not a
* pretend multi-process listing. deliberately NOT here: a real
* per-pid subdirectory tree (/proc/1/status etc) -- would need
* virtual DIRECTORIES, not just virtual files (kfs_resolve/kfs_stat/
* ls would all need to learn to fake a listing, not just fd_open a
* generator), a materially bigger change for two processes that are
* already fully described by one flat /proc/ps line each.
*/
include "../fs/kfs.nsh";
include "exec.nsh";
include "../drivers/cpu.nsh";
include "alloc.nsh";
void kfs_info(ptr buf);
global u8 kbd_shift;
global u8 kbd_caps;
/* reads one byte at an arbitrary offset from a raw ptr -- same
* technique as kfs.nsc/fd.nsc's own ptr_byte_at (duplicated here
* rather than shared, same as those two already duplicate it from
* each other: nsc has no way to export a plain, non-global helper
* function across files without it becoming part of some subsystem's
* public api, and this one belongs to none of them). */
u8
ptr_byte_at(ptr base, u64 index)
{
ptr p;
u64 word;
p = base + index;
word = (u64)*p;
return (u8)(word & (u64)0xff);
}
/* writes one byte at an arbitrary offset into a raw ptr buffer -- a
* read-modify-write of the containing 8-byte word, same technique
* fd_read_stdin/fd_read_from_cache already use for the same reason:
* a plain `*(buf + off) = (i64)ch` would zero-extend ch across the
* whole word and clobber up to 7 bytes already written just after it. */
void
virtfs_put_byte(ptr buf, u64 off, u8 ch)
{
u64 wordoff;
u64 byteoff;
u64 word;
wordoff = off & ~(u64)7;
byteoff = off & (u64)7;
word = (u64)*(buf + wordoff);
word = word & ~((u64)0xff << (byteoff * (u64)8));
word = word | ((u64)ch << (byteoff * (u64)8));
*(buf + wordoff) = (i64)word;
}
/* writes a null-terminated literal into buf starting at off, returns
* the offset just past the last byte written (NOT including the
* terminator -- callers chain these to build up a whole generated
* file's content one piece at a time). */
u64
virtfs_put_str(ptr buf, u64 off, ptr str)
{
u64 i;
u8 ch;
i = 0;
while(1)
{
ch = ptr_byte_at(str, i);
if(ch == (u8)0)
{
return off + i;
}
virtfs_put_byte(buf, off + i, ch);
i = i + (u64)1;
}
return off + i;
}
/* writes val as decimal ascii digits starting at off, returns the
* offset just past the last digit written. */
u64
virtfs_put_dec(ptr buf, u64 off, u64 val)
{
i8 digits[24];
u64 n;
u64 i;
if(val == (u64)0)
{
virtfs_put_byte(buf, off, (u8)48); /* '0' */
return off + (u64)1;
}
n = 0;
while(val > (u64)0)
{
digits[n] = (i8)((u8)48 + (u8)(val % (u64)10));
val = val / (u64)10;
n = n + (u64)1;
}
i = n;
while(i > (u64)0)
{
i = i - (u64)1;
virtfs_put_byte(buf, off, (u8)digits[i]);
off = off + (u64)1;
}
return off;
}
/* true if the first name_len bytes at name exactly match the first
* lit_len bytes at lit AND the lengths match -- unlike kfs.nsc's own
* kfs_name_matches, there's no trailing-null-terminator check needed:
* name/name_len here is always a raw (path-buffer, length) pair from
* kfs_resolve_parent, never a stored, null-padded dirent field. */
i32
virtfs_name_eq(ptr name, u64 name_len, ptr lit, u64 lit_len)
{
u64 i;
if(name_len != lit_len)
{
return 0;
}
i = 0;
while(i < name_len)
{
if(ptr_byte_at(name, i) != ptr_byte_at(lit, i))
{
return 0;
}
i = i + (u64)1;
}
return 1;
}
i32
virtfs_gen_version(ptr buf)
{
u64 off;
off = 0;
off = virtfs_put_str(buf, off, "kaboom-mainline-0.0.1\n");
return (i32)off;
}
/* plan9's own convention -- always reports whoever is CURRENTLY
* reading this file, not a fixed pid. that falls out for free here:
* anything actually able to open/read a file at all is, by
* definition, a running process, so the deepest live slot (see
* exec.nsc's own top note on the process table) is always the honest
* answer, whatever the current nesting depth actually is. */
i32
virtfs_gen_self(ptr buf)
{
u64 off;
ptr name;
name = &kaboom_proc_name[((u64)kaboom_proc_depth - (u64)1) * (u64)24];
off = 0;
off = virtfs_put_str(buf, off, "pid: ");
off = virtfs_put_dec(buf, off, kaboom_current_pid());
off = virtfs_put_str(buf, off, "\nname: ");
off = virtfs_put_str(buf, off, name);
off = virtfs_put_str(buf, off, "\n");
return (i32)off;
}
/* the whole process table, one line each -- "ps is literal ls /proc",
* as literal as kaboom's actual process model (exec.nsc's top note)
* makes honest: every slot from 1 up to the current depth, outermost
* (sh, depth 1) first. src/user/ps.nsc is nothing more than a
* hardcoded `cat` of this file. */
i32
virtfs_gen_ps(ptr buf)
{
u64 off;
u64 slot;
off = 0;
off = virtfs_put_str(buf, off, "PID CMD\n");
slot = 0;
while(slot < (u64)kaboom_proc_depth)
{
off = virtfs_put_dec(buf, off, kaboom_proc_pid[slot]);
off = virtfs_put_str(buf, off, " ");
off = virtfs_put_str(buf, off, &kaboom_proc_name[slot * (u64)24]);
off = virtfs_put_str(buf, off, "\n");
slot = slot + (u64)1;
}
return (i32)off;
}
/* live colosseum usage -- heap_colosseum_used/_total/fs_colosseum_used/
* _total (alloc.nsc) exist specifically so this can report real
* numbers without alloc.nsc needing to export struct arena's layout
* anywhere. */
i32
virtfs_gen_mem(ptr buf)
{
u64 off;
off = 0;
off = virtfs_put_str(buf, off, "heap: ");
off = virtfs_put_dec(buf, off, heap_colosseum_used());
off = virtfs_put_str(buf, off, "/");
off = virtfs_put_dec(buf, off, heap_colosseum_total());
off = virtfs_put_str(buf, off, " bytes\nfs: ");
off = virtfs_put_dec(buf, off, fs_colosseum_used());
off = virtfs_put_str(buf, off, "/");
off = virtfs_put_dec(buf, off, fs_colosseum_total());
off = virtfs_put_str(buf, off, " bytes\n");
return (i32)off;
}
u64 vmm_frames_free(void);
i32
virtfs_gen_vmm(ptr buf)
{
u64 off;
off = 0;
off = virtfs_put_str(buf, off, "frames free: ");
off = virtfs_put_dec(buf, off, vmm_frames_free());
off = virtfs_put_str(buf, off, " (");
off = virtfs_put_dec(buf, off, vmm_frames_free() * (u64)4096);
off = virtfs_put_str(buf, off, " bytes)\n");
return (i32)off;
}
i32
virtfs_gen_kbd(ptr buf)
{
u64 off;
off = 0;
off = virtfs_put_str(buf, off, "shift: ");
off = virtfs_put_dec(buf, off, (u64)kbd_shift);
off = virtfs_put_str(buf, off, "\ncaps: ");
off = virtfs_put_dec(buf, off, (u64)kbd_caps);
off = virtfs_put_str(buf, off, "\n");
return (i32)off;
}
/* reuses kfs_info's existing 4 numbers (same ones the info/klogs
* commands already print), just formatted as a plain-text file
* instead of a syscall-filled struct. */
i32
virtfs_gen_kfs(ptr buf)
{
i8 info[32];
ptr p;
u64 total_blocks;
u64 used_blocks;
u64 inode_count;
u64 next_free_inode;
u64 off;
kfs_info(&info[0]);
p = &info[0];
total_blocks = (u64)*p;
used_blocks = (u64)*(p + 8);
inode_count = (u64)*(p + 16);
next_free_inode = (u64)*(p + 24);
off = 0;
off = virtfs_put_str(buf, off, "blocks: ");
off = virtfs_put_dec(buf, off, used_blocks);
off = virtfs_put_str(buf, off, "/");
off = virtfs_put_dec(buf, off, total_blocks);
off = virtfs_put_str(buf, off, "\ninodes: ");
off = virtfs_put_dec(buf, off, next_free_inode);
off = virtfs_put_str(buf, off, "/");
off = virtfs_put_dec(buf, off, inode_count);
off = virtfs_put_str(buf, off, "\n");
return (i32)off;
}
/* vendor + brand string, via cpu.nsc's cpuid wrappers -- see that
* file's own note on why nothing past that (no feature-flag decoding)
* is here yet. */
i32
virtfs_gen_cpu(ptr buf)
{
i8 vendor[13];
i8 brand[49];
u64 off;
cpu_vendor(&vendor[0]);
cpu_brand(&brand[0]);
off = 0;
off = virtfs_put_str(buf, off, "vendor: ");
off = virtfs_put_str(buf, off, &vendor[0]);
off = virtfs_put_str(buf, off, "\nbrand: ");
off = virtfs_put_str(buf, off, &brand[0]);
off = virtfs_put_str(buf, off, "\n");
return (i32)off;
}
/* the actual dispatch: given a resolved PARENT directory lba and the
* final path component (name/name_len, exactly what
* kfs_resolve_parent hands its callers), returns the generated
* content's length written into buf, or -1 if this isn't a name this
* file recognizes (the caller, fd_open, falls back to a real
* kfs_read_file in that case).
*
* the kfs_proc_dir_lba != kfs_root_dir_lba / kfs_int_dir_lba !=
* kfs_root_dir_lba guards matter on an old disk image built before
* disk.pl created /proc and /int at all: kfs_mount falls both back to
* root in that case (same fallback kfs_bin_dir_lba already used), and
* without this guard a root-level file that happened to be named
* "version" or "mem" would be wrongly treated as virtual. */
global i32
virtfs_read(u64 parent_lba, ptr name, u64 name_len, ptr buf)
{
if(parent_lba == kfs_proc_dir_lba && kfs_proc_dir_lba != kfs_root_dir_lba)
{
if(virtfs_name_eq(name, name_len, "version", (u64)7) == 1)
{
return virtfs_gen_version(buf);
}
if(virtfs_name_eq(name, name_len, "self", (u64)4) == 1)
{
return virtfs_gen_self(buf);
}
if(virtfs_name_eq(name, name_len, "ps", (u64)2) == 1)
{
return virtfs_gen_ps(buf);
}
return -1;
}
if(parent_lba == kfs_int_dir_lba && kfs_int_dir_lba != kfs_root_dir_lba)
{
if(virtfs_name_eq(name, name_len, "mem", (u64)3) == 1)
{
return virtfs_gen_mem(buf);
}
if(virtfs_name_eq(name, name_len, "kbd", (u64)3) == 1)
{
return virtfs_gen_kbd(buf);
}
if(virtfs_name_eq(name, name_len, "kfs", (u64)3) == 1)
{
return virtfs_gen_kfs(buf);
}
if(virtfs_name_eq(name, name_len, "cpu", (u64)3) == 1)
{
return virtfs_gen_cpu(buf);
}
if(virtfs_name_eq(name, name_len, "vmm", (u64)3) == 1)
{
return virtfs_gen_vmm(buf);
}
return -1;
}
return -1;
}