hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-10-04 16:03:42 +0200
committerhachem <im@hachem.wtf>2026-10-04 16:03:42 +0200
commit52dab7fc85d877e80e3032f0afabae906adebad8 (patch)
treeb6d56299b839413bb77b1b8c8fa9f7a682124a05 /kernel
parenta8a35a00a3c3c57cfbef2311d0348376c2e83208 (diff)
feat: fs tree and free list alloc
Diffstat (limited to 'kernel')
-rw-r--r--kernel/font.rv1
-rw-r--r--kernel/fs.rv355
-rw-r--r--kernel/input.rv25
-rw-r--r--kernel/proc.rv23
-rw-r--r--kernel/seed.rv9
-rw-r--r--kernel/shell.rv110
-rw-r--r--kernel/vfs.rv57
-rw-r--r--kernel/vmm.rv1
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)