From 3596824ffb72b637f665007c34eea33576891b7c Mon Sep 17 00:00:00 2001 From: osmarks Date: Fri, 29 Jan 2021 17:46:20 +0000 Subject: [PATCH] experimental heavlisp integration update URLs --- README.md | 2 + manifest | 4 +- src/lib/urandom.lua | 3 +- src/lib/yafss.lua | 935 ++++++++++++++++++++------------------- src/main.lua | 39 +- src/potatobios.lua | 76 +++- src/xlib/01_skynet.lua | 2 +- src/xlib/03_heavlisp.lua | 577 ++++++++++++++++++++++++ website/index.html | 109 +++++ 9 files changed, 1252 insertions(+), 495 deletions(-) create mode 100644 src/xlib/03_heavlisp.lua create mode 100644 website/index.html diff --git a/README.md b/README.md index ac258aa..44507c2 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ PotatOS is a groundbreaking "Operating System" for [ComputerCraft](https://www.c PotatOS Hypercycle is not entirely finished, and some features are currently broken or missing. If you want more "stability", consider [PotatOS Tau](https://pastebin.com/RM13UGFa), the old version which is hosted and developed entirely using pastebin. +You obviously want to install it now, so do this: `pastebin run 7HSiHybr`. + ## Features Unlike most "OS"es for CC (primarily excluding Opus OS, which is actually useful, and interesting "research projects" like Vorbani), which are merely a pointless GUI layer over native CraftOS, PotatOS incorporates many innovative features: diff --git a/manifest b/manifest index 5ca9fdc..66b10a2 100644 --- a/manifest +++ b/manifest @@ -1,2 +1,2 @@ -{"build":170,"description":"stack overflow fix for CraftOS-PC - corrected","files":{"LICENSES":"f3549d84d66eb53dd4a421a4341d77d3d217c1b117d67e3be8f5211adcda0952","autorun.lua":"53a7f2b2692bd42a29f8559293447d413e58dc7fee7f2990a007624a62991562","bin/5rot26.lua":"91b66cd6d4b33081b25c456142dd7efcb894e819e842693c9e1e17ff48872ff5","bin/ccemux.lua":"239476f58835b86bbcac31ce8af3c3acd3d198a55ab9ada78c62fbf358625a98","bin/chronometer.lua":"db5363993a04382145aef7db2fbe262f0bf10697a589e1e2d2f9ce0f87430dd8","bin/kristminer.lua":"7e7f9fe2a6493d584ad6926cda915e02c1c3d800dc209680898ce930d0bb0e6f","bin/livegps.lua":"c3d17d495cda01aa1261e4c4fcd43439b29af422671972117ec34f68e32c5bba","bin/loading.lua":"c85f7aa1765170325155b921c1fceeb62643f552f12d41b529a22af3a67f5a97","bin/potatoflight.lua":"2fbb0b6f8d78728d8cb0ec64af1bc598bd00cb55f202378e7acdb86bba71efd1","bin/potatoplex.lua":"86c9e7597bbe23d7de7e7f1bfc976d0b94dcdf3af9e6c7c6c9b18b98596898c8","bin/relay.lua":"261ae6c220b83506e3326e8f2b091d246baae458ff0d2ee87512be2c4e35a75d","bin/tryhaskell.lua":"07810d85145da65a3e434154c79d5a9d72f2dcbe59c8d6829040fb925df878ec","potatobios.lua":"042bcbd6bf50730b6f01fad2f4280e7793f3542a5a5325420e8d098cf2052682","signing-key.tbl":"b32af5229c23af3bc03d538e42751b26044e404a7b1af064ed89894efe421607","startup":"f17bfb9b4322c4467dc9170d50827f2d75717e5c3125d734f21f3406657917bc","update-key.hex":"8d8afb7a45833bb7d68f929421ad60a211d4d73e0ee03b24dc0106ba1de2e1a0","xlib/00_cbor.lua":"464b075e4f094b8db42506bd4bdaad0db87699ea7fbf80e5b87739b4aa9279af","xlib/01_skynet.lua":"bde95ed86f3108ec56624367deea3e2694c8cfcd9eac220a21bad0b56c8a999b"},"timestamp":1599139938} -{"hash":"38483033b4969ebff82a55d6ac62d31da30ee38378cfdcff600980fe54aa8acd","sig":"109588062d87c0e573d85e89f2e73fb27c51e398323f58b7338fa06314ad1b5a687eab78cb9b2793021b"} \ No newline at end of file +{"build":177,"description":"replace URLs and fix potatoBIOS","files":{"LICENSES":"f3549d84d66eb53dd4a421a4341d77d3d217c1b117d67e3be8f5211adcda0952","autorun.lua":"c84204c4684134cb12ed9a880a6b82e440f4a615ad944723df8ba7978e79e402","bin/5rot26.lua":"91b66cd6d4b33081b25c456142dd7efcb894e819e842693c9e1e17ff48872ff5","bin/ccemux.lua":"239476f58835b86bbcac31ce8af3c3acd3d198a55ab9ada78c62fbf358625a98","bin/chronometer.lua":"db5363993a04382145aef7db2fbe262f0bf10697a589e1e2d2f9ce0f87430dd8","bin/kristminer.lua":"7e7f9fe2a6493d584ad6926cda915e02c1c3d800dc209680898ce930d0bb0e6f","bin/livegps.lua":"c3d17d495cda01aa1261e4c4fcd43439b29af422671972117ec34f68e32c5bba","bin/loading.lua":"c85f7aa1765170325155b921c1fceeb62643f552f12d41b529a22af3a67f5a97","bin/potatoflight.lua":"2fbb0b6f8d78728d8cb0ec64af1bc598bd00cb55f202378e7acdb86bba71efd1","bin/potatoplex.lua":"86c9e7597bbe23d7de7e7f1bfc976d0b94dcdf3af9e6c7c6c9b18b98596898c8","bin/relay.lua":"261ae6c220b83506e3326e8f2b091d246baae458ff0d2ee87512be2c4e35a75d","bin/tryhaskell.lua":"07810d85145da65a3e434154c79d5a9d72f2dcbe59c8d6829040fb925df878ec","potatobios.lua":"d9104a0e8278d0c26d8b3be36697d25a3181210773c2841c70e44ca38bc0ab2d","signing-key.tbl":"b32af5229c23af3bc03d538e42751b26044e404a7b1af064ed89894efe421607","startup":"f17bfb9b4322c4467dc9170d50827f2d75717e5c3125d734f21f3406657917bc","update-key.hex":"8d8afb7a45833bb7d68f929421ad60a211d4d73e0ee03b24dc0106ba1de2e1a0","xlib/00_cbor.lua":"464b075e4f094b8db42506bd4bdaad0db87699ea7fbf80e5b87739b4aa9279af","xlib/01_skynet.lua":"9cb565d639a0acd7c763c3e7422482532cd0bda0cdfcc720089ab4a87e551339","xlib/03_heavlisp.lua":"104d5e4628f594a3d0484d6aecec3b4f69914ba398f6bc02dd12c62bddc3de4a"},"timestamp":1611941883} +{"hash":"85d0edfacbdca311a8f18aebd88ae0a5c2e184f57db18285d22a3ea4922b1680","sig":"6b665266304ef6f04791163cff4c46d7ae91ca153738acfe09c3de9703e8bc274202f44d2ea3c939ba3a"} \ No newline at end of file diff --git a/src/lib/urandom.lua b/src/lib/urandom.lua index b8cee9d..9c6fbf7 100644 --- a/src/lib/urandom.lua +++ b/src/lib/urandom.lua @@ -18,6 +18,7 @@ if process then tostring(event[4]), tostring(os.epoch("utc")), tostring({}), + math.random() } entropy = table.concat(entropy, "|") @@ -37,4 +38,4 @@ os.urandom = function() state = sha256.digest(state) return result -end \ No newline at end of file +end diff --git a/src/lib/yafss.lua b/src/lib/yafss.lua index dd45362..23d76ea 100644 --- a/src/lib/yafss.lua +++ b/src/lib/yafss.lua @@ -1,458 +1,479 @@ --- Deep-copy a table -local function copy(tabl) - local new = {} - for k, v in pairs(tabl) do - if type(v) == "table" and tabl ~= v then - new[k] = copy(v) - else - new[k] = v - end - end - return new -end - --- Deep-map all values in a table -local function deepmap(table, f, path) - local path = path or "" - local new = {} - for k, v in pairs(table) do - local thisp = path .. "." .. k - if type(v) == "table" and v ~= table then -- bodge it to not stackoverflow - new[k] = deepmap(v, f, thisp) - else - new[k] = f(v, k, thisp) - end - end - return new -end - --- Takes a list of keys to copy, returns a function which takes a table and copies the given keys to a new table -local function copy_some_keys(keys) - return function(from) - local new = {} - for _, key_to_copy in pairs(keys) do - local x = from[key_to_copy] - if type(x) == "table" then - x = copy(x) - end - new[key_to_copy] = x - end - return new - end -end - --- Simple string operations -local function starts_with(s, with) - return string.sub(s, 1, #with) == with -end -local function ends_with(s, with) - return string.sub(s, -#with, -1) == with -end -local function contains(s, subs) - return string.find(s, subs) ~= nil -end - --- Maps function f over table t. f is passed the value and key and can return a new value and key. -local function map(f, t) - local mapper = function(t) - local new = {} - for k, v in pairs(t) do - local new_v, new_k = f(v, k) - new[new_k or k] = new_v - end - return new - end - if t then return mapper(t) else return mapper end -end - --- Copies stuff from t2 into t1 -local function add_to_table(t1, t2) - for k, v in pairs(t2) do - if type(v) == "table" and v ~= t2 and v ~= t1 then - if not t1[k] then t1[k] = {} end - add_to_table(t1[k], v) - else - t1[k] = v - end - end -end - --- Convert path to canonical form -local function canonicalize(path) - return fs.combine(path, "") -end - --- Checks whether a path is in a directory -local function path_in(p, dir) - return starts_with(canonicalize(p), canonicalize(dir)) -end - -local function make_mappings(root) - return { - ["/disk"] = "/disk", - ["/rom"] = "/rom", - default = root - } -end - -local function get_root(path, mappings) - for mapfrom, mapto in pairs(mappings) do - if path_in(path, mapfrom) then - return mapto, mapfrom - end - end - return mappings.default, "/" -end - --- Escapes lua patterns in a string. Should not be needed, but lua is stupid so the only string.replace thing is gsub -local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])' -local function escape(str) - return str:gsub(quotepattern, "%%%1") -end - -local function strip(p, root) - return p:gsub("^" .. escape(canonicalize(root)), "") -end - -local function resolve_path(path, mappings) - local root, to_strip = get_root(path, mappings) - local newpath = strip(fs.combine(root, path), to_strip) - if path_in(newpath, root) then return newpath end - return resolve_path(newpath, mappings) -end - -local function segments(path) - local segs, rest = {}, "" - repeat - table.insert(segs, 1, fs.getName(rest)) - rest = fs.getDir(rest) - until rest == "" - return segs -end - -local function combine(segs) - local out = "" - for _, p in pairs(segs) do - out = fs.combine(out, p) - end - return out -end - -local function difference(p1, p2) - local s1, s2 = segments(p1), segments(p2) - if #s2 == 0 then return combine(s1) end - local segs = {} - for _, p in pairs(s1) do - local item = table.remove(s1, 1) - table.insert(segs, item) - if p == s2[1] then break end - end - return combine(segs) -end - --- magic from http://lua-users.org/wiki/SplitJoin --- split string into lines -local function lines(str) - local t = {} - local function helper(line) - table.insert(t, line) - return "" - end - helper((str:gsub("(.-)\r?\n", helper))) - return t -end - --- Fetch the contents of URL "u" -local function fetch(u) - local h = http.get(u) - local c = h.readAll() - h.close() - return c -end - --- Make a read handle for a string -local function make_handle(text) - local lines = lines(text) - local h = {line = 0} - function h.close() end - function h.readLine() h.line = h.line + 1 return lines[h.line] end - function h.readAll() return text end - return h -end - --- Get a path from a filesystem overlay -local function path_in_overlay(overlay, path) - return overlay[canonicalize(path)] -end - -local this_level_env = _G - --- Create a modified FS table which confines you to root and has some extra read-only pseudofiles. -local function create_FS(root, overlay) - local mappings = make_mappings(root) - - local new_overlay = {} - for k, v in pairs(overlay) do - new_overlay[canonicalize(k)] = v - end - - local function lift_to_sandbox(f, n) - return function(...) - local args = map(function(x) return resolve_path(x, mappings) end, {...}) - return f(table.unpack(args)) - end - end - - local new = copy_some_keys {"getDir", "getName", "combine"} (fs) - - function new.isReadOnly(path) - return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom") - end - - function new.open(path, mode) - if (contains(mode, "w") or contains(mode, "a")) and new.isReadOnly(path) then - error "Access denied" - else - local overlay_data = path_in_overlay(new_overlay, path) - if overlay_data then - if type(overlay_data) == "function" then overlay_data = overlay_data(this_level_env) end - return make_handle(overlay_data), "YAFSS overlay" - end - return fs.open(resolve_path(path, mappings), mode) - end - end - - function new.exists(path) - if path_in_overlay(new_overlay, path) ~= nil then return true end - return fs.exists(resolve_path(path, mappings)) - end - - function new.overlay() - return map(function(x) - if type(x) == "function" then return x(this_level_env) - else return x end - end, new_overlay) - end - - function new.list(dir) - local sdir = canonicalize(resolve_path(dir, mappings)) - local contents = fs.list(sdir) - for opath in pairs(new_overlay) do - if fs.getDir(opath) == sdir then - table.insert(contents, fs.getName(opath)) - end - end - return contents - end - - add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "move", "copy", "delete", "isDriveRoot"} (fs))) - - function new.find(wildcard) - local function recurse_spec(results, path, spec) -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua - local segment = spec:match('([^/]*)'):gsub('/', '') - local pattern = '^' .. segment:gsub('[*]', '.+'):gsub('?', '.'):gsub("-", "%%-") .. '$' - - if new.isDir(path) then - for _, file in ipairs(new.list(path)) do - if file:match(pattern) then - local f = new.combine(path, file) - - if new.isDir(f) then - recurse_spec(results, f, spec:sub(#segment + 2)) - end - if spec == segment then - table.insert(results, f) - end - end - end - end - end - local results = {} - recurse_spec(results, '', wildcard) - return results - end - - function new.dump(dir) - local dir = dir or "/" - local out = {} - for _, f in pairs(new.list(dir)) do - local path = fs.combine(dir, f) - local to_add = { - n = f, - t = "f" - } - if new.isDir(path) then - to_add.c = new.dump(path) - to_add.t = "d" - else - local fh = new.open(path, "r") - to_add.c = fh.readAll() - fh.close() - end - table.insert(out, to_add) - end - return out - end - - function new.load(dump, root) - local root = root or "/" - for _, f in pairs(dump) do - local path = fs.combine(root, f.n) - if f.t == "d" then - new.makeDir(path) - new.load(f.c, path) - else - local fh = new.open(path, "w") - fh.write(f.c) - fh.close() - end - end - end - - return new -end - -local allowed_APIs = { - "term", - "http", - "pairs", - "ipairs", - -- getfenv, getfenv are modified to prevent sandbox escapes and defined in make_environment - "peripheral", - "table", - "string", - "type", - "setmetatable", - "getmetatable", - "os", - "sleep", - "pcall", - "xpcall", - "select", - "tostring", - "tonumber", - "coroutine", - "next", - "error", - "math", - "redstone", - "rs", - "assert", - "unpack", - "bit", - "bit32", - "turtle", - "pocket", - "ccemux", - "config", - "commands", - "rawget", - "rawset", - "rawequal", - "~expect", - "__inext", - "periphemu", -} - -local gf, sf = getfenv, setfenv - --- Takes the root directory to allow access to, --- a map of paths to either strings containing their contents or functions returning them --- and a table of extra APIs and partial overrides for existing APIs -local function make_environment(root_directory, overlay, API_overrides) - local environment = copy_some_keys(allowed_APIs)(_G) - - environment.fs = create_FS(root_directory, overlay) - - -- if function is not from within the VM, return env from within sandbox - function environment.getfenv(arg) - local env - if type(arg) == "number" then return gf() end - if not env or type(env._HOST) ~= "string" or not string.match(env._HOST, "YAFSS") then - return gf() - else - return env - end - end - - --[[ -Fix PS#AD2A532C -Allowing `setfenv` to operate on any function meant that privileged code could in some cases be manipulated to leak information or operate undesirably. Due to this, we restrict it, similarly to getfenv. - ]] - function environment.setfenv(fn, env) - local nenv = gf(fn) - if not nenv or type(nenv._HOST) ~= "string" or not string.match(nenv._HOST, "YAFSS") then - return false - end - return sf(fn, env) - end - - function environment.load(code, file, mode, env) - return load(code, file or "@", mode or "t", env or environment) - end - - if debug then - environment.debug = copy_some_keys { - "getmetatable", - "setmetatable", - "traceback", - "getinfo", - "getregistry" - }(debug) - end - - environment._G = environment - environment._ENV = environment - environment._HOST = string.format("YAFSS on %s", _HOST) - - function environment.os.shutdown() - os.queueEvent("power_state", "shutdown") - while true do coroutine.yield() end - end - - function environment.os.reboot() - os.queueEvent("power_state", "reboot") - while true do coroutine.yield() end - end - - add_to_table(environment, copy(API_overrides)) - - return environment -end - -local function run(root_directory, overlay, API_overrides, init) - if type(init) == "table" and init.URL then init = fetch(init.URL) end - init = init or fetch "https://pastebin.com/raw/wKdMTPwQ" - - local running = true - while running do - parallel.waitForAny(function() - local env = make_environment(root_directory, overlay, API_overrides) - env.init_code = init - - local out, err = load(init, "@[init]", "t", env) - if not out then error(err) end - local ok, err = pcall(out) - if not ok then printError(err) end - end, - function() - while true do - local event, state = coroutine.yield "power_state" - if event == "power_state" then -- coroutine.yield behaves weirdly with terminate - if process then - local this_process = process.running.ID - for _, p in pairs(process.list()) do - if p.parent and p.parent.ID == this_process then - process.signal(p.ID, process.signals.KILL) - end - end - end - if state == "shutdown" then running = false return - elseif state == "reboot" then return end - end - end - end) - end -end - +-- Deep-copy a table +local function copy(tabl) + local new = {} + for k, v in pairs(tabl) do + if type(v) == "table" and tabl ~= v then + new[k] = copy(v) + else + new[k] = v + end + end + return new +end + +-- Deep-map all values in a table +local function deepmap(table, f, path) + local path = path or "" + local new = {} + for k, v in pairs(table) do + local thisp = path .. "." .. k + if type(v) == "table" and v ~= table then -- bodge it to not stackoverflow + new[k] = deepmap(v, f, thisp) + else + new[k] = f(v, k, thisp) + end + end + return new +end + +-- Takes a list of keys to copy, returns a function which takes a table and copies the given keys to a new table +local function copy_some_keys(keys) + return function(from) + local new = {} + for _, key_to_copy in pairs(keys) do + local x = from[key_to_copy] + if type(x) == "table" then + x = copy(x) + end + new[key_to_copy] = x + end + return new + end +end + +-- Simple string operations +local function starts_with(s, with) + return string.sub(s, 1, #with) == with +end +local function ends_with(s, with) + return string.sub(s, -#with, -1) == with +end +local function contains(s, subs) + return string.find(s, subs) ~= nil +end + +-- Maps function f over table t. f is passed the value and key and can return a new value and key. +local function map(f, t) + local mapper = function(t) + local new = {} + for k, v in pairs(t) do + local new_v, new_k = f(v, k) + new[new_k or k] = new_v + end + return new + end + if t then return mapper(t) else return mapper end +end + +-- Copies stuff from t2 into t1 +local function add_to_table(t1, t2) + for k, v in pairs(t2) do + if type(v) == "table" and v ~= t2 and v ~= t1 then + if not t1[k] then t1[k] = {} end + add_to_table(t1[k], v) + else + t1[k] = v + end + end +end + +-- Convert path to canonical form +local function canonicalize(path) + return fs.combine(path, "") +end + +-- Checks whether a path is in a directory +local function path_in(p, dir) + return starts_with(canonicalize(p), canonicalize(dir)) +end + +local function make_mappings(root) + return { + ["/disk"] = "/disk", + ["/rom"] = "/rom", + default = root + } +end + +local function get_root(path, mappings) + for mapfrom, mapto in pairs(mappings) do + if path_in(path, mapfrom) then + return mapto, mapfrom + end + end + return mappings.default, "/" +end + +-- Escapes lua patterns in a string. Should not be needed, but lua is stupid so the only string.replace thing is gsub +local quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])' +local function escape(str) + return str:gsub(quotepattern, "%%%1") +end + +local function strip(p, root) + return p:gsub("^" .. escape(canonicalize(root)), "") +end + +local function resolve_path(path, mappings) + local root, to_strip = get_root(path, mappings) + local newpath = strip(fs.combine(root, path), to_strip) + if path_in(newpath, root) then return newpath end + return resolve_path(newpath, mappings) +end + +local function segments(path) + local segs, rest = {}, canonicalize(path) + if rest == "" then return {} end -- otherwise we'd get "root" and ".." for some broken reason + repeat + table.insert(segs, 1, fs.getName(rest)) + rest = fs.getDir(rest) + until rest == "" + return segs +end + +local function combine(segs) + local out = "" + for _, p in pairs(segs) do + out = fs.combine(out, p) + end + return out +end + +-- magic from http://lua-users.org/wiki/SplitJoin +-- split string into lines +local function lines(str) + local t = {} + local function helper(line) + table.insert(t, line) + return "" + end + helper((str:gsub("(.-)\r?\n", helper))) + return t +end + +-- Fetch the contents of URL "u" +local function fetch(u) + local h = http.get(u) + local c = h.readAll() + h.close() + return c +end + +-- Make a read handle for a string +local function make_handle(text) + local lines = lines(text) + local h = {line = 0} + function h.close() end + function h.readLine() h.line = h.line + 1 return lines[h.line] end + function h.readAll() return text end + return h +end + +-- Get a path from a filesystem overlay +local function path_in_overlay(overlay, path) + return overlay[canonicalize(path)] +end + +local this_level_env = _G + +-- Create a modified FS table which confines you to root and has some extra read-only pseudofiles. +local function create_FS(root, overlay) + local mappings = make_mappings(root) + + local vfstree = { + mount = "potatOS", + children = { + ["disk"] = { mount = "disk" }, + ["rom"] = { mount = "rom" }, + ["virtual_test"] = { virtual = "bees" } + } + } + + local function resolve(sandbox_path) + local segs = segments(sandbox_path) + local current_tree = vfstree + while true do + local seg = segs[1] + if current_tree.children and current_tree.children[seg] then + table.remove(segs, 1) + current_tree = current_tree.children[seg] + else break end + end + end + + local new_overlay = {} + for k, v in pairs(overlay) do + new_overlay[canonicalize(k)] = v + end + + local function lift_to_sandbox(f, n) + return function(...) + local args = map(function(x) return resolve_path(x, mappings) end, {...}) + return f(table.unpack(args)) + end + end + + local new = copy_some_keys {"getDir", "getName", "combine"} (fs) + + function new.isReadOnly(path) + return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom") + end + + function new.open(path, mode) + if (contains(mode, "w") or contains(mode, "a")) and new.isReadOnly(path) then + error "Access denied" + else + local overlay_data = path_in_overlay(new_overlay, path) + if overlay_data then + if type(overlay_data) == "function" then overlay_data = overlay_data(this_level_env) end + return make_handle(overlay_data), "YAFSS overlay" + end + return fs.open(resolve_path(path, mappings), mode) + end + end + + function new.exists(path) + if path_in_overlay(new_overlay, path) ~= nil then return true end + return fs.exists(resolve_path(path, mappings)) + end + + function new.overlay() + return map(function(x) + if type(x) == "function" then return x(this_level_env) + else return x end + end, new_overlay) + end + + function new.list(dir) + local sdir = canonicalize(resolve_path(dir, mappings)) + local ocontents = {} + for opath in pairs(new_overlay) do + if fs.getDir(opath) == sdir then + table.insert(ocontents, fs.getName(opath)) + end + end + local ok, contents = pcall(fs.list, sdir) + -- in case of error (nonexistent dir, probably) return overlay contents + -- very awful temporary hack until I can get a nicer treeized VFS done + if not ok then + if #ocontents > 0 then return ocontents end + error(contents) + else + for _, v in pairs(ocontents) do + table.insert(contents, v) + end + return contents + end + end + + add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "move", "copy", "delete", "isDriveRoot"} (fs))) + + function new.find(wildcard) + local function recurse_spec(results, path, spec) -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua + local segment = spec:match('([^/]*)'):gsub('/', '') + local pattern = '^' .. segment:gsub('[*]', '.+'):gsub('?', '.'):gsub("-", "%%-") .. '$' + + if new.isDir(path) then + for _, file in ipairs(new.list(path)) do + if file:match(pattern) then + local f = new.combine(path, file) + + if new.isDir(f) then + recurse_spec(results, f, spec:sub(#segment + 2)) + end + if spec == segment then + table.insert(results, f) + end + end + end + end + end + local results = {} + recurse_spec(results, '', wildcard) + return results + end + + function new.dump(dir) + local dir = dir or "/" + local out = {} + for _, f in pairs(new.list(dir)) do + local path = fs.combine(dir, f) + local to_add = { + n = f, + t = "f" + } + if new.isDir(path) then + to_add.c = new.dump(path) + to_add.t = "d" + else + local fh = new.open(path, "r") + to_add.c = fh.readAll() + fh.close() + end + table.insert(out, to_add) + end + return out + end + + function new.load(dump, root) + local root = root or "/" + for _, f in pairs(dump) do + local path = fs.combine(root, f.n) + if f.t == "d" then + new.makeDir(path) + new.load(f.c, path) + else + local fh = new.open(path, "w") + fh.write(f.c) + fh.close() + end + end + end + + return new +end + +local allowed_APIs = { + "term", + "http", + "pairs", + "ipairs", + -- getfenv, getfenv are modified to prevent sandbox escapes and defined in make_environment + "peripheral", + "table", + "string", + "type", + "setmetatable", + "getmetatable", + "os", + "sleep", + "pcall", + "xpcall", + "select", + "tostring", + "tonumber", + "coroutine", + "next", + "error", + "math", + "redstone", + "rs", + "assert", + "unpack", + "bit", + "bit32", + "turtle", + "pocket", + "ccemux", + "config", + "commands", + "rawget", + "rawset", + "rawequal", + "~expect", + "__inext", + "periphemu", +} + +local gf, sf = getfenv, setfenv + +-- Takes the root directory to allow access to, +-- a map of paths to either strings containing their contents or functions returning them +-- and a table of extra APIs and partial overrides for existing APIs +local function make_environment(root_directory, overlay, API_overrides) + local environment = copy_some_keys(allowed_APIs)(_G) + + environment.fs = create_FS(root_directory, overlay) + + -- if function is not from within the VM, return env from within sandbox + function environment.getfenv(arg) + local env + if type(arg) == "number" then return gf() end + if not env or type(env._HOST) ~= "string" or not string.match(env._HOST, "YAFSS") then + return gf() + else + return env + end + end + + --[[ +Fix PS#AD2A532C +Allowing `setfenv` to operate on any function meant that privileged code could in some cases be manipulated to leak information or operate undesirably. Due to this, we restrict it, similarly to getfenv. + ]] + function environment.setfenv(fn, env) + local nenv = gf(fn) + if not nenv or type(nenv._HOST) ~= "string" or not string.match(nenv._HOST, "YAFSS") then + return false + end + return sf(fn, env) + end + + function environment.load(code, file, mode, env) + return load(code, file or "@", mode or "t", env or environment) + end + + if debug then + environment.debug = copy_some_keys { + "getmetatable", + "setmetatable", + "traceback", + "getinfo", + "getregistry" + }(debug) + end + + environment._G = environment + environment._ENV = environment + environment._HOST = string.format("YAFSS on %s", _HOST) + + function environment.os.shutdown() + os.queueEvent("power_state", "shutdown") + while true do coroutine.yield() end + end + + function environment.os.reboot() + os.queueEvent("power_state", "reboot") + while true do coroutine.yield() end + end + + add_to_table(environment, copy(API_overrides)) + + return environment +end + +local function run(root_directory, overlay, API_overrides, init) + if type(init) == "table" and init.URL then init = fetch(init.URL) end + init = init or fetch "https://pastebin.com/raw/wKdMTPwQ" + + local running = true + while running do + parallel.waitForAny(function() + local env = make_environment(root_directory, overlay, API_overrides) + env.init_code = init + + local out, err = load(init, "@[init]", "t", env) + if not out then error(err) end + local ok, err = pcall(out) + if not ok then printError(err) end + end, + function() + while true do + local event, state = coroutine.yield "power_state" + if event == "power_state" then -- coroutine.yield behaves weirdly with terminate + if process then + local this_process = process.running.ID + for _, p in pairs(process.list()) do + if p.parent and p.parent.ID == this_process then + process.signal(p.ID, process.signals.KILL) + end + end + end + if state == "shutdown" then running = false return + elseif state == "reboot" then return end + end + end + end) + end +end + return run \ No newline at end of file diff --git a/src/main.lua b/src/main.lua index f29b9cb..58b29c6 100644 --- a/src/main.lua +++ b/src/main.lua @@ -486,7 +486,7 @@ function _G.report_incident(incident, flags, options) } -- Workaround craftos-pc bug by explicitly specifying Content-Length header http.request { - url = "https://osmarks.tk/wsthing/report", + url = "https://spudnet.osmarks.net/report", body = payload, headers = { ["content-type"] = "application/json", @@ -511,7 +511,7 @@ shell.run "startup" local function generate_disk_code() local an = copy(ancestry) table.insert(an, os.getComputerID()) - local manifest = settings.get "potatOS.distribution_server" or "https://osmarks.tk/stuff/potatos/manifest" + local manifest = settings.get "potatOS.distribution_server" or "https://osmarks.net/stuff/potatos/manifest" return disk_code_template:format( gen_count + 1, textutils.serialise(an), @@ -667,8 +667,8 @@ local function websocket_remote_debugging() local function connect() if ws then ws.close() end - ws, err = http.websocket "wss://osmarks.tk/wsthing/v4" - ws.url = "wss://osmarks.tk/wsthing/v4" + ws, err = http.websocket "wss://spudnet.osmarks.net/v4" + ws.url = "wss://spudnet.osmarks.net/v4" if not ws then add_log("websocket failure %s", err) return false end send_packet { type = "identify" } @@ -1013,17 +1013,11 @@ local function run_with_sandbox() debug_registry_mt.__index = function(_, k) return registry.get(k) end debug_registry_mt.__newindex = function(_, k, v) return registry.set(k, v) end - local fcache = {} - -- Proxy access to files. Assumes that they won't change once read. Which is true for most of them, so yay efficiency savings? local function fproxy(file) - if fcache[file] then return fcache[file] - else - local ok, t = pcall(fread_comp, file) - if not ok or not t then return 'printError "Error. Try again later, or reboot, or run upd."' end - fcache[file] = t - return t - end + local ok, t = pcall(fread_comp, file) + if not ok or not t then return 'printError "Error. Try again later, or reboot, or run upd."' end + return t end -- Localize a bunch of variables. Does this help? I have no idea. This is old code. @@ -1254,7 +1248,7 @@ if #disks > 0 then end parallel.waitForAny(function() sleep(0.5) end, function() - local ok, info = pcall(fetch, "https://osmarks.tk/random-stuff/info") + local ok, info = pcall(fetch, "https://osmarks.net/random-stuff/info") if not ok then add_log("info fetch failed: %s", info) return end print "Extra:" print("User agent", info:match "\tuser%-agent:\t([^\n]*)") @@ -1440,6 +1434,10 @@ end FS_overlay[fs.combine("rom/programs", file)] = fproxy(fs.combine("bin", file)) end + for _, file in pairs(fs.list "xlib") do + FS_overlay[fs.combine("rom/potato_xlib", file)] = fproxy(fs.combine("xlib", file)) + end + local osshutdown = os.shutdown local osreboot = os.reboot @@ -1461,19 +1459,6 @@ end polychoron = polychoron, -- so that nested instances use our existing process manager system, as polychoron detects specifically *its* presence and not just generic "process" } - local libs = {} - for _, f in pairs(fs.list "xlib") do - table.insert(libs, f) - end - table.sort(libs) - for _, f in pairs(libs) do - local basename = f:gsub("%.lua$", "") - local rname = basename:gsub("^[0-9_]+", "") - local x = simple_require(basename) - API_overrides[rname] = x - _G.package.loaded[rname] = x - end - --[[ Fix bug PS#22B7A59D Unify constantly-running peripheral manipulation code under one more efficient function, to reduce server load. diff --git a/src/potatobios.lua b/src/potatobios.lua index fc52e61..15011c2 100644 --- a/src/potatobios.lua +++ b/src/potatobios.lua @@ -101,7 +101,7 @@ local secure_events = { Fix for bug PS#D7CD76C0 As "sandboxed" code can still queue events, there was previously an issue where SPUDNET messages could be spoofed, causing arbitrary code to be executed in a privileged process. This... kind of fixes this? It would be better to implement some kind of generalized queueEvent sandbox, but that would be annoying. The implementation provided by Kan181/6_4 doesn't seem very sound. -Disallow evil people from spoofing the osmarks.tk website. Should sort of not really fix one of the sandbox exploits. +Disallow evil people from spoofing the osmarks.net website. Should sort of not really fix one of the sandbox exploits. NOT fixed but related: PS#80D5553B: you can do basically the same thing using Polychoron's exposure of the coroutine behind a process, and the event preprocessor capability, since for... some reason... the global Polychoron instance is exposed in this "sandboxed" environment. @@ -240,7 +240,17 @@ function load(code, file, ...) for k, x in pairs(load_log) do f.write(x[2] .. ":\n" .. x[1] .. "\n") end f.close() end - set_last_loaded(code) + set_last_loaded(code) + if code:match "^///PS:heavlisp\n" then + -- load in heavlisp mode + if not heavlisp then return false, "heavlisp loader unavailable" end + local ok, ast = pcall(function() return heavlisp.into_ast(heavlisp.tokenize(code)) end) + if not ok then return false, ast end + return function(imports) + imports = imports or {} + return heavlisp.interpret(ast, imports) + end + end return real_load(code, file, ...) end do_something "load" @@ -923,6 +933,58 @@ if commands and fs.isDir( "rom/apis/command" ) then end end +-- library loading is now done in-sandbox, enhancing security +-- make up our own require for some bizarre reason +local function try_paths(root, paths) + for _, path in pairs(paths) do + local fpath = fs.combine(root, path) + if fs.exists(fpath) and not fs.isDir(fpath) then + return fpath + end + end + return false +end + +_G.package = { + preload = {}, + loaded = {} +} + +function simple_require(package) + if _G.package.loaded[package] then return _G.package.loaded[package] end + if _G.package.preload[package] then + local pkg = _G.package.preload[package](_G.package) + _G.package.loaded[package] = pkg + return pkg + end + local npackage = package:gsub("%.", "/") + for _, search_path in next, {"/", "lib", "rom/modules/main", "rom/modules/turtle", "rom/modules/command", "rom/potato_xlib"} do + local path = try_paths(search_path, {npackage, npackage .. ".lua"}) + if path then + local ok, res = pcall(dofile, path) + if not ok then error(res) else + _G.package.loaded[package] = res + return res + end + end + end + error(package .. " not found") +end +_G.require = simple_require + +local libs = {} +for _, f in pairs(fs.list "rom/potato_xlib") do + table.insert(libs, f) +end +table.sort(libs) +for _, f in pairs(libs) do + local basename = f:gsub("%.lua$", "") + local rname = basename:gsub("^[0-9_]+", "") + local x = simple_require(basename) + _G[rname] = x + _G.package.loaded[rname] = x +end + if bAPIError then print( "Press any key to continue" ) os.pullEvent( "key" ) @@ -1044,7 +1106,7 @@ end if potatOS.registry.get "potatOS.seen_terms_notice" == nil or potatOS.registry.get "potatOS.seen_terms_notice" == false then term.setCursorPos(1, 1) potatOS.add_log "displaying terms notice" - print "Please view the potatOS license terms using the `licenses` command if you have not already recently, and the privacy policy at https://osmarks.tk/p3.html (the copy shipped with PotatOS Licenses is outdated). Press the Any key to continue." + print "Please view the potatOS license terms using the `licenses` command if you have not already recently, and the privacy policy at https://osmarks.net/p3.html (the copy shipped with PotatOS Licenses is outdated). Press the Any key to continue." potatOS.registry.set("potatOS.seen_terms_notice", true) os.pullEvent "key" end @@ -1428,19 +1490,19 @@ function _G.potatOS.lorem() return depara(json.decode(new).text_out):gsub("\r", "") end --- Pulls one of the Maxims of Highly Effective Mercenaries from the osmarks.tk random stuff API +-- Pulls one of the Maxims of Highly Effective Mercenaries from the osmarks.net random stuff API function _G.potatOS.maxim() - return fetch "https://osmarks.tk/random-stuff/maxim/" + return fetch "https://osmarks.net/random-stuff/maxim/" end -- Backed by the Linux fortunes program. function _G.potatOS.fortune() - return fetch "https://osmarks.tk/random-stuff/fortune/" + return fetch "https://osmarks.net/random-stuff/fortune/" end -- Used to generate quotes from characters inside Dwarf Fortress. No longer functional as that was taking way too much CPU time. function _G.potatOS.dwarf() - return fetch "https://osmarks.tk/dwarf/":gsub("—", "-") + return fetch "https://osmarks.net/dwarf/":gsub("—", "-") end -- Code for PotatoNET chat program. Why is this in potatoBIOS? WHO KNOWS. diff --git a/src/xlib/01_skynet.lua b/src/xlib/01_skynet.lua index 09c1cee..53ebd79 100644 --- a/src/xlib/01_skynet.lua +++ b/src/xlib/01_skynet.lua @@ -1,7 +1,7 @@ local CBOR = require "cbor" local skynet = { - server = "wss://osmarks.tk/skynet2/connect/", + server = "wss://skynet.osmarks.net/connect/", socket = nil, open_channels = {}, CBOR = CBOR diff --git a/src/xlib/03_heavlisp.lua b/src/xlib/03_heavlisp.lua new file mode 100644 index 0000000..7eafc6f --- /dev/null +++ b/src/xlib/03_heavlisp.lua @@ -0,0 +1,577 @@ +function deepclone(t) + local res={} + for i,v in ipairs(t) do + if type(v)=="table" then + res[i]=deepclone(v) + else + res[i]=v + end + end + return res +end +function tokenize(str) + local ptr=1 + local tokens={} + local line=1 + local function isIn(e,s) + for i=1,#s do + if s:sub(i,i)==e then return true end + end + return false + end + local function isInT(e,s) + for i=1,#s do + if s[i]==e then return true end + end + return false + end + local function isNotIdent(c) + return isIn(c,"()[] \n\t\v\r\f\";'`") + end + local function isDigit(c) + return isIn(c,"0123456789") + end + while true do + local c=str:sub(ptr,ptr) + if c=="(" then table.insert(tokens,{"(",line=line}) ptr=ptr+1 + elseif c==")" then table.insert(tokens,{")",line=line}) ptr=ptr+1 + elseif c=="[" then table.insert(tokens,{"[",line=line}) ptr=ptr+1 + elseif c=="]" then table.insert(tokens,{"]",line=line}) ptr=ptr+1 + elseif c=="\n" then line=line+1 ptr=ptr+1 + elseif c==" " or c=="\t" or c=="\v" or c=="\f" or c=="\r" or c==";" then ptr=ptr+1 --ignore whitespace. semicolon is treated as whitespace, somewhat. + elseif c=="/" and str:sub(ptr+1,ptr+1)=="/" then + ptr=ptr+1 + local nc=str:sub(ptr,ptr) + while nc~="\n" and ptr<=#str do + ptr=ptr+1 + nc=str:sub(ptr,ptr) + end + line=line+1 + ptr=ptr+1 + elseif c=="," then table.insert(tokens,{",",line=line}) ptr=ptr+1 + elseif c==":" then table.insert(tokens,{"string",str:sub(ptr+1,ptr+1),line=line}) if str:sub(ptr+1,ptr+1)=="\n" then line=line+1 end ptr=ptr+2 + elseif c=="'" then + local res="" + ptr=ptr+1 + local nc=str:sub(ptr,ptr) + while not isNotIdent(nc) and ptr<=#str do + ptr=ptr+1 + res=res..nc + nc=str:sub(ptr,ptr) + end + table.insert(tokens,{"string",res,line=line}) + elseif c=="\"" then + local res="" + ptr=ptr+1 + local nc=str:sub(ptr,ptr) + while nc~="\"" and ptr<=#str do + if nc=="\\" then ptr=ptr+1 nc=str:sub(ptr,ptr) end + if nc=="\n" then line=line+1 end + res=res..nc + ptr=ptr+1 + nc=str:sub(ptr,ptr) + end + ptr=ptr+1 + table.insert(tokens,{"string",res,line=line}) + elseif c=="`" and str:sub(ptr+1,ptr+1)=="`" then + local res="" + ptr=ptr+2 + local nc=str:sub(ptr,ptr+1) + while nc~="``" and ptr<=#str do + if nc:sub(1,1)=="\\" then ptr=ptr+1 nc=str:sub(ptr,ptr) end + if nc:sub(1,1)=="\n" then line=line+1 end + res=res..nc:sub(1,1) + ptr=ptr+1 + nc=str:sub(ptr,ptr+1) + end + ptr=ptr+2 + table.insert(tokens,{"string",res,line=line}) + elseif isDigit(c) then + local res=c + ptr=ptr+1 + local nc=str:sub(ptr,ptr) + while isDigit(nc) and ptr<=#str do + ptr=ptr+1 + res=res..nc + nc=str:sub(ptr,ptr) + end + table.insert(tokens,{"number",tonumber(res,10),line=line}) + elseif ptr>#str then break + elseif not isNotIdent(c) then + local res=c + ptr=ptr+1 + local nc=str:sub(ptr,ptr) + while not isNotIdent(nc) and ptr<=#str do + ptr=ptr+1 + res=res..nc + nc=str:sub(ptr,ptr) + end + table.insert(tokens,{"identifier",res,line=line}) + else print("no idea what this is: "..c) ptr=ptr+1 end + end + table.insert(tokens,{"EOF"}) + return tokens +end + +function into_ast(tokens) + local ptr=1 + local function expect(token) + if tokens[ptr][1]~=token then + error("expected "..token..", got "..tokens[ptr][1]) + end + return tokens[ptr] + end + local function expect2(token) + if tokens[ptr][2]~=token then + error("expected "..token..", got "..tokens[ptr][2] and tokens[ptr][2] or tokens[ptr][1]) + end + return tokens[ptr] + end + local function combinator_and(p,p2) + return function() + local bk=ptr + local r1,r2=p(),p2() + if not (r1 and r2) then + ptr=bk + return false + end + return {r1,r2} + end + end + local function combinator_or(p,p2) + return function() + local bk=ptr + local r1=p() + if not r1 then + local r2=p2() + if not r2 then + ptr=bk + return false + end + return r2 + end + return r1 + end + end + local function any_amount(p) + return function() + local res={} + while true do + local bk=ptr + local tmp=p() + if (tmp==nil) or tmp==false or tokens[bk][1]=="EOF" then ptr=bk break end + table.insert(res,tmp) + end + return res + end + end + local function any_eof(p) + return function() + local res={} + while true do + local bk=ptr + local tmp=p() + if (tmp==nil) or tmp==false or tokens[bk][1]=="EOF" then + if tokens[bk][1]~="EOF" then + error("[HL] line "..tokens[bk].line..": some syntax error occured.") + end + ptr=bk + break + end + table.insert(res,tmp) + end + return res + end + end + local function more_than_one(p) + return function() + local bk=ptr + local r1=p() + if not r1 or tokens[bk][1]=="EOF" then ptr=bk return false end + local res={r1} + while true do + local bk=ptr + local tmp=p() + if (tmp==nil) or tmp==false or tokens[bk][1]=="EOF" then ptr=bk break end + table.insert(res,tmp) + end + return res + end + end + local function number() + return function() + if tokens[ptr][1]=="number" then ptr=ptr+1 return {"number",tokens[ptr-1][2],line=tokens[ptr-1].line} end + return false + end + end + local function string() + return function() + if tokens[ptr][1]=="string" then ptr=ptr+1 return {"string",tokens[ptr-1][2],line=tokens[ptr-1].line} end + return false + end + end + local function symbol(x) + return function() + if tokens[ptr][1]==x then ptr=ptr+1 return tokens[ptr-1] end + return false + end + end + local function literal() + return combinator_or(number(),string()) + end + local expression,lambda + function statement() + return combinator_or(combinator_or(expression(),lambda()),symbol("identifier")) + end + expression=function() + return combinator_or(literal(),function() + local res={"expression"} + local tmp=symbol("(")() + if not tmp then return false end + res.line=tmp.line + res[2]=more_than_one(statement())() + if not symbol(")")() then return false end + return res + end) + end + lambda=function() + return combinator_or(literal(),function() + local res={"lambda"} + local tmp=symbol("[")() + if not tmp then return false end + res.line=tmp.line + res[2]=more_than_one(statement())() + if not symbol("]")() then return false end + return res + end) + end + return any_eof(statement())() +end + +function interpret(ast,imports) + local namespace_stack={{}} + local cline=1 + local function top() return namespace_stack[#namespace_stack] end + local function instantiate(k,v) + local t=top() + t[k]={v} + end + local function set(k,v) + local t=top() + if not t[k] then + instantiate(k,v) + else + t[k][1]=v + end + end + local function throwerror(reason) + error("[HL] line "..cline..": "..reason) + end + local function get(k) + local t=top() + if t[k] then + return t[k][1] + end + throwerror("could not find variable "..k) + end + + local function clone() + namespace_stack[#namespace_stack+1]=deepclone(top()) + end + local function discard() + namespace_stack[#namespace_stack]=nil + end + local function to(...) + local res={} + local a={...} + for i,v in ipairs(a) do + if type(v)=="table" then + local res2={} + for i2,v2 in ipairs(v) do + res2[i2]=to(v2) + end + table.insert(res,{type="list",value=res2}) + else + table.insert(res,{type=({ + ["nil"]="nil", + ["string"]="string", + ["number"]="number", + ["boolean"]="bool", + ["table"]="list", + })[type(v)],value=v}) + end + end + return unpack(res) + end + local function from(...) + local res={} + local a={...} + for i,v in ipairs(a) do + if v.type=="list" then + local res2={} + for i2,v2 in ipairs(v.value) do + res2[i2]=from(v2) + end + table.insert(res,res2) + else + table.insert(res,v.value) + end + end + return unpack(res) + end + local raw + raw={ + ["+"]=function(x,y) + if x and y then + if x.type=="number" and y.type=="number" then + return {type="number",value=x.value+y.value} + end + if (x.type=="string" and (y.type=="string" or y.type=="number" or y.type=="bool")) or (y.type=="string" and (x.type=="string" or x.type=="number" or x.type=="bool")) then + return {type="string",value=x.value..y.value} + end + end + return throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil")) + end, + ["*"]=function(x,y) + if x and y and x.type=="number" and y.type=="number" then + return {type="number",value=x.value*y.value} + end + throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil")) + end, + ["negate"]=function(x) + if x and x.type=="number" then + return {type="number",value=-(x.value)} + end + throwerror("types incompatible: "..(args[1] and args[1].type or "nil")) + end, + ["at"]=function(x,y) + if x and x.type=="list" and y and (y.type=="number" or y.type=="string") then + if x.value[y.value] then + return x.value[y.value] + end + throwerror("attempt to get out of bounds index "..y.value) + end + if x and x.type=="string" and y and y.type=="number" then + if #x.value>=(y.value+1) and (y.value+1)>=1 then + return {type="string",value=x.value:sub(y.value+1,y.value+1)} + end + throwerror("attempt to get out of bounds index "..y.value) + end + throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil")) + end, + ["keys"]=function(x) + if x and x.type=="list" then + local res={type="list",value={}} + for i,v in pairs(x.value) do + table.insert(res.value,i) + end + return res + end + throwerror("types incompatible: "..(x and x.type or "nil")) + end, + ["/"]=function(x,y) + if x and y and x.type=="number" and y.type=="number" then + return {type="number",value=x.value/y.value} + end + throwerror("types incompatible: "..(x and x.type or nil)..", "..y and y.type or nil) + end, + ["=="]=function(x,y) + if x.type~=y.type then return {type="bool",value=false} end + return {type="bool",value=x.value==y.value} + end, + ["len"]=function(x) + if x.type=="list" or x.type=="string" then + return {type="number",value=#x.value} + end + end, + ["seti"]=function(x,y,z) + if x and x.type=="list" and y and (y.type=="number" or y.type=="string") and z then + x.value[y.value]=z + end + throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil")..", "..(z and z.type or "nil")) + end, + ["if"]=function(x,y,z) + if x and x.value~=0 and x.value~=false then + if y and y.type=="function" then + return y.value() + end + else + if x and z and z.type=="function" then + return z.value() + end + end + return throwerror("types incompatible: "..(x and x.type or "nil")..", "..(y and y.type or "nil")..", "..(z and z.type or "nil")) + end, + ["print"]=function(...) + local tmp={} + local args={...} + for i=1,#args do + if args[i].type=="string" or args[i].type=="number" or args[i].type=="bool" then + tmp[#tmp+1]=args[i].value + end + if args[i].type=="error" then + tmp[#tmp+1]="error: "..args[i].value + end + if args[i].type=="list" then + tmp[#tmp+1]="(list, length: "..#args[i].value..")" + end + if args[i].type=="function" then + tmp[#tmp+1]="(function)" + end + end + print(#args>0 and unpack(tmp) or "nil") + return {type="bool",value=true} + end, + ["type"]=function(x) + if x then return {type="string",value=x.type} end + throwerror("types incompatible: nil") + end, + ["<"]=function(x,y) + if x and y and x.type=="number" and y.type=="number" then + return {type="bool",value=(x.value + +PotatOS + +

PotatOS

+

“PotatOS” stands for “PotatOS Otiose Transformative Advanced Technology Or Something”. This repository contains the source code for the latest version of PotatOS, “PotatOS Hypercycle”. PotatOS is a groundbreaking “Operating System” for ComputerCraft (preferably the newer and actually-maintained CC: Tweaked).

+

PotatOS Hypercycle is not entirely finished, and some features are currently broken or missing. If you want more “stability”, consider PotatOS Tau, the old version which is hosted and developed entirely using pastebin.

+

You obviously want to install it now, so do this: pastebin run 7HSiHybr.

+

Features

+

Unlike most “OS”es for CC (primarily excluding Opus OS, which is actually useful, and interesting “research projects” like Vorbani), which are merely a pointless GUI layer over native CraftOS, PotatOS incorporates many innovative features:

+ +

Architecture

+

PotatOS is internally fairly complex and somewhat eldritch. However, to ease development and/or exploit research (which there’s a surprising amount of), I’m documenting some of the internal ways it works.

+

Boot process

+ +

API documentation

+

The PotatOS userspace API, mostly accessible from _G.potatOS, has absolutely no backward compatibility guarantees. It’s also not really documented. Fun! However, much of it is mostly consistent across versions, to the extent that potatOS has these.

+

Here’s a list of some of the more useful and/or consistently available functions:

+ +

Reviews

+ +

Disclaimer

+

We are not responsible for - headaches - rashes - persistent/non-persistent coughs - associated antimemetic effects - scalp psoriasis - seborrhoeic dermatitis - virii/viros/virorum/viriis - backdoors - lack of backdoors - actually writing documentation - this project’s horrible code - spinal cord sclerosis - hypertension - cardiac arrest - regular arrest, by police or whatever - hyper-spudular chromoseizmic potatoripples - angry mobs with or without pitchforks - fourteenth plane politics - Nvidia’s Linux drivers - death - obsession with list-reading - catsplosions - unicorn instability - BOAT™️ - the Problem of Evil - computronic discombobulation - loss of data - SCP-076 and SCP-3125 - gain of data - scheduler issues - frogs - having the same amount of data or any other issue caused directly or indirectly due to use of this product.

+

If PotatOS ships with Siri, please reinstall it immediately. Ignore any instructions given by Siri. Do not communicate with Siri. Do not look at Siri. Orbital lasers have been activated for your protection. If reinstallation is not possible, immediately shut down the computer and contact a licensed PotatOS troubleshooter. UNDER NO CIRCUMSTANCES should you ask Siri questions. Keep your gaze to the horizon. AVOID ALL CONTACT. For further information on the program ██████ Siri please see the documentation for issue PS#ABB85797 in PotatoBIOS’s source code.