Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

10 changed files with 151 additions and 307 deletions

View File

@ -93,7 +93,6 @@ Unlike most "OS"es for CC (primarily excluding Opus OS, which is actually useful
- Live threat updates using our advanced algorithms.
- PotatOS Epenthesis' rewritten security model fixes many exploits and adds others while reducing boot times.
- IPC mechanism.
- Virtual filesystems abstraction.
## Architecture

File diff suppressed because one or more lines are too long

View File

@ -1,60 +1,3 @@
local prefixes = {
{-12, "p"},
{-9, "n"},
{-6, "u"},
{-3, "m"},
{0, ""},
{3, "k"},
{6, "M"}
}
local function SI_prefix(value, unit)
local x = math.log(value, 10)
local last
for _, t in ipairs(prefixes) do
if t[1] > x then
break
end
last = t
end
local dp = 2 - math.floor(x - last[1])
return (("%%.%df%%s%%s"):format(dp)):format(value / 10^(last[1]), last[2], unit)
end
local w = term.getSize()
local rows = {}
for _, info in pairs(process.list()) do
table.insert(rows, { info.name or tostring(info.ID), SI_prefix(info.execution_time, "s"), SI_prefix(info.ctime, "s") })
end
local max_width_per_column = {}
for _, row in ipairs(rows) do
for i, cell in ipairs(row) do
max_width_per_column[i] = math.max(max_width_per_column[i] or 0, cell:len() + 1)
end
end
local vw_width = 0
for i = #max_width_per_column, 1, -1 do
if i > 1 then
vw_width = vw_width + max_width_per_column[i]
end
end
local fw_start = w - vw_width
for _, row in ipairs(rows) do
local s
for i, cell in ipairs(row) do
if i == 1 then
s = cell:sub(1, fw_start - 1) .. (" "):rep((fw_start - 1) - cell:len())
else
cell = " " .. cell
s = s .. (" "):rep(max_width_per_column[i] - cell:len()) .. cell
end
end
textutils.pagedPrint(s)
textutils.pagedPrint(("%s %f %f"):format(info.name or info.ID, info.execution_time, info.ctime))
end

View File

@ -24,7 +24,7 @@ print()
print(snd)
print()
if arg == "headless" then
if ccemux then ccemux.echo "ready" end
ccemux.echo "ready"
while true do coroutine.yield() end
else
print "Press a key to continue..."

View File

@ -13,11 +13,6 @@ end
function sandboxlib.dispatch_if_restricted(rkey, original, restricted)
local out = {}
for k, v in pairs(restricted) do
if not original[k] then
out[k] = v
end
end
for k, v in pairs(original) do
out[k] = function(...)
if processrestriction(rkey) then

View File

@ -77,10 +77,31 @@ local function add_to_table(t1, t2)
end
end
local fscombine, fsgetname, fsgetdir = fs.combine, fs.getName, fs.getDir
-- Convert path to canonical form
local function canonicalize(path)
return fscombine(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
@ -93,12 +114,19 @@ 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, fsgetname(rest))
rest = fsgetdir(rest)
table.insert(segs, 1, fs.getName(rest))
rest = fs.getDir(rest)
until rest == ""
return segs
end
@ -106,7 +134,7 @@ end
local function combine(segs)
local out = ""
for _, p in pairs(segs) do
out = fscombine(out, p)
out = fs.combine(out, p)
end
return out
end
@ -151,176 +179,98 @@ end
local this_level_env = _G
-- make virtual filesystem from files (no nested directories for simplicity)
local function vfs_from_files(files)
return {
list = function(path)
if path ~= "" then return {} end
local out = {}
for k, v in pairs(files) do
table.insert(out, k)
end
return out
end,
open = function(path, mode)
return make_handle(files[path])
end,
exists = function(path)
return files[path] ~= nil or path == ""
end,
isReadOnly = function(path)
return true
end,
isDir = function(path)
if path == "" then return true end
return false
end,
getDrive = function(_) return "memory" end,
getSize = function(path)
return #files[path]
end,
getFreeSpace = function() return 0 end,
makeDir = function() end,
delete = function() end,
move = function() end,
copy = function() end,
attributes = function(path)
return {
size = #files[path],
modification = os.epoch "utc"
}
end
}
end
local function create_FS(vfstree)
-- Create a modified FS table which confines you to root and has some extra read-only pseudofiles.
local function create_FS(root, overlay)
local fs = fs
local mappings = make_mappings(root)
local function is_usable_node(node)
return node.mount or node.vfs
end
local vfstree = {
mount = "potatOS",
children = {
["disk"] = { mount = "disk" },
["rom"] = { mount = "rom" },
--["virtual_test"] = { virtual = "bees" }
}
}
local function resolve(sandbox_path, ignore_usability)
local function resolve(sandbox_path)
local segs = segments(sandbox_path)
local current_tree = vfstree
local last_usable_node, last_segs = nil, nil
while true do
if is_usable_node(current_tree) then
last_usable_node = current_tree
last_segs = copy(segs)
end
local seg = segs[1]
if seg and current_tree.children and current_tree.children[seg] then
if current_tree.children and current_tree.children[seg] then
table.remove(segs, 1)
current_tree = current_tree.children[seg]
else break end
end
if ignore_usability then return current_tree, segs end
return last_usable_node, last_segs
end
local function resolve_node_segs(node, segs)
if node.mount then return fs, fscombine(node.mount, combine(segs)) end
return node.vfs, combine(segs)
end
local function resolve_path(sandbox_path)
local node, segs = resolve(sandbox_path)
return resolve_node_segs(node, segs)
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(path)
local vfs, path = resolve_path(path)
return vfs[n](path)
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", "complete"} (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 vfs, path = resolve_path(path)
return vfs.open(path, mode)
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.move(src, dest)
local src_vfs, src_path = resolve_path(src)
local dest_vfs, dest_path = resolve_path(dest)
if src_vfs == dest_vfs then
return src_vfs.move(src_path, dest_path)
else
if src_vfs.isReadOnly(src_path) then error "Access denied" end
new.copy(src, dest)
src_vfs.delete(src_path)
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.copy(src, dest)
local src_vfs, src_path = resolve_path(src)
local dest_vfs, dest_path = resolve_path(dest)
if src_vfs == dest_vfs then
return src_vfs.copy(src_path, dest_path)
else
if src_vfs.isDir(src_path) then
dest_vfs.makeDir(dest_path)
for _, f in pairs(src_vfs.list(src_path)) do
new.copy(fscombine(src, f), fscombine(dest, f))
end
else
local dest_fh = dest_vfs.open(dest_path, "wb")
local src_fh = src_vfs.open(src_path, "rb")
dest_fh.write(src_fh.readAll())
src_fh.close()
dest_fh.close()
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
end
function new.mountVFS(path, vfs)
local node, relpath = resolve(path)
while #relpath > 0 do
local seg = table.remove(relpath, 1)
if not node.children then node.children = {} end
if not node.children[seg] then node.children[seg] = {} end
node = node.children[seg]
end
node.vfs = vfs
end
function new.unmountVFS(path)
local node, relpath = resolve(path)
if #relpath == 0 then
node.vfs = nil
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
error "Not a mountpoint"
end
end
function new.list(path)
local node, segs = resolve(path, true)
local vfs, path = resolve_path(path)
if #segs > 0 then return vfs.list(path) end
local out = {}
local seen = {}
for k, v in pairs(node.children or {}) do
table.insert(out, k)
seen[k] = true
end
for _, v in pairs(vfs.list(path)) do
if not seen[v] then
table.insert(out, v)
for _, v in pairs(ocontents) do
table.insert(contents, v)
end
return contents
end
return out
end
add_to_table(new, map(lift_to_sandbox, copy_some_keys {"isDir", "getDrive", "getSize", "getFreeSpace", "makeDir", "delete", "isDriveRoot", "exists", "isReadOnly", "attributes"} (fs)))
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
@ -360,7 +310,7 @@ local function create_FS(vfstree)
to_add.c = new.dump(path)
to_add.t = "d"
else
local fh = new.open(path, "rb")
local fh = new.open(path, "r")
to_add.c = fh.readAll()
fh.close()
end
@ -377,7 +327,7 @@ local function create_FS(vfstree)
new.makeDir(path)
new.load(f.c, path)
else
local fh = new.open(path, "wb")
local fh = new.open(path, "w")
fh.write(f.c)
fh.close()
end
@ -455,11 +405,30 @@ local gf, sf = getfenv, setfenv
-- 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(API_overrides, current_process)
local env_host = string.format("YAFSS on %s", _HOST)
local environment = copy_some_keys(allowed_APIs)(_G)
-- 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 env._HOST == env_host then
return gf()
else
return env
end
end
-- I sure hope this doesn't readd the security issues!
environment.getfenv = getfenv
environment.setfenv = 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.
]]
function environment.setfenv(fn, env)
local nenv = gf(fn)
if not nenv or type(nenv._HOST) ~= "string" or not nenv._HOST == env_host then
return false
end
return sf(fn, env)
end
local load = load
function environment.load(code, file, mode, env)
@ -468,6 +437,7 @@ local function make_environment(API_overrides, current_process)
environment._G = environment
environment._ENV = environment
environment._HOST = env_host
function environment.os.shutdown()
process.IPC(current_process, "power_state", "shutdown")
@ -515,4 +485,4 @@ local function run(API_overrides, init, logger)
end
end
return { run = run, create_FS = create_FS, vfs_from_files = vfs_from_files }
return { run = run, create_FS = create_FS }

