1
0
forked from osmarks/potatOS

experimental heavlisp integration

update URLs
This commit is contained in:
osmarks 2021-01-29 17:46:20 +00:00
parent 99a9d3de6e
commit 3596824ffb
9 changed files with 1252 additions and 495 deletions

View File

@ -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. 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. 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 ## 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: 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:

View File

@ -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} {"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":"38483033b4969ebff82a55d6ac62d31da30ee38378cfdcff600980fe54aa8acd","sig":"109588062d87c0e573d85e89f2e73fb27c51e398323f58b7338fa06314ad1b5a687eab78cb9b2793021b"} {"hash":"85d0edfacbdca311a8f18aebd88ae0a5c2e184f57db18285d22a3ea4922b1680","sig":"6b665266304ef6f04791163cff4c46d7ae91ca153738acfe09c3de9703e8bc274202f44d2ea3c939ba3a"}

View File

@ -18,6 +18,7 @@ if process then
tostring(event[4]), tostring(event[4]),
tostring(os.epoch("utc")), tostring(os.epoch("utc")),
tostring({}), tostring({}),
math.random()
} }
entropy = table.concat(entropy, "|") entropy = table.concat(entropy, "|")
@ -37,4 +38,4 @@ os.urandom = function()
state = sha256.digest(state) state = sha256.digest(state)
return result return result
end end

View File

