# vfs mount table # # tools only ever touch vfs not fs now since they never know or care whether a path is a real # file on disk or something, so they can read/write to shit like /dev/null for example const T_FILE = 1 const T_DIR = 2 const T_CHAR = 3 # stream fn parent_dir(path) -> string do const parts = path:split("/") let out = "" for index in 0..(parts:len() - 1) do if (parts[index] != "") out = out ~ "/" ~ parts[index] end return if (out == "") "/" else out end fn leaf(path) -> string do const parts = path:split("/") return parts[parts:len() - 1] end global parse_pid = fn(text) -> num 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 number end const TASK_STATES = { "free", "ready", "dead", "blocked" } const procfs = { writable = :false, fn body(self, path) -> string | :nil do if (path == "/proc/uptime") return "#{math.floor(uptime() / 100)}s\n" if (path == "/proc/meminfo") return "#{(mem_free() shr 20)} MiB free / #{(mem_total() shr 20)} MiB total\n" if path:starts_with?("/proc/") do const pid = parse_pid(path[6..]) if (pid >= 0 and pid < task_max() and task_state(pid) != 0) return "pid #{pid}: #{TASK_STATES[task_state(pid)]}\n" end return :nil end, fn entries(self, path) -> table | :nil do if (path != "/proc") return :nil let out = { { name = "uptime", type = T_FILE, size = self:size("/proc/uptime") }, { name = "meminfo", type = T_FILE, size = self:size("/proc/meminfo") }, } for pid in 0..task_max() do if (task_state(pid) != 0) out:push({ name = "#{pid}", type = T_FILE, size = self:size("/proc/#{pid}") }) end return out end, fn is_dir(self, path) -> bool do return path == "/proc" end, fn size(self, path) -> num do const b = self:body(path) if (b == :nil) return -1 return b:len() end, fn read_bytes(self, path, offset, length) -> string do const b = self:body(path) if (b == :nil) return "" let stop = offset + length if (stop > b:len()) stop = b:len() if (offset >= stop) return "" return b[offset..stop] end, } const DEV_SAMPLE = 64 fn dev_size(name) -> num do if (name == "zero" or name == "random") return DEV_SAMPLE if (name == "null" or name == "full") return 0 return -1 end global devfs = { writable = :true, rng = 0, # random bs go fn next_byte(self) -> num do if (self.rng == 0) self.rng = (uptime() band 0xffffffff) bor 1 let x = self.rng x = x bxor ((x shl 13) band 0xffffffff) x = x bxor (x shr 17) x = x bxor ((x shl 5) band 0xffffffff) self.rng = x band 0xffffffff return self.rng band 0xff end, fn entries(self, path) -> table | :nil do if (path != "/dev") return :nil const names = { "null", "zero", "random", "full" } let out = {} for index in 0..names:len() do const name = names[index] out:push({ name = name, type = T_CHAR, size = dev_size(name) }) end return out end, fn is_dir(self, path) -> bool do return path == "/dev" end, fn size(self, path) -> num do return dev_size(leaf(path)) end, fn read_bytes(self, path, offset, length) -> string do const name = leaf(path) if name == "zero" do let out = "" for index in 0..length do out = out ~ string.of_ascii(0) end return out end if name == "random" do let out = "" # 33..126 is the printable ascii range for index in 0..length do out = out ~ string.of_ascii(33 + (self:next_byte() band 0x3f)) end return out end return "" # null and full read as empty end, fn store(self, path, text) -> num do const name = leaf(path) if (name == "null") return 1 if name == "full" do fail("#{path}: no space left on device\n") return 0 end fail("#{path}: read-only device\n") return 0 end, fn write_at(self, path, offset, text) do self:store(path, text) end, } global vfs = { mounts = { { at = "/proc", fs = procfs }, { at = "/dev", fs = devfs }, }, fn mount(self, at, backend) do self.mounts:push({ at = at, fs = backend }) end, fn backend(self, path) do let best = :nil for index in 0..self.mounts:len() do const m = self.mounts[index] if (path == m.at or path:starts_with?(m.at ~ "/")) do if (best == :nil or m.at:len() > best.at:len()) best = m end end return if (best == :nil) fs else best.fs end, fn children_of(self, dir) -> table do let out = {} for index in 0..self.mounts:len() do const at = self.mounts[index].at if (parent_dir(at) == dir) out:push(leaf(at)) end return out end, fn entries(self, path) -> table | :nil do let out = self:backend(path):entries(path) const kids = self:children_of(path) if kids:len() > 0 do if (out == :nil) out = {} for index in 0..kids:len() do out:push({ name = kids[index], type = T_DIR, size = 0 }) end end return out end, fn is_dir(self, path) -> bool do for index in 0..self.mounts:len() do if (self.mounts[index].at == path) return :true end return self:backend(path):is_dir(path) end, fn size(self, path) -> num do return self:backend(path):size(path) end, fn read_bytes(self, path, offset, length) -> string do return self:backend(path):read_bytes(path, offset, length) end, fn store(self, path, text) -> num do const b = self:backend(path) if (b == fs) return fs:store(path, text) if b.writable == :false do fail("#{path}: read-only filesystem\n") return 0 end return b:store(path, text) end, fn write_at(self, path, offset, text) do const b = self:backend(path) if b == fs do fs:write_at(path, offset, text) return :nil end if b.writable == :false do fail("#{path}: read-only filesystem\n") return :nil end b:write_at(path, offset, text) end, fn mkdir(self, path) do if self:backend(path) != fs do fail("#{path}: read-only filesystem\n") return :nil end fs:mkdir(path) end, fn unlink(self, path) do if self:backend(path) != fs do fail("#{path}: read-only filesystem\n") return :nil end fs:unlink(path) end, fn rm(self, path) do if self:backend(path) != fs do fail("rm: #{path}: read-only filesystem\n") return :nil end fs:rm(path) end, fn cat(self, path) do const b = self:backend(path) if b == fs do fs:cat(path) return :nil end const sz = b:size(path) if sz < 0 do fail("cat: #{path}: no such file\n") return :nil end puts(b:read_bytes(path, 0, sz)) end, fn ls(self, path) do const items = self:entries(path) if items == :nil do const sz = self:size(path) if (sz >= 0) puts(" #{leaf(path)} #{sz} bytes\n") else fail("ls: #{path}: no such directory\n") return :nil end if items:len() == 0 do puts(" (empty)\n") return :nil end for index in 0..items:len() do const entry = items[index] if (entry.type == T_DIR) puts(" #{entry.name}/\n") else if (entry.type == T_CHAR) puts(" #{entry.name}\n") else puts(" #{entry.name} #{entry.size} bytes\n") end end, }