diff options
| author | hachem <im@hachem.wtf> | 2026-10-04 16:03:42 +0200 |
|---|---|---|
| committer | hachem <im@hachem.wtf> | 2026-10-04 16:03:42 +0200 |
| commit | 52dab7fc85d877e80e3032f0afabae906adebad8 (patch) | |
| tree | b6d56299b839413bb77b1b8c8fa9f7a682124a05 /kernel | |
| parent | a8a35a00a3c3c57cfbef2311d0348376c2e83208 (diff) | |
feat: fs tree and free list alloc
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/font.rv | 1 | ||||
| -rw-r--r-- | kernel/fs.rv | 355 | ||||
| -rw-r--r-- | kernel/input.rv | 25 | ||||
| -rw-r--r-- | kernel/proc.rv | 23 | ||||
| -rw-r--r-- | kernel/seed.rv | 9 | ||||
| -rw-r--r-- | kernel/shell.rv | 110 | ||||
| -rw-r--r-- | kernel/vfs.rv | 57 | ||||
| -rw-r--r-- | kernel/vmm.rv | 1 |
8 files changed, 440 insertions, 141 deletions
diff --git a/kernel/font.rv b/kernel/font.rv index 0720dd4..7735b38 100644 --- a/kernel/font.rv +++ b/kernel/font.rv @@ -2,6 +2,7 @@ # ascii 0x20..0x7f, indexed by (code - 32) # one byte per row, 16 rows # bit 0 (lsb) of a byte is the leftmost pixel (vga data is msb first, reversed here) + global FONT = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, # space { 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00 }, # ! diff --git a/kernel/fs.rv b/kernel/fs.rv index 0c98c36..c045950 100644 --- a/kernel/fs.rv +++ b/kernel/fs.rv @@ -1,32 +1,37 @@ -# revelfs, a tiny fat style filesystem, now an object you talk to as fs:method() -# # on disk layout for the 16 mib test disk (32768 sectors): -# sector 0 superblock (magic + total sectors) -# sectors 1..8 directory, 128 entries of 32 bytes (16 per sector) -# sectors 9..136 fat, one 2 byte entry per sector on the whole disk -# sectors 137.. file data, one block == one sector -# a fat entry points at the next block in a files chain, 0 is free, END is the last +# sector 0 superblock (magic + total sectors + root dir block) +# sectors 1..128 fat, one 2 byte entry per block on the whole disk +# sectors 129.. data, used for both file bytes and directory entry blocks +# +# a directory is just a file whose data blocks hold 32 byte entries. the +# superblock points at the root dir, paths are walked from there. a fat entry +# points at the next block in a chain, 0 is free, END is the last const SB_LBA = 0 -const DIR_LBA = 1 -const DIR_SECTORS = 8 -const DIR_ENTRIES = 128 -const FAT_LBA = 9 +const FAT_LBA = 1 const FAT_SECTORS = 128 -const DATA_LBA = 137 +const DATA_LBA = 129 const BLOCK = 512 +const ENTS = 16 # 32 byte entries per 512 byte block const NAME_MAX = 24 -const MAGIC = 0x53465652 # "RVFS" little endian -const END = 0xffff # end of chain marker in the fat +const MAGIC = 0x32465652 # "RVF2" little endian +const END = 0xffff # end of chain marker in the fat +const T_FILE = 1 +const T_DIR = 2 -# pure index math, no state, so theyre plain helpers not methods -fn dir_lba(i) DIR_LBA + (i shr 4) -fn dir_off(i) (i band 15) * 32 +fn split_path(path) do + const raw = path:split("/") + let parts = {} + for i in 0..raw:len() do + if raw[i]:len() > 0 parts:push(raw[i]) + end + return parts +end -# buf is a scratch sector for the superblock/directory/data, fat is a separate -# scratch sector so fat lookups dont trample a directory sector were mid edit +# buf is a scratch sector for superblocks/dirs/data, fat is a separate scratch so +# fat lookups dont trample a dir block were mid edit. global fs = { - buf = 0, fat = 0, ready = :false, total = 0, + buf = 0, fat = 0, ready = :false, total = 0, root = 0, fat_get = fn(self, block) do disk_read(FAT_LBA + (block shr 8), self.fat) @@ -40,8 +45,6 @@ global fs = { disk_write(lba, self.fat) end, - # first free data block, or 0 if full. scans a whole fat sector per read so this - # isnt snails pace fat_alloc = fn(self) do let block = DATA_LBA while block < self.total do @@ -65,39 +68,145 @@ global fs = { end end, - # dir entry is 32 bytes: - # +0 used + # entry is 32 bytes: + # +0 type (0 free/1 file/2 dir) # +1 name len # +2 first block # +4 size - # +8 name - name_at = fn(self, base, len) do + # +8 name bytes + ent_name = fn(self, eoff) do + const len = mem_read(self.buf + eoff + 1, 1) let out = "" - for k in 0..len do out = out ~ string.of_ascii(mem_read(base + 8 + k, 1)) end + for k in 0..len do out = out ~ string.of_ascii(mem_read(self.buf + eoff + 8 + k, 1)) end out end, - name_matches = fn(self, base, target) do - const len = mem_read(base + 1, 1) - if len != target:len() return :false - return self:name_at(base, len) == target + ent_write = fn(self, eoff, type, name, first, size) do + mem_write(self.buf + eoff, type, 1) + mem_write(self.buf + eoff + 1, name:len(), 1) + mem_write(self.buf + eoff + 2, first, 2) + mem_write(self.buf + eoff + 4, size, 4) + for k in 0..name:len() do mem_write(self.buf + eoff + 8 + k, name[k]:ascii(), 1) end + end, + + dir_lookup = fn(self, dir_first, name) do + let block = dir_first + while (block != 0 and block != END) do + const nxt = self:fat_get(block) + disk_read(block, self.buf) + for slot in 0..ENTS do + const eoff = slot * 32 + if mem_read(self.buf + eoff, 1) != 0 and self:ent_name(eoff) == name do + return { type = mem_read(self.buf + eoff, 1), first = mem_read(self.buf + eoff + 2, 2), size = mem_read(self.buf + eoff + 4, 4) } + end + end + block = nxt + end + return :nil + end, + + # add or overwrite `name` in the dir + # grows a block if every slot is taken + dir_set = fn(self, dir_first, name, type, first, size) do + let block = dir_first + let found_block = 0 + let found_off = 0 + let free_block = 0 + let free_off = -1 + let last_block = dir_first + while (block != 0 and block != END) do + last_block = block + const nxt = self:fat_get(block) + disk_read(block, self.buf) + for slot in 0..ENTS do + const eoff = slot * 32 + if mem_read(self.buf + eoff, 1) == 0 do + if free_off < 0 do free_block = block free_off = eoff end + end + else do + if self:ent_name(eoff) == name do found_block = block found_off = eoff end + end + end + block = nxt + end + + let target_block = 0 + let target_off = 0 + if found_block != 0 do target_block = found_block target_off = found_off end + else if free_off >= 0 do target_block = free_block target_off = free_off end + else do + const nb = self:fat_alloc() + if nb == 0 return :false + self:fat_set(nb, END) + self:fat_set(last_block, nb) + mem_zero(self.buf, BLOCK) + disk_write(nb, self.buf) + target_block = nb + target_off = 0 + end + + disk_read(target_block, self.buf) + self:ent_write(target_off, type, name, first, size) + disk_write(target_block, self.buf) + return :true + end, + + dir_clear = fn(self, dir_first, name) do + let block = dir_first + while (block != 0 and block != END) do + const nxt = self:fat_get(block) + disk_read(block, self.buf) + for slot in 0..ENTS do + const eoff = slot * 32 + if mem_read(self.buf + eoff, 1) != 0 and self:ent_name(eoff) == name do + const info = { type = mem_read(self.buf + eoff, 1), first = mem_read(self.buf + eoff + 2, 2) } + mem_write(self.buf + eoff, 0, 1) + disk_write(block, self.buf) + return info + end + end + block = nxt + end + return :nil + end, + + dir_empty = fn(self, dir_first) do + let block = dir_first + while (block != 0 and block != END) do + const nxt = self:fat_get(block) + disk_read(block, self.buf) + for slot in 0..ENTS do + if mem_read(self.buf + slot * 32, 1) != 0 return :false + end + block = nxt + end + return :true end, - find = fn(self, name) do - for i in 0..DIR_ENTRIES do - disk_read(dir_lba(i), self.buf) - const base = self.buf + dir_off(i) - if mem_read(base, 1) == 1 and self:name_matches(base, name) return i + resolve = fn(self, path) do + const parts = split_path(path) + let cur = self.root + let ent = { type = T_DIR, first = self.root, size = 0 } + for i in 0..parts:len() do + if ent.type != T_DIR return :nil + const found = self:dir_lookup(cur, parts[i]) + if found == :nil return :nil + ent = found + cur = found.first end - return -1 + return ent end, - dir_alloc = fn(self) do - for i in 0..DIR_ENTRIES do - disk_read(dir_lba(i), self.buf) - if mem_read(self.buf + dir_off(i), 1) == 0 return i + resolve_parent = fn(self, path) do + const parts = split_path(path) + if parts:len() == 0 return :nil + let pdir = self.root + for i in 0..(parts:len() - 1) do + const found = self:dir_lookup(pdir, parts[i]) + if found == :nil or found.type != T_DIR return :nil + pdir = found.first end - return -1 + return { dir = pdir, name = parts[parts:len() - 1] } end, mount = fn(self) do @@ -106,10 +215,13 @@ global fs = { self.total = disk_sectors() if self.total == 0 do self.ready = :false return :nil end disk_read(SB_LBA, self.buf) - self.ready = if mem_read(self.buf, 4) == MAGIC :true else :false + if mem_read(self.buf, 4) == MAGIC do + self.root = mem_read(self.buf + 8, 2) + self.ready = :true + end + else do self.ready = :false end end, - # ready is a field so the gate cant be called `ready`, this complains if theres no fs ensure = fn(self) do if self.ready return :true puts("no filesystem, run mkfs first\n") @@ -119,28 +231,44 @@ global fs = { format = fn(self) do if disk_sectors() == 0 do puts("mkfs: no disk\n") return :nil end mem_zero(self.buf, BLOCK) + for s in 0..FAT_SECTORS do disk_write(FAT_LBA + s, self.buf) end + const root = self:fat_alloc() + self:fat_set(root, END) + mem_zero(self.buf, BLOCK) + disk_write(root, self.buf) + self.root = root + mem_zero(self.buf, BLOCK) mem_write(self.buf, MAGIC, 4) mem_write(self.buf + 4, self.total, 4) + mem_write(self.buf + 8, root, 2) disk_write(SB_LBA, self.buf) - mem_zero(self.buf, BLOCK) - for s in 0..DIR_SECTORS do disk_write(DIR_LBA + s, self.buf) end - for s in 0..FAT_SECTORS do disk_write(FAT_LBA + s, self.buf) end self.ready = :true puts("formatted #{self.total} sectors, #{(self.total shr 1)} KiB\n") end, - # silent core of write, returns 1 ok / 0 fail - store = fn(self, name, text) do + mkdir = fn(self, path) do + if self:ensure() == :false return :nil + const pp = self:resolve_parent(path) + if pp == :nil do puts("mkdir: #{path}: bad path\n") return :nil end + if pp.name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return :nil end + if self:dir_lookup(pp.dir, pp.name) != :nil do puts("mkdir: #{pp.name}: already exists\n") return :nil end + const nb = self:fat_alloc() + if nb == 0 do puts("disk full\n") return :nil end + self:fat_set(nb, END) + mem_zero(self.buf, BLOCK) + disk_write(nb, self.buf) + self:dir_set(pp.dir, pp.name, T_DIR, nb, 0) + end, + + store = fn(self, path, text) do if self:ensure() == :false return 0 - if name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return 0 end - let idx = self:find(name) - if idx >= 0 do - disk_read(dir_lba(idx), self.buf) - self:free_chain(mem_read(self.buf + dir_off(idx) + 2, 2)) - end - else do - idx = self:dir_alloc() - if idx < 0 do puts("directory full\n") return 0 end + const pp = self:resolve_parent(path) + if pp == :nil do puts("#{path}: bad path\n") return 0 end + if pp.name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return 0 end + const existing = self:dir_lookup(pp.dir, pp.name) + if existing != :nil do + if existing.type == T_DIR do puts("#{pp.name}: is a directory\n") return 0 end + self:free_chain(existing.first) end const size = text:len() @@ -150,7 +278,7 @@ global fs = { while written < size do const block = self:fat_alloc() if block == 0 do puts("disk full\n") return 0 end - self:fat_set(block, END) # reserve + self:fat_set(block, END) if first == 0 do first = block end else do self:fat_set(prev, block) end mem_zero(self.buf, BLOCK) @@ -163,40 +291,26 @@ global fs = { disk_write(block, self.buf) prev = block end - - disk_read(dir_lba(idx), self.buf) - const base = self.buf + dir_off(idx) - mem_write(base, 1, 1) - mem_write(base + 1, name:len(), 1) - mem_write(base + 2, first, 2) - mem_write(base + 4, size, 4) - for k in 0..name:len() do mem_write(base + 8 + k, name[k]:ascii(), 1) end - disk_write(dir_lba(idx), self.buf) + self:dir_set(pp.dir, pp.name, T_FILE, first, size) return 1 end, - # write a file, gee i wonder what a function called `write` does - write = fn(self, name, text) do - if self:store(name, text) == 1 puts("wrote #{name} (#{text:len()} bytes)\n") + write = fn(self, path, text) do + if self:store(path, text) == 1 puts("wrote #{path} (#{text:len()} bytes)\n") end, - size = fn(self, name) do - const idx = self:find(name) - if idx < 0 return -1 - disk_read(dir_lba(idx), self.buf) - return mem_read(self.buf + dir_off(idx) + 4, 4) + size = fn(self, path) do + const ent = self:resolve(path) + if ent == :nil or ent.type == T_DIR return -1 + return ent.size end, - # read up to len bytes from offset, returned as a string (shorter at eof) - read_bytes = fn(self, name, offset, len) do - const idx = self:find(name) - if idx < 0 return "" - disk_read(dir_lba(idx), self.buf) - const base = self.buf + dir_off(idx) - let block = mem_read(base + 2, 2) - const size = mem_read(base + 4, 4) + read_bytes = fn(self, path, offset, len) do + const ent = self:resolve(path) + if ent == :nil or ent.type == T_DIR return "" + let block = ent.first let stop = offset + len - if stop > size do stop = size end + if stop > ent.size do stop = ent.size end let pos = 0 let out = "" while (block != 0 and block != END and pos < stop) do @@ -216,17 +330,16 @@ global fs = { return out end, - cat = fn(self, name) do + cat = fn(self, path) do if self:ensure() == :false return :nil - const idx = self:find(name) - if idx < 0 do puts("cat: #{name}: no such file\n") return :nil end - disk_read(dir_lba(idx), self.buf) - const base = self.buf + dir_off(idx) - let block = mem_read(base + 2, 2) - let remaining = mem_read(base + 4, 4) + const ent = self:resolve(path) + if ent == :nil do puts("cat: #{path}: no such file\n") return :nil end + if ent.type == T_DIR do puts("cat: #{path}: is a directory\n") return :nil end + let block = ent.first + let remaining = ent.size let out = "" while (block != 0 and block != END and remaining > 0) do - const nxt = self:fat_get(block) # grab next before disk_read reuses self.buf + const nxt = self:fat_get(block) disk_read(block, self.buf) const chunk = if remaining > BLOCK BLOCK else remaining for k in 0..chunk do out = out ~ string.of_ascii(mem_read(self.buf + k, 1)) end @@ -237,31 +350,49 @@ global fs = { puts("\n") end, - ls = fn(self) do - if self:ensure() == :false return :nil + ls = fn(self, path) do + if self:ensure() == :false return -1 + const ent = self:resolve(path) + if ent == :nil do puts("ls: #{path}: no such file\n") return -1 end + if ent.type != T_DIR do puts(" #{path} #{ent.size} bytes\n") return -1 end let count = 0 - for i in 0..DIR_ENTRIES do - disk_read(dir_lba(i), self.buf) - const base = self.buf + dir_off(i) - if mem_read(base, 1) == 1 do - puts(" #{self:name_at(base, mem_read(base + 1, 1))} #{mem_read(base + 4, 4)} bytes\n") - count += 1 + let block = ent.first + while (block != 0 and block != END) do + const nxt = self:fat_get(block) + disk_read(block, self.buf) + for slot in 0..ENTS do + const eoff = slot * 32 + const t = mem_read(self.buf + eoff, 1) + if t != 0 do + const nm = self:ent_name(eoff) + if t == T_DIR do puts(" #{nm}/\n") end + else do puts(" #{nm} #{mem_read(self.buf + eoff + 4, 4)} bytes\n") end + count += 1 + end end + block = nxt end - if count == 0 puts(" (empty)\n") + return count end, - rm = fn(self, name) do + rm = fn(self, path) do if self:ensure() == :false return :nil - const idx = self:find(name) - if idx < 0 do puts("rm: #{name}: no such file\n") return :nil end - disk_read(dir_lba(idx), self.buf) - const base = self.buf + dir_off(idx) - self:free_chain(mem_read(base + 2, 2)) - disk_read(dir_lba(idx), self.buf) - mem_write(self.buf + dir_off(idx), 0, 1) - disk_write(dir_lba(idx), self.buf) - puts("removed #{name}\n") + const pp = self:resolve_parent(path) + if pp == :nil do puts("rm: #{path}: no such file\n") return :nil end + const ent = self:dir_lookup(pp.dir, pp.name) + if ent == :nil do puts("rm: #{path}: no such file\n") return :nil end + if ent.type == T_DIR and self:dir_empty(ent.first) == :false do + puts("rm: #{pp.name}: directory not empty\n") + return :nil + end + self:free_chain(ent.first) + self:dir_clear(pp.dir, pp.name) + puts("removed #{pp.name}\n") + end, + + is_dir = fn(self, path) do + const ent = self:resolve(path) + return ent != :nil and ent.type == T_DIR end, } diff --git a/kernel/input.rv b/kernel/input.rv index fe25c81..7e0d156 100644 --- a/kernel/input.rv +++ b/kernel/input.rv @@ -1,7 +1,7 @@ # keyboard scancode table # ts is not a good way to write a driver lmao + const KBD = " 1234567890-= qwertyuiop[] asdfghjkl;'` \\zxcvbnm,./ " -# same layout, shifted. index 42 (left shift) / 54 (right shift) stay blank const KBD_SHIFT = " !@#$%^&*()_+ QWERTYUIOP{} ASDFGHJKL:\"~ |ZXCVBNM<>? " # 8259 pic @@ -25,11 +25,8 @@ end # she stroke my keys till i handle # no fr this is just a key stroke handler -global shell = { line = "", shift = :false } +global shell = { line = "", shift = :false, ext = :false } -# decode one scancode to a character string, or :nil for releases / modifiers / -# unmapped keys. this is the only scancode decoder now, used by both the shell -# and the read() syscall fn decode_key(code) do if code == 0x2a or code == 0x36 do shell.shift = :true return :nil end # shift down if code == 0xaa or code == 0xb6 do shell.shift = :false return :nil end # shift up @@ -44,12 +41,24 @@ fn decode_key(code) do end global on_key = fn(code) do + if code == 0xe0 do shell.ext = :true return :nil end + if shell.ext do + shell.ext = :false + let final = 0 + if code == 0x48 do final = 65 end # up + else if code == 0x50 do final = 66 end # down + else if code == 0x4d do final = 67 end # right + else if code == 0x4b do final = 68 end # left + if final != 0 do + stdin_deliver(27) + stdin_deliver(91) + stdin_deliver(final) + end + return :nil + end const ch = decode_key(code) if ch == :nil return :nil - # a process blocked in read() gets first dibs, if it takes the byte, the shell - # doesnt see it (the process echoes its own input via write()) if stdin_deliver(ch:ascii()) return :nil - # otherwise the shells line editor if ch == "\n" do puts("\n") run(shell.line) shell.line = "" return :nil end if ch == "\x08" do if shell.line:len() > 0 do shell.line = shell.line:sub(0, shell.line:len() - 1) puts("\x08 \x08") end diff --git a/kernel/proc.rv b/kernel/proc.rv index f76c511..2f8bb2f 100644 --- a/kernel/proc.rv +++ b/kernel/proc.rv @@ -2,8 +2,9 @@ const PT_LOAD = 1 const PAGE_FLAGS = 6 # write | user +const ARG_VADDR = 0x50000000 # where we map the argument string in the new space -fn load_elf(base) do +fn load_elf(base, arg) do if mem_read(base, 4) != 0x464c457f do puts("not an ELF\n") return :nil @@ -34,14 +35,24 @@ fn load_elf(base) do end end const stack_top = 0x7fff0000 - as_map(space, stack_top - 4096, frame_alloc(), PAGE_FLAGS) - proc_run(space, entry, stack_top) + for p in 0..4 do as_map(space, stack_top - (p + 1) * 4096, frame_alloc(), PAGE_FLAGS) end + let arg_ptr = 0 + const arg_len = arg:len() + if arg_len > 0 do + const argframe = frame_alloc() + mem_zero(argframe, 4096) + for k in 0..arg_len do mem_write(argframe + k, arg[k]:ascii(), 1) end + as_map(space, ARG_VADDR, argframe, PAGE_FLAGS) + arg_ptr = ARG_VADDR + end + proc_run(space, entry, stack_top, arg_ptr, arg_len) end -# launch a program by name, :true if one matched -fn run_prog(name) do +fn run_prog(name, arg) do + let n = name + if n:starts_with?("/bin/") n = n[5..] for i in 0..prog_count() do - if prog_name(i) == name do load_elf(prog_phys(i)) return :true end + if prog_name(i) == n do load_elf(prog_phys(i), arg) return :true end end puts("no such program: #{name}\n") return :false diff --git a/kernel/seed.rv b/kernel/seed.rv index 72f0efd..8c0d9b7 100644 --- a/kernel/seed.rv +++ b/kernel/seed.rv @@ -1,5 +1,10 @@ # js dw about this ok? fn seed_fs() do - fs:write("i have big balls.txt", "dude my balls are actually massive") - fs:write("balls.txt", "its lowk insane how big my balls are") + fs:mkdir("/usr") + fs:mkdir("/etc") + fs:mkdir("/home") + + fs:store("/etc/motd", "welcome to revel") + fs:store("/home/balls.txt", "its lowk insane how big my balls are") + fs:store("/home/i have big balls.txt", "dude my balls are actually massive") end diff --git a/kernel/shell.rv b/kernel/shell.rv index f72387a..40930c5 100644 --- a/kernel/shell.rv +++ b/kernel/shell.rv @@ -19,10 +19,14 @@ fn cmd_help() do help_row("zoom in|out", "scale the font") help_row("echo <text>", "print it back") puts("\n files\n") - help_row("ls", "list files") - help_row("cat <name>", "print a file") - help_row("write <name> <text>", "create or overwrite") - help_row("rm <name>", "delete a file") + help_row("ls [path]", "list a directory") + help_row("cd <path>", "change directory") + help_row("pwd", "print working dir") + help_row("mkdir <path>", "make a directory") + help_row("cat <path>", "print a file") + help_row("edit <path>", "line editor, . to save") + help_row("write <path> <text>", "create or overwrite") + help_row("rm <path>", "delete a file or empty dir") help_row("mkfs", "format the disk") puts("\n programs\n") help_row("run", "list programs") @@ -34,7 +38,7 @@ fn welcome() do let fsline = "no filesystem" if fs.ready do fsline = "revelfs mounted" end puts("\n") - puts(" a tiny os whose brain lives in revo, zig only touches the metal\n") + puts(" kernel written in revo - by an alcoholic\n") puts(" #{(mem_total() shr 20)} MiB ram | #{fsline} | #{prog_count()} programs\n") puts(" type help to see what it does\n\n% ") end @@ -43,7 +47,6 @@ fn arg_name(line, start) do let quoted = :false let begin = start if (begin < line:len() and line[begin] == "\"") do begin += 1 quoted = :true end - # scan to the end of the name, a closing quote or a space depending on how it opened let i = begin if quoted do while (i < line:len() and line[i] != "\"") do i += 1 end @@ -52,29 +55,103 @@ fn arg_name(line, start) do while (i < line:len() and line[i] != " ") do i += 1 end end const name = line[begin..i] - # step past the closing quote and the separating space so rest points at the text let rest = i if (quoted and rest < line:len()) do rest += 1 end if (rest < line:len() and line[rest] == " ") do rest += 1 end { name = name, rest = rest } end +global wd = { path = "/" } + +global editor = { active = :false, path = "", buf = "" } + +fn editor_start(path) do + editor.active = :true + editor.path = path + editor.buf = "" + const sz = fs:size(path) + if sz > 0 do + editor.buf = fs:read_bytes(path, 0, sz) + puts(editor.buf) + end + puts("-- editing #{path}, a single . on a line saves and exits --\n") +end + +fn editor_line(line) do + if line == "." do + fs:store(editor.path, editor.buf) + editor.active = :false + puts("saved #{editor.path} (#{editor.buf:len()} bytes)\n% ") + end + else do editor.buf = editor.buf ~ line ~ "\n" end +end + +fn cmd_edit(line) do + const a = arg_name(line, 5) + if a.name:len() == 0 do puts("usage: edit <path>\n% ") return :nil end + editor_start(abspath(a.name)) +end + +fn abspath(arg) do + if arg:len() == 0 return wd.path + let p = arg + if arg[0] != "/" do + if wd.path == "/" do p = "/#{arg}" end + else do p = "#{wd.path}/#{arg}" end + end + while (p:len() > 1 and p[p:len() - 1] == "/") do p = p[0..(p:len() - 1)] end + return p +end + +fn parent_of(path) do + if path == "/" return "/" + let i = path:len() - 1 + while (i > 0 and path[i] != "/") do i -= 1 end + if i == 0 return "/" + return path[0..i] +end + +fn cmd_cd(words) do + const arg = if words:len() > 1 words[1] else "/" + const target = if arg == ".." parent_of(wd.path) else abspath(arg) + if target == "/proc" or target == "/bin" or fs:is_dir(target) do wd.path = target end + else do puts("cd: #{arg}: not a directory\n") end +end + +fn cmd_pwd() puts("#{wd.path}\n") + +fn cmd_ls(words) do + const target = if words:len() > 1 abspath(words[1]) else wd.path + if vfs_owns(target) do vfs_ls(target) return :nil end + const n = fs:ls(target) + # the virtual mounts arent real dir entries, overlay them on the root listing + if target == "/" do puts(" bin/\n proc/\n") end + else if n == 0 do puts(" (empty)\n") end +end + +fn cmd_mkdir(words) do + if words:len() < 2 do puts("usage: mkdir <name>\n") return :nil end + fs:mkdir(abspath(words[1])) +end + fn cmd_write(line) do const a = arg_name(line, 6) if a.name:len() == 0 do puts("usage: write <name> <text>\n") return :nil end - fs:write(a.name, line[a.rest..]) + fs:write(abspath(a.name), line[a.rest..]) end fn cmd_cat(line) do const a = arg_name(line, 4) if a.name:len() == 0 do puts("usage: cat <name>\n") return :nil end - fs:cat(a.name) + const p = abspath(a.name) + if vfs_owns(p) do vfs_cat(p) return :nil end + fs:cat(p) end fn cmd_rm(line) do const a = arg_name(line, 3) if a.name:len() == 0 do puts("usage: rm <name>\n") return :nil end - fs:rm(a.name) + fs:rm(abspath(a.name)) end fn zoom_by(delta) do @@ -136,12 +213,18 @@ fn cmd_uptime() puts("#{math.floor(uptime() / 100)}s (#{uptime()} ticks)\n") fn cmd_ps() puts("#{proc_count()} process(es) running\n") fn run(line) do + if editor.active do editor_line(line) return :nil end if line:trim():len() == 0 do puts("% ") return :nil end const words = line:split(" ") const cmd = words[0] + if cmd == "edit" do cmd_edit(line) return :nil end if cmd == "run" do if words:len() <= 1 do list_progs() puts("% ") return :nil end - if run_prog(words[1]) == :true return :nil + const prog = words[1] + # everything after "run <prog> " is the programs argument string + const start = 4 + prog:len() + 1 + const arg = if line:len() > start line[start..] else "" + if run_prog(prog, arg) == :true return :nil puts("% ") return :nil end @@ -154,10 +237,13 @@ fn run(line) do | "ps" => cmd_ps() | "zoom" => cmd_zoom(words) | "mkfs" => fs:format() - | "ls" => fs:ls() + | "ls" => cmd_ls(words) | "cat" => cmd_cat(line) | "write" => cmd_write(line) | "rm" => cmd_rm(line) + | "mkdir" => cmd_mkdir(words) + | "cd" => cmd_cd(words) + | "pwd" => cmd_pwd() | "echo" => if line:len() > 5 puts("#{line[5..]}\n") else puts("\n") | _ => puts("unknown command: #{cmd}\n") puts("% ") diff --git a/kernel/vfs.rv b/kernel/vfs.rv new file mode 100644 index 0000000..5d2d1de --- /dev/null +++ b/kernel/vfs.rv @@ -0,0 +1,57 @@ +# shitty vfs implementation +# proc and /bin arent on disk, theyre completely virtual + +const TASK_STATES = { "free", "ready", "dead", "blocked" } + +fn vfs_owns(path) do + if path == "/proc" or path:starts_with?("/proc/") return :true + if path == "/bin" or path:starts_with?("/bin/") return :true + return :false +end + +fn parse_pid(s) do + if s:len() == 0 return -1 + let n = 0 + for i in 0..s:len() do + const c = s[i]:ascii() + if c < 48 or c > 57 return -1 + n = n * 10 + (c - 48) + end + return n +end + +fn vfs_ls(path) do + if path == "/proc" do + puts(" uptime\n meminfo\n") + for i in 0..task_max() do + if task_state(i) != 0 puts(" #{i}\n") + end + end + else if path == "/bin" do + for i in 0..prog_count() do puts(" #{prog_name(i)}\n") end + end + else do puts("ls: #{path}: no such directory\n") end +end + +fn vfs_cat(path) do + if path == "/proc/uptime" do puts("#{math.floor(uptime() / 100)}s\n") return :nil end + if path == "/proc/meminfo" do puts("#{(mem_free() shr 20)} MiB free / #{(mem_total() shr 20)} MiB total\n") return :nil end + if path:starts_with?("/proc/") do + const pid = parse_pid(path[6..]) + if pid >= 0 and pid < task_max() and task_state(pid) != 0 do + puts("pid #{pid}: #{TASK_STATES[task_state(pid)]}\n") + return :nil + end + puts("cat: #{path}: no such file\n") + return :nil + end + if path:starts_with?("/bin/") do + const nm = path[5..] + for i in 0..prog_count() do + if prog_name(i) == nm do puts("#{nm}: elf program, #{prog_size(i)} bytes\n") return :nil end + end + puts("cat: #{path}: no such file\n") + return :nil + end + puts("cat: #{path}: no such file\n") +end diff --git a/kernel/vmm.rv b/kernel/vmm.rv index ece5682..a4c0eea 100644 --- a/kernel/vmm.rv +++ b/kernel/vmm.rv @@ -4,7 +4,6 @@ const PTE_ADDR = 0x000FFFFFFFFFF000 fn pte_idx(virt, level) (virt shr (12 + 9 * level)) band 0x1ff -# next level table phys under entry `index` of table `table`, creating it if absent fn next_table(table, index) do const slot = table + index * 8 const entry = mem_read(slot, 8) |
