hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/fs.rv517
-rw-r--r--kernel/input.rv8
-rw-r--r--kernel/main.rv4
-rw-r--r--kernel/seed.rv13
-rw-r--r--kernel/shell.rv482
-rw-r--r--kernel/tty.rv3
6 files changed, 794 insertions, 233 deletions
diff --git a/kernel/fs.rv b/kernel/fs.rv
index 1e72dc4..f732573 100644
--- a/kernel/fs.rv
+++ b/kernel/fs.rv
@@ -19,220 +19,329 @@ const END = 0xffff # end of chain marker in the fat
const T_FILE = 1
const T_DIR = 2
-fn split_path(path) do
+#* into non empty parts *#
+fn split_path(path) -> table<string> do
const raw_parts = path:split("/")
let parts = {}
+
for index in 0..raw_parts:len() do
- if raw_parts[index]:len() > 0 parts:push(raw_parts[index])
+ if (raw_parts[index]:len() > 0) parts:push(raw_parts[index])
end
+
return parts
end
-# scratch is a spare sector for superblocks/dirs/data, fat is a separate scratch
-# so fat lookups dont trample a dir block were mid edit.
global fs = {
- scratch = 0, fat = 0, ready = :false, total = 0, root = 0,
+ # scratch is a spare sector for superblocks/dirs/data
+ scratch = 0,
+ # fat is a separate scratch
+ # so fat lookups dont trample a dir block were mid edit.
+ fat = 0,
+ ready = :false, total = 0, root = 0,
- fat_get = fn(self, block) do
+ fn fat_get(self, block) -> num do
disk_read(FAT_LBA + (block shr 8), self.fat)
- mem_read(self.fat + (block band 255) * 2, 2)
+
+ return mem_read(self.fat + (block band 255) * 2, 2)
end,
- fat_set = fn(self, block, value) do
+ fn fat_set(self, block, value) do
const sector = FAT_LBA + (block shr 8)
disk_read(sector, self.fat)
mem_write(self.fat + (block band 255) * 2, value, 2)
disk_write(sector, self.fat)
end,
- fat_alloc = fn(self) do
+ fn fat_alloc(self) -> num do
let block = DATA_LBA
- while block < self.total do
+
+ while (block < self.total) do
disk_read(FAT_LBA + (block shr 8), self.fat)
+
let offset = block band 255
+
while (offset < 256 and block < self.total) do
- if mem_read(self.fat + offset * 2, 2) == 0 return block
+ if mem_read(self.fat + offset * 2, 2) == 0 do
+ return block
+ end
+
offset += 1
block += 1
end
end
- 0
+
+ return 0
end,
- free_chain = fn(self, first) do
+ fn free_chain(self, first) do
let block = first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
self:fat_set(block, 0)
block = next_block
end
end,
- # entry is 32 bytes:
- # +0 type (0 free/1 file/2 dir)
- # +1 name len
- # +2 first block
- # +4 size
- # +8 name bytes
- ent_name = fn(self, entry_offset) do
+ #* entry is 32 bytes:
+ - +0 type (0 free/1 file/2 dir)
+ - +1 name len
+ - +2 first block
+ - +4 size
+ - +8 name bytes *#
+ fn ent_name(self, entry_offset) -> string do
const name_length = mem_read(self.scratch + entry_offset + 1, 1)
let result = ""
- for index in 0..name_length do result = result ~ string.of_ascii(mem_read(self.scratch + entry_offset + 8 + index, 1)) end
- result
+
+ for index in 0..name_length do
+ result = result ~ string.of_ascii(mem_read(self.scratch + entry_offset + 8 + index, 1))
+ end
+
+ return result
end,
- ent_write = fn(self, entry_offset, type, name, first, size) do
+ fn ent_write(self, entry_offset, type, name, first, size) do
mem_write(self.scratch + entry_offset, type, 1)
mem_write(self.scratch + entry_offset + 1, name:len(), 1)
mem_write(self.scratch + entry_offset + 2, first, 2)
mem_write(self.scratch + entry_offset + 4, size, 4)
- for index in 0..name:len() do mem_write(self.scratch + entry_offset + 8 + index, name[index]:ascii(), 1) end
+
+ for index in 0..name:len() do
+ mem_write(self.scratch + entry_offset + 8 + index, name[index]:ascii(), 1)
+ end
end,
- dir_lookup = fn(self, dir_first, name) do
+ fn dir_lookup(self, dir_first, name) -> { type: num, first: num, size: num } | :nil do
let block = dir_first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
+
if mem_read(self.scratch + entry_offset, 1) != 0 and self:ent_name(entry_offset) == name do
return { type = mem_read(self.scratch + entry_offset, 1), first = mem_read(self.scratch + entry_offset + 2, 2), size = mem_read(self.scratch + entry_offset + 4, 4) }
end
end
+
block = next_block
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
+ #* add or overwrite `name` in the dir
+ grows a block if every slot is taken *#
+ fn dir_set(self, dir_first, name, type, first, size) -> bool do
let block = dir_first
let found_block = 0
let found_offset = 0
let free_block = 0
let free_offset = -1
let last_block = dir_first
+
while (block != 0 and block != END) do
last_block = block
+
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
+
if mem_read(self.scratch + entry_offset, 1) == 0 do
- if free_offset < 0 do free_block = block free_offset = entry_offset end
- end
- else do
- if self:ent_name(entry_offset) == name do found_block = block found_offset = entry_offset end
+ if free_offset < 0 do
+ free_block = block
+ free_offset = entry_offset
+ end
+ end else do
+ if self:ent_name(entry_offset) == name do
+ found_block = block
+ found_offset = entry_offset
+ end
end
end
+
block = next_block
end
let target_block = 0
let target_offset = 0
- if found_block != 0 do target_block = found_block target_offset = found_offset end
- else if free_offset >= 0 do target_block = free_block target_offset = free_offset end
- else do
- const new_block = self:fat_alloc()
- if new_block == 0 return :false
- self:fat_set(new_block, END)
- self:fat_set(last_block, new_block)
- mem_zero(self.scratch, BLOCK)
- disk_write(new_block, self.scratch)
- target_block = new_block
- target_offset = 0
- end
+
+ match
+ | found_block != 0 => do
+ target_block = found_block
+ target_offset = found_offset
+ end
+ | free_offset >= 0 => do
+ target_block = free_block
+ target_offset = free_offset
+ end
+ | _ => do
+ const new_block = self:fat_alloc()
+
+ if new_block == 0 do
+ return :false
+ end
+
+ self:fat_set(new_block, END)
+ self:fat_set(last_block, new_block)
+ mem_zero(self.scratch, BLOCK)
+ disk_write(new_block, self.scratch)
+ target_block = new_block
+ target_offset = 0
+ end
disk_read(target_block, self.scratch)
self:ent_write(target_offset, type, name, first, size)
disk_write(target_block, self.scratch)
+
return :true
end,
- dir_clear = fn(self, dir_first, name) do
+ fn dir_clear(self, dir_first, name) -> { type: num, first: num } | :nil do
let block = dir_first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
+
if mem_read(self.scratch + entry_offset, 1) != 0 and self:ent_name(entry_offset) == name do
const info = { type = mem_read(self.scratch + entry_offset, 1), first = mem_read(self.scratch + entry_offset + 2, 2) }
+
mem_write(self.scratch + entry_offset, 0, 1)
disk_write(block, self.scratch)
+
return info
end
end
+
block = next_block
end
+
return :nil
end,
- dir_empty = fn(self, dir_first) do
+ fn dir_empty(self, dir_first) -> bool do
let block = dir_first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
- if mem_read(self.scratch + slot * 32, 1) != 0 return :false
+ if mem_read(self.scratch + slot * 32, 1) != 0 do
+ return :false
+ end
end
+
block = next_block
end
+
return :true
end,
- resolve = fn(self, path) do
+ # resolve a path to its entry
+ fn resolve(self, path) -> { type: num, first: num, size: num } | :nil do
const parts = split_path(path)
let current = self.root
let entry = { type = T_DIR, first = self.root, size = 0 }
+
for index in 0..parts:len() do
- if entry.type != T_DIR return :nil
+ if entry.type != T_DIR do
+ return :nil
+ end
+
const found = self:dir_lookup(current, parts[index])
- if found == :nil return :nil
+
+ if found == :nil do
+ return :nil
+ end
+
entry = found
current = found.first
end
+
return entry
end,
- resolve_parent = fn(self, path) do
+ # resolve a path to its parent dir and name
+ fn resolve_parent(self, path) -> { dir: num, name: string } | :nil do
const parts = split_path(path)
- if parts:len() == 0 return :nil
+
+ if parts:len() == 0 do
+ return :nil
+ end
+
let parent_dir = self.root
+
for index in 0..(parts:len() - 1) do
const found = self:dir_lookup(parent_dir, parts[index])
- if found == :nil or found.type != T_DIR return :nil
+
+ if found == :nil or found.type != T_DIR do
+ return :nil
+ end
+
parent_dir = found.first
end
+
return { dir = parent_dir, name = parts[parts:len() - 1] }
end,
- mount = fn(self) do
+ fn mount(self) do
self.scratch = frame_alloc()
self.fat = frame_alloc()
self.total = disk_sectors()
- if self.total == 0 do self.ready = :false return :nil end
+
+ if self.total == 0 do
+ self.ready = :false
+ return :nil
+ end
+
disk_read(SB_LBA, self.scratch)
+
if mem_read(self.scratch, 4) == MAGIC do
self.root = mem_read(self.scratch + 8, 2)
self.ready = :true
+ end else do
+ self.ready = :false
end
- else do self.ready = :false end
end,
- ensure = fn(self) do
- if self.ready return :true
+ fn ensure(self) -> bool do
+ if self.ready do
+ return :true
+ end
+
puts("no filesystem, run mkfs first\n")
- :false
+
+ return :false
end,
- format = fn(self) do
- if disk_sectors() == 0 do puts("mkfs: no disk\n") return :nil end
+ fn format(self) do
+ if disk_sectors() == 0 do
+ puts("mkfs: no disk\n")
+ return :nil
+ end
+
mem_zero(self.scratch, BLOCK)
- for sector in 0..FAT_SECTORS do disk_write(FAT_LBA + sector, self.scratch) end
+
+ for sector in 0..FAT_SECTORS do
+ disk_write(FAT_LBA + sector, self.scratch)
+ end
+
const root = self:fat_alloc()
+
self:fat_set(root, END)
mem_zero(self.scratch, BLOCK)
disk_write(root, self.scratch)
@@ -243,31 +352,70 @@ global fs = {
mem_write(self.scratch + 8, root, 2)
disk_write(SB_LBA, self.scratch)
self.ready = :true
+
puts("formatted #{self.total} sectors, #{(self.total shr 1)} KiB\n")
end,
- mkdir = fn(self, path) do
- if self:ensure() == :false return :nil
+ fn mkdir(self, path) do
+ if self:ensure() == :false do
+ return :nil
+ end
+
const parent = self:resolve_parent(path)
- if parent == :nil do fail("mkdir: #{path}: bad path\n") return :nil end
- if parent.name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return :nil end
- if self:dir_lookup(parent.dir, parent.name) != :nil do fail("mkdir: #{parent.name}: already exists\n") return :nil end
+
+ if parent == :nil do
+ fail("mkdir: #{path}: bad path\n")
+ return :nil
+ end
+
+ if parent.name:len() > NAME_MAX do
+ fail("name too long (max #{NAME_MAX})\n")
+ return :nil
+ end
+
+ if self:dir_lookup(parent.dir, parent.name) != :nil do
+ fail("mkdir: #{parent.name}: already exists\n")
+ return :nil
+ end
+
const new_block = self:fat_alloc()
- if new_block == 0 do fail("disk full\n") return :nil end
+
+ if new_block == 0 do
+ fail("disk full\n")
+ return :nil
+ end
+
self:fat_set(new_block, END)
mem_zero(self.scratch, BLOCK)
disk_write(new_block, self.scratch)
self:dir_set(parent.dir, parent.name, T_DIR, new_block, 0)
end,
- store = fn(self, path, text) do
- if self:ensure() == :false return 0
+ fn store(self, path, text) -> num do
+ if self:ensure() == :false do
+ return 0
+ end
+
const parent = self:resolve_parent(path)
- if parent == :nil do puts("#{path}: bad path\n") return 0 end
- if parent.name:len() > NAME_MAX do puts("name too long (max #{NAME_MAX})\n") return 0 end
+
+ if parent == :nil do
+ puts("#{path}: bad path\n")
+ return 0
+ end
+
+ if parent.name:len() > NAME_MAX do
+ puts("name too long (max #{NAME_MAX})\n")
+ return 0
+ end
+
const existing = self:dir_lookup(parent.dir, parent.name)
+
if existing != :nil do
- if existing.type == T_DIR do fail("#{parent.name}: is a directory\n") return 0 end
+ if existing.type == T_DIR do
+ fail("#{parent.name}: is a directory\n")
+ return 0
+ end
+
self:free_chain(existing.first)
end
@@ -275,185 +423,318 @@ global fs = {
let first = 0
let previous = 0
let written = 0
- while written < size do
+
+ while (written < size) do
const block = self:fat_alloc()
- if block == 0 do fail("disk full\n") return 0 end
+
+ if block == 0 do
+ fail("disk full\n")
+ return 0
+ end
+
self:fat_set(block, END)
- if first == 0 do first = block end
- else do self:fat_set(previous, block) end
+
+ if first == 0 do
+ first = block
+ end else do
+ self:fat_set(previous, block)
+ end
+
mem_zero(self.scratch, BLOCK)
# blit a whole block of the string at once, per byte was O(n^2) and choked
# the second files got into the kilobytes
let chunk = size - written
- if chunk > BLOCK do chunk = BLOCK end
+
+ if (chunk > BLOCK) chunk = BLOCK
+
blit_str(self.scratch, text[written..(written + chunk)])
disk_write(block, self.scratch)
written += chunk
previous = block
end
+
self:dir_set(parent.dir, parent.name, T_FILE, first, size)
+
return 1
end,
- write = fn(self, path, text) do
- if self:store(path, text) == 1 puts("wrote #{path} (#{text:len()} bytes)\n")
+ fn write(self, path, text) do
+ if (self:store(path, text) == 1) puts("wrote #{path} (#{text:len()} bytes)\n")
end,
- size = fn(self, path) do
+ fn size(self, path) -> num do
const entry = self:resolve(path)
- if entry == :nil or entry.type == T_DIR return -1
+
+ if entry == :nil or entry.type == T_DIR do
+ return -1
+ end
+
return entry.size
end,
- read_bytes = fn(self, path, offset, length) do
+ fn read_bytes(self, path, offset, length) -> string do
const entry = self:resolve(path)
- if entry == :nil or entry.type == T_DIR return ""
+
+ if entry == :nil or entry.type == T_DIR do
+ return ""
+ end
+
let block = entry.first
let stop = offset + length
- if stop > entry.size do stop = entry.size end
+
+ if (stop > entry.size) stop = entry.size
+
let position = 0
let result = ""
+
while (block != 0 and block != END and position < stop) do
const next_block = self:fat_get(block)
- if (position + BLOCK > offset and position < stop) do
+
+ if position + BLOCK > offset and position < stop do
disk_read(block, self.scratch)
# pull this blocks slice in one go, per byte concat was O(n^2)
let from = 0
- if offset > position do from = offset - position end
+
+ if (offset > position) from = offset - position
+
let to = BLOCK
- if stop - position < BLOCK do to = stop - position end
+
+ if (stop - position < BLOCK) to = stop - position
+
result = result ~ phys_to_str(self.scratch + from, to - from)
end
+
position += BLOCK
block = next_block
end
+
return result
end,
- read_into = fn(self, path, dest) do
+ fn read_into(self, path, dest) -> num do
const entry = self:resolve(path)
- if entry == :nil or entry.type == T_DIR return 0
+
+ if entry == :nil or entry.type == T_DIR do
+ return 0
+ end
+
let block = entry.first
let offset = 0
let remaining = entry.size
+
while (block != 0 and block != END and remaining > 0) do
const next_block = self:fat_get(block)
+
disk_read(block, dest + offset)
offset += BLOCK
remaining -= BLOCK
block = next_block
end
+
return entry.size
end,
# TODO: ts needs a rewrite its not cheap and rewrites every file from scratch
- write_at = fn(self, path, offset, bytes) do
+ fn write_at(self, path, offset, bytes) -> num do
const size = self:size(path)
let base = ""
+
if size > 0 do base = self:read_bytes(path, 0, size) end
+
let prefix = ""
- if offset <= base:len() do prefix = base[0..offset] end
- else do
+
+ if offset <= base:len() do
+ prefix = base[0..offset]
+ end else do
prefix = base
- while prefix:len() < offset do prefix = prefix ~ string.of_ascii(0) end
+ while (prefix:len() < offset) do prefix = prefix ~ string.of_ascii(0) end
end
+
const after = offset + bytes:len()
let tail = ""
+
if after < base:len() do tail = base[after..] end
+
return self:store(path, prefix ~ bytes ~ tail)
end,
- cat = fn(self, path) do
- if self:ensure() == :false return :nil
+ fn cat(self, path) do
+ if self:ensure() == :false do
+ return :nil
+ end
+
const entry = self:resolve(path)
- if entry == :nil do fail("cat: #{path}: no such file\n") return :nil end
- if entry.type == T_DIR do fail("cat: #{path}: is a directory\n") return :nil end
+
+ if entry == :nil do
+ fail("cat: #{path}: no such file\n")
+ return :nil
+ end
+
+ if entry.type == T_DIR do
+ fail("cat: #{path}: is a directory\n")
+ return :nil
+ end
+
let block = entry.first
let remaining = entry.size
let result = ""
+
while (block != 0 and block != END and remaining > 0) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
- const chunk = if remaining > BLOCK BLOCK else remaining
- for index in 0..chunk do result = result ~ string.of_ascii(mem_read(self.scratch + index, 1)) end
+
+ const chunk = if (remaining > BLOCK) BLOCK else remaining
+
+ for index in 0..chunk do
+ result = result ~ string.of_ascii(mem_read(self.scratch + index, 1))
+ end
+
remaining -= chunk
block = next_block
end
+
puts(result)
puts("\n")
end,
- ls = fn(self, path) do
- if self:ensure() == :false return -1
+ fn ls(self, path) -> num do
+ if self:ensure() == :false do
+ return -1
+ end
+
const entry = self:resolve(path)
- if entry == :nil do fail("ls: #{path}: no such file\n") return -1 end
- if entry.type != T_DIR do puts(" #{path} #{entry.size} bytes\n") return -1 end
+
+ if entry == :nil do
+ fail("ls: #{path}: no such file\n")
+ return -1
+ end
+
+ if entry.type != T_DIR do
+ puts(" #{path} #{entry.size} bytes\n")
+ return -1
+ end
+
let count = 0
let block = entry.first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
const entry_type = mem_read(self.scratch + entry_offset, 1)
+
if entry_type != 0 do
const name = self:ent_name(entry_offset)
- if entry_type == T_DIR do puts(" #{name}/\n") end
- else do puts(" #{name} #{mem_read(self.scratch + entry_offset + 4, 4)} bytes\n") end
+
+ if entry_type == T_DIR do
+ puts(" #{name}/\n")
+ end else do
+ puts(" #{name} #{mem_read(self.scratch + entry_offset + 4, 4)} bytes\n")
+ end
+
count += 1
end
end
+
block = next_block
end
+
return count
end,
- rm = fn(self, path) do
- if self:ensure() == :false return :nil
+ fn rm(self, path) do
+ if self:ensure() == :false do
+ return :nil
+ end
+
const parent = self:resolve_parent(path)
- if parent == :nil do fail("rm: #{path}: no such file\n") return :nil end
+
+ if parent == :nil do
+ fail("rm: #{path}: no such file\n")
+ return :nil
+ end
+
const entry = self:dir_lookup(parent.dir, parent.name)
- if entry == :nil do fail("rm: #{path}: no such file\n") return :nil end
+
+ if entry == :nil do
+ fail("rm: #{path}: no such file\n")
+ return :nil
+ end
+
if entry.type == T_DIR and self:dir_empty(entry.first) == :false do
fail("rm: #{parent.name}: directory not empty\n")
return :nil
end
+
self:free_chain(entry.first)
self:dir_clear(parent.dir, parent.name)
puts("removed #{parent.name}\n")
end,
- is_dir = fn(self, path) do
+ fn is_dir(self, path) -> bool do
const entry = self:resolve(path)
+
return entry != :nil and entry.type == T_DIR
end,
- entries = fn(self, path) do
+ fn entries(self, path) -> table | :nil do
const entry = self:resolve(path)
- if entry == :nil or entry.type != T_DIR return :nil
+
+ if entry == :nil or entry.type != T_DIR do
+ return :nil
+ end
+
let result = {}
let block = entry.first
+
while (block != 0 and block != END) do
const next_block = self:fat_get(block)
+
disk_read(block, self.scratch)
+
for slot in 0..ENTS do
const entry_offset = slot * 32
const entry_type = mem_read(self.scratch + entry_offset, 1)
- if entry_type != 0 result:push({ name = self:ent_name(entry_offset), type = entry_type, size = mem_read(self.scratch + entry_offset + 4, 4) })
+
+ if entry_type != 0 do
+ result:push({
+ name = self:ent_name(entry_offset),
+ type = entry_type,
+ size = mem_read(self.scratch + entry_offset + 4, 4),
+ })
+ end
end
+
block = next_block
end
+
return result
end,
- unlink = fn(self, path) do
+ fn unlink(self, path) -> bool do
const parent = self:resolve_parent(path)
- if parent == :nil return :false
+
+ if parent == :nil do
+ return :false
+ end
+
const entry = self:dir_lookup(parent.dir, parent.name)
- if entry == :nil return :false
- if (entry.type == T_DIR and self:dir_empty(entry.first) == :false) return :false
+
+ if entry == :nil do
+ return :false
+ end
+
+ if entry.type == T_DIR and self:dir_empty(entry.first) == :false do
+ return :false
+ end
+
self:free_chain(entry.first)
self:dir_clear(parent.dir, parent.name)
+
return :true
end,
}
diff --git a/kernel/input.rv b/kernel/input.rv
index ed0f4f7..139edf0 100644
--- a/kernel/input.rv
+++ b/kernel/input.rv
@@ -34,7 +34,13 @@ fn decode_key(code) do
if code == 0x9d do shell.ctrl = :false return :nil end # ctrl up
if code >= 128 return :nil # other releases
if code == 28 return "\n" # enter
- if code == 14 return "\x08" # backspace
+ if code == 14 do
+ if shell.ctrl do
+ return "\x7f"
+ end
+
+ return "\x08"
+ end # backspace (ctrl: word erase)
if code < 2 return :nil
if code >= KEYMAP:len() return :nil
const character = if shell.shift KEYMAP_SHIFT[code] else KEYMAP[code]
diff --git a/kernel/main.rv b/kernel/main.rv
index 93de758..3547665 100644
--- a/kernel/main.rv
+++ b/kernel/main.rv
@@ -15,6 +15,10 @@ if (disk_sectors() != 0 and fs.ready == :false) do
seed_fs()
end
+if fs:size("/etc/profile") >= 0 do
+ run_script_path("/etc/profile", "profile", make_ctx("", { "" }))
+end
+
# lung will be happy
const default_face = load_ttf("/usr/share/terminess.ttf", 32)
if default_face != :nil do
diff --git a/kernel/seed.rv b/kernel/seed.rv
index f673f68..9dc2dc5 100644
--- a/kernel/seed.rv
+++ b/kernel/seed.rv
@@ -24,8 +24,17 @@ fn seed_fs() do
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")
+ fs:store("/etc/profile", """
+ # /etc/profile runs once at shell startup, keep it quick
+
+ const motd_size = fs:size("/etc/motd")
+ if motd_size > 0 do
+ puts(fs:read_bytes("/etc/motd", 0, motd_size))
+ puts("\n")
+ end
+ """)
+ fs:store("/home/balls.txt", "its lowk insane how big my balls are lol jk\n\t- hachem")
+ fs:store("/home/i have big balls.txt", "dude my balls are actually massive lol jk\n\t- hachem")
seed_bin()
seed_fonts()
diff --git a/kernel/shell.rv b/kernel/shell.rv
index 2811e5a..99ead73 100644
--- a/kernel/shell.rv
+++ b/kernel/shell.rv
@@ -1,21 +1,31 @@
# i eventually want the shell to be a standalone program and shit
-fn pad(text, width) do
+fn pad(text, width) -> string do
let result = text
- while result:len() < width do result = result ~ " " end
+
+ while (result:len() < width)
+ result = result ~ " "
+
return result
end
fn help_row(label, description) do
let col = pad(label, 22)
- if label:len() >= 22 do col = label ~ " " end
+
+ if label:len() >= 22 do
+ col = label ~ " "
+ end
+
puts(" #{col}#{description}\n")
end
fn help_group(title, group_name) do
puts("\n #{title}\n")
+
for index in 0..commands:len() do
- if commands[index].group == group_name do help_row(commands[index].usage, commands[index].help) end
+ if commands[index].group == group_name do
+ help_row(commands[index].usage, commands[index].help)
+ end
end
end
@@ -44,12 +54,17 @@ fn cmd_help(context) do
puts("\n fonts on disk (font load /usr/share/x.psf)\n")
for index in 0..font_files:len() do help_row(font_files[index].name, "") end
end
+
puts("\n")
end
fn welcome() do
let fs_status = "no filesystem"
- if fs.ready do fs_status = "revelfs mounted" end
+
+ if fs.ready do
+ fs_status = "revelfs mounted"
+ end
+
puts("\n")
puts(" kernel written in revo - by an alcoholic\n")
puts(" #{(mem_total() shr 20)} MiB ram | #{fs_status} | #{prog_count()} programs\n")
@@ -58,288 +73,533 @@ fn welcome() do
end
# global so /bin scripts can call it from a nested eval fiber
-global arg_name = fn(line, start) do
+global arg_name = fn(line, start) -> { name: string, rest: num } do
let quoted = :false
let begin = start
- if (begin < line:len() and line[begin] == "\"") do begin += 1 quoted = :true end
+
+ if begin < line:len() and line[begin] == "\"" do
+ begin += 1
+ quoted = :true
+ end
+
let index = begin
+
if quoted do
- while (index < line:len() and line[index] != "\"") do index += 1 end
- end
- else do
- while (index < line:len() and line[index] != " ") do index += 1 end
+ while (index < line:len() and line[index] != "\"")
+ index += 1
+ end else do
+ while (index < line:len() and line[index] != " ")
+ index += 1
end
+
const name = line[begin..index]
let rest = index
- 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 }
+
+ if quoted and rest < line:len() do
+ rest += 1
+ end
+
+ if rest < line:len() and line[rest] == " " do
+ rest += 1
+ end
+
+ return { name = name, rest = rest }
end
-fn parent_of(path) do
- if path == "/" return "/"
+fn parent_of(path) -> string do
+ if path == "/" do
+ return "/"
+ end
+
let index = path:len() - 1
- while (index > 0 and path[index] != "/") do index -= 1 end
- if index == 0 return "/"
+
+ while (index > 0 and path[index] != "/")
+ index -= 1
+
+ if index == 0 do
+ return "/"
+ end
+
return path[0..index]
end
fn cmd_cd(context) do
- const arg = if context.argv:len() > 1 context.argv[1] else "/"
- const target = if arg == ".." parent_of(wd.path) else expand_path(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") shell_status.code = 1 end
+ const arg = if (context.argv:len() > 1) context.argv[1] else "/"
+
+ const target = if (arg == "..") parent_of(wd.path) else expand_path(arg)
+
+ if target == "/proc"
+ or target == "/bin"
+ or fs:is_dir(target) do
+ wd.path = target
+ end else do
+ fail("cd: #{arg}: not a directory\n")
+ end
end
-fn cmd_pwd(context) puts("#{wd.path}\n")
+fn cmd_pwd(context)
+ puts("#{wd.path}\n")
-fn wipe() console:clear()
-fn cmd_clear(context) wipe()
+fn wipe()
+ console:clear()
+
+fn cmd_clear(context)
+ wipe()
fn zoom_by(delta) do
const entry = console.font_entry
+
# ttf is vector, stepping the size interactively is fine
- if (entry != :nil and entry.kind == :ttf) do
+ if entry != :nil and entry.kind == :ttf do
let size = entry.size + delta * 4
- if size < 8 do size = 8 end
- if size > 72 do size = 72 end
- if size == entry.size do puts("zoom #{size}px\n") return :nil end
+
+ if (size < 8) size = 8
+ if (size > 72) size = 72
+
+ if size == entry.size do
+ puts("zoom #{size}px\n")
+ return :nil
+ end
+
const face = load_ttf(entry.path, size)
- if face == :nil do puts("zoom: rasterize failed\n") return :nil end
+
+ if face == :nil do
+ puts("zoom: rasterize failed\n")
+ return :nil
+ end
+
add_font(entry.name, face.glyphs, face.width, face.height, :ttf, entry.path, size)
console:set_font(find_font(entry.name))
puts("zoom #{size}px\n")
+
return :nil
end
+
# bitmap fonts js use integer pixel scale
let scale = console.scale + delta
- if scale < 1 do scale = 1 end
- if scale > 6 do scale = 6 end
+
+ if (scale < 1) scale = 1
+ if (scale > 6) scale = 6
+
console.scale = scale
wipe()
+
puts("zoom #{scale}x\n")
end
fn cmd_zoom(context) do
- const direction = if context.argv:len() > 1 context.argv[1] else ""
- if direction == "in" do zoom_by(1) end
- else if direction == "out" do zoom_by(-1) end
- else do puts("usage: zoom in | zoom out\n") end
+ const direction =
+ if (context.argv:len() > 1)
+ context.argv[1]
+ else ""
+
+ match direction
+ | "in" => zoom_by(1)
+ | "out" => zoom_by(-1)
+ | _ => puts("usage: zoom in | zoom out\n")
end
-fn font_basename(path) do
+fn font_basename(path) -> string do
let start = 0
let index = 0
- while index < path:len() do
- if path[index] == "/" do start = index + 1 end
+
+ while (index < path:len()) do
+ if path[index] == "/" do
+ start = index + 1
+ end
+
index += 1
end
+
let name = path[start..]
let dot = -1
- for i in 0..name:len() do if name[i] == "." do dot = i end end
- if dot > 0 do name = name[0..dot] end
+
+ for i in 0..name:len() do
+ if (name[i] == ".")
+ dot = i
+ end
+
+ if (dot > 0)
+ name = name[0..dot]
+
return name
end
-fn load_psf(path) do
+fn load_psf(path) -> { glyphs: table, width: num, height: num } | :nil do
const size = fs:size(path)
- if size < 4 return :nil
+
+ if size < 4 do
+ return :nil
+ end
+
const data = fs:read_bytes(path, 0, size)
- if (data[0]:ascii() != 0x36 or data[1]:ascii() != 0x04) do return :nil end
+
+ if data[0]:ascii() != 0x36 or data[1]:ascii() != 0x04 do
+ return :nil
+ end
+
const height = data[3]:ascii()
- if (height == 0 or size < 4 + 128 * height) do return :nil end
+
+ if height == 0 or size < 4 + 128 * height do
+ return :nil
+ end
+
let glyphs = {}
+
for code in 32..128 do
const base = 4 + code * height
let rows = {}
- for row in 0..height do rows:push(reverse8(data[base + row]:ascii())) end
+
+ for row in 0..height do
+ rows:push(reverse8(data[base + row]:ascii()))
+ end
+
glyphs:push(rows)
end
+
return { glyphs = glyphs, width = 8, height = height }
end
-fn has_slash(text) do
- for index in 0..text:len() do if text[index] == "/" return :true end
+fn has_slash(text) -> bool do
+ for index in 0..text:len() do
+ if (text[index] == "/")
+ return :true
+ end
+
return :false
end
-fn ends_with(text, suffix) do
- if text:len() < suffix:len() return :false
+fn ends_with(text, suffix) -> bool do
+ if text:len() < suffix:len() do
+ return :false
+ end
+
return text[(text:len() - suffix:len())..] == suffix
end
-fn str_to_int(text) do
+fn str_to_int(text) -> num do
let value = 0
+
for index in 0..text:len() do
const code = text[index]:ascii()
- if (code >= 48 and code <= 57) do value = value * 10 + (code - 48) end
+
+ if code >= 48 and code <= 57 do
+ value = value * 10 + (code - 48)
+ end
end
+
return value
end
-fn font_file(name) do
- if has_slash(name) do
- const p = expand_path(name)
- if (ends_with(p, ".ttf") and fs:size(p) >= 0) return { path = p, kind = :ttf }
- if (ends_with(p, ".psf") and fs:size(p) >= 0) return { path = p, kind = :psf }
- if fs:size(p ~ ".ttf") >= 0 return { path = p ~ ".ttf", kind = :ttf }
- if fs:size(p ~ ".psf") >= 0 return { path = p ~ ".psf", kind = :psf }
- return { path = p, kind = :none }
- end
- if fs:size("/usr/share/#{name}.psf") >= 0 return { path = "/usr/share/#{name}.psf", kind = :psf }
- if fs:size("/usr/share/#{name}.ttf") >= 0 return { path = "/usr/share/#{name}.ttf", kind = :ttf }
- return { path = name, kind = :none }
+#* resolve a font name to a path and a kind *#
+fn font_file(name) -> { path: string, kind: ( :ttf | :psf | :none ) } do
+ return match
+ | has_slash(name) => do
+ const p = expand_path(name)
+
+ if ends_with(p, ".ttf") and fs:size(p) >= 0 do
+ return { path = p, kind = :ttf }
+ end
+
+ if ends_with(p, ".psf") and fs:size(p) >= 0 do
+ return { path = p, kind = :psf }
+ end
+
+ if fs:size(p ~ ".ttf") >= 0 do
+ return { path = p ~ ".ttf", kind = :ttf }
+ end
+
+ if fs:size(p ~ ".psf") >= 0 do
+ return { path = p ~ ".psf", kind = :psf }
+ end
+
+ return { path = p, kind = :none }
+ end
+ | fs:size("/usr/share/#{name}.psf") >= 0 => { path = "/usr/share/#{name}.psf", kind = :psf }
+ | fs:size("/usr/share/#{name}.ttf") >= 0 => { path = "/usr/share/#{name}.ttf", kind = :ttf }
+ | _ => { path = name, kind = :none }
end
fn cmd_font(context) do
if context.argv:len() < 2 do
puts("fonts:")
- for index in 0..fonts:len() do puts(" #{fonts[index].name}(#{fonts[index].width}x#{fonts[index].height})") end
+
+ for index in 0..fonts:len() do
+ puts(" #{fonts[index].name}(#{fonts[index].width}x#{fonts[index].height})")
+ end
+
puts("\n")
const font_files = fs:entries("/usr/share")
if font_files != :nil do
puts("on disk:")
- for index in 0..font_files:len() do puts(" #{font_files[index].name}") end
+
+ for index in 0..font_files:len() do
+ puts(" #{font_files[index].name}")
+ end
+
puts("\n")
end
+
return :nil
end
+
# `font load <path> [size]` still works, but a bare name/path is the same thing
let name = context.argv[1]
let arg_at = 1
+
if name == "load" do
- if context.argv:len() < 3 do puts("usage: font load <path> [size]\n") return :nil end
+ if context.argv:len() < 3 do
+ puts("usage: font load <path> [size]\n")
+ return :nil
+ end
+
name = context.argv[2]
arg_at = 2
end
+
# an explicit size (ttf only) forces a fresh rasterize. without one we just
# switch to whatever is already in the registry under that name
let size = 0
- if context.argv:len() > arg_at + 1 do size = str_to_int(context.argv[arg_at + 1]) end
+
+ if context.argv:len() > arg_at + 1 do
+ size = str_to_int(context.argv[arg_at + 1])
+ end
+
if size == 0 do
const existing = find_font(name)
- if existing != :nil do console:set_font(existing) return :nil end
+
+ if existing != :nil do
+ console:set_font(existing)
+ return :nil
+ end
end
+
const info = font_file(name)
- if info.kind == :none do puts("font: #{name}: no such font\n") return :nil end
+
+ if info.kind == :none do
+ puts("font: #{name}: no such font\n")
+ return :nil
+ end
+
const reg_name = font_basename(info.path)
+
if info.kind == :psf do
const prior = find_font(reg_name)
- if prior != :nil do console:set_font(prior) return :nil end
+
+ if prior != :nil do
+ console:set_font(prior)
+ return :nil
+ end
+
const parsed = load_psf(info.path)
- if parsed == :nil do puts("font: #{name}: not a psf font\n") return :nil end
+
+ if parsed == :nil do
+ puts("font: #{name}: not a psf font\n")
+ return :nil
+ end
+
add_font(reg_name, parsed.glyphs, parsed.width, parsed.height, :bitmap, :nil, 0)
console:set_font(find_font(reg_name))
+
return :nil
end
+
# ttf: rasterize ascii at the requested size (default 18)
- if size == 0 do size = 18 end
+ if size == 0 do
+ size = 18
+ end
+
const face = load_ttf(info.path, size)
- if face == :nil do puts("font: #{name}: bad ttf\n") return :nil end
+
+ if face == :nil do
+ puts("font: #{name}: bad ttf\n")
+ return :nil
+ end
+
add_font(reg_name, face.glyphs, face.width, face.height, :ttf, info.path, size)
console:set_font(find_font(reg_name))
end
-fn bcd(byte) (byte shr 4) * 10 + (byte band 15)
-fn rtc(cmos_reg) do
+fn bcd(byte) -> num do
+ return (byte shr 4) * 10 + (byte band 15)
+end
+
+fn rtc(cmos_reg) -> num do
outb(0x70, cmos_reg)
- inb(0x71)
+ return inb(0x71)
end
-fn cmd_time(context) puts("utc #{bcd(rtc(4))}:#{bcd(rtc(2))}:#{bcd(rtc(0))}\n")
-# 0x prefixed 16 digit hex
-fn hex(value) do
+fn cmd_time(context) do
+ const hour = 4 |> rtc |> bcd
+ const min = 2 |> rtc |> bcd
+ const sec = 0 |> rtc |> bcd
+
+ puts("utc #{hour}:#{min}:#{sec}\n")
+end
+
+#* 0x prefixed 16 digit hex *#
+fn hex(value) -> string do
const digits = "0123456789abcdef"
let result = ""
- for index in 0..16 do result = result ~ digits[(value shr ((15 - index) * 4)) band 15] end
- "0x#{result}"
+
+ for index in 0..16 do
+ result = result ~ digits[(value shr ((15 - index) * 4)) band 15]
+ end
+
+ return "0x#{result}"
end
-fn mem_type_name(kind) do
- if kind == 0 return "usable"
- if kind == 1 return "reserved"
- if kind == 2 return "acpi-reclaimable"
- if kind == 3 return "acpi-nvs"
- if kind == 4 return "bad"
- if kind == 5 return "bootloader-reclaimable"
- if kind == 6 return "kernel+modules"
- if kind == 7 return "framebuffer"
- "unknown"
+fn mem_type_name(kind) -> string do
+ match kind
+ | 0 => "usable"
+ | 1 => "reserved"
+ | 2 => "acpi-reclaimable"
+ | 3 => "acpi-nvs"
+ | 4 => "bad"
+ | 5 => "bootloader-reclaimable"
+ | 6 => "kernel+modules"
+ | 7 => "framebuffer"
+ | _ => "unknown"
end
-# the boot memory map dump
+#* the boot memory map dump *#
fn dump_memmap() do
puts("memory map:\n")
+
for index in 0..memmap_count() do
puts(" #{hex(memmap_base(index))} len=#{hex(memmap_len(index))} type=#{mem_type_name(memmap_kind(index))}\n")
end
+
puts("usable: #{(mem_total() shr 20)} MiB, free: #{(mem_free() shr 20)} MiB\n")
end
-fn cmd_mem(context) puts("#{(mem_free() shr 20)} MiB free / #{(mem_total() shr 20)} MiB usable\n")
-fn cmd_uptime(context) puts("#{math.floor(uptime() / 100)}s (#{uptime()} ticks)\n")
-fn cmd_ps(context) puts("#{proc_count()} process(es) running\n")
-fn cmd_echo(context) do if context.line:len() > 5 puts("#{context.line[5..]}\n") else puts("\n") end
-fn cmd_mkfs(context) fs:format()
+fn cmd_mem(context)
+ puts("#{(mem_free() shr 20)} MiB free / #{(mem_total() shr 20)} MiB usable\n")
+
+fn cmd_uptime(context)
+ puts("#{math.floor(uptime() / 100)}s (#{uptime()} ticks)\n")
+
+fn cmd_ps(context)
+ puts("#{proc_count()} process(es) running\n")
-fn exec_file(target) do
+fn cmd_echo(context) do
+ if context.line:len() > 5 do
+ puts("#{context.line[5..]}\n")
+ end else do
+ puts("\n")
+ end
+end
+
+fn cmd_mkfs(context)
+ fs:format()
+
+#* resolve a command name to a path and a kind *#
+fn exec_file(target) -> { path: string, kind: ( :elf | :script | :none ) } do
let candidates = {}
+
if has_slash(target) do
candidates:push(expand_path(target))
candidates:push(expand_path(target) ~ ".rv")
- end
- else do
+ end else do
candidates:push(expand_path(target))
candidates:push(expand_path(target) ~ ".rv")
candidates:push("/usr/bin/#{target}")
candidates:push("/bin/#{target}.rv")
end
+
for index in 0..candidates:len() do
const path = candidates[index]
+
if fs:size(path) >= 0 do
const head = fs:read_bytes(path, 0, 4)
- if (head:len() >= 4 and head[0]:ascii() == 127 and head[1]:ascii() == 69 and head[2]:ascii() == 76 and head[3]:ascii() == 70) return { path = path, kind = :elf }
+
+ if head:len() >= 4
+ and head[0]:ascii() == 127
+ and head[1]:ascii() == 69
+ and head[2]:ascii() == 76
+ and head[3]:ascii() == 70 do
+ return { path = path, kind = :elf }
+ end
+
return { path = path, kind = :script }
end
end
+
return { path = target, kind = :none }
end
fn cmd_run(context) do
- if context.argv:len() <= 1 do list_progs() return :nil end
+ if context.argv:len() <= 1 do
+ list_progs()
+ return :nil
+ end
+
const target = context.argv[1]
const info = exec_file(target)
- if info.kind == :none do fail("run: #{target}: not found\n") return :nil end
+
+ if info.kind == :none do
+ fail("run: #{target}: not found\n")
+ return :nil
+ end
+
const sub_argv = context.rest:split(" ")
const sub_ctx = make_ctx(context.rest, sub_argv)
+
if info.kind == :script do
run_script_path(info.path, target, sub_ctx)
+
return :nil
end
- if load_file_elf(info.path, sub_ctx.rest) == :true return :hold
+
+ if load_file_elf(info.path, sub_ctx.rest) == :true do
+ return :hold
+ end
+
fail("run: #{target}: not executable\n")
+
return :nil
end
-fn run(line) do
+#* run one shell line *#
+fn run(line) -> :nil do
# the fact that the shell needs to interface directly with ed isnt, very nice
# this is a TODO btw
- if gils_ed_active() do gils_ed_feed(line) return :nil end
- if line:trim():len() == 0 do puts(prompt()) return :nil end
+ if gils_ed_active() do
+ gils_ed_feed(line)
+ return :nil
+ end
+
+ if line:trim():len() == 0 do
+ puts(prompt())
+ return :nil
+ end
+
const argv = line:split(" ")
const context = make_ctx(line, argv)
shell_status.code = 0
const command = lookup(argv[0])
+
if command != :nil do
# :hold means the command took over (modal editor / launched a program) so
# we dont draw a fresh prompt, otherwise we do
const result = command.handler(context)
- if result != :hold do puts(prompt()) end
+
+ if result != :hold do
+ puts(prompt())
+ end
+
+ return :nil
+ end
+
+ if run_script(argv[0], context) == :true do
+ puts(prompt())
+ return :nil
+ end
+
+ if run_prog(argv[0], context.rest) == :true do
return :nil
end
- if run_script(argv[0], context) == :true do puts(prompt()) return :nil end
- if run_prog(argv[0], context.rest) == :true return :nil
shell_status.code = 127
puts("#{argv[0]}: command not found\n")
puts(prompt())
diff --git a/kernel/tty.rv b/kernel/tty.rv
index 5fcd224..680289f 100644
--- a/kernel/tty.rv
+++ b/kernel/tty.rv
@@ -137,7 +137,8 @@ global tty = {
if byte == 27 do self.esc = 1 return :nil end # ESC, start a sequence
if byte == 10 do self:submit() return :nil end # enter
- if byte == 8 or byte == 127 do self:backspace() return :nil end # bs / del
+ if byte == 8 do self:backspace() return :nil end # bs
+ if byte == 127 do self:erase_word() return :nil end # del (ctrl-bs): erase word
if byte == 1 do self:home() return :nil end # ctrl-a
if byte == 5 do self:to_end() return :nil end # ctrl-e
if byte == 3 do self:cancel() return :nil end # ctrl-c