hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--boot/boot.zig21
-rw-r--r--boot/idt.zig2
-rw-r--r--boot/regs.zig2
-rw-r--r--boot/sched.zig2
-rw-r--r--bridge/bridge.zig35
-rw-r--r--bridge/hosts.zig83
-rw-r--r--bridge/serial.zig4
-rw-r--r--build.zig30
-rw-r--r--gils/cat.rv10
-rw-r--r--gils/cp.rv15
-rw-r--r--gils/ed.rv190
-rw-r--r--gils/gils.rv50
-rw-r--r--gils/head.rv27
-rw-r--r--gils/ls.rv10
-rw-r--r--gils/mkdir.rv5
-rw-r--r--gils/mv.rv14
-rw-r--r--gils/rm.rv6
-rw-r--r--gils/tail.rv29
-rw-r--r--gils/touch.rv9
-rw-r--r--gils/tree.rv35
-rw-r--r--gils/wc.rv22
-rw-r--r--kernel/console.rv111
-rw-r--r--kernel/fs.rv393
-rw-r--r--kernel/input.rv34
-rw-r--r--kernel/main.rv2
-rw-r--r--kernel/proc.rv75
-rw-r--r--kernel/seed.rv13
-rw-r--r--kernel/shell.rv277
-rw-r--r--kernel/vfs.rv38
-rw-r--r--kernel/vmm.rv14
m---------revo0
32 files changed, 1040 insertions, 526 deletions
diff --git a/Makefile b/Makefile
index 57a6554..5b44c0b 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,10 @@
# make kernel : just compile the kernel ELF
# make clean : remove build output (kernel, ISO, caches)
# make distclean : also remove Limine's built host tool
+#
+# WARNING: DO NOT FUCK W/ THE \ DELIMETERS THEY ARE MADE TO BE PRETTY
+# ON OUTPUT, NOT PRETTY IN CODE. IF YOU DONT LIKE IT, GO
+# SHOKE ON A TIT
ZIG ?= zig
QEMU ?= qemu-system-x86_64
@@ -22,9 +26,7 @@ kernel:
limine/limine:
$(MAKE) -C limine
-# WARNING: DO NOT FUCK W/ THE \ DELIMETERS THEY ARE MADE TO BE PRETTY
-# ON OUTPUT, NOT PRETTY IN CODE. IF YOU DONT LIKE IT, GO
-# SHOKE ON A TIT
+
iso: kernel limine/limine
rm -rf iso_root
mkdir -p iso_root/boot/limine iso_root/EFI/BOOT
diff --git a/boot/boot.zig b/boot/boot.zig
index fff8a6b..4918881 100644
--- a/boot/boot.zig
+++ b/boot/boot.zig
@@ -60,6 +60,9 @@ fn kPhysToVirt(p: u64) u64 {
fn kAllocFrame() u64 {
return pmm.alloc() orelse 0;
}
+fn kAllocContig(n: u64) u64 {
+ return pmm.allocContig(@intCast(n)) orelse 0;
+}
fn kCreateAddrspace() u64 {
return vmm.createAddressSpace() orelse 0;
}
@@ -91,7 +94,7 @@ fn kMemmapLen(i: u64) u64 {
}
fn kMemmapKind(i: u64) u64 {
- return if (memmapEntry(i)) |e| @intFromEnum(e.type) else 0;
+ return if (memmapEntry(i)) |e| @backingInt(e.type) else 0;
}
fn kMemFree() u64 {
@@ -161,8 +164,23 @@ fn kTaskState(i: u64) u64 {
return sched.taskState(i);
}
+// limine doesnt fully enable SSE the only reason i need this
+// is because Zig 0.17 bullshit
+inline fn enableSse() void {
+ asm volatile (
+ \\ mov %%cr0, %%rax
+ \\ and $0xfffb, %%ax
+ \\ or $0x2, %%ax
+ \\ mov %%rax, %%cr0
+ \\ mov %%cr4, %%rax
+ \\ or $0x600, %%rax
+ \\ mov %%rax, %%cr4
+ ::: .{ .rax = true, .memory = true });
+}
+
// entry(_start)
export fn _start() callconv(.c) noreturn {
+ enableSse();
serial.init();
serial.write("if you see this, it means revel didn't shit the bed\r\n");
@@ -217,6 +235,7 @@ export fn _start() callconv(.c) noreturn {
.disk_sectors = &kDiskSectors,
.disk_read = &kDiskRead,
.disk_write = &kDiskWrite,
+ .alloc_contig = &kAllocContig,
};
// hand the kernel heap + framebuffer + low level ops to revo and bring up the vm
diff --git a/boot/idt.zig b/boot/idt.zig
index 0ffd116..15e6588 100644
--- a/boot/idt.zig
+++ b/boot/idt.zig
@@ -29,7 +29,7 @@ const Idtr = packed struct {
base: u64,
};
-var idt = [_]Gate{@bitCast(@as(u128, 0))} ** 256;
+var idt: [256]Gate = @splat(@bitCast(@as(u128, 0)));
var idtr: Idtr = undefined;
const names = [_][]const u8{
diff --git a/boot/regs.zig b/boot/regs.zig
index 7b2bb88..3a30860 100644
--- a/boot/regs.zig
+++ b/boot/regs.zig
@@ -24,7 +24,7 @@ pub const Frame = extern struct {
ss: u64,
};
-// push/pop the 15 gprs in frames exact order
+// push/pop the 15 gprs in frames exact order
// (rax pushed first -> highest field, r15 last -> at rsp = &frame)
pub const PUSH_GPRS =
\\push %rax
diff --git a/boot/sched.zig b/boot/sched.zig
index 1842309..383b40d 100644
--- a/boot/sched.zig
+++ b/boot/sched.zig
@@ -84,7 +84,7 @@ pub fn taskMax() u64 {
}
pub fn taskState(i: u64) u64 {
if (i >= MAX_TASKS) return 0;
- return @intFromEnum(tasks[i].state);
+ return @backingInt(tasks[i].state);
}
var proc_exited_flag: bool = false;
diff --git a/bridge/bridge.zig b/bridge/bridge.zig
index 71f7023..87548eb 100644
--- a/bridge/bridge.zig
+++ b/bridge/bridge.zig
@@ -15,16 +15,25 @@ pub const KernelOps = hosts.KernelOps;
// the vm outlives boot() now, so we juts make this hoe static
var g_vm: ?*revo.VM = null;
-const init_program = @embedFile("k_font") ++ "\n" ++
- @embedFile("k_console") ++ "\n" ++
- @embedFile("k_vmm") ++ "\n" ++
- @embedFile("k_fs") ++ "\n" ++
- @embedFile("k_vfs") ++ "\n" ++
- @embedFile("k_seed") ++ "\n" ++
- @embedFile("k_proc") ++ "\n" ++
- @embedFile("k_shell") ++ "\n" ++
- @embedFile("k_input") ++ "\n" ++
- @embedFile("k_main");
+// NOTE: ORDER MATTERS
+const init_program = blk: {
+ var s: []const u8 = @embedFile("k_font") ++ "\n" ++
+ @embedFile("k_console") ++ "\n" ++
+ @embedFile("k_vmm") ++ "\n" ++
+ @embedFile("k_fs") ++ "\n" ++
+ @embedFile("k_vfs") ++ "\n" ++
+ @embedFile("k_seed") ++ "\n";
+
+ // gils shit
+ const gils_embedded = .{ "gils", "ed" };
+ for (gils_embedded) |g| s = s ++ @embedFile("k_gils_" ++ g) ++ "\n";
+
+ s = s ++ @embedFile("k_proc") ++ "\n" ++
+ @embedFile("k_shell") ++ "\n" ++
+ @embedFile("k_input") ++ "\n" ++
+ @embedFile("k_main");
+ break :blk s;
+};
pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void {
serial.setSink(out);
@@ -50,7 +59,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void
};
out("bridge: revo VM up\r\n");
- hosts.registerPrimitives(vm) catch {
+ hosts.passShitToRevo(vm) catch {
out("bridge: registering primitives failed\r\n");
return;
};
@@ -64,7 +73,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void
.err => |failure| {
out("bridge: compile error: ");
var sw = serial.SinkWriter.init();
- revo.lang.renderError(alloc, &sw.interface, .{ .name = "kernel", .text = init_program }, failure) catch {};
+ revo.lang.renderError(alloc, &sw.interface, .{ .name = "kernel", .text = init_program }, failure, .{}) catch {};
out("\r\n");
return;
},
@@ -86,7 +95,7 @@ pub fn boot(alloc: std.mem.Allocator, out: Sink, fb: ?Fb, ops: ?KernelOps) void
.err => |failure| {
out("\r\nbridge: runtime error: ");
var sw = serial.SinkWriter.init();
- failure.render(alloc, &sw.interface, init_program) catch {};
+ failure.render(alloc, &sw.interface, init_program, false) catch {};
out("\r\n");
},
}
diff --git a/bridge/hosts.zig b/bridge/hosts.zig
index e329316..51362dc 100644
--- a/bridge/hosts.zig
+++ b/bridge/hosts.zig
@@ -8,6 +8,22 @@ const Data = convert.Data;
const argInt = convert.argInt;
const emit = serial.emit;
+// gils shit
+const Script = struct { name: []const u8, src: []const u8 };
+const scripts = [_]Script{
+ .{ .name = "ls", .src = @embedFile("k_gils_ls") },
+ .{ .name = "tree", .src = @embedFile("k_gils_tree") },
+ .{ .name = "cat", .src = @embedFile("k_gils_cat") },
+ .{ .name = "rm", .src = @embedFile("k_gils_rm") },
+ .{ .name = "mkdir", .src = @embedFile("k_gils_mkdir") },
+ .{ .name = "touch", .src = @embedFile("k_gils_touch") },
+ .{ .name = "cp", .src = @embedFile("k_gils_cp") },
+ .{ .name = "mv", .src = @embedFile("k_gils_mv") },
+ .{ .name = "wc", .src = @embedFile("k_gils_wc") },
+ .{ .name = "head", .src = @embedFile("k_gils_head") },
+ .{ .name = "tail", .src = @embedFile("k_gils_tail") },
+};
+
// the actual framebuffer, the data layout feels pretty self explanatory
pub const Fb = struct {
ptr: [*]u8,
@@ -50,6 +66,7 @@ pub const KernelOps = struct {
disk_sectors: *const fn () u64,
disk_read: *const fn (u64, u64) u64, // lba, phys buf -> 1 ok / 0 fail
disk_write: *const fn (u64, u64) u64, // lba, phys buf -> 1 ok / 0 fail
+ alloc_contig: *const fn (u64) u64, // n contiguous pages -> phys base, 0 on fail
};
var g_kops: ?KernelOps = null;
@@ -337,7 +354,63 @@ fn hostDiskWrite(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
}
// pass shit to revo
-pub fn registerPrimitives(vm: *revo.VM) !void {
+fn hostAllocContig(args: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ const n = argInt(u64, args, 0) orelse return HostResult.other("alloc_contig: bad count");
+ return HostResult.data(Data.new.num(kops().alloc_contig(n)));
+}
+
+// pass shit to revo
+fn hostBlitStr(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
+ const phys = argInt(u64, args, 0) orelse return HostResult.other("blit_str: bad addr");
+ const sid = args[1].asString() orelse return HostResult.other("blit_str: not a string");
+ const bytes = vm.stringValue(sid);
+ const dst: [*]u8 = @ptrFromInt(kops().phys_to_virt(phys));
+ @memcpy(dst[0..bytes.len], bytes);
+ return HostResult.data(Data.new.num(bytes.len));
+}
+
+// pass shit to revo
+fn hostPhysToStr(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
+ const phys = argInt(u64, args, 0) orelse return HostResult.other("phys_to_str: bad addr");
+ const len = argInt(usize, args, 1) orelse return HostResult.other("phys_to_str: bad len");
+ const src: [*]const u8 = @ptrFromInt(kops().phys_to_virt(phys));
+ return HostResult.data(try vm.ownValueString(src[0..len]));
+}
+
+// pass shit to revo
+fn hostScriptCount(_: []const revo.Value, _: *revo.VM) anyerror!HostResult {
+ return HostResult.data(Data.new.num(scripts.len));
+}
+
+// pass shit to revo
+fn hostScriptName(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
+ const i = argInt(usize, args, 0) orelse return HostResult.other("script_name: bad index");
+ if (i >= scripts.len) return HostResult.other("script_name: out of range");
+ return HostResult.data(try vm.ownValueString(scripts[i].name));
+}
+
+// pass shit to revo
+fn hostScriptSrc(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
+ const i = argInt(usize, args, 0) orelse return HostResult.other("script_src: bad index");
+ if (i >= scripts.len) return HostResult.other("script_src: out of range");
+ return HostResult.data(try vm.ownValueString(scripts[i].src));
+}
+
+// pass shit to revo
+fn hostEvalScript(args: []const revo.Value, vm: *revo.VM) anyerror!HostResult {
+ const sid = args[0].asString() orelse return HostResult.other("eval_script: not a string");
+ const src = vm.stringValue(sid);
+ const res = revo.run.runModule(vm, "<script>", src, false) catch |e| {
+ return HostResult.data(try vm.ownValueString(@errorName(e)));
+ };
+ return switch (res) {
+ .ok => HostResult.data(Data.new.nil()),
+ .err => HostResult.data(try vm.ownValueString("script runtime error")),
+ };
+}
+
+// pass shit to revo
+pub fn passShitToRevo(vm: *revo.VM) !void {
const define = revo.baselib.host.define;
const T = revo.baselib.host.ParamType;
try vm.registerGlobal("outb", try vm.installHost("outb", define(&[_]T{ .number, .number }, hostOutb)));
@@ -377,4 +450,12 @@ pub fn registerPrimitives(vm: *revo.VM) !void {
try vm.registerGlobal("disk_sectors", try vm.installHost("disk_sectors", define(&[_]T{}, hostDiskSectors)));
try vm.registerGlobal("disk_read", try vm.installHost("disk_read", define(&[_]T{ .number, .number }, hostDiskRead)));
try vm.registerGlobal("disk_write", try vm.installHost("disk_write", define(&[_]T{ .number, .number }, hostDiskWrite)));
+
+ try vm.registerGlobal("eval_script", try vm.installHost("eval_script", define(&[_]T{.string}, hostEvalScript)));
+ try vm.registerGlobal("script_count", try vm.installHost("script_count", define(&[_]T{}, hostScriptCount)));
+ try vm.registerGlobal("script_name", try vm.installHost("script_name", define(&[_]T{.number}, hostScriptName)));
+ try vm.registerGlobal("script_src", try vm.installHost("script_src", define(&[_]T{.number}, hostScriptSrc)));
+ try vm.registerGlobal("alloc_contig", try vm.installHost("alloc_contig", define(&[_]T{.number}, hostAllocContig)));
+ try vm.registerGlobal("blit_str", try vm.installHost("blit_str", define(&[_]T{ .number, .string }, hostBlitStr)));
+ try vm.registerGlobal("phys_to_str", try vm.installHost("phys_to_str", define(&[_]T{ .number, .number }, hostPhysToStr)));
}
diff --git a/bridge/serial.zig b/bridge/serial.zig
index ec0bdb9..f765efe 100644
--- a/bridge/serial.zig
+++ b/bridge/serial.zig
@@ -43,6 +43,10 @@ fn ioOperate(_: ?*anyopaque, operation: std.Io.Operation) std.Io.Cancelable!std.
.file_read_streaming => .{ .file_read_streaming = error.InputOutput },
.device_io_control => .{ .device_io_control = -1 },
.net_receive => .{ .net_receive = .{ error.NetworkDown, 0 } },
+ // no networking on bare metal (yet)
+ .net_send => .{ .net_send = .{ null, 0 } },
+ .net_read => .{ .net_read = error.Unexpected },
+ .net_write => .{ .net_write = error.Unexpected },
};
}
diff --git a/build.zig b/build.zig
index 3575cf0..d55961f 100644
--- a/build.zig
+++ b/build.zig
@@ -1,16 +1,17 @@
const std = @import("std");
+// the gils coreutils
+const gils_tools = .{ "gils", "ls", "tree", "cat", "rm", "mkdir", "touch", "cp", "mv", "wc", "head", "tail", "ed" };
+
pub fn build(b: *std.Build) void {
- // Default to ReleaseSafe because Debug pulls Zig's
- // UBSan runtime and other sanitization, which needs
- // f128/SSE ops, which i don't provide.
- const optimize = b.option(std.builtin.OptimizeMode, "optimize", "optimize mode") orelse .ReleaseSafe;
+ // Default to ReleaseSafe because Debug pulls Zig's UBSan runtime and other
+ // sanitization, which needs f128/SSE ops, which i don't provide.
+ const optimize = b.option(std.builtin.OptimizeMode, "optimize", "optimize mode") orelse .safe;
- // The kernel must NOT touch SSE/MMX/AVX until it has
- // enabled them itself, so I strip those features and
- // let the compiler use a soft-float ABI
- // `code_model = .kernel` keeps relocations valid for
- // the higher-half load address in boot/linker.ld.
+ // The kernel must NOT touch SSE/MMX/AVX until it has enabled them itself, so
+ // I strip those features and let the compiler use a soft-float ABI
+ // `code_model = .kernel` keeps relocations valid for the higher-half load
+ // address in boot/linker.ld.
//
// In other words. Going raw baby
var query: std.Target.Query = .{
@@ -18,13 +19,11 @@ pub fn build(b: *std.Build) void {
.os_tag = .freestanding,
.abi = .none,
};
+
const Feature = std.Target.x86.Feature;
query.cpu_features_sub.addFeature(@intFromEnum(Feature.mmx));
- query.cpu_features_sub.addFeature(@intFromEnum(Feature.sse));
- query.cpu_features_sub.addFeature(@intFromEnum(Feature.sse2));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.avx));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.avx2));
- query.cpu_features_add.addFeature(@intFromEnum(Feature.soft_float));
const target = b.resolveTargetQuery(query);
@@ -78,10 +77,13 @@ pub fn build(b: *std.Build) void {
bridge_mod.addAnonymousImport(e[0], .{ .root_source_file = b.path(e[1]) });
}
+ // load gils
+ inline for (gils_tools) |g| {
+ bridge_mod.addAnonymousImport("k_gils_" ++ g, .{ .root_source_file = b.path("gils/" ++ g ++ ".rv") });
+ }
+
kernel.root_module.addImport("bridge", bridge_mod);
- // each ring 3 program compiles to its own freestanding elf, embedded under
- // user_<name>. boot copies them to frames and the shells `run <name>` loads one
inline for (.{ "hexview", "calc", "primes", "save" }) |prog| {
const cc = b.addSystemCommand(&.{
"zig", "cc",
diff --git a/gils/cat.rv b/gils/cat.rv
new file mode 100644
index 0000000..1cca54b
--- /dev/null
+++ b/gils/cat.rv
@@ -0,0 +1,10 @@
+# meow
+
+const context = env.ctx
+const parsed = arg_name(context.rest, 0)
+if parsed.name:len() == 0 do puts("usage: cat <path>\n") end
+else do
+ const path = abspath(parsed.name)
+ if vfs_owns(path) do vfs_cat(path) end
+ else do fs:cat(path) end
+end
diff --git a/gils/cp.rv b/gils/cp.rv
new file mode 100644
index 0000000..5988e54
--- /dev/null
+++ b/gils/cp.rv
@@ -0,0 +1,15 @@
+# copy a file not cyberpunk
+# HAAAAAANNNNKKKK
+
+const context = env.ctx
+if context.argv:len() < 3 do puts("usage: cp <source> <destination>\n") end
+else do
+ const source = abspath(context.argv[1])
+ const destination = abspath(context.argv[2])
+ const size = fs:size(source)
+ if size < 0 do puts("cp: #{source}: no such file\n") end
+ else do
+ const contents = if size > 0 fs:read_bytes(source, 0, size) else ""
+ if fs:store(destination, contents) == 1 do puts("#{source} -> #{destination}\n") end
+ end
+end
diff --git a/gils/ed.rv b/gils/ed.rv
new file mode 100644
index 0000000..00ff50a
--- /dev/null
+++ b/gils/ed.rv
@@ -0,0 +1,190 @@
+# simple ass modal ass line ass editor
+
+fn list_insert(items, index, value) do
+ let result = {}
+ for position in 0..index do result:push(items[position]) end
+ result:push(value)
+ for position in index..items:len() do result:push(items[position]) end
+ return result
+end
+
+fn list_delete(items, low, high) do
+ let result = {}
+ for position in 0..items:len() do if (position < low or position > high) result:push(items[position]) end
+ return result
+end
+
+fn ed_is_digit(character) do
+ const code = character:ascii()
+ return code >= 48 and code <= 57
+end
+
+# parse one address at index
+fn ed_addr(state, line, index) do
+ if index >= line:len() return { value = -1, next = index }
+ if line[index] == "." return { value = state.dot, next = index + 1 }
+ if line[index] == "$" return { value = state.lines:len() - 1, next = index + 1 }
+ if ed_is_digit(line[index]) do
+ let number = 0
+ let scan = index
+ while (scan < line:len() and ed_is_digit(line[scan])) do number = number * 10 + (line[scan]:ascii() - 48) scan += 1 end
+ return { value = number - 1, next = scan }
+ end
+ return { value = -1, next = index }
+end
+
+# split a command line into low, high and command
+fn ed_parse(state, line) do
+ let index = 0
+ let low = -1
+ let high = -1
+ if (line:len() > 0 and (line[0] == "," or line[0] == "%")) do
+ low = 0
+ high = state.lines:len() - 1
+ index = 1
+ end
+ else do
+ const first_addr = ed_addr(state, line, 0)
+ if first_addr.value >= 0 do low = first_addr.value index = first_addr.next end
+ if (index < line:len() and line[index] == ",") do
+ index += 1
+ const second_addr = ed_addr(state, line, index)
+ if second_addr.value >= 0 do high = second_addr.value index = second_addr.next end
+ else do high = state.lines:len() - 1 end
+ end
+ end
+ if low < 0 do low = state.dot end
+ if high < 0 do high = low end
+ const command = if index < line:len() line[index..] else ""
+ return { low = low, high = high, command = command }
+end
+
+fn ed_print(state, low, high, numbered) do
+ let index = low
+ while index <= high do
+ if (index >= 0 and index < state.lines:len()) do
+ if numbered do puts("#{index + 1}\t#{state.lines[index]}\n") end
+ else do puts("#{state.lines[index]}\n") end
+ end
+ index += 1
+ end
+ if (high >= 0 and high < state.lines:len()) do state.dot = high end
+end
+
+fn ed_goto(state, low) do
+ if state.lines:len() == 0 do puts("?\n") return :nil end
+ let target = low
+ if target < 0 do target = 0 end
+ if target >= state.lines:len() do target = state.lines:len() - 1 end
+ state.dot = target
+ puts("#{state.lines[target]}\n")
+end
+
+fn ed_begin_input(state, position) do
+ let clamped = position
+ if clamped < 0 do clamped = 0 end
+ if clamped > state.lines:len() do clamped = state.lines:len() end
+ state.input = :true
+ state.insert_at = clamped
+end
+
+fn ed_delete(state, low, high) do
+ state.lines = list_delete(state.lines, low, high)
+ state.dirty = :true
+ let new_dot = low
+ if new_dot >= state.lines:len() do new_dot = state.lines:len() - 1 end
+ if new_dot < 0 do new_dot = 0 end
+ state.dot = new_dot
+end
+
+fn ed_change(state, low, high) do
+ ed_delete(state, low, high)
+ ed_begin_input(state, low)
+end
+
+fn ed_join(items) do
+ let result = ""
+ for index in 0..items:len() do result = result ~ items[index] ~ "\n" end
+ return result
+end
+
+fn ed_write(state, command) do
+ let path = state.path
+ if command:len() > 1 do
+ const filename = command[1..]:trim()
+ if filename:len() > 0 do path = abspath(filename) end
+ end
+ if path:len() == 0 do puts("?\n") return :nil end
+ const contents = ed_join(state.lines)
+ fs:store(path, contents)
+ state.path = path
+ state.dirty = :false
+ puts("#{contents:len()}\n")
+end
+
+fn ed_quit(state) do
+ state.active = :false
+ puts("% ")
+end
+
+global ed_state = { active = :false, input = :false, path = "", lines = {}, dot = 0, insert_at = 0, dirty = :false }
+
+fn gils_ed(context) do
+ ed_state.active = :true
+ ed_state.input = :false
+ ed_state.dirty = :false
+ ed_state.lines = {}
+ ed_state.path = if context.argv:len() > 1 abspath(context.argv[1]) else ""
+ if ed_state.path:len() > 0 do
+ const size = fs:size(ed_state.path)
+ if size > 0 do
+ const contents = fs:read_bytes(ed_state.path, 0, size)
+ const raw_lines = contents:split("\n")
+ for index in 0..raw_lines:len() do
+ if (index < raw_lines:len() - 1 or raw_lines[index]:len() > 0) do ed_state.lines:push(raw_lines[index]) end
+ end
+ puts("#{size}\n")
+ end
+ else do puts("#{ed_state.path}: new file\n") end
+ end
+ ed_state.dot = if ed_state.lines:len() > 0 ed_state.lines:len() - 1 else 0
+ return :hold
+end
+
+fn gils_ed_feed(line) do
+ const state = ed_state
+ if state.input do
+ if line == "." do state.input = :false end
+ else do
+ state.lines = list_insert(state.lines, state.insert_at, line)
+ state.insert_at += 1
+ state.dot = state.insert_at - 1
+ state.dirty = :true
+ end
+ return :nil
+ end
+ if line:len() == 0 do
+ if state.lines:len() == 0 do puts("?\n") return :nil end
+ if state.dot < state.lines:len() - 1 do state.dot += 1 end
+ puts("#{state.lines[state.dot]}\n")
+ return :nil
+ end
+ const parsed = ed_parse(state, line)
+ const command_char = if parsed.command:len() > 0 parsed.command[0] else ""
+ match command_char
+ | "p" => ed_print(state, parsed.low, parsed.high, :false)
+ | "n" => ed_print(state, parsed.low, parsed.high, :true)
+ | "a" => ed_begin_input(state, parsed.low + 1)
+ | "i" => ed_begin_input(state, parsed.low)
+ | "c" => ed_change(state, parsed.low, parsed.high)
+ | "d" => ed_delete(state, parsed.low, parsed.high)
+ | "w" => ed_write(state, parsed.command)
+ | "q" => ed_quit(state)
+ | "=" => puts("#{state.dot + 1}\n")
+ | "" => ed_goto(state, parsed.low)
+ | _ => puts("?\n")
+end
+
+fn gils_ed_active() ed_state.active
+
+register("ed [path]", "gils", "the standard editor (a i d p w q)", gils_ed)
diff --git a/gils/gils.rv b/gils/gils.rv
new file mode 100644
index 0000000..1a61cc5
--- /dev/null
+++ b/gils/gils.rv
@@ -0,0 +1,50 @@
+# gils: better than Gnu utILS
+global commands = {}
+
+fn register(usage, group, help, handler) do
+ const name = usage:split(" ")[0]
+ commands:push({ name = name, usage = usage, group = group, help = help, handler = handler })
+end
+
+fn lookup(name) do
+ for index in 0..commands:len() do
+ if commands[index].name == name return commands[index]
+ end
+ return :nil
+end
+
+fn make_ctx(line, argv) do
+ const name = argv[0]
+ let rest = ""
+ const cut = name:len() + 1
+ if line:len() > cut do rest = line[cut..] end
+ return { line = line, argv = argv, rest = rest }
+end
+
+# scripts read their args off env.ctx, stashed here before eval
+global env = { ctx = :nil }
+
+# working dir + path resolver, shared by every gils tool and the shell. lives
+# here so its declared before anything that uses it gets concatenated
+global wd = { path = "/" }
+
+global abspath = fn(arg) do
+ if arg:len() == 0 return wd.path
+ let path = arg
+ if arg[0] != "/" do
+ if wd.path == "/" do path = "/#{arg}" end
+ else do path = "#{wd.path}/#{arg}" end
+ end
+ while (path:len() > 1 and path[path:len() - 1] == "/") do path = path[0..(path:len() - 1)] end
+ return path
+end
+
+fn run_script(name, ctx) do
+ const path = "/bin/#{name}.rv"
+ const size = fs:size(path)
+ if size < 0 return :false
+ env.ctx = ctx
+ const error_msg = eval_script(fs:read_bytes(path, 0, size))
+ if error_msg != :nil do puts("#{name}: #{error_msg}\n") end
+ return :true
+end
diff --git a/gils/head.rv b/gils/head.rv
new file mode 100644
index 0000000..edfd096
--- /dev/null
+++ b/gils/head.rv
@@ -0,0 +1,27 @@
+# gimme some head lung
+
+fn file_lines(path) do
+ const size = fs:size(path)
+ if size < 0 return :nil
+ const contents = if size > 0 fs:read_bytes(path, 0, size) else ""
+ const raw_lines = contents:split("\n")
+ let result = {}
+ for index in 0..raw_lines:len() do
+ if (index < raw_lines:len() - 1 or raw_lines[index]:len() > 0) result:push(raw_lines[index])
+ end
+ return result
+end
+
+const context = env.ctx
+if context.argv:len() < 2 do puts("usage: head [n] <path>\n") end
+else do
+ let limit = 10
+ let path_arg = context.argv[1]
+ if (context.argv:len() >= 3 and parse_pid(context.argv[1]) >= 0) do limit = parse_pid(context.argv[1]) path_arg = context.argv[2] end
+ const lines = file_lines(abspath(path_arg))
+ if lines == :nil do puts("head: no such file\n") end
+ else do
+ let index = 0
+ while (index < lines:len() and index < limit) do puts("#{lines[index]}\n") index += 1 end
+ end
+end
diff --git a/gils/ls.rv b/gils/ls.rv
new file mode 100644
index 0000000..f7b9cf7
--- /dev/null
+++ b/gils/ls.rv
@@ -0,0 +1,10 @@
+# list a directory
+
+const context = env.ctx
+const target = if context.argv:len() > 1 abspath(context.argv[1]) else wd.path
+if vfs_owns(target) do vfs_ls(target) end
+else do
+ const count = fs:ls(target)
+ if target == "/" do puts(" proc/\n") end
+ else if count == 0 do puts(" (empty)\n") end
+end
diff --git a/gils/mkdir.rv b/gils/mkdir.rv
new file mode 100644
index 0000000..cb630d1
--- /dev/null
+++ b/gils/mkdir.rv
@@ -0,0 +1,5 @@
+# make a directory
+
+const context = env.ctx
+if context.argv:len() < 2 do puts("usage: mkdir <path>\n") end
+else do fs:mkdir(abspath(context.argv[1])) end
diff --git a/gils/mv.rv b/gils/mv.rv
new file mode 100644
index 0000000..95d9a40
--- /dev/null
+++ b/gils/mv.rv
@@ -0,0 +1,14 @@
+# move or rename a file
+
+const context = env.ctx
+if context.argv:len() < 3 do puts("usage: mv <source> <destination>\n") end
+else do
+ const source = abspath(context.argv[1])
+ const destination = abspath(context.argv[2])
+ const size = fs:size(source)
+ if size < 0 do puts("mv: #{source}: no such file\n") end
+ else do
+ const contents = if size > 0 fs:read_bytes(source, 0, size) else ""
+ if fs:store(destination, contents) == 1 do fs:unlink(source) puts("#{source} -> #{destination}\n") end
+ end
+end
diff --git a/gils/rm.rv b/gils/rm.rv
new file mode 100644
index 0000000..92e21c7
--- /dev/null
+++ b/gils/rm.rv
@@ -0,0 +1,6 @@
+# delete a file or an empty directory
+
+const context = env.ctx
+const parsed = arg_name(context.rest, 0)
+if parsed.name:len() == 0 do puts("usage: rm <path>\n") end
+else do fs:rm(abspath(parsed.name)) end
diff --git a/gils/tail.rv b/gils/tail.rv
new file mode 100644
index 0000000..9cd5647
--- /dev/null
+++ b/gils/tail.rv
@@ -0,0 +1,29 @@
+# my tail is 16 inches long
+# and its between my legs
+
+fn file_lines(path) do
+ const size = fs:size(path)
+ if size < 0 return :nil
+ const contents = if size > 0 fs:read_bytes(path, 0, size) else ""
+ const raw_lines = contents:split("\n")
+ let result = {}
+ for index in 0..raw_lines:len() do
+ if (index < raw_lines:len() - 1 or raw_lines[index]:len() > 0) result:push(raw_lines[index])
+ end
+ return result
+end
+
+const context = env.ctx
+if context.argv:len() < 2 do puts("usage: tail [n] <path>\n") end
+else do
+ let limit = 10
+ let path_arg = context.argv[1]
+ if (context.argv:len() >= 3 and parse_pid(context.argv[1]) >= 0) do limit = parse_pid(context.argv[1]) path_arg = context.argv[2] end
+ const lines = file_lines(abspath(path_arg))
+ if lines == :nil do puts("tail: no such file\n") end
+ else do
+ let start = lines:len() - limit
+ if start < 0 do start = 0 end
+ for index in start..lines:len() do puts("#{lines[index]}\n") end
+ end
+end
diff --git a/gils/touch.rv b/gils/touch.rv
new file mode 100644
index 0000000..2dd0d8c
--- /dev/null
+++ b/gils/touch.rv
@@ -0,0 +1,9 @@
+# dont touch my nono square
+
+const context = env.ctx
+if context.argv:len() < 2 do puts("usage: touch <path>\n") end
+else do
+ const path = abspath(context.argv[1])
+ if fs:is_dir(path) do puts("touch: #{path}: is a directory\n") end
+ else if fs:size(path) < 0 do fs:store(path, "") end
+end
diff --git a/gils/tree.rv b/gils/tree.rv
new file mode 100644
index 0000000..b052adb
--- /dev/null
+++ b/gils/tree.rv
@@ -0,0 +1,35 @@
+# recursive directory listing.
+
+fn tree_walk(path, prefix) do
+ const items = fs:entries(path)
+ if items == :nil return :nil
+ for index in 0..items:len() do
+ const entry = items[index]
+ const last = index == items:len() - 1
+ let branch = "|-- "
+ if last do branch = "`-- " end # font doesnt support fancy ascii symbols
+ const slash = if entry.type == 2 "/" else ""
+ puts("#{prefix}#{branch}#{entry.name}#{slash}\n")
+ if entry.type == 2 do
+ const child = if path == "/" "/#{entry.name}" else "#{path}/#{entry.name}"
+ let indent = "| "
+ if last do indent = " " end
+ tree_walk(child, prefix ~ indent)
+ end
+ end
+end
+
+const context = env.ctx
+const target = if context.argv:len() > 1 abspath(context.argv[1]) else wd.path
+if (vfs_owns(target) and target != "/") do puts("tree: #{target}: virtual dir\n") end
+else if (target != "/" and fs:is_dir(target) == :false) do puts("tree: #{target}: not a directory\n") end
+else do
+ puts("#{target}\n")
+ tree_walk(target, "")
+ # /proc is the one thing not on disk so tack it on at root
+ if target == "/" do
+ puts("`-- proc/\n")
+ puts(" |-- uptime\n")
+ puts(" `-- meminfo\n")
+ end
+end
diff --git a/gils/wc.rv b/gils/wc.rv
new file mode 100644
index 0000000..2f8be3d
--- /dev/null
+++ b/gils/wc.rv
@@ -0,0 +1,22 @@
+# word count
+
+const context = env.ctx
+if context.argv:len() < 2 do puts("usage: wc <path>\n") end
+else do
+ const path = abspath(context.argv[1])
+ const size = fs:size(path)
+ if size < 0 do puts("wc: #{path}: no such file\n") end
+ else do
+ const contents = if size > 0 fs:read_bytes(path, 0, size) else ""
+ let line_count = 0
+ let word_count = 0
+ let in_word = :false
+ for index in 0..contents:len() do
+ const character = contents[index]
+ if character == "\n" do line_count += 1 end
+ if (character == " " or character == "\n" or character == "\t") do in_word = :false end
+ else if in_word == :false do word_count += 1 in_word = :true end
+ end
+ puts(" #{line_count} #{word_count} #{size} #{context.argv[1]}\n")
+ end
+end
diff --git a/kernel/console.rv b/kernel/console.rv
index 65fe03c..7948a74 100644
--- a/kernel/console.rv
+++ b/kernel/console.rv
@@ -1,79 +1,76 @@
-const FW = 8 # glyph width in pixels
-const FH = 16 # glyph height in pixels (8x16 vga bitmap font)
-const FG = 0xd0d0d0
-const CON_BG = 0x101018
-const CUR = 0x50fa7b
+const FONT_WIDTH = 8 # glyph width in pixels
+const FONT_HEIGHT = 16 # glyph height in pixels (8x16 vga bitmap font)
+const FG_COLOR = 0xd0d0d0
+const BG_COLOR = 0x101018
+const CURSOR_COLOR = 0x50fa7b
-global con = {
- col = 0, row = 0, scale = 1, shown = :false, cur_x = 0, cur_y = 0, blink = 0,
+global console = {
+ column = 0, row = 0, scale = 1, shown = :false, cursor_x = 0, cursor_y = 0, blink = 0,
# cell size in pixels at the current zoom
- cell_w = fn(self) FW * self.scale,
- cell_h = fn(self) FH * self.scale,
- cols = fn(self) fb_width() / self:cell_w(),
- rows = fn(self) fb_height() / self:cell_h(),
+ cell_width = fn(self) FONT_WIDTH * self.scale,
+ cell_height = fn(self) FONT_HEIGHT * self.scale,
+ columns = fn(self) fb_width() / self:cell_width(),
+ rows = fn(self) fb_height() / self:cell_height(),
- draw_glyph = fn(self, col, row, ch) do
- const x0 = col * self:cell_w()
- const y0 = row * self:cell_h()
- fill_rect(x0, y0, self:cell_w(), self:cell_h(), CON_BG) # clear the cell
- const code = ch:ascii()
+ draw_glyph = fn(self, column, row, character) do
+ const origin_x = column * self:cell_width()
+ const origin_y = row * self:cell_height()
+ fill_rect(origin_x, origin_y, self:cell_width(), self:cell_height(), BG_COLOR) # clear the cell
+ const code = character:ascii()
if code < 32 return :nil
if code > 127 return :nil
const glyph = FONT[code - 32]
- for fy in 0..FH do
- const bits = glyph[fy]
- for fx in 0..FW do
- if (bits shr fx) band 1 == 1 fill_rect(x0 + fx * self.scale, y0 + fy * self.scale, self.scale, self.scale, FG)
+ for glyph_row in 0..FONT_HEIGHT do
+ const bits = glyph[glyph_row]
+ for glyph_col in 0..FONT_WIDTH do
+ if (bits shr glyph_col) band 1 == 1 fill_rect(origin_x + glyph_col * self.scale, origin_y + glyph_row * self.scale, self.scale, self.scale, FG_COLOR)
end
end
end,
newline = fn(self) do
- self.col = 0
+ self.column = 0
self.row += 1
if self.row >= self:rows() do
- fb_scroll(self:cell_h(), CON_BG)
+ fb_scroll(self:cell_height(), BG_COLOR)
self.row = self:rows() - 1
end
end,
# we dont do shift/cntrl, thats a job for future me lmao
# NOTE: future me kinda delt with it, i was mad drunk
- putc = fn(self, ch) do
- const code = ch:ascii()
+ putc = fn(self, character) do
+ const code = character:ascii()
if code == 10 do self:newline() return :nil end # \n
- if code == 13 do self.col = 0 return :nil end # \r
- if code == 8 do # backspace: move left
- if self.col > 0 self.col -= 1
+ if code == 13 do self.column = 0 return :nil end # \r
+ if code == 8 do # backspace: move left
+ if self.column > 0 self.column -= 1
return :nil
end
if code < 32 return :nil
- self:draw_glyph(self.col, self.row, ch)
- self.col += 1
- if self.col >= self:cols() self:newline()
+ self:draw_glyph(self.column, self.row, character)
+ self.column += 1
+ if self.column >= self:columns() self:newline()
end,
clear = fn(self) do
- fill_rect(0, 0, fb_width(), fb_height(), CON_BG)
- self.col = 0
+ fill_rect(0, 0, fb_width(), fb_height(), BG_COLOR)
+ self.column = 0
self.row = 0
end,
- # the cursor is an overlay block. its cleared before any drawing and shown after,
- # so it never sits under a glyph, cur_x/cur_y remember where it was actually drawn
- # so we always clear the right cell even after the head moved (backspace and shit)
cursor_clear = fn(self) do
if self.shown do
- fill_rect(self.cur_x * self:cell_w(), self.cur_y * self:cell_h(), self:cell_w(), self:cell_h(), CON_BG)
+ fill_rect(self.cursor_x * self:cell_width(), self.cursor_y * self:cell_height(), self:cell_width(), self:cell_height(), BG_COLOR)
self.shown = :false
end
end,
cursor_show = fn(self) do
- self.cur_x = self.col
- self.cur_y = self.row
- fill_rect(self.cur_x * self:cell_w(), self.cur_y * self:cell_h(), self:cell_w(), self:cell_h(), CUR)
+ self.cursor_x = self.column
+ self.cursor_y = self.row
+ fill_rect(self.cursor_x * self:cell_width(), self.cursor_y * self:cell_height(), self:cell_width(), self:cell_height(), CURSOR_COLOR)
self.shown = :true
end,
@@ -84,36 +81,36 @@ global con = {
end,
}
-fn puts(str) serial_puts(str)
+global puts = fn(text) serial_puts(text)
global flush_console = fn() do
- con:cursor_clear()
- con:drain()
- con.blink = 0
- con:cursor_show()
+ console:cursor_clear()
+ console:drain()
+ console.blink = 0
+ console:cursor_show()
end
global on_tick = fn(ticks) do
let byte = serial_next()
if byte >= 0 do
- con:cursor_clear()
- con:putc(string.of_ascii(byte))
- con:drain()
- con.blink = 0
- con:cursor_show()
+ console:cursor_clear()
+ console:putc(string.of_ascii(byte))
+ console:drain()
+ console.blink = 0
+ console:cursor_show()
return :nil
end
if proc_exited() do
- con:cursor_clear()
+ console:cursor_clear()
puts("% ")
- con:drain()
- con.blink = 0
- con:cursor_show()
+ console:drain()
+ console.blink = 0
+ console:cursor_show()
return :nil
end
- con.blink += 1
- if con.blink >= 50 do
- con.blink = 0
- if con.shown con:cursor_clear() else con:cursor_show()
+ console.blink += 1
+ if console.blink >= 50 do
+ console.blink = 0
+ if console.shown console:cursor_clear() else console:cursor_show()
end
end
diff --git a/kernel/fs.rv b/kernel/fs.rv
index c045950..6229fd5 100644
--- a/kernel/fs.rv
+++ b/kernel/fs.rv
@@ -20,39 +20,39 @@ const T_FILE = 1
const T_DIR = 2
fn split_path(path) do
- const raw = path:split("/")
+ const raw_parts = path:split("/")
let parts = {}
- for i in 0..raw:len() do
- if raw[i]:len() > 0 parts:push(raw[i])
+ for index in 0..raw_parts:len() do
+ if raw_parts[index]:len() > 0 parts:push(raw_parts[index])
end
return parts
end
-# 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.
+# 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 = {
- buf = 0, fat = 0, ready = :false, total = 0, root = 0,
+ scratch = 0, fat = 0, ready = :false, total = 0, root = 0,
fat_get = fn(self, block) do
disk_read(FAT_LBA + (block shr 8), self.fat)
mem_read(self.fat + (block band 255) * 2, 2)
end,
- fat_set = fn(self, block, val) do
- const lba = FAT_LBA + (block shr 8)
- disk_read(lba, self.fat)
- mem_write(self.fat + (block band 255) * 2, val, 2)
- disk_write(lba, self.fat)
+ fat_set = fn(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
let block = DATA_LBA
while block < self.total do
disk_read(FAT_LBA + (block shr 8), self.fat)
- let off = block band 255
- while (off < 256 and block < self.total) do
- if mem_read(self.fat + off * 2, 2) == 0 return block
- off += 1
+ let offset = block band 255
+ while (offset < 256 and block < self.total) do
+ if mem_read(self.fat + offset * 2, 2) == 0 return block
+ offset += 1
block += 1
end
end
@@ -62,9 +62,9 @@ global fs = {
free_chain = fn(self, first) do
let block = first
while (block != 0 and block != END) do
- const nxt = self:fat_get(block)
+ const next_block = self:fat_get(block)
self:fat_set(block, 0)
- block = nxt
+ block = next_block
end
end,
@@ -74,33 +74,33 @@ global fs = {
# +2 first block
# +4 size
# +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(self.buf + eoff + 8 + k, 1)) end
- out
+ ent_name = fn(self, entry_offset) 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
end,
- 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
+ ent_write = fn(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
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)
+ const next_block = self:fat_get(block)
+ disk_read(block, self.scratch)
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) }
+ 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 = nxt
+ block = next_block
end
return :nil
end,
@@ -110,62 +110,62 @@ global fs = {
dir_set = fn(self, dir_first, name, type, first, size) do
let block = dir_first
let found_block = 0
- let found_off = 0
+ let found_offset = 0
let free_block = 0
- let free_off = -1
+ let free_offset = -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)
+ const next_block = self:fat_get(block)
+ disk_read(block, self.scratch)
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
+ 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(eoff) == name do found_block = block found_off = eoff end
+ if self:ent_name(entry_offset) == name do found_block = block found_offset = entry_offset end
end
end
- block = nxt
+ block = next_block
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
+ 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 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
+ 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
- disk_read(target_block, self.buf)
- self:ent_write(target_off, type, name, first, size)
- disk_write(target_block, self.buf)
+ 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
let block = dir_first
while (block != 0 and block != END) do
- const nxt = self:fat_get(block)
- disk_read(block, self.buf)
+ const next_block = self:fat_get(block)
+ disk_read(block, self.scratch)
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)
+ 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 = nxt
+ block = next_block
end
return :nil
end,
@@ -173,50 +173,50 @@ global fs = {
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)
+ const next_block = self:fat_get(block)
+ disk_read(block, self.scratch)
for slot in 0..ENTS do
- if mem_read(self.buf + slot * 32, 1) != 0 return :false
+ if mem_read(self.scratch + slot * 32, 1) != 0 return :false
end
- block = nxt
+ block = next_block
end
return :true
end,
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])
+ 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
+ const found = self:dir_lookup(current, parts[index])
if found == :nil return :nil
- ent = found
- cur = found.first
+ entry = found
+ current = found.first
end
- return ent
+ return entry
end,
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])
+ 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
- pdir = found.first
+ parent_dir = found.first
end
- return { dir = pdir, name = parts[parts:len() - 1] }
+ return { dir = parent_dir, name = parts[parts:len() - 1] }
end,
mount = fn(self) do
- self.buf = frame_alloc()
+ self.scratch = frame_alloc()
self.fat = frame_alloc()
self.total = disk_sectors()
if self.total == 0 do self.ready = :false return :nil end
- disk_read(SB_LBA, self.buf)
- if mem_read(self.buf, 4) == MAGIC do
- self.root = mem_read(self.buf + 8, 2)
+ 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
@@ -230,68 +230,68 @@ 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
+ mem_zero(self.scratch, BLOCK)
+ 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.buf, BLOCK)
- disk_write(root, self.buf)
+ mem_zero(self.scratch, BLOCK)
+ disk_write(root, self.scratch)
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.scratch, BLOCK)
+ mem_write(self.scratch, MAGIC, 4)
+ mem_write(self.scratch + 4, self.total, 4)
+ 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
- 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)
+ const parent = self:resolve_parent(path)
+ if parent == :nil do puts("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 puts("mkdir: #{parent.name}: already exists\n") return :nil end
+ const new_block = self:fat_alloc()
+ if new_block == 0 do puts("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
- 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)
+ 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
+ const existing = self:dir_lookup(parent.dir, parent.name)
if existing != :nil do
- if existing.type == T_DIR do puts("#{pp.name}: is a directory\n") return 0 end
+ if existing.type == T_DIR do puts("#{parent.name}: is a directory\n") return 0 end
self:free_chain(existing.first)
end
const size = text:len()
let first = 0
- let prev = 0
+ let previous = 0
let written = 0
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)
if first == 0 do first = block end
- else do self:fat_set(prev, block) end
- mem_zero(self.buf, BLOCK)
- let k = 0
- while (k < BLOCK and written < size) do
- mem_write(self.buf + k, text[written]:ascii(), 1)
- k += 1
- written += 1
- end
- disk_write(block, self.buf)
- prev = block
+ 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
+ blit_str(self.scratch, text[written..(written + chunk)])
+ disk_write(block, self.scratch)
+ written += chunk
+ previous = block
end
- self:dir_set(pp.dir, pp.name, T_FILE, first, size)
+ self:dir_set(parent.dir, parent.name, T_FILE, first, size)
return 1
end,
@@ -300,102 +300,131 @@ global fs = {
end,
size = fn(self, path) do
- const ent = self:resolve(path)
- if ent == :nil or ent.type == T_DIR return -1
- return ent.size
+ const entry = self:resolve(path)
+ if entry == :nil or entry.type == T_DIR return -1
+ return entry.size
end,
- 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 > ent.size do stop = ent.size end
- let pos = 0
- let out = ""
- while (block != 0 and block != END and pos < stop) do
- const nxt = self:fat_get(block)
- if (pos + BLOCK > offset and pos < stop) do
- disk_read(block, self.buf)
- let k = 0
- while (k < BLOCK) do
- const abs = pos + k
- if (abs >= offset and abs < stop) do out = out ~ string.of_ascii(mem_read(self.buf + k, 1)) end
- k += 1
- end
+ read_bytes = fn(self, path, offset, length) do
+ const entry = self:resolve(path)
+ if entry == :nil or entry.type == T_DIR return ""
+ let block = entry.first
+ let stop = offset + length
+ if stop > entry.size do stop = entry.size end
+ 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
+ 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
+ let to = BLOCK
+ if stop - position < BLOCK do to = stop - position end
+ result = result ~ phys_to_str(self.scratch + from, to - from)
end
- pos += BLOCK
- block = nxt
+ position += BLOCK
+ block = next_block
end
- return out
+ return result
end,
cat = fn(self, path) do
if self:ensure() == :false return :nil
- 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 = ""
+ const entry = self:resolve(path)
+ if entry == :nil do puts("cat: #{path}: no such file\n") return :nil end
+ if entry.type == T_DIR do puts("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 nxt = self:fat_get(block)
- disk_read(block, self.buf)
+ const next_block = self:fat_get(block)
+ disk_read(block, self.scratch)
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
+ for index in 0..chunk do result = result ~ string.of_ascii(mem_read(self.scratch + index, 1)) end
remaining -= chunk
- block = nxt
+ block = next_block
end
- puts(out)
+ puts(result)
puts("\n")
end,
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
+ const entry = self:resolve(path)
+ if entry == :nil do puts("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 = ent.first
+ let block = entry.first
while (block != 0 and block != END) do
- const nxt = self:fat_get(block)
- disk_read(block, self.buf)
+ const next_block = self:fat_get(block)
+ disk_read(block, self.scratch)
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
+ 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
count += 1
end
end
- block = nxt
+ block = next_block
end
return count
end,
rm = fn(self, path) do
if self:ensure() == :false return :nil
- 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")
+ const parent = self:resolve_parent(path)
+ if parent == :nil do puts("rm: #{path}: no such file\n") return :nil end
+ const entry = self:dir_lookup(parent.dir, parent.name)
+ if entry == :nil do puts("rm: #{path}: no such file\n") return :nil end
+ if entry.type == T_DIR and self:dir_empty(entry.first) == :false do
+ puts("rm: #{parent.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")
+ self:free_chain(entry.first)
+ self:dir_clear(parent.dir, parent.name)
+ puts("removed #{parent.name}\n")
end,
is_dir = fn(self, path) do
- const ent = self:resolve(path)
- return ent != :nil and ent.type == T_DIR
+ const entry = self:resolve(path)
+ return entry != :nil and entry.type == T_DIR
+ end,
+
+ entries = fn(self, path) do
+ const entry = self:resolve(path)
+ if entry == :nil or entry.type != T_DIR return :nil
+ 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) })
+ end
+ block = next_block
+ end
+ return result
+ end,
+
+ unlink = fn(self, path) do
+ const parent = self:resolve_parent(path)
+ if parent == :nil return :false
+ 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
+ self:free_chain(entry.first)
+ self:dir_clear(parent.dir, parent.name)
+ return :true
end,
}
global fs_store = fn(name, text) fs:store(name, text)
global fs_size = fn(name) fs:size(name)
-global fs_read_bytes = fn(name, offset, len) fs:read_bytes(name, offset, len)
+global fs_read_bytes = fn(name, offset, length) fs:read_bytes(name, offset, length)
diff --git a/kernel/input.rv b/kernel/input.rv
index 7e0d156..26ce545 100644
--- a/kernel/input.rv
+++ b/kernel/input.rv
@@ -1,8 +1,8 @@
# keyboard scancode table
# ts is not a good way to write a driver lmao
-const KBD = " 1234567890-= qwertyuiop[] asdfghjkl;'` \\zxcvbnm,./ "
-const KBD_SHIFT = " !@#$%^&*()_+ QWERTYUIOP{} ASDFGHJKL:\"~ |ZXCVBNM<>? "
+const KEYMAP = " 1234567890-= qwertyuiop[] asdfghjkl;'` \\zxcvbnm,./ "
+const KEYMAP_SHIFT = " !@#$%^&*()_+ QWERTYUIOP{} ASDFGHJKL:\"~ |ZXCVBNM<>? "
# 8259 pic
fn pic_remap() do
@@ -25,7 +25,7 @@ end
# she stroke my keys till i handle
# no fr this is just a key stroke handler
-global shell = { line = "", shift = :false, ext = :false }
+global shell = { line = "", shift = :false, extended = :false }
fn decode_key(code) do
if code == 0x2a or code == 0x36 do shell.shift = :true return :nil end # shift down
@@ -34,16 +34,16 @@ fn decode_key(code) do
if code == 28 return "\n" # enter
if code == 14 return "\x08" # backspace
if code < 2 return :nil
- if code >= KBD:len() return :nil
- const ch = if shell.shift KBD_SHIFT[code] else KBD[code]
- if ch == " " and code != 0x39 return :nil # blank cells = modifiers/unmapped
- ch
+ if code >= KEYMAP:len() return :nil
+ const character = if shell.shift KEYMAP_SHIFT[code] else KEYMAP[code]
+ if character == " " and code != 0x39 return :nil # blank cells = modifiers/unmapped
+ character
end
global on_key = fn(code) do
- if code == 0xe0 do shell.ext = :true return :nil end
- if shell.ext do
- shell.ext = :false
+ if code == 0xe0 do shell.extended = :true return :nil end
+ if shell.extended do
+ shell.extended = :false
let final = 0
if code == 0x48 do final = 65 end # up
else if code == 0x50 do final = 66 end # down
@@ -56,14 +56,14 @@ global on_key = fn(code) do
end
return :nil
end
- const ch = decode_key(code)
- if ch == :nil return :nil
- if stdin_deliver(ch:ascii()) return :nil
- if ch == "\n" do puts("\n") run(shell.line) shell.line = "" return :nil end
- if ch == "\x08" do
+ const character = decode_key(code)
+ if character == :nil return :nil
+ if stdin_deliver(character:ascii()) return :nil
+ if character == "\n" do puts("\n") run(shell.line) shell.line = "" return :nil end
+ if character == "\x08" do
if shell.line:len() > 0 do shell.line = shell.line:sub(0, shell.line:len() - 1) puts("\x08 \x08") end
return :nil
end
- shell.line ~= ch
- puts(ch)
+ shell.line ~= character
+ puts(character)
end
diff --git a/kernel/main.rv b/kernel/main.rv
index 4a6fb58..568d695 100644
--- a/kernel/main.rv
+++ b/kernel/main.rv
@@ -5,7 +5,7 @@ pit_init()
pic_unmask(0) # pit (irq0) -> cursor blink / uptime
pic_unmask(1) # keyboard (irq1)
-cmd_clear()
+wipe()
dump_memmap()
fs:mount()
diff --git a/kernel/proc.rv b/kernel/proc.rv
index 2f8bb2f..5019fa8 100644
--- a/kernel/proc.rv
+++ b/kernel/proc.rv
@@ -10,56 +10,71 @@ fn load_elf(base, arg) do
return :nil
end
const entry = mem_read(base + 24, 8)
- const phoff = mem_read(base + 32, 8)
- const phnum = mem_read(base + 56, 2)
- const phentsize = mem_read(base + 54, 2)
+ const program_header_offset = mem_read(base + 32, 8)
+ const program_header_count = mem_read(base + 56, 2)
+ const program_header_size = mem_read(base + 54, 2)
const space = as_create()
- for i in 0..phnum do
- const phdr = base + phoff + i * phentsize
- if mem_read(phdr, 4) == PT_LOAD do
- const offset = mem_read(phdr + 8, 8)
- const vaddr = mem_read(phdr + 16, 8)
- const filesz = mem_read(phdr + 32, 8)
- const memsz = mem_read(phdr + 40, 8)
- let seg_off = 0
- while seg_off < memsz do
+ for index in 0..program_header_count do
+ const header = base + program_header_offset + index * program_header_size
+ if mem_read(header, 4) == PT_LOAD do
+ const file_offset = mem_read(header + 8, 8)
+ const virtual_address = mem_read(header + 16, 8)
+ const file_size = mem_read(header + 32, 8)
+ const memory_size = mem_read(header + 40, 8)
+ let segment_offset = 0
+ while segment_offset < memory_size do
const frame = frame_alloc()
mem_zero(frame, 4096)
- if seg_off < filesz do
- const left = filesz - seg_off
- mem_copy(frame, base + offset + seg_off, if left > 4096 4096 else left)
+ if segment_offset < file_size do
+ const remaining = file_size - segment_offset
+ mem_copy(frame, base + file_offset + segment_offset, if remaining > 4096 4096 else remaining)
end
- as_map(space, vaddr + seg_off, frame, PAGE_FLAGS)
- seg_off = seg_off + 4096
+ as_map(space, virtual_address + segment_offset, frame, PAGE_FLAGS)
+ segment_offset = segment_offset + 4096
end
end
end
const stack_top = 0x7fff0000
- for p in 0..4 do as_map(space, stack_top - (p + 1) * 4096, frame_alloc(), PAGE_FLAGS) end
+ for page in 0..4 do as_map(space, stack_top - (page + 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)
+ const arg_frame = frame_alloc()
+ mem_zero(arg_frame, 4096)
+ for index in 0..arg_len do mem_write(arg_frame + index, arg[index]:ascii(), 1) end
+ as_map(space, ARG_VADDR, arg_frame, PAGE_FLAGS)
arg_ptr = ARG_VADDR
end
proc_run(space, entry, stack_top, arg_ptr, arg_len)
end
+# one reused contiguous buffer to stage an elf image read off the disk, since
+# load_elf wants a flat base to mem_read from and a file lands in scattered frames
+# its a weird arch but wtv
+global loader = { scratch = 0 }
+const SCRATCH_PAGES = 64
+
+fn load_file_elf(path, arg) do
+ const size = fs:size(path)
+ if size < 0 do puts("#{path}: not found\n") return :false end
+ if size > SCRATCH_PAGES * 4096 do puts("#{path}: too big to load\n") return :false end
+ if loader.scratch == 0 do loader.scratch = alloc_contig(SCRATCH_PAGES) end
+ if loader.scratch == 0 do puts("no memory for elf load\n") return :false end
+ blit_str(loader.scratch, fs:read_bytes(path, 0, size))
+ load_elf(loader.scratch, arg)
+ return :true
+end
+
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) == n do load_elf(prog_phys(i), arg) return :true end
- end
- puts("no such program: #{name}\n")
- return :false
+ let bare_name = name
+ if bare_name:starts_with?("/bin/") bare_name = bare_name[5..]
+ const path = "/bin/#{bare_name}"
+ if fs:size(path) < 0 return :false
+ return load_file_elf(path, arg)
end
fn list_progs() do
puts("programs:")
- for i in 0..prog_count() do puts(" #{prog_name(i)}") end
+ for index in 0..prog_count() do puts(" #{prog_name(index)}") end
puts("\n")
end
diff --git a/kernel/seed.rv b/kernel/seed.rv
index 8c0d9b7..ad3f4e9 100644
--- a/kernel/seed.rv
+++ b/kernel/seed.rv
@@ -1,4 +1,15 @@
# js dw about this ok?
+
+fn seed_bin() do
+ fs:mkdir("/bin")
+ for index in 0..prog_count() do
+ fs:store("/bin/#{prog_name(index)}", phys_to_str(prog_phys(index), prog_size(index)))
+ end
+ for index in 0..script_count() do
+ fs:store("/bin/#{script_name(index)}.rv", script_src(index))
+ end
+end
+
fn seed_fs() do
fs:mkdir("/usr")
fs:mkdir("/etc")
@@ -7,4 +18,6 @@ fn seed_fs() do
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")
+
+ seed_bin()
end
diff --git a/kernel/shell.rv b/kernel/shell.rv
index 40930c5..5d0a1c2 100644
--- a/kernel/shell.rv
+++ b/kernel/shell.rv
@@ -1,190 +1,117 @@
-# this is just a simple prompt to test that shit is working
+# i eventually want the shell to be a standalone program and shit
-fn pad(s, width) do
- let out = s
- while out:len() < width do out = out ~ " " end
- return out
+fn pad(text, width) do
+ let result = text
+ while result:len() < width do result = result ~ " " end
+ return result
end
-fn help_row(cmd, desc) puts(" #{pad(cmd, 20)}#{desc}\n")
+fn help_row(label, description) puts(" #{pad(label, 20)}#{description}\n")
-fn cmd_help() do
- puts("\n shell\n")
- help_row("help", "show this")
- help_row("clear", "wipe the screen")
- help_row("time", "utc clock")
- help_row("mem", "free / total ram")
- help_row("uptime", "seconds since boot")
- help_row("ps", "running processes")
- help_row("zoom in|out", "scale the font")
- help_row("echo <text>", "print it back")
- puts("\n files\n")
- 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")
- help_row("run <name>", "")
+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
+ end
+end
+
+fn cmd_help(context) do
+ help_group("shell", "shell")
+ help_group("gils (builtin)", "gils")
+ # the rest of gils, and the programs, are real files in /bin now
+ const items = fs:entries("/bin")
+ if items != :nil do
+ puts("\n gils (/bin scripts)\n")
+ for index in 0..items:len() do
+ const name = items[index].name
+ if (name:len() > 3 and name[(name:len() - 3)..] == ".rv") do help_row(name[0..(name:len() - 3)], "") end
+ end
+ puts("\n programs (/bin)\n")
+ for index in 0..items:len() do
+ const name = items[index].name
+ if (name:len() <= 3 or name[(name:len() - 3)..] != ".rv") do help_row(name, "") end
+ end
+ end
puts("\n")
end
fn welcome() do
- let fsline = "no filesystem"
- if fs.ready do fsline = "revelfs mounted" end
+ let fs_status = "no filesystem"
+ 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 | #{fsline} | #{prog_count()} programs\n")
+ puts(" #{(mem_total() shr 20)} MiB ram | #{fs_status} | #{prog_count()} programs\n")
puts(" type help to see what it does\n\n% ")
end
-fn arg_name(line, start) do
+# global so /bin scripts can call it from a nested eval fiber
+global arg_name = fn(line, start) do
let quoted = :false
let begin = start
if (begin < line:len() and line[begin] == "\"") do begin += 1 quoted = :true end
- let i = begin
+ let index = begin
if quoted do
- while (i < line:len() and line[i] != "\"") do i += 1 end
+ while (index < line:len() and line[index] != "\"") do index += 1 end
end
else do
- while (i < line:len() and line[i] != " ") do i += 1 end
+ while (index < line:len() and line[index] != " ") do index += 1 end
end
- const name = line[begin..i]
- let rest = i
+ 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 }
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]
+ let index = path:len() - 1
+ while (index > 0 and path[index] != "/") do index -= 1 end
+ if index == 0 return "/"
+ return path[0..index]
end
-fn cmd_cd(words) do
- const arg = if words:len() > 1 words[1] else "/"
+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 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(abspath(a.name), line[a.rest..])
-end
+fn cmd_pwd(context) puts("#{wd.path}\n")
-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
- 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(abspath(a.name))
-end
+fn wipe() console:clear()
+fn cmd_clear(context) wipe()
fn zoom_by(delta) do
- let scale = con.scale + delta
+ let scale = console.scale + delta
if scale < 1 do scale = 1 end
if scale > 6 do scale = 6 end
- con.scale = scale
- cmd_clear()
+ console.scale = scale
+ wipe()
puts("zoom #{scale}x\n")
end
-fn cmd_zoom(words) do
- const dir = if words:len() > 1 words[1] else ""
- if dir == "in" do zoom_by(1) end
- else if dir == "out" do zoom_by(-1) 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
end
-fn cmd_clear() con:clear()
-
fn bcd(byte) (byte shr 4) * 10 + (byte band 15)
-fn rtc(reg) do
- outb(0x70, reg)
+fn rtc(cmos_reg) do
+ outb(0x70, cmos_reg)
inb(0x71)
end
-fn cmd_time() puts("utc #{bcd(rtc(4))}:#{bcd(rtc(2))}:#{bcd(rtc(0))}\n")
+fn cmd_time(context) puts("utc #{bcd(rtc(4))}:#{bcd(rtc(2))}:#{bcd(rtc(0))}\n")
# 0x prefixed 16 digit hex
-fn hex(n) do
+fn hex(value) do
const digits = "0123456789abcdef"
- let out = ""
- for i in 0..16 do out = out ~ digits[(n shr ((15 - i) * 4)) band 15] end
- "0x#{out}"
+ let result = ""
+ for index in 0..16 do result = result ~ digits[(value shr ((15 - index) * 4)) band 15] end
+ "0x#{result}"
end
fn mem_type_name(kind) do
@@ -202,49 +129,55 @@ end
# the boot memory map dump
fn dump_memmap() do
puts("memory map:\n")
- for i in 0..memmap_count() do
- puts(" #{hex(memmap_base(i))} len=#{hex(memmap_len(i))} type=#{mem_type_name(memmap_kind(i))}\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() puts("#{(mem_free() shr 20)} MiB free / #{(mem_total() shr 20)} MiB usable\n")
-fn cmd_uptime() puts("#{math.floor(uptime() / 100)}s (#{uptime()} ticks)\n")
-fn cmd_ps() puts("#{proc_count()} process(es) running\n")
+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_run(context) do
+ if context.argv:len() <= 1 do list_progs() return :nil end
+ const parsed = arg_name(context.rest, 0)
+ if run_prog(parsed.name, context.rest[parsed.rest..]) == :true return :hold
+ puts("no such program: #{parsed.name}\n")
+ return :nil
+end
fn run(line) do
- if editor.active do editor_line(line) return :nil end
+ # the fact that the shell needs to interface directly with ed isnt, very nice
+ if gils_ed_active() do gils_ed_feed(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
- 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("% ")
+ const argv = line:split(" ")
+ const context = make_ctx(line, argv)
+ 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("% ") end
return :nil
end
- match cmd
- | "help" => cmd_help()
- | "clear" => cmd_clear()
- | "time" => cmd_time()
- | "mem" => cmd_mem()
- | "uptime" => cmd_uptime()
- | "ps" => cmd_ps()
- | "zoom" => cmd_zoom(words)
- | "mkfs" => fs:format()
- | "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("% ")
+
+ if run_script(argv[0], context) == :true do puts("% ") return :nil end
+ if run_prog(argv[0], context.rest) == :true return :nil
+ puts("#{argv[0]}: command not found\n% ")
end
+
+register("help", "shell", "show this", cmd_help)
+register("clear", "shell", "wipe the screen", cmd_clear)
+register("time", "shell", "utc clock", cmd_time)
+register("mem", "shell", "free / total ram", cmd_mem)
+register("uptime", "shell", "seconds since boot", cmd_uptime)
+register("ps", "shell", "running processes", cmd_ps)
+register("zoom in|out", "shell", "scale the font", cmd_zoom)
+register("echo <text>", "shell", "print it back", cmd_echo)
+register("mkfs", "shell", "format the disk", cmd_mkfs)
+register("cd <path>", "gils", "change directory", cmd_cd)
+register("pwd", "gils", "print working dir", cmd_pwd)
+register("run [name] [arg]", "shell", "run a program explicitly", cmd_run)
diff --git a/kernel/vfs.rv b/kernel/vfs.rv
index 5d2d1de..22c399e 100644
--- a/kernel/vfs.rv
+++ b/kernel/vfs.rv
@@ -3,37 +3,33 @@
const TASK_STATES = { "free", "ready", "dead", "blocked" }
-fn vfs_owns(path) do
+global vfs_owns = fn(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)
+global parse_pid = fn(text) do
+ if text:len() == 0 return -1
+ let number = 0
+ for index in 0..text:len() do
+ const code = text[index]:ascii()
+ if code < 48 or code > 57 return -1
+ number = number * 10 + (code - 48)
end
- return n
+ return number
end
-fn vfs_ls(path) do
+global vfs_ls = fn(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")
+ for pid in 0..task_max() do
+ if task_state(pid) != 0 puts(" #{pid}\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
+global vfs_cat = fn(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
@@ -45,13 +41,5 @@ fn vfs_cat(path) do
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 a4c0eea..0de5f33 100644
--- a/kernel/vmm.rv
+++ b/kernel/vmm.rv
@@ -2,7 +2,7 @@
# everything here only writes small entries we construct, so its f64 safe
const PTE_ADDR = 0x000FFFFFFFFFF000
-fn pte_idx(virt, level) (virt shr (12 + 9 * level)) band 0x1ff
+fn pte_index(virtual_address, level) (virtual_address shr (12 + 9 * level)) band 0x1ff
fn next_table(table, index) do
const slot = table + index * 8
@@ -14,10 +14,10 @@ fn next_table(table, index) do
frame
end
-fn as_map(pml4, virt, phys, flags) do
- const pdpt = next_table(pml4, pte_idx(virt, 3))
- const pd = next_table(pdpt, pte_idx(virt, 2))
- const pt = next_table(pd, pte_idx(virt, 1))
- mem_write(pt + pte_idx(virt, 0) * 8, (phys band PTE_ADDR) bor flags bor 1, 8)
- invlpg(virt)
+fn as_map(pml4, virtual_address, physical_address, flags) do
+ const pdpt = next_table(pml4, pte_index(virtual_address, 3))
+ const pd = next_table(pdpt, pte_index(virtual_address, 2))
+ const pt = next_table(pd, pte_index(virtual_address, 1))
+ mem_write(pt + pte_index(virtual_address, 0) * 8, (physical_address band PTE_ADDR) bor flags bor 1, 8)
+ invlpg(virtual_address)
end
diff --git a/revo b/revo
-Subproject 8b0b88a34b4ae803e8b68bedd1574300d011891
+Subproject c3d5ede438c8b8e32ddd692a0e2e337665a2b63