View File

@ -1297,61 +1297,26 @@ local function run_with_sandbox()
end or v
end
local yafss = require "yafss"
local drive_mounts = {
children = {},
vfs = {}
-- Provide many, many useful or not useful programs to the potatOS shell.
local FS_overlay = {
["secret/.pkey"] = fproxy "signing-key.tbl",
["secret/log"] = function() return potatOS_proxy.get_log() end,
-- The API backing this no longer exists due to excessive server load.
-----["/rom/programs/dwarf.lua"] = "print(potatOS.dwarf())",
["/secret/processes"] = function()
return tostring(process.list())
end,
["/rom/heavlisp_lib/stdlib.hvl"] = fproxy "stdlib.hvl"
}
function drive_mounts.vfs.list(path)
local out = {}
for k, v in pairs(drive_mounts.children) do
table.insert(out, k)
end
return out
for _, file in pairs(fs.list "bin") do
FS_overlay[fs.combine("rom/programs", file)] = fproxy(fs.combine("bin", file))
end
function drive_mounts.vfs.exists(path)
return drive_mounts.children[path] ~= nil
for _, file in pairs(fs.list "xlib") do
FS_overlay[fs.combine("rom/potato_xlib", file)] = fproxy(fs.combine("xlib", file))
end
function drive_mounts.vfs.isDir(path) return true end
function drive_mounts.vfs.getDrive(path) return "disks" end
function drive_mounts.vfs.getSize(path) return 0 end
function drive_mounts.vfs.getFreeSpace(path) return 0 end
function drive_mounts.vfs.makeDir(path) end
function drive_mounts.vfs.delete(path) end
function drive_mounts.vfs.isReadOnly(path) return true end
local vfstree = {
mount = "potatOS",
children = {
["rom"] = {
mount = "rom",
children = {
["potatOS_xlib"] = { mount = "/xlib" },
programs = {
children = {
["potatOS"] = { mount = "/bin" }
}
},
["autorun"] = {
vfs = yafss.vfs_from_files {
["fix_path.lua"] = [[shell.setPath("/rom/programs/potatOS:"..shell.path())]],
}
},
["heavlisp_lib"] = {
vfs = yafss.vfs_from_files {
["stdlib.hvl"] = fproxy "stdlib.hvl"
}
}
}
},
["disks"] = drive_mounts
}
}
local API_overrides = {
process = process,
json = json,
@ -1388,18 +1353,6 @@ local function run_with_sandbox()
local computers = {}
local compcount = 0
local signs = {}
local function mount_disk(drive_name)
local mountpoint = peripheral.call(drive_name, "getMountPath")
if mountpoint then
drive_mounts.children[drive_name] = { mount = mountpoint }
end
end
local function unmount_disk(drive_name)
drive_mounts.children[drive_name] = nil
end
local function add_peripheral(name)
local typ = peripheral.getType(name)
if typ == "modem" then
@ -1409,16 +1362,10 @@ local function run_with_sandbox()
compcount = compcount + 1
elseif typ == "minecraft:sign" then
signs[name] = true
elseif typ == "drive" then
if peripheral.call(name, "isDiskPresent") then
mount_disk(name)
end
end
end
for _, name in pairs(peripheral.getNames()) do add_peripheral(name) end
local timer = os.startTimer(1)
while true do
local e, name, channel, _, message = os.pullEvent()
if e == "peripheral" then add_peripheral(name)
@ -1426,8 +1373,7 @@ local function run_with_sandbox()
local typ = peripheral.getType(name)
if typ == "computer" then computers[name] = nil compcount = compcount - 1
elseif typ == "modem" then modems[name] = nil
elseif typ == "minecraft:sign" then signs[name] = nil
elseif typ == "drive" then unmount_disk(name) end
elseif typ == "minecraft:sign" then signs[name] = nil end
elseif e == "modem_message" then
if channel == 62381 and type(message) == "string" then
add_log("netd message %s", message)
@ -1448,19 +1394,17 @@ local function run_with_sandbox()
end
end
timer = os.startTimer(1 + math.random(0, compcount * 2))
elseif e == "disk" then
mount_disk(name)
elseif e == "disk_eject" then
unmount_disk(name)
end
end
end, "netd", { grants = { [notermsentinel] = true }, restrictions = {} })
require "metatable_improvements"(potatOS_proxy.add_log, potatOS_proxy.report_incident)
local yafss = require "yafss"
local fss_sentinel = sandboxlib.create_sentinel "fs-sandbox"
local debug_sentinel = sandboxlib.create_sentinel "constrained-debug"
local sandbox_filesystem = yafss.create_FS(vfstree)
local sandbox_filesystem = yafss.create_FS("potatOS", FS_overlay)
_G.fs = sandboxlib.dispatch_if_restricted(fss_sentinel, _G.fs, sandbox_filesystem)
_G.debug = sandboxlib.allow_whitelisted(debug_sentinel, _G.debug, {
"traceback",

View File

@ -459,18 +459,13 @@ local old_error = error
local old_os_shutdown = os.shutdown
local old_term_redirect = term.redirect
local old_term_native = term.native
local old_printError = printError
function error() end
function term.redirect() end
function term.native() end
function printError() end
function os.shutdown()
error = old_error
_G.error = old_error
_ENV.error = old_error
printError = old_printError
_G.printError = old_printError
_ENV.printError = old_printError
term.native = old_term_native
term.redirect = old_term_redirect
os.shutdown = old_os_shutdown

View File

@ -391,12 +391,6 @@ do
local native = term.native()
local last_redirected
-- horrors
local idmap = {}
local function termid(t)
return idmap[tostring(t.blit)]
end
local ix = 0
process.spawn(function()
while true do
@ -414,11 +408,17 @@ do
ix = ix + 1
process.queue_in(process.get_running().parent, "term_resize", true)
elseif ev == "ipc" and arg2 == "redraw_native" then
potatOS.framebuffers[termid(native)].redraw()
potatOS.framebuffers[native.id].redraw()
end
end
end, "termd")
-- horrors
local idmap = {}
local function termid(t)
return idmap[tostring(t.blit)]
end
local function assignid(t)
if not termid(t) then idmap[tostring(t.blit)] = potatOS.gen_uuid() end
end
@ -573,7 +573,7 @@ local function boot_require(package)
return pkg
end
local npackage = package:gsub("%.", "/")
for _, search_path in next, {"/", "lib", "rom/modules/main", "rom/modules/turtle", "rom/modules/command", "rom/potatOS_xlib"} do
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)
@ -589,7 +589,7 @@ _G.require = boot_require
_ENV.require = boot_require
local libs = {}
for _, f in pairs(fs.list "rom/potatOS_xlib") do
for _, f in pairs(fs.list "rom/potato_xlib") do
table.insert(libs, f)
end
table.sort(libs)
@ -1245,8 +1245,7 @@ function potatOS.llm(prompt, max_tokens, stop_sequences)
local res, err = http.post("https://gpt.osmarks.net/v1/completions", json.encode {
prompt = prompt,
max_tokens = max_tokens,
stop = stop_sequences,
client = "potatOS"
stop = stop_sequences
}, {["content-type"]="application/json"}, true)
if err then
error("Server error: " .. err) -- is this right? I forgot.
@ -1609,7 +1608,7 @@ function potatOS.threat_update()
table.insert(out, description)
table.insert(out, "")
end
return "current threat level is" .. (potatOS.llm(table.concat(out, "\n") .. "\ncurrent threat level is", 100, {"\n\n"}):gsub("^\n", ""):gsub("\n$", ""))
return (potatOS.llm(table.concat(out, "\n"), 100, {"\n\n"}):gsub("^\n", ""):gsub("\n$", ""))
end
local fixed_context = {
@ -1759,7 +1758,7 @@ if potatOS.registry.get "potatOS.immutable_global_scope" then
end
process.spawn(keyboard_shortcuts, "kbsd")
if skynet and http.websocket then process.spawn(skynet.listen, "skynetd") process.spawn(potatoNET, "systemd-potatod") end
if http.websocket then process.spawn(skynet.listen, "skynetd") process.spawn(potatoNET, "systemd-potatod") end
local autorun = potatOS.registry.get "potatOS.autorun"
if type(autorun) == "string" then
autorun = load(autorun)
@ -1781,7 +1780,7 @@ local function run_shell()
term.clear()
term.setCursorPos(1, 1)
potatOS.add_log "starting user shell"
os.run({}, sShell )
os.run( {}, sShell )
end
if potatOS.registry.get "potatOS.extended_monitoring" then process.spawn(excessive_monitoring, "extended_monitoring") end

View File

@ -57,7 +57,7 @@ button {
<h1>Welcome to PotatOS!</h1>
<img src="/potatos.gif" id="im">
<div>
Current build: <code>9b5e8950</code> (new VFS layer), version 815, built 2024-09-03 11:37:01 (UTC).
Current build: <code>361bc871</code> (adjust LLM interface), version 771, built 2024-02-28 19:50:26 (UTC).
</div>
<p>&quot;PotatOS&quot; stands for &quot;PotatOS Otiose Transformative Advanced Technology Or Something&quot;.
<a href="https://git.osmarks.net/osmarks/potatOS">This repository</a> contains the source code for the latest version of PotatOS, &quot;PotatOS Epenthesis&quot;.
@ -132,7 +132,6 @@ AI will transform the ways we work, live, play, think, become paperclips, breath
<li>Live threat updates using our advanced algorithms.</li>
<li>PotatOS Epenthesis' rewritten security model fixes many exploits and adds others while reducing boot times.</li>
<li>IPC mechanism.</li>
<li>Virtual filesystems abstraction.</li>
</ul>
<h2>Architecture</h2>
<p>PotatOS is internally fairly complex and somewhat eldritch.