@ -1,458 +1,479 @@
-- Deep-copy a table -- Deep-copy a table
local function copy(tabl) local function copy(tabl)
local new = {} local new = {}
for k, v in pairs(tabl) do for k, v in pairs(tabl) do
if type(v) == "table" and tabl ~= v then if type(v) == "table" and tabl ~= v then
new[k] = copy(v) new[k] = copy(v)
else else
new[k] = v new[k] = v
end end
end end
return new return new
end end
-- Deep-map all values in a table -- Deep-map all values in a table
local function deepmap(table, f, path) local function deepmap(table, f, path)
local path = path or "" local path = path or ""
local new = {} local new = {}
for k, v in pairs(table) do for k, v in pairs(table) do
local thisp = path .. "." .. k local thisp = path .. "." .. k
if type(v) == "table" and v ~= table then -- bodge it to not stackoverflow if type(v) == "table" and v ~= table then -- bodge it to not stackoverflow
new[k] = deepmap(v, f, thisp) new[k] = deepmap(v, f, thisp)
else else
new[k] = f(v, k, thisp) new[k] = f(v, k, thisp)
end end
end end
return new return new
end end
-- Takes a list of keys to copy, returns a function which takes a table and copies the given keys to a new table -- 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) local function copy_some_keys(keys)
return function(from) return function(from)
local new = {} local new = {}
for _, key_to_copy in pairs(keys) do for _, key_to_copy in pairs(keys) do
local x = from[key_to_copy] local x = from[key_to_copy]
if type(x) == "table" then if type(x) == "table" then
x = copy(x) x = copy(x)
end end
new[key_to_copy] = x new[key_to_copy] = x
end end
return new return new
end end
end end
-- Simple string operations -- Simple string operations
local function starts_with(s, with) local function starts_with(s, with)
return string.sub(s, 1, #with) == with return string.sub(s, 1, #with) == with
end end
local function ends_with(s, with) local function ends_with(s, with)
return string.sub(s, -#with, -1) == with return string.sub(s, -#with, -1) == with
end end
local function contains(s, subs) local function contains(s, subs)
return string.find(s, subs) ~= nil return string.find(s, subs) ~= nil
end end
-- Maps function f over table t. f is passed the value and key and can return a new value and key. -- 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 function map(f, t)
local mapper = function(t) local mapper = function(t)
local new = {} local new = {}
for k, v in pairs(t) do for k, v in pairs(t) do
local new_v, new_k = f(v, k) local new_v, new_k = f(v, k)
new[new_k or k] = new_v new[new_k or k] = new_v
end end
return new return new
end end
if t then return mapper(t) else return mapper end if t then return mapper(t) else return mapper end
end end
-- Copies stuff from t2 into t1 -- Copies stuff from t2 into t1
local function add_to_table(t1, t2) local function add_to_table(t1, t2)
for k, v in pairs(t2) do for k, v in pairs(t2) do
if type(v) == "table" and v ~= t2 and v ~= t1 then if type(v) == "table" and v ~= t2 and v ~= t1 then
if not t1[k] then t1[k] = {} end if not t1[k] then t1[k] = {} end
add_to_table(t1[k], v) add_to_table(t1[k], v)
else else
t1[k] = v t1[k] = v
end end
end end
end end
-- Convert path to canonical form -- Convert path to canonical form
local function canonicalize(path) local function canonicalize(path)
return fs.combine(path, "") return fs.combine(path, "")
end end
-- Checks whether a path is in a directory -- Checks whether a path is in a directory
local function path_in(p, dir) local function path_in(p, dir)
return starts_with(canonicalize(p), canonicalize(dir)) return starts_with(canonicalize(p), canonicalize(dir))
end end
local function make_mappings(root) local function make_mappings(root)
return { return {
["/disk"] = "/disk", ["/disk"] = "/disk",
["/rom"] = "/rom", ["/rom"] = "/rom",
default = root default = root
} }
end end
local function get_root(path, mappings) local function get_root(path, mappings)
for mapfrom, mapto in pairs(mappings) do for mapfrom, mapto in pairs(mappings) do
if path_in(path, mapfrom) then if path_in(path, mapfrom) then
return mapto, mapfrom return mapto, mapfrom
end end
end end
return mappings.default, "/" return mappings.default, "/"
end end
-- Escapes lua patterns in a string. Should not be needed, but lua is stupid so the only string.replace thing is gsub -- 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 quotepattern = '(['..("%^$().[]*+-?"):gsub("(.)", "%%%1")..'])'
local function escape(str) local function escape(str)
return str:gsub(quotepattern, "%%%1") return str:gsub(quotepattern, "%%%1")
end end
local function strip(p, root) local function strip(p, root)
return p:gsub("^" .. escape(canonicalize(root)), "") return p:gsub("^" .. escape(canonicalize(root)), "")
end end
local function resolve_path(path, mappings) local function resolve_path(path, mappings)
local root, to_strip = get_root(path, mappings) local root, to_strip = get_root(path, mappings)
local newpath = strip(fs.combine(root, path), to_strip) local newpath = strip(fs.combine(root, path), to_strip)
if path_in(newpath, root) then return newpath end if path_in(newpath, root) then return newpath end
return resolve_path(newpath, mappings) return resolve_path(newpath, mappings)
end end
local function segments(path) local function segments(path)
local segs, rest = {}, "" local segs, rest = {}, canonicalize(path)
repeat if rest == "" then return {} end -- otherwise we'd get "root" and ".." for some broken reason
table.insert(segs, 1, fs.getName(rest)) repeat
rest = fs.getDir(rest) table.insert(segs, 1, fs.getName(rest))
until rest == "" rest = fs.getDir(rest)
return segs until rest == ""
end return segs
end
local function combine(segs)
local out = "" local function combine(segs)
for _, p in pairs(segs) do local out = ""
out = fs.combine(out, p) for _, p in pairs(segs) do
end out = fs.combine(out, p)
return out end
end return out
end
local function difference(p1, p2)
local s1, s2 = segments(p1), segments(p2) -- magic from http://lua-users.org/wiki/SplitJoin
if #s2 == 0 then return combine(s1) end -- split string into lines
local segs = {} local function lines(str)
for _, p in pairs(s1) do local t = {}
local item = table.remove(s1, 1) local function helper(line)
table.insert(segs, item) table.insert(t, line)
if p == s2[1] then break end return ""
end end
return combine(segs) helper((str:gsub("(.-)\r?\n", helper)))
end return t
end
-- magic from http://lua-users.org/wiki/SplitJoin
-- split string into lines -- Fetch the contents of URL "u"
local function lines(str) local function fetch(u)
local t = {} local h = http.get(u)
local function helper(line) local c = h.readAll()
table.insert(t, line) h.close()
return "" return c
end end
helper((str:gsub("(.-)\r?\n", helper)))
return t -- Make a read handle for a string
end local function make_handle(text)
local lines = lines(text)
-- Fetch the contents of URL "u" local h = {line = 0}
local function fetch(u) function h.close() end
local h = http.get(u) function h.readLine() h.line = h.line + 1 return lines[h.line] end
local c = h.readAll() function h.readAll() return text end
h.close() return h
return c end
end
-- Get a path from a filesystem overlay
-- Make a read handle for a string local function path_in_overlay(overlay, path)
local function make_handle(text) return overlay[canonicalize(path)]
local lines = lines(text) end
local h = {line = 0}
function h.close() end local this_level_env = _G
function h.readLine() h.line = h.line + 1 return lines[h.line] end
function h.readAll() return text end -- Create a modified FS table which confines you to root and has some extra read-only pseudofiles.
return h local function create_FS(root, overlay)
end local mappings = make_mappings(root)
-- Get a path from a filesystem overlay local vfstree = {
local function path_in_overlay(overlay, path) mount = "potatOS",
return overlay[canonicalize(path)] children = {
end ["disk"] = { mount = "disk" },
["rom"] = { mount = "rom" },
local this_level_env = _G ["virtual_test"] = { virtual = "bees" }
}
-- 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 function resolve(sandbox_path)
local segs = segments(sandbox_path)
local new_overlay = {} local current_tree = vfstree
for k, v in pairs(overlay) do while true do
new_overlay[canonicalize(k)] = v local seg = segs[1]
end if current_tree.children and current_tree.children[seg] then
table.remove(segs, 1)
local function lift_to_sandbox(f, n) current_tree = current_tree.children[seg]
return function(...) else break end
local args = map(function(x) return resolve_path(x, mappings) end, {...}) end
return f(table.unpack(args)) end
end
end local new_overlay = {}
for k, v in pairs(overlay) do
local new = copy_some_keys {"getDir", "getName", "combine"} (fs) new_overlay[canonicalize(k)] = v
end
function new.isReadOnly(path)
return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom") local function lift_to_sandbox(f, n)
end return function(...)
local args = map(function(x) return resolve_path(x, mappings) end, {...})
function new.open(path, mode) return f(table.unpack(args))
if (contains(mode, "w") or contains(mode, "a")) and new.isReadOnly(path) then end
error "Access denied" end
else
local overlay_data = path_in_overlay(new_overlay, path) local new = copy_some_keys {"getDir", "getName", "combine"} (fs)
if overlay_data then
if type(overlay_data) == "function" then overlay_data = overlay_data(this_level_env) end function new.isReadOnly(path)
return make_handle(overlay_data), "YAFSS overlay" return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom")
end end
return fs.open(resolve_path(path, mappings), mode)
end function new.open(path, mode)
end if (contains(mode, "w") or contains(mode, "a")) and new.isReadOnly(path) then
error "Access denied"
function new.exists(path) else
if path_in_overlay(new_overlay, path) ~= nil then return true end local overlay_data = path_in_overlay(new_overlay, path)
return fs.exists(resolve_path(path, mappings)) if overlay_data then
end if type(overlay_data) == "function" then overlay_data = overlay_data(this_level_env) end
return make_handle(overlay_data), "YAFSS overlay"
function new.overlay() end
return map(function(x) return fs.open(resolve_path(path, mappings), mode)
if type(x) == "function" then return x(this_level_env) end
else return x end end
end, new_overlay)
end function new.exists(path)
if path_in_overlay(new_overlay, path) ~= nil then return true end
function new.list(dir) return fs.exists(resolve_path(path, mappings))
local sdir = canonicalize(resolve_path(dir, mappings)) end
local contents = fs.list(sdir)
for opath in pairs(new_overlay) do function new.overlay()
if fs.getDir(opath) == sdir then return map(function(x)
table.insert(contents, fs.getName(opath)) if type(x) == "function" then return x(this_level_env)
end else return x end
end end, new_overlay)
return contents end
end
function new.list(dir)
add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "move", "copy", "delete", "isDriveRoot"} (fs))) local sdir = canonicalize(resolve_path(dir, mappings))
local ocontents = {}
function new.find(wildcard) for opath in pairs(new_overlay) do
local function recurse_spec(results, path, spec) -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua if fs.getDir(opath) == sdir then
local segment = spec:match('([^/]*)'):gsub('/', '') table.insert(ocontents, fs.getName(opath))
local pattern = '^' .. segment:gsub('[*]', '.+'):gsub('?', '.'):gsub("-", "%%-") .. '$' end
end
if new.isDir(path) then local ok, contents = pcall(fs.list, sdir)
for _, file in ipairs(new.list(path)) do -- in case of error (nonexistent dir, probably) return overlay contents
if file:match(pattern) then -- very awful temporary hack until I can get a nicer treeized VFS done
local f = new.combine(path, file) if not ok then
if #ocontents > 0 then return ocontents end
if new.isDir(f) then error(contents)
recurse_spec(results, f, spec:sub(#segment + 2)) else
end for _, v in pairs(ocontents) do
if spec == segment then table.insert(contents, v)
table.insert(results, f) end
end return contents
end end
end end
end
end add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "move", "copy", "delete", "isDriveRoot"} (fs)))
local results = {}
recurse_spec(results, '', wildcard) function new.find(wildcard)
return results local function recurse_spec(results, path, spec) -- From here: https://github.com/Sorroko/cclite/blob/62677542ed63bd4db212f83da1357cb953e82ce3/src/emulator/native_api.lua
end local segment = spec:match('([^/]*)'):gsub('/', '')
local pattern = '^' .. segment:gsub('[*]', '.+'):gsub('?', '.'):gsub("-", "%%-") .. '$'
function new.dump(dir)
local dir = dir or "/" if new.isDir(path) then
local out = {} for _, file in ipairs(new.list(path)) do
for _, f in pairs(new.list(dir)) do if file:match(pattern) then
local path = fs.combine(dir, f) local f = new.combine(path, file)
local to_add = {
n = f, if new.isDir(f) then
t = "f" recurse_spec(results, f, spec:sub(#segment + 2))
} end
if new.isDir(path) then if spec == segment then
to_add.c = new.dump(path) table.insert(results, f)
to_add.t = "d" end
else end
local fh = new.open(path, "r") end
to_add.c = fh.readAll() end
fh.close() end
end local results = {}
table.insert(out, to_add) recurse_spec(results, '', wildcard)
end return results
return out end
end
function new.dump(dir)
function new.load(dump, root) local dir = dir or "/"
local root = root or "/" local out = {}
for _, f in pairs(dump) do for _, f in pairs(new.list(dir)) do
local path = fs.combine(root, f.n) local path = fs.combine(dir, f)
if f.t == "d" then local to_add = {
new.makeDir(path) n = f,
new.load(f.c, path) t = "f"
else }
local fh = new.open(path, "w") if new.isDir(path) then
fh.write(f.c) to_add.c = new.dump(path)
fh.close() to_add.t = "d"
end else
end local fh = new.open(path, "r")
end to_add.c = fh.readAll()
fh.close()
return new end
end table.insert(out, to_add)
end
local allowed_APIs = { return out
"term", end
"http",
"pairs", function new.load(dump, root)
"ipairs", local root = root or "/"
-- getfenv, getfenv are modified to prevent sandbox escapes and defined in make_environment for _, f in pairs(dump) do
"peripheral", local path = fs.combine(root, f.n)
"table", if f.t == "d" then
"string", new.makeDir(path)
"type", new.load(f.c, path)
"setmetatable", else
"getmetatable", local fh = new.open(path, "w")
"os", fh.write(f.c)
"sleep", fh.close()
"pcall", end
"xpcall", end
"select", end
"tostring",
"tonumber", return new
"coroutine", end
"next",
"error", local allowed_APIs = {
"math", "term",
"redstone", "http",
"rs", "pairs",
"assert", "ipairs",
"unpack", -- getfenv, getfenv are modified to prevent sandbox escapes and defined in make_environment
"bit", "peripheral",
"bit32", "table",
"turtle", "string",
"pocket", "type",
"ccemux", "setmetatable",
"config", "getmetatable",
"commands", "os",
"rawget", "sleep",
"rawset", "pcall",
"rawequal", "xpcall",
"~expect", "select",
"__inext", "tostring",
"periphemu", "tonumber",
} "coroutine",
"next",
local gf, sf = getfenv, setfenv "error",
"math",
-- Takes the root directory to allow access to, "redstone",
-- a map of paths to either strings containing their contents or functions returning them "rs",
-- and a table of extra APIs and partial overrides for existing APIs "assert",
local function make_environment(root_directory, overlay, API_overrides) "unpack",
local environment = copy_some_keys(allowed_APIs)(_G) "bit",
"bit32",
environment.fs = create_FS(root_directory, overlay) "turtle",
"pocket",
-- if function is not from within the VM, return env from within sandbox "ccemux",
function environment.getfenv(arg) "config",
local env "commands",
if type(arg) == "number" then return gf() end "rawget",
if not env or type(env._HOST) ~= "string" or not string.match(env._HOST, "YAFSS") then "rawset",
return gf() "rawequal",
else "~expect",
return env "__inext",
end "periphemu",
end }
--[[ local gf, sf = getfenv, setfenv
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. -- Takes the root directory to allow access to,
]] -- a map of paths to either strings containing their contents or functions returning them
function environment.setfenv(fn, env) -- and a table of extra APIs and partial overrides for existing APIs
local nenv = gf(fn) local function make_environment(root_directory, overlay, API_overrides)
if not nenv or type(nenv._HOST) ~= "string" or not string.match(nenv._HOST, "YAFSS") then local environment = copy_some_keys(allowed_APIs)(_G)
return false
end environment.fs = create_FS(root_directory, overlay)
return sf(fn, env)
end -- if function is not from within the VM, return env from within sandbox
function environment.getfenv(arg)
function environment.load(code, file, mode, env) local env
return load(code, file or "@<input>", mode or "t", env or environment) if type(arg) == "number" then return gf() end
end if not env or type(env._HOST) ~= "string" or not string.match(env._HOST, "YAFSS") then
return gf()
if debug then else
environment.debug = copy_some_keys { return env
"getmetatable", end
"setmetatable", end
"traceback",
"getinfo", --[[
"getregistry" Fix PS#AD2A532C
}(debug) 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.
end ]]
function environment.setfenv(fn, env)
environment._G = environment local nenv = gf(fn)
environment._ENV = environment if not nenv or type(nenv._HOST) ~= "string" or not string.match(nenv._HOST, "YAFSS") then
environment._HOST = string.format("YAFSS on %s", _HOST) return false
end
function environment.os.shutdown() return sf(fn, env)
os.queueEvent("power_state", "shutdown") end
while true do coroutine.yield() end
end function environment.load(code, file, mode, env)
return load(code, file or "@<input>", mode or "t", env or environment)
function environment.os.reboot() end
os.queueEvent("power_state", "reboot")
while true do coroutine.yield() end if debug then
end environment.debug = copy_some_keys {
"getmetatable",
add_to_table(environment, copy(API_overrides)) "setmetatable",
"traceback",
return environment "getinfo",
end "getregistry"
}(debug)
local function run(root_directory, overlay, API_overrides, init) end
if type(init) == "table" and init.URL then init = fetch(init.URL) end
init = init or fetch "https://pastebin.com/raw/wKdMTPwQ" environment._G = environment
environment._ENV = environment
local running = true environment._HOST = string.format("YAFSS on %s", _HOST)
while running do
parallel.waitForAny(function() function environment.os.shutdown()
local env = make_environment(root_directory, overlay, API_overrides) os.queueEvent("power_state", "shutdown")
env.init_code = init while true do coroutine.yield() end
end
local out, err = load(init, "@[init]", "t", env)
if not out then error(err) end function environment.os.reboot()
local ok, err = pcall(out) os.queueEvent("power_state", "reboot")
if not ok then printError(err) end while true do coroutine.yield() end
end, end
function()
while true do add_to_table(environment, copy(API_overrides))
local event, state = coroutine.yield "power_state"
if event == "power_state" then -- coroutine.yield behaves weirdly with terminate return environment
if process then end
local this_process = process.running.ID
for _, p in pairs(process.list()) do local function run(root_directory, overlay, API_overrides, init)
if p.parent and p.parent.ID == this_process then if type(init) == "table" and init.URL then init = fetch(init.URL) end
process.signal(p.ID, process.signals.KILL) init = init or fetch "https://pastebin.com/raw/wKdMTPwQ"
end
end local running = true
end while running do
if state == "shutdown" then running = false return parallel.waitForAny(function()
elseif state == "reboot" then return end local env = make_environment(root_directory, overlay, API_overrides)
end env.init_code = init
end
end) local out, err = load(init, "@[init]", "t", env)
end if not out then error(err) end
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 return run

View File

@ -486,7 +486,7 @@ function _G.report_incident(incident, flags, options)
} }
-- Workaround craftos-pc bug by explicitly specifying Content-Length header -- Workaround craftos-pc bug by explicitly specifying Content-Length header
http.request { http.request {
url = "https://osmarks.tk/wsthing/report", url = "https://spudnet.osmarks.net/report",
body = payload, body = payload,
headers = { headers = {
["content-type"] = "application/json", ["content-type"] = "application/json",
@ -511,7 +511,7 @@ shell.run "startup"
local function generate_disk_code() local function generate_disk_code()
local an = copy(ancestry) local an = copy(ancestry)
table.insert(an, os.getComputerID()) 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( return disk_code_template:format(
gen_count + 1, gen_count + 1,
textutils.serialise(an), textutils.serialise(an),
@ -667,8 +667,8 @@ local function websocket_remote_debugging()
local function connect() local function connect()
if ws then ws.close() end if ws then ws.close() end
ws, err = http.websocket "wss://osmarks.tk/wsthing/v4" ws, err = http.websocket "wss://spudnet.osmarks.net/v4"
ws.url = "wss://osmarks.tk/wsthing/v4" ws.url = "wss://spudnet.osmarks.net/v4"
if not ws then add_log("websocket failure %s", err) return false end if not ws then add_log("websocket failure %s", err) return false end
send_packet { type = "identify" } 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.__index = function(_, k) return registry.get(k) end
debug_registry_mt.__newindex = function(_, k, v) return registry.set(k, v) 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) local function fproxy(file)
if fcache[file] then return fcache[file] local ok, t = pcall(fread_comp, file)
else if not ok or not t then return 'printError "Error. Try again later, or reboot, or run upd."' end
local ok, t = pcall(fread_comp, file) return t
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
end end
-- Localize a bunch of variables. Does this help? I have no idea. This is old code. -- 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 end
parallel.waitForAny(function() sleep(0.5) end, parallel.waitForAny(function() sleep(0.5) end,
function() 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 if not ok then add_log("info fetch failed: %s", info) return end
print "Extra:" print "Extra:"
print("User agent", info:match "\tuser%-agent:\t([^\n]*)") 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)) FS_overlay[fs.combine("rom/programs", file)] = fproxy(fs.combine("bin", file))
end 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 osshutdown = os.shutdown
local osreboot = os.reboot 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" 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 Fix bug PS#22B7A59D
Unify constantly-running peripheral manipulation code under one more efficient function, to reduce server load. Unify constantly-running peripheral manipulation code under one more efficient function, to reduce server load.

View File

@ -101,7 +101,7 @@ local secure_events = {
Fix for bug PS#D7CD76C0 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. 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. 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: 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. 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 for k, x in pairs(load_log) do f.write(x[2] .. ":\n" .. x[1] .. "\n") end
f.close() f.close()
end 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, ...) return real_load(code, file, ...)
end end
do_something "load" do_something "load"
@ -923,6 +933,58 @@ if commands and fs.isDir( "rom/apis/command" ) then
end end
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 if bAPIError then
print( "Press any key to continue" ) print( "Press any key to continue" )
os.pullEvent( "key" ) 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 if potatOS.registry.get "potatOS.seen_terms_notice" == nil or potatOS.registry.get "potatOS.seen_terms_notice" == false then
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
potatOS.add_log "displaying terms notice" 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) potatOS.registry.set("potatOS.seen_terms_notice", true)
os.pullEvent "key" os.pullEvent "key"
end end
@ -1428,19 +1490,19 @@ function _G.potatOS.lorem()
return depara(json.decode(new).text_out):gsub("\r", "") return depara(json.decode(new).text_out):gsub("\r", "")
end 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() function _G.potatOS.maxim()
return fetch "https://osmarks.tk/random-stuff/maxim/" return fetch "https://osmarks.net/random-stuff/maxim/"
end end
-- Backed by the Linux fortunes program. -- Backed by the Linux fortunes program.
function _G.potatOS.fortune() function _G.potatOS.fortune()
return fetch "https://osmarks.tk/random-stuff/fortune/" return fetch "https://osmarks.net/random-stuff/fortune/"
end end
-- Used to generate quotes from characters inside Dwarf Fortress. No longer functional as that was taking way too much CPU time. -- 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() function _G.potatOS.dwarf()
return fetch "https://osmarks.tk/dwarf/":gsub("", "-") return fetch "https://osmarks.net/dwarf/":gsub("", "-")
end end
-- Code for PotatoNET chat program. Why is this in potatoBIOS? WHO KNOWS. -- Code for PotatoNET chat program. Why is this in potatoBIOS? WHO KNOWS.

View File

@ -1,7 +1,7 @@
local CBOR = require "cbor" local CBOR = require "cbor"
local skynet = { local skynet = {
server = "wss://osmarks.tk/skynet2/connect/", server = "wss://skynet.osmarks.net/connect/",
socket = nil, socket = nil,
open_channels = {}, open_channels = {},
CBOR = CBOR CBOR = CBOR

577
src/xlib/03_heavlisp.lua Normal file
View File

@ -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<y.value)}
end
throwerror("types incompatible: "..(x and x.type or nil)..", "..y and y.type or nil)
end,
["newvar"]=function(...)
local args={...}
if args[1] and args[1].type=="string" then
if args[3] and args[3].type=="bool" and args[3].value==true then
local tmp=top()
discard()
instantiate(args[1].value,args[2])
namespace_stack[#namespace_stack+1]=tmp
instantiate(args[1].value,args[2])
else
instantiate(args[1].value,args[2])
end
return {type="bool",value=true}
end
return throwerror("types incompatible: "..(args[1] and args[1].type or "nil"))
end,
["var"]=function(...)
local args={...}
if args[1] and args[1].type=="string" then set(args[1].value,args[2]) return {type="bool",value=true} end
return throwerror("types incompatible: "..(args[1] and args[1].type or "nil"))
end,
["extern"]=function(x,y)
if x and x.type=="string" then
local namespace=""
if y and y.type=="string" then
namespace=y.value.."_"
end
local f=io.open(x.value,"r")
if not f then
return {type="bool",value=false}
end
local i=f:read("*a")
f:close()
local xp=interpret(into_ast(tokenize(i)),imports).exports
if xp then
for k,v in pairs(xp) do
instantiate(namespace..k,{type="function",value=v})
end
return {type="bool",value=true}
end
end
return {type="bool",value=false}
end,
}
for i,v in pairs(raw) do
set(i,{type="function",value=v})
end
for i,v in pairs(imports or {}) do
set("lua_"..i,{type="function",value=function(...)
return to(v(from(...)))
end})
end
instantiate("true",{type="bool",value=true})
instantiate("false",{type="bool",value=false})
local function exec(node)
local nodet=node[1]
cline=node.line
if nodet=="string" then return {type="string",value=node[2]} end
if nodet=="number" then return {type="number",value=node[2]} end
if nodet=="identifier" then
local r=get(node[2])
if not r then
return {type="error",value="not found"}
else
return r
end
end
if nodet=="expression" then
local vl=node[2]
local to_call=nil
local list_res={}
for i,v in ipairs(vl) do
local evr=exec(v)
if i==1 and evr.type=="function" then
to_call=evr
else
table.insert(list_res,evr)
end
end
if to_call then
local m=to_call.value(unpack(list_res))
--print(m)
return m
else
return {type="list",value=list_res}
end
end
if nodet=="lambda" then
local vl=node[2]
local to_call=nil
local list_res={}
for i,v in ipairs(vl) do
table.insert(list_res,v)
end
local cscope=top()
return {type="function",value=function(...)
local a={...}
clone()
for i,v in pairs(cscope) do
if not namespace_stack[#namespace_stack][i] then namespace_stack[#namespace_stack][i]=v end
end
for i=1,#a do
instantiate("arg"..i,a[i])
end
instantiate("argc",#a)
local retval=nil
for i,v in ipairs(list_res) do
retval=exec(v)
end
if not retval then
return throwerror("functions cannot have no return value")
end
discard()
return retval
end}
end
print("oh apio oh "..nodet)
end
local rv=nil
for i=1,#ast do
rv=exec(ast[i])
end
local exports={}
local lua_exports={throwerror=throwerror}
if rv and rv.type=="list" then
if rv.value[1] and rv.value[1].value=="exports" then
for i=2,#rv.value,2 do
if rv.value[i] and rv.value[i].type=="string" then
if rv.value[i+1] and rv.value[i+1].type=="function" then
exports[rv.value[i].value]=rv.value[i+1].value
lua_exports[rv.value[i].value]=function(...) return from(rv.value[i+1].value(to(...))) end
end
end
end
end
end
return {returned=rv,exports=exports,lua_exports=lua_exports}
end
local function run(x,lua)
return interpret(into_ast(tokenize(x)),lua)
end
return {run=run, interpret=interpret, into_ast=into_ast, tokenize=tokenize}

109
website/index.html Normal file
View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<meta charset="utf8">
<title>PotatOS</title>
<meta name="description" content="PotatOS Otiose Transformative Advanced Technology Or Something">
<h1 id="potatos">PotatOS</h1>
<p>“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 <a href="https://www.computercraft.info/">ComputerCraft</a> (preferably the newer and actually-maintained <a href="https://tweaked.cc/">CC: Tweaked</a>).</p>
<p>PotatOS Hypercycle is not entirely finished, and some features are currently broken or missing. If you want more “stability”, consider <a href="https://pastebin.com/RM13UGFa">PotatOS Tau</a>, the old version which is hosted and developed entirely using pastebin.</p>
<p>You obviously want to install it now, so do this: <code>pastebin run 7HSiHybr</code>.</p>
<h2 id="features">Features</h2>
<p>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:</p>
<ul>
<li>Fortunes/Dwarf Fortress output (UPDATE: no longer available)/Chuck Norris jokes on boot (wait, IS this a feature?)</li>
<li>(other) viruses (how do you get them in the first place? running random files like this?) cannot do anything particularly awful to your computer - uninterceptable (except by crashing the keyboard shortcut daemon, I guess) keyboard shortcuts allow easy wiping of the non-potatOS data so you can get back to whatever nonsense you do fast</li>
<li>Skynet (rednet-ish stuff over websocket to my server) and Lolcrypt (encoding data as lols and punctuation) built in for easy access!</li>
<li>Convenient OS-y APIs - add keyboard shortcuts, spawn background processes &amp; do “multithreading”-ish stuff.</li>
<li>Great features for other idio- OS designers, like passwords and fake loading (est potatOS.stupidity.loading [time], est potatOS.stupidity.password [password]).</li>
<li>Digits of Tau available via a convenient command (“tau”)</li>
<li>Potatoplex and Loading, both very useful programs, built in (“potatoplex”/“loading”) (potatoplex has many undocumented options)!</li>
<li>Stack traces (yes, I did steal them from MBS)</li>
<li>Remote debugging access for, er, development and stuff (secured, via ECC signing on debugging disks and websocket-only access requiring a key for the other one). Totally not backdoors.</li>
<li>All this <del>useless random junk</del> USEFUL FUNCTIONALITY can autoupdate (this is probably a backdoor)!</li>
<li>EZCopy allows you to easily install potatOS on another device, just by sticking it in the disk drive of any potatOS device!</li>
<li>fs.load and fs.dump - probably helpful somehow.</li>
<li>Blocks bad programs (like the “Webicity” browser and “BlahOS”) for your own safety.</li>
<li>Fully-featured process manager. Very fully-featured. No existing code uses most of the features.</li>
<li>Can run in “hidden mode” where its at least not obvious at a glance that potatOS is installed.</li>
<li>Connects to SPUDNET.</li>
<li>Convenient, simple uninstall with the “uninstall” command.</li>
<li>Turns on any networked potatOS computers!</li>
<li>Edits connected signs to use as ad displays.</li>
<li>A recycle bin.</li>
<li>An exorcise command, which is like delete but better.</li>
<li>Support for a wide variety of Lorem Ipsum.</li>
<li>The PotatOS Registry - Like the Windows one, but better. Edit its contents with “est” (that is not a typod “set”).</li>
<li>A window manager. Its bundled, at least. Not actually <em>tested</em>. Like most of the bundled programs.</li>
<li>5rot26 encryption program.</li>
<li>A license information viewing program!</li>
<li>“b”, a command to print the alphabet.</li>
<li>A command to view the source of any potatOS function.</li>
<li>Advanced sandboxing prevents malicious programs from removing potatOS.</li>
<li>Reimplements the string metatable bug!</li>
<li>A frontend for tryhaskell.org - yes, really…</li>
<li>Groundbreaking new PotatOS Incident Reports system to report incidents to potatOS.</li>
<li>Might be GDPR-compliant!</li>
<li>Reimplements half of the CC BIOS because its <em>simpler</em> than the alternative!</li>
<li>Contains between 0 and 1041058 exploits. Estimation of more precise values is still in progress.</li>
<li>Now organized using “folder” technology and developed in an IDE! Also now has a build process, but no minification.</li>
<li>Integrated logging mechanism for debugging.</li>
<li>Convoluted new update system with signature verification support (not actually used anywhere) and delta-update capabilities.</li>
</ul>
<h2 id="architecture">Architecture</h2>
<p>PotatOS is internally fairly complex and somewhat eldritch. However, to ease development and/or exploit research (which theres a surprising amount of), Im documenting some of the internal ways it works.</p>
<h3 id="boot-process">Boot process</h3>
<ul>
<li>normal ComputerCraft boot process - <code>bios.lua</code> runs <code>rom/programs/shell.lua</code> (or maybe multishell first) runs <code>rom/startup.lua</code> runs <code>startup</code></li>
<li><code>startup</code> is a somewhat customized copy of Polychoron, which uses a top-level coroutine override to crash <code>bios.lua</code>s <code>parallel.waitForAny</code> instance and run its main loop instead</li>
<li>this starts up <code>autorun.lua</code> (which is a compiled bundle of <code>main.lua</code> and <code>lib/*</code>)</li>
<li>some initialization takes place - the screen is reconfigured a bit, SPF is configured, logfiles are opened, a random seed is generated before user code can meddle, some CraftOS-PC configuration settings are set</li>
<li>The update daemon is started, and will check for updates every 300±50 seconds</li>
<li><code>run_with_sandbox</code> runs - if this errors, potatOS will enter a “critical error” state in which it attempts to update after 10 seconds</li>
<li>more initialization occurs - the device UUID is loaded/generated, a FS overlay is generated, the table of potatOS API functions is configured, <code>xlib/*</code> (userspace libraries) are loaded into the userspace environment, <code>netd</code> (the LAN commands/peripheral daemon) starts, the SPUDNET and disk daemons start (unless configured not to)</li>
<li>the main sandbox process starts up</li>
<li>YAFSS (Yet Another File System Sandbox, the sandboxing library in use) generates an environment table from the overrides, FS overlay and other configuration. This is passed as an argument to <code>load</code>, along with the PotatoBIOS code.</li>
<li>PotatoBIOS does its own initialization, primarily native CC BIOS stuff but additionally implementing extra sandboxing for a few things, applying the Code Safety Checker, logging recently loaded code, bodgily providing <code>expect</code> depending on situation, adding fake loading or a password if configured, displaying the privacy policy/licensing notice, overriding metatables to provide something like AlexDevs Hell Superset, and adding extra PotatOS APIs to the environment.</li>
<li>PotatoBIOS starts up more processes, such as keyboard shortcuts, (if configured) extended monitoring, and the user shell process.</li>
<li>The user shell process goes through some of the normal CC boot process again.</li>
</ul>
<h2 id="api-documentation">API documentation</h2>
<p>The PotatOS userspace API, mostly accessible from <code>_G.potatOS</code>, has absolutely no backward compatibility guarantees. Its also not really documented. Fun! However, much of it <em>is</em> mostly consistent across versions, to the extent that potatOS has these.</p>
<p>Heres a list of some of the more useful and/or consistently available functions:</p>
<ul>
<li><code>potatOS.add_log(message: string, ...formattingArgs: any)</code> - add a line to the log file - supports <code>string.format</code>-style formatting</li>
<li><code>potatOS.build -&gt; string</code> - the currently installed potatOS versions build ID (short form)</li>
<li><code>potatOS.chuck_norris() -&gt; string</code> - fetch random Chuck Norris joke from web API</li>
<li><code>potatOS.fortune() -&gt; string</code> - fetch random <code>fortune</code> from web API</li>
<li><code>potatOS.evilify()</code> - mess up 1 in 10 keypresses</li>
<li><code>potatOS.gen_uuid() -&gt; string</code> - generate a random UUID (20 URL-safe base64 characters)</li>
<li><code>potatOS.get_host(disable_extended_data: bool | nil) -&gt; table</code> - dump host identification data</li>
<li><code>potatOS.get_location() -&gt; number, number, number | nil</code> - get GPS location, if available. This is fetched every 60 seconds if GPS and a modem is available</li>
<li><code>potatOS.init_screens()</code> - reset palettes to default</li>
<li><code>potatOS.print_hi()</code> - print the text <code>hi</code></li>
<li><code>potatOS.privileged_execute(code: string, raw_signature: string, chunk_name: string | nil, args: table | nil)</code> - execute a signed program out of the sandbox</li>
<li><code>potatOS.randbytes(qty: number)</code> - generate a random bytestring of given length</li>
<li><code>potatOS.read(filename: string) -&gt; string | bool</code> - read contents of out of sandbox file - if not found, returns false</li>
<li><code>potatOS.register_keyboard_shortcut(keycode: number, handler: () -&gt; nil)</code> - register a function to run when RightCtrl and the specified keycode are pressed.</li>
<li><code>potatOS.registry.get(key: string) -&gt; any | nil</code> - retrieve the value at the given key from the PotatOS Registry at the given key. Returns <code>nil</code> if not found.</li>
<li><code>potatOS.registry.set(key: string, value: any)</code> - set the given key to the given value in the PotatOS Registry. Values must be serializable using PotatOS-BLODS, i.e. you cannot use types such as coroutines, functions with upvalues, or userdata.</li>
<li><code>potatOS.report_incident(text: string, flags: table | nil, options: table | nil)</code> - Report an incident to SPUDNET-PIR. <code>flags</code> is a table of strings which can be used to search for incidents. <code>options</code> may contain the following keys: <code>disable_extended_data</code> (send less information with report), <code>code</code> (code sample to display with nice formatting in UI), and <code>extra_meta</code> (additional informatio to send).</li>
<li><code>potatOS.rot13(x: string) -&gt; string</code> - rot13-encode the given value. Rot13 is a stateless, keyless, symmetric cipher.</li>
<li><code>potatOS.tau -&gt; string</code> - approximately 8101 digits of the mathematical constant τ (tau)</li>
<li><code>potatOS.update()</code> - force a system update</li>
<li><code>potatOS.uuid -&gt; string</code> - get the systems PotatOS UUID. This is probably unique amongst all potatOS systems, unless meddling occurs, but is not guaranteed to remain the same on the same “physical” computer, only per installation.</li>
<li><code>process.spawn(fn: () -&gt; nil, name: string | nil, options: table) -&gt; number</code> - spawn a process using the global Polychoron process manager instance. Returns the ID.</li>
<li><code>process.info(ID: number) -&gt; table</code> - get information about a process, by ID</li>
<li><code>process.list() -&gt; table</code> - get information for all running processes</li>
<li><code>_G.init_code -&gt; string</code> - the source code of the running PotatoBIOS instance</li>
</ul>
<h2 id="reviews">Reviews</h2>
<ul>
<li>“literally just asm but even worse”</li>
<li>“i am an imaginary construct of your mind”</li>
<li>“oh god please dont kill me ill say whatever you want for the review please”</li>
<li>“[ANTIMEME EXPUNGED]”</li>
<li>“why is there an interpret brain[REDACTED] command?”</li>
<li>“pastebin run RM13UGFa”</li>
</ul>
<h2 id="disclaimer">Disclaimer</h2>
<p>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 projects 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 - Nvidias 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.</p>
<p>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 PotatoBIOSs source code.</p>