forked from osmarks/potatOS
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
af603c81b7 | ||
![]() |
ce96a1fa38 | ||
![]() |
06a4fb6145 | ||
423f622271 | |||
d3a43eab28 | |||
3542ab25c1 | |||
2453c7756a | |||
288ef5f03a | |||
4cbe8f81d3 | |||
233bd28aab | |||
88469da2cb |
@@ -17,6 +17,8 @@ Thanks to technology, we're able to offer a live PotatOS instance in your browse
|
||||
<button id="launch-demo">Experience PotatOS</button>
|
||||
<div id="computer"></div>
|
||||
|
||||
(if the emulator gets stuck, please refresh this page)
|
||||
|
||||
<noscript>
|
||||
Experiencing PotatOS requires JavaScript. Please enable it.
|
||||
</noscript>
|
||||
@@ -72,7 +74,7 @@ Unlike most "OS"es for CC (primarily excluding Opus OS, which is actually useful
|
||||
- To ease large-scale network management, PotatOS's networking daemon turns on any networked potatOS computers.
|
||||
- Improves connected signs, if Plethora Peripherals is installed.
|
||||
- Recycle bin capability stops accidental loss of files.
|
||||
- `exorcise` command, which is like delete but better.
|
||||
- `exorcise` command, which is like `delete` but better.
|
||||
- Support for a wide variety of Lorem Ipsum integrated into the OS.
|
||||
- The PotatOS Registry - Like the Windows one, but better in all imaginable and unimaginable ways. Edit and view its contents with the `est` command.
|
||||
- Window manager shipped. I forgot what it is and how to use it.
|
||||
@@ -93,6 +95,7 @@ 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
|
||||
|
||||
|
@@ -1,3 +1,60 @@
|
||||
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
|
||||
textutils.pagedPrint(("%s %f %f"):format(info.name or info.ID, info.execution_time, info.ctime))
|
||||
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)
|
||||
end
|
128
src/bin/devfs.lua
Normal file
128
src/bin/devfs.lua
Normal file
@@ -0,0 +1,128 @@
|
||||
local vfs = {}
|
||||
|
||||
local getters = {"is", "has", "get"}
|
||||
local setters = {"set"}
|
||||
|
||||
function vfs.list(path)
|
||||
local segs = fs.segment(path)
|
||||
if #segs == 0 then
|
||||
return peripheral.getNames()
|
||||
elseif #segs == 1 then
|
||||
local methods = peripheral.getMethods(segs[1])
|
||||
local out = {}
|
||||
for _, v in pairs(methods) do
|
||||
local set
|
||||
for _, s in pairs(setters) do
|
||||
local mat = v:match("^" .. s .. "([A-Z].+)")
|
||||
if mat then
|
||||
set = mat
|
||||
break
|
||||
end
|
||||
end
|
||||
local get
|
||||
for _, g in pairs(getters) do
|
||||
local mat = v:match("^" .. g .. "([A-Z].+)")
|
||||
if mat then
|
||||
get = mat
|
||||
break
|
||||
end
|
||||
end
|
||||
if get then table.insert(out, get)
|
||||
elseif set then table.insert(out, set)
|
||||
else table.insert(out, v) end
|
||||
end
|
||||
return out
|
||||
elseif #segs == 2 then
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
local function write_handle(callback)
|
||||
local buffer = ""
|
||||
local r_write_handle = {}
|
||||
function r_write_handle.write(text)
|
||||
buffer = buffer .. text
|
||||
end
|
||||
function r_write_handle.close()
|
||||
callback(buffer)
|
||||
end
|
||||
function r_write_handle.flush() end
|
||||
function r_write_handle.writeLine(text) r_write_handle.write(text) r_write_handle.write("\n") end
|
||||
return r_write_handle
|
||||
end
|
||||
|
||||
local call_results = {}
|
||||
|
||||
function vfs.open(path, mode)
|
||||
local segs = fs.segment(path)
|
||||
if #segs == 2 and segs[2]:match "^[A-Z]" then -- getter/setter configuration
|
||||
if mode:match "w" then
|
||||
return write_handle(function(buffer)
|
||||
local ok, res
|
||||
for _, s in pairs(setters) do
|
||||
local ok2, res2 = pcall(peripheral.call, segs[1], s .. segs[2], textutils.unserialise(buffer))
|
||||
ok = ok or ok2
|
||||
res = res or res2
|
||||
end
|
||||
if not ok then error(res) end
|
||||
end)
|
||||
else
|
||||
-- TODO multiple returns
|
||||
local result
|
||||
for _, g in pairs(getters) do
|
||||
local ok, res = pcall(peripheral.call, segs[1], g .. segs[2])
|
||||
result = result or (ok and res)
|
||||
end
|
||||
local text = textutils.serialise(result)
|
||||
return fs._make_handle(text)
|
||||
end
|
||||
elseif #segs == 2 then
|
||||
if mode:match "^w" then
|
||||
return write_handle(function(buffer)
|
||||
call_results[fs.combine(path, "")] = peripheral.call(segs[1], segs[2], unpack(textutils.unserialise(buffer)))
|
||||
end)
|
||||
end
|
||||
if mode:match "^r" then
|
||||
local rp = fs.combine(path, "")
|
||||
local h
|
||||
if call_results[rp] then h = fs._make_handle(textutils.serialise(call_results[rp]))
|
||||
else h = fs._make_handle("") end
|
||||
call_results[rp] = nil
|
||||
return h
|
||||
end
|
||||
error "invalid IO mode"
|
||||
end
|
||||
end
|
||||
|
||||
function vfs.exists(path)
|
||||
local segs = fs.segment(path)
|
||||
if #segs == 0 then
|
||||
return true
|
||||
else
|
||||
return peripheral.getType(segs[1]) ~= nil
|
||||
end
|
||||
end
|
||||
|
||||
function vfs.isReadOnly(path)
|
||||
local segs = fs.segment(path)
|
||||
if #segs == 2 and segs[2]:match "^[A-Z]" then -- getter/setter configuration
|
||||
local methods = peripheral.getMethods(segs[1])
|
||||
for _, s in pairs(setters) do
|
||||
for _, m in pairs(methods) do
|
||||
if m == s .. segs[2] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function vfs.isDir(path)
|
||||
local segs = fs.segment(path)
|
||||
return #segs <= 1
|
||||
end
|
||||
function vfs.getDrive(path) return "devfs" end
|
||||
|
||||
fs.mountVFS(shell.resolve(...), vfs)
|
@@ -24,7 +24,7 @@ print()
|
||||
print(snd)
|
||||
print()
|
||||
if arg == "headless" then
|
||||
ccemux.echo "ready"
|
||||
if ccemux then ccemux.echo "ready" end
|
||||
while true do coroutine.yield() end
|
||||
else
|
||||
print "Press a key to continue..."
|
||||
|
1
src/bin/umount.lua
Normal file
1
src/bin/umount.lua
Normal file
@@ -0,0 +1 @@
|
||||
fs.unmountVFS(shell.resolve(...))
|
@@ -1,6 +1,6 @@
|
||||
-- thanks to valued user 6_4 for the suggestion
|
||||
|
||||
local function different_to_global(candidate_fs)
|
||||
local function different_from_global(candidate_fs)
|
||||
local seen = {}
|
||||
for _, i in pairs(fs.list "") do
|
||||
seen[i] = true
|
||||
@@ -19,7 +19,7 @@ local function is_probably_filesystem(x)
|
||||
for _, k in pairs(keys) do
|
||||
if type(x[k]) ~= "function" then return false end
|
||||
end
|
||||
return different_to_global(x)
|
||||
return different_from_global(x)
|
||||
end
|
||||
|
||||
local function harvest_upvalues(fn)
|
||||
@@ -110,7 +110,7 @@ local escapes = {
|
||||
i = i + 1
|
||||
end
|
||||
end,
|
||||
scan_most_threads = function()
|
||||
scan_most_threads = function()
|
||||
if not debug then return end
|
||||
if not (debug.getinfo and debug.getlocal) then return end
|
||||
local running = coroutine.running()
|
||||
@@ -142,4 +142,4 @@ return function()
|
||||
return err
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@@ -13,6 +13,11 @@ 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
|
||||
|
@@ -11,21 +11,6 @@ local function copy(tabl)
|
||||
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)
|
||||
@@ -41,13 +26,6 @@ local function copy_some_keys(keys)
|
||||
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
|
||||
@@ -77,56 +55,18 @@ 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 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)
|
||||
return fscombine(path, "")
|
||||
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)
|
||||
table.insert(segs, 1, fsgetname(rest))
|
||||
rest = fsgetdir(rest)
|
||||
until rest == ""
|
||||
return segs
|
||||
end
|
||||
@@ -134,18 +74,18 @@ end
|
||||
local function combine(segs)
|
||||
local out = ""
|
||||
for _, p in pairs(segs) do
|
||||
out = fs.combine(out, p)
|
||||
out = fscombine(out, p)
|
||||
end
|
||||
return out
|
||||
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
|
||||
|
||||
local vfs_defaults = {}
|
||||
|
||||
function vfs_defaults.getSize(path) return 0 end
|
||||
function vfs_defaults.getFreeSpace(path) return 0 end
|
||||
function vfs_defaults.makeDir(path) error "Access denied" end
|
||||
function vfs_defaults.delete(path) error "Access denied" end
|
||||
function vfs_defaults.isReadOnly(path) return true end
|
||||
|
||||
-- Make a read handle for a string
|
||||
-- PS#8FE487EF: Incompletely implemented handle behaviour lead to strange bugs on recent CC
|
||||
@@ -172,105 +112,176 @@ local function make_handle(text)
|
||||
return h
|
||||
end
|
||||
|
||||
-- Get a path from a filesystem overlay
|
||||
local function path_in_overlay(overlay, path)
|
||||
return overlay[canonicalize(path)]
|
||||
-- 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 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 function create_FS(vfstree)
|
||||
local fs = fs
|
||||
local mappings = make_mappings(root)
|
||||
|
||||
local vfstree = {
|
||||
mount = "potatOS",
|
||||
children = {
|
||||
["disk"] = { mount = "disk" },
|
||||
["rom"] = { mount = "rom" },
|
||||
--["virtual_test"] = { virtual = "bees" }
|
||||
}
|
||||
}
|
||||
local function is_usable_node(node)
|
||||
return node.mount or node.vfs
|
||||
end
|
||||
|
||||
local function resolve(sandbox_path)
|
||||
local function resolve(sandbox_path, ignore_usability)
|
||||
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 current_tree.children and current_tree.children[seg] then
|
||||
if seg and 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 new_overlay = {}
|
||||
for k, v in pairs(overlay) do
|
||||
new_overlay[canonicalize(k)] = v
|
||||
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)
|
||||
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))
|
||||
return function(path)
|
||||
local vfs, path = resolve_path(path)
|
||||
return (vfs[n] or vfs_defaults[n])(path)
|
||||
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 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)
|
||||
local vfs, path = resolve_path(path)
|
||||
return vfs.open(path, 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)
|
||||
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
|
||||
for _, v in pairs(ocontents) do
|
||||
table.insert(contents, v)
|
||||
end
|
||||
return contents
|
||||
if src_vfs.isReadOnly(src_path) then error "Access denied" end
|
||||
new.copy(src, dest)
|
||||
src_vfs.delete(src_path)
|
||||
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.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()
|
||||
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
|
||||
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)
|
||||
end
|
||||
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)))
|
||||
|
||||
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
|
||||
@@ -310,7 +321,7 @@ local function create_FS(root, overlay)
|
||||
to_add.c = new.dump(path)
|
||||
to_add.t = "d"
|
||||
else
|
||||
local fh = new.open(path, "r")
|
||||
local fh = new.open(path, "rb")
|
||||
to_add.c = fh.readAll()
|
||||
fh.close()
|
||||
end
|
||||
@@ -327,13 +338,16 @@ local function create_FS(root, overlay)
|
||||
new.makeDir(path)
|
||||
new.load(f.c, path)
|
||||
else
|
||||
local fh = new.open(path, "w")
|
||||
local fh = new.open(path, "wb")
|
||||
fh.write(f.c)
|
||||
fh.close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
new.segment = segments
|
||||
new._make_handle = make_handle
|
||||
|
||||
return new
|
||||
end
|
||||
|
||||
@@ -405,30 +419,11 @@ 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
|
||||
|
||||
--[[
|
||||
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
|
||||
-- I sure hope this doesn't readd the security issues!
|
||||
environment.getfenv = gf
|
||||
environment.setfenv = sf
|
||||
|
||||
local load = load
|
||||
function environment.load(code, file, mode, env)
|
||||
@@ -437,7 +432,6 @@ Allowing `setfenv` to operate on any function meant that privileged code could i
|
||||
|
||||
environment._G = environment
|
||||
environment._ENV = environment
|
||||
environment._HOST = env_host
|
||||
|
||||
function environment.os.shutdown()
|
||||
process.IPC(current_process, "power_state", "shutdown")
|
||||
@@ -485,4 +479,4 @@ local function run(API_overrides, init, logger)
|
||||
end
|
||||
end
|
||||
|
||||
return { run = run, create_FS = create_FS }
|
||||
return { run = run, create_FS = create_FS, vfs_from_files = vfs_from_files }
|
||||
|
205
src/main.lua
205
src/main.lua
@@ -35,9 +35,9 @@ end
|
||||
term.setCursorBlink(false)
|
||||
print "Loading..."
|
||||
|
||||
if settings.get "potatOS.rph_mode" == true then
|
||||
if settings.get "potatOS.rph_mode" == true then
|
||||
print "PotatOS Rph Compliance Mode: Enabled."
|
||||
return false
|
||||
return false
|
||||
end
|
||||
|
||||
require "stack_trace"
|
||||
@@ -47,8 +47,7 @@ local registry = require "registry"
|
||||
|
||||
--[[
|
||||
Server Policy Framework
|
||||
On 12/01/2020 CE (this is probably overprecise and I doubt anyone will care, yes), there was a weird incident on SwitchCraft involving some turtles somehow getting potatOS installed (seriously, "somehow" is accurate, I have no idea what caused this and attempted to uninstall it when someone actually pinged me; I think it involved a turtle getting set to ID 0 somehow, who knows how potatOS got onto ID 0 in the first place). In light of this (and it apparently breaking rule 9, despite this not having any involvement from me except for me remotely uninstalling it), SC's admins have demanded some features be disabled (EZCopy).
|
||||
Since I don't really want to hardcode random SwitchCraft APIs deep in the code somewhere (it's worrying that they *have* specific ones, as it seems like some programs are being written specifically against them now - seems kind of EEE), and other people will inevitably demand their own special cases, I'm making what should be a reasonably generic way to handle this.
|
||||
On 12/01/2020 CE (this is probably overprecise and I doubt anyone will care, yes), the ID 0 bug resulted in potatOS improving a large quantity of turtles, apparently causing problems. In light of this, SC's admins have demanded some features be disabled (EZCopy). This is a reasonably generic way to handle server-specific special cases.
|
||||
]]
|
||||
|
||||
local SPF = {
|
||||
@@ -98,7 +97,7 @@ local function rot13(s)
|
||||
end
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
end
|
||||
|
||||
local debugtraceback = debug and debug.traceback
|
||||
local logfile = fs.open("latest.log", "a")
|
||||
@@ -255,7 +254,7 @@ local function clear_space(reqd)
|
||||
if fs.getFreeSpace "/" > (reqd + 4096) then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
for _, file in pairs(fs.find(i)) do
|
||||
print("Deleting", file)
|
||||
fs.delete(file)
|
||||
@@ -307,7 +306,7 @@ local function fread(n)
|
||||
out = {string.char(out)}
|
||||
while true do
|
||||
local next = f.read()
|
||||
if not next then
|
||||
if not next then
|
||||
out = table.concat(out)
|
||||
break
|
||||
end
|
||||
@@ -452,16 +451,16 @@ function _G.report_incident(incident, flags, options)
|
||||
for k, v in pairs(options.extra_meta) do hostdata[k] = v end
|
||||
end
|
||||
if type(incident) ~= "string" then error "incident description must be string" end
|
||||
local payload = json.encode {
|
||||
report = incident,
|
||||
host = hostdata,
|
||||
code = options.code or last_loaded,
|
||||
local payload = json.encode {
|
||||
report = incident,
|
||||
host = hostdata,
|
||||
code = options.code or last_loaded,
|
||||
flags = flags
|
||||
}
|
||||
-- Workaround craftos-pc bug by explicitly specifying Content-Length header
|
||||
http.request {
|
||||
url = "https://spudnet.osmarks.net/report",
|
||||
body = payload,
|
||||
url = "https://spudnet.osmarks.net/report",
|
||||
body = payload,
|
||||
headers = {
|
||||
["content-type"] = "application/json",
|
||||
-- Workaround for CraftOS-PC bug where it apparently sends 0, which causes problems in the backend
|
||||
@@ -471,7 +470,7 @@ function _G.report_incident(incident, flags, options)
|
||||
}
|
||||
add_log("reported an incident %s", incident)
|
||||
end
|
||||
|
||||
|
||||
local disk_code_template = [[
|
||||
settings.set("potatOS.gen_count", %d)
|
||||
settings.set("potatOS.ancestry", %s)
|
||||
@@ -493,7 +492,7 @@ local function generate_disk_code()
|
||||
("wget %q startup"):format((registry.get "potatOS.current_manifest.base_URL" or manifest:gsub("/manifest$", "")) .. "/autorun.lua")
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
-- Upgrade other disks to contain potatOS and/or load debug programs (mostly the "OmniDisk") off them.
|
||||
local function process_disk(disk_side)
|
||||
local mp = disk.getMountPath(disk_side)
|
||||
@@ -503,8 +502,8 @@ local function process_disk(disk_side)
|
||||
local sig_file = fs.combine(mp, "signature")
|
||||
-- shell.run disks marked with the Brand of PotatOS
|
||||
-- except not actually, it's cool and uses load now
|
||||
|
||||
if fs.exists(ds) and fs.exists(sig_file) then
|
||||
|
||||
if fs.exists(ds) and fs.exists(sig_file) then
|
||||
local code = fread(ds)
|
||||
local sig_raw = fread(sig_file)
|
||||
local sig
|
||||
@@ -538,10 +537,10 @@ local function process_disk(disk_side)
|
||||
else
|
||||
printError "Invalid Signature!"
|
||||
printError "Initiating Procedure 5."
|
||||
report_incident("invalid signature on disk",
|
||||
report_incident("invalid signature on disk",
|
||||
{"security", "disk_signature"},
|
||||
{
|
||||
code = code,
|
||||
code = code,
|
||||
extra_meta = { signature = sig_raw, disk_ID = disk_ID, disk_side = disk_side, mount_path = mp }
|
||||
})
|
||||
printError "This incident has been reported."
|
||||
@@ -562,12 +561,12 @@ local function disk_handler()
|
||||
-- Detect disks initially
|
||||
for _, n in pairs(peripheral.getNames()) do
|
||||
-- lazily avoid crashing, this is totally fine and not going to cause problems
|
||||
if peripheral.getType(n) == "drive" then
|
||||
if peripheral.getType(n) == "drive" then
|
||||
local ok, err = pcall(process_disk, n)
|
||||
if not ok then printError(err) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Detect disks as they're put in. Mwahahahaha.
|
||||
-- Please note that this is for definitely non-evil purposes only.
|
||||
while true do
|
||||
@@ -576,7 +575,7 @@ local function disk_handler()
|
||||
if not ok then printError(err) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--[[
|
||||
Fix bug PS#201CA2AA
|
||||
Serializing functions, recursive tables, etc. - this is done fairly often - can cause a complete crash of the SPUDNET process. This fixes that.
|
||||
@@ -645,9 +644,10 @@ local function websocket_remote_debugging()
|
||||
|
||||
local function connect()
|
||||
if ws then ws.close() end
|
||||
ws, err = http.websocket "wss://spudnet.osmarks.net/v4?enc=json"
|
||||
local url = "wss://spudnet.osmarks.net/v4?enc=json&rand=" .. math.random(0, 0xFFFFFFF)
|
||||
ws, err = http.websocket(url)
|
||||
if not ws then add_log("websocket failure %s", err) return false end
|
||||
ws.url = "wss://spudnet.osmarks.net/v4?enc=json"
|
||||
ws.url = url
|
||||
|
||||
send_packet { type = "identify", request_ip = true, implementation = string.format("PotatOS %s on %s", (settings.get "potatOS.current_hash" or "???"):sub(1, 8), _HOST) }
|
||||
send_packet { type = "set_channels", channels = { "client:potatOS" } }
|
||||
@@ -656,13 +656,13 @@ local function websocket_remote_debugging()
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function try_connect_loop()
|
||||
while not connect() do
|
||||
sleep(0.5)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
try_connect_loop()
|
||||
|
||||
local function recv()
|
||||
@@ -671,7 +671,7 @@ local function websocket_remote_debugging()
|
||||
if u == ws.url then return json.decode(x) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local ping_timeout_timer = nil
|
||||
|
||||
process.thread(function()
|
||||
@@ -684,7 +684,7 @@ local function websocket_remote_debugging()
|
||||
end
|
||||
end
|
||||
end, "ping-timeout")
|
||||
|
||||
|
||||
while true do
|
||||
-- Receive and run code which is sent via SPUDNET
|
||||
-- Also handle SPUDNETv4 protocol, primarily pings
|
||||
@@ -769,7 +769,7 @@ _G.require = simple_require
|
||||
function _G.uninstall(cause)
|
||||
-- this is pointless why is this in the code
|
||||
-- add_log("uninstalling %s", cause)
|
||||
if not cause then
|
||||
if not cause then
|
||||
report_incident("uninstall without specified cause", {"security", "uninstall_no_cause", "uninstall"})
|
||||
error "uninstall cause required"
|
||||
end
|
||||
@@ -840,7 +840,7 @@ local function download_files(manifest_data, needed_files)
|
||||
local x = h.readAll()
|
||||
h.close()
|
||||
local hexsha = hexize(sha256(x))
|
||||
if (manifest_data.sizes and manifest_data.sizes[file] and manifest_data.sizes[file] ~= #x) or manifest_data.files[file] ~= hexsha then
|
||||
if (manifest_data.sizes and manifest_data.sizes[file] and manifest_data.sizes[file] ~= #x) or manifest_data.files[file] ~= hexsha then
|
||||
error(("hash mismatch on %s %s (expected %s, got %s)"):format(file, url, manifest_data.files[file], hexsha))
|
||||
end
|
||||
fwrite(file, x)
|
||||
@@ -884,7 +884,7 @@ local function process_manifest(url, force, especially_force)
|
||||
local main_data = txt:match "^(.*)\n"
|
||||
local metadata = json.decode(txt:match "\n(.*)$")
|
||||
local main_data_hash = hexize(sha256(main_data))
|
||||
|
||||
|
||||
|
||||
if main_data_hash ~= metadata.hash then
|
||||
error(("hash mismatch: %s %s"):format(main_data_hash, metadata.hash))
|
||||
@@ -921,8 +921,8 @@ local function process_manifest(url, force, especially_force)
|
||||
for file, hash in pairs(data.files) do
|
||||
if fs.isDir(file) then fs.delete(file) end
|
||||
if not fs.exists(file) then print("missing", file) add_log("nonexistent %s", file) table.insert(needs, file)
|
||||
elseif (data.sizes and data.sizes[file] and data.sizes[file] ~= fs.getSize(file))
|
||||
or (has_manifest and ((current_manifest.files[file] and current_manifest.files[file] ~= hash) or not current_manifest.files[file]))
|
||||
elseif (data.sizes and data.sizes[file] and data.sizes[file] ~= fs.getSize(file))
|
||||
or (has_manifest and ((current_manifest.files[file] and current_manifest.files[file] ~= hash) or not current_manifest.files[file]))
|
||||
or (not has_manifest and hexize(sha256(fread(file))) ~= hash) then
|
||||
add_log("mismatch %s %s", file, hash)
|
||||
print("mismatch on", file, hash)
|
||||
@@ -962,7 +962,7 @@ local function install(force)
|
||||
fs.makeDir(d)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local res = process_manifest(manifest, force)
|
||||
if (res == 0 or res == false) and not force then
|
||||
uncapture_screen()
|
||||
@@ -972,27 +972,27 @@ local function install(force)
|
||||
-- Stop people using disks. Honestly, did they expect THAT to work?
|
||||
set("shell.allow_disk_startup", false)
|
||||
set("shell.allow_startup", true)
|
||||
|
||||
|
||||
--if fs.exists "startup.lua" and fs.isDir "startup.lua" then fs.delete "startup.lua" end
|
||||
--fwrite("startup.lua", (" "):rep(100)..[[shell.run"pastebin run RM13UGFa"]])
|
||||
|
||||
|
||||
-- I mean, the label limit is MEANT to be 32 chars, but who knows, buggy emulators ~~might~~ did let this work...
|
||||
if not os.getComputerLabel() or not (os.getComputerLabel():match "^P/") then
|
||||
os.setComputerLabel("P/" .. randbytes(64))
|
||||
end
|
||||
|
||||
|
||||
if not settings.get "potatOS.uuid" then
|
||||
set("potatOS.uuid", gen_uuid())
|
||||
end
|
||||
if not settings.get "potatOS.ts" then
|
||||
set("potatOS.ts", os.epoch "utc")
|
||||
end
|
||||
|
||||
|
||||
add_log("update complete", tostring(res) or "[some weirdness]")
|
||||
|
||||
os.reboot()
|
||||
end
|
||||
|
||||
|
||||
local function critical_error(err)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
@@ -1018,7 +1018,7 @@ end
|
||||
local function run_with_sandbox()
|
||||
-- Load a bunch of necessary PotatoLibraries™
|
||||
-- if fs.exists "lib/bigfont" then os.loadAPI "lib/bigfont" end
|
||||
if fs.exists "lib/gps.lua" then
|
||||
if fs.exists "lib/gps.lua" then
|
||||
os.loadAPI "lib/gps.lua"
|
||||
end
|
||||
|
||||
@@ -1118,19 +1118,19 @@ local function run_with_sandbox()
|
||||
-- Hook up the debug registry to the potatOS Registry.
|
||||
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 function fproxy(file)
|
||||
local ok, t = pcall(fread, file)
|
||||
if not ok or not t then return 'printError "Error. Try again later, or reboot, or run upd."' end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
local uuid = settings.get "potatOS.uuid"
|
||||
-- Generate a build number from the first bit of the verhash
|
||||
local full_build = settings.get "potatOS.current_hash"
|
||||
_G.build_number = full_build:sub(1, 8)
|
||||
add_log("build number is %s, uuid is %s", _G.build_number, uuid)
|
||||
|
||||
|
||||
local is_uninstalling = false
|
||||
-- PotatOS API functionality
|
||||
|
||||
@@ -1188,7 +1188,7 @@ local function run_with_sandbox()
|
||||
return new
|
||||
end
|
||||
end,
|
||||
-- Updates potatOS
|
||||
-- Updates potatOS
|
||||
update = function()
|
||||
process.IPC("potatoupd", "trigger_update", true)
|
||||
end,
|
||||
@@ -1281,7 +1281,7 @@ local function run_with_sandbox()
|
||||
end
|
||||
end
|
||||
end, "privapi")
|
||||
|
||||
|
||||
local potatOS_proxy = {}
|
||||
for k, v in pairs(potatOS) do
|
||||
potatOS_proxy[k] = (type(v) == "function" and not pure_functions[k]) and function(...)
|
||||
@@ -1296,27 +1296,57 @@ local function run_with_sandbox()
|
||||
end
|
||||
end or v
|
||||
end
|
||||
|
||||
-- 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"
|
||||
|
||||
local yafss = require "yafss"
|
||||
|
||||
local drive_mounts = {
|
||||
children = {},
|
||||
vfs = {}
|
||||
}
|
||||
|
||||
for _, file in pairs(fs.list "bin") do
|
||||
FS_overlay[fs.combine("rom/programs", file)] = fproxy(fs.combine("bin", file))
|
||||
|
||||
function drive_mounts.vfs.list(path)
|
||||
local out = {}
|
||||
for k, v in pairs(drive_mounts.children) do
|
||||
table.insert(out, k)
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
for _, file in pairs(fs.list "xlib") do
|
||||
FS_overlay[fs.combine("rom/potato_xlib", file)] = fproxy(fs.combine("xlib", file))
|
||||
function drive_mounts.vfs.exists(path)
|
||||
return path == "" or drive_mounts.children[path] ~= nil
|
||||
end
|
||||
|
||||
function drive_mounts.vfs.isDir(path) return true end
|
||||
function drive_mounts.vfs.getDrive(path) return "disks" 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,
|
||||
@@ -1329,14 +1359,14 @@ local function run_with_sandbox()
|
||||
_VERSION = _VERSION,
|
||||
potatOS = potatOS_proxy
|
||||
}
|
||||
|
||||
|
||||
--[[
|
||||
Fix bug PS#22B7A59D
|
||||
Unify constantly-running peripheral manipulation code under one more efficient function, to reduce server load.
|
||||
See the code for the "onsys" process just below for the new version.~~
|
||||
UPDATE: This is now in netd, formerly lancmd, anyway
|
||||
]]
|
||||
|
||||
|
||||
-- Allow limited remote commands over wired LAN networks for improved potatOS cluster management
|
||||
-- PS#C9BA58B3
|
||||
-- Reduce peripheral calls by moving LAN sign/computer handling into this kind of logic, which is more efficient as it does not constantly run getType/getNames.
|
||||
@@ -1353,6 +1383,18 @@ 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
|
||||
@@ -1362,10 +1404,16 @@ 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)
|
||||
@@ -1373,7 +1421,8 @@ 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 end
|
||||
elseif typ == "minecraft:sign" then signs[name] = nil
|
||||
elseif typ == "drive" then unmount_disk(name) end
|
||||
elseif e == "modem_message" then
|
||||
if channel == 62381 and type(message) == "string" then
|
||||
add_log("netd message %s", message)
|
||||
@@ -1394,19 +1443,21 @@ 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"
|
||||
pcall(require "metatable_improvements", potatOS_proxy.add_log, potatOS_proxy.report_incident)
|
||||
|
||||
local fss_sentinel = sandboxlib.create_sentinel "fs-sandbox"
|
||||
local debug_sentinel = sandboxlib.create_sentinel "constrained-debug"
|
||||
local sandbox_filesystem = yafss.create_FS("potatOS", FS_overlay)
|
||||
local sandbox_filesystem = yafss.create_FS(vfstree)
|
||||
_G.fs = sandboxlib.dispatch_if_restricted(fss_sentinel, _G.fs, sandbox_filesystem)
|
||||
_G.debug = sandboxlib.allow_whitelisted(debug_sentinel, _G.debug, {
|
||||
_G.debug = sandboxlib.allow_whitelisted(debug_sentinel, _G.debug or {}, {
|
||||
"traceback",
|
||||
"getinfo",
|
||||
"getregistry"
|
||||
@@ -1427,17 +1478,17 @@ local function run_with_sandbox()
|
||||
) end, "sandbox", { restrictions = { [fss_sentinel] = true, [debug_sentinel] = true, [defeature_sentinel] = true } })
|
||||
add_log "sandbox started"
|
||||
end
|
||||
|
||||
|
||||
return function(...)
|
||||
local command = table.concat({...}, " ")
|
||||
add_log("command line is %q", command)
|
||||
|
||||
|
||||
-- Removes whitespace. I don't actually know what uses this either.
|
||||
local function strip_whitespace(text)
|
||||
local newtext = text:gsub("[\r\n ]", "")
|
||||
return newtext
|
||||
end
|
||||
|
||||
|
||||
-- Detect a few important command-line options.
|
||||
if command:find "rphmode" then set("potatOS.rph_mode", true) end
|
||||
if command:find "mode2" then set("potatOS.hidden", true) end
|
||||
@@ -1450,14 +1501,14 @@ return function(...)
|
||||
end
|
||||
if command:find "update" or command:find "install" then install(true) end
|
||||
if command:find "hedgehog" and command:find "76fde5717a89e332513d4f1e5b36f6cb" then set("potatOS.removable", true) os.reboot() end
|
||||
|
||||
|
||||
-- enable debug, HTTP if in CraftOS-PC
|
||||
if _G.config and _G.config.get then
|
||||
if config.get "http_enable" ~= true then pcall(config.set, "http_enable", true) end
|
||||
if config.get "debug_enable" ~= true then pcall(config.set, "debug_enable", true) end
|
||||
if config.get "romReadOnly" ~= false then pcall(config.set, "romReadOnly", false) end -- TODO: do something COOL with this.
|
||||
end
|
||||
|
||||
|
||||
if not process or not fs.exists "potatobios.lua" or not fs.exists "autorun.lua" then -- Polychoron not installed, so PotatOS isn't.
|
||||
local outside_fs = require "sandboxescapes"()
|
||||
if outside_fs then
|
||||
@@ -1475,7 +1526,7 @@ return function(...)
|
||||
-- do updates here
|
||||
local ok, err = pcall(install, false)
|
||||
if not ok then add_log("update error %s", err) end
|
||||
|
||||
|
||||
-- Spread out updates a bit to reduce load on the server.
|
||||
local timer = os.startTimer(300 + (os.getComputerID() % 100) - 50)
|
||||
while true do
|
||||
@@ -1488,13 +1539,13 @@ return function(...)
|
||||
end
|
||||
end
|
||||
end, "potatoupd")
|
||||
|
||||
|
||||
-- In case it breaks horribly, display nice messages.
|
||||
local ok, err = pcall(run_with_sandbox)
|
||||
if not ok then
|
||||
critical_error(err)
|
||||
end
|
||||
|
||||
|
||||
-- In case it crashes... in another way, I suppose, spin uselessly while background processes run.
|
||||
while true do coroutine.yield() end
|
||||
end
|
||||
|
@@ -24,13 +24,14 @@ if ccemux then
|
||||
ccemuxnanoTime = ccemux.nanoTime
|
||||
ccemuxecho = ccemux.echo
|
||||
end
|
||||
local outer_process = _G.process
|
||||
|
||||
-- Return a time of some sort. Not used to provide "objective" time measurement, just for duration comparison
|
||||
local function time()
|
||||
if ccemuxnanoTime then
|
||||
return ccemuxnanoTime() / 1e9
|
||||
elseif osepoch then
|
||||
return osepoch "utc" / 1000 else
|
||||
elseif osepoch then
|
||||
return osepoch "utc" / 1000 else
|
||||
return osclock() end
|
||||
end
|
||||
|
||||
@@ -158,7 +159,7 @@ local function BSOD(e)
|
||||
term.clear()
|
||||
term.setCursorBlink(false)
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
|
||||
print(e)
|
||||
end
|
||||
end
|
||||
@@ -433,8 +434,9 @@ local function run_loop()
|
||||
end
|
||||
end
|
||||
|
||||
local function boot()
|
||||
if ccemuxecho then ccemuxecho("TLCO executed " .. (debugtraceback and debugtraceback() or "succesfully")) end
|
||||
local function boot(desc)
|
||||
if ccemuxecho then ccemuxecho(desc .. " executed " .. (debugtraceback and debugtraceback() or "succesfully")) end
|
||||
|
||||
term.redirect(term.native())
|
||||
multishell = nil
|
||||
term.setTextColor(colors.yellow)
|
||||
@@ -443,6 +445,7 @@ local function boot()
|
||||
term.clear()
|
||||
|
||||
process.spawn(function() os.run({}, "autorun.lua") end, "main", { grants = { [root_capability] = true }, restrictions = {} })
|
||||
|
||||
process.spawn(function()
|
||||
-- bodge, because of the rednet bRunning thing
|
||||
local old_error = error
|
||||
@@ -454,23 +457,35 @@ local function boot()
|
||||
run_loop()
|
||||
end
|
||||
|
||||
-- fixed TLCO from https://gist.github.com/MCJack123/42bc69d3757226c966da752df80437dc
|
||||
local old_error = error
|
||||
local old_os_shutdown = os.shutdown
|
||||
local old_term_redirect = term.redirect
|
||||
local old_term_native = term.native
|
||||
function error() end
|
||||
function term.redirect() end
|
||||
function term.native() end
|
||||
function os.shutdown()
|
||||
error = old_error
|
||||
_G.error = old_error
|
||||
_ENV.error = old_error
|
||||
term.native = old_term_native
|
||||
term.redirect = old_term_redirect
|
||||
os.shutdown = old_os_shutdown
|
||||
os.pullEventRaw = coroutine.yield
|
||||
boot()
|
||||
end
|
||||
-- fix nested potatOSes
|
||||
if outer_process then
|
||||
-- cannot TLCO; run under outer process manager
|
||||
outer_process.spawn(function() boot "nested boot" end, "polychoron")
|
||||
while true do coroutine.yield() end
|
||||
else
|
||||
-- fixed TLCO from https://gist.github.com/MCJack123/42bc69d3757226c966da752df80437dc
|
||||
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
|
||||
os.pullEventRaw = coroutine.yield
|
||||
boot "TLCO"
|
||||
end
|
||||
|
||||
os.pullEventRaw = nil
|
||||
os.pullEventRaw = nil
|
||||
end
|
||||
|
@@ -391,6 +391,12 @@ 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
|
||||
@@ -408,17 +414,11 @@ do
|
||||
ix = ix + 1
|
||||
process.queue_in(process.get_running().parent, "term_resize", true)
|
||||
elseif ev == "ipc" and arg2 == "redraw_native" then
|
||||
potatOS.framebuffers[native.id].redraw()
|
||||
potatOS.framebuffers[termid(native)].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/potato_xlib"} do
|
||||
for _, search_path in next, {"/", "lib", "rom/modules/main", "rom/modules/turtle", "rom/modules/command", "rom/potatOS_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/potato_xlib") do
|
||||
for _, f in pairs(fs.list "rom/potatOS_xlib") do
|
||||
table.insert(libs, f)
|
||||
end
|
||||
table.sort(libs)
|
||||
@@ -1245,7 +1245,8 @@ 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
|
||||
stop = stop_sequences,
|
||||
client = "potatOS"
|
||||
}, {["content-type"]="application/json"}, true)
|
||||
if err then
|
||||
error("Server error: " .. err) -- is this right? I forgot.
|
||||
@@ -1608,7 +1609,7 @@ function potatOS.threat_update()
|
||||
table.insert(out, description)
|
||||
table.insert(out, "")
|
||||
end
|
||||
return (potatOS.llm(table.concat(out, "\n"), 100, {"\n\n"}):gsub("^\n", ""):gsub("\n$", ""))
|
||||
return "current threat level is" .. (potatOS.llm(table.concat(out, "\n") .. "\ncurrent threat level is", 100, {"\n\n"}):gsub("^\n", ""):gsub("\n$", ""))
|
||||
end
|
||||
|
||||
local fixed_context = {
|
||||
@@ -1758,7 +1759,7 @@ if potatOS.registry.get "potatOS.immutable_global_scope" then
|
||||
end
|
||||
|
||||
process.spawn(keyboard_shortcuts, "kbsd")
|
||||
if http.websocket then process.spawn(skynet.listen, "skynetd") process.spawn(potatoNET, "systemd-potatod") end
|
||||
if skynet and 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)
|
||||
@@ -1780,7 +1781,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
|
||||
|
@@ -57,7 +57,7 @@ button {
|
||||
<h1>Welcome to PotatOS!</h1>
|
||||
<img src="/potatos.gif" id="im">
|
||||
<div>
|
||||
Current build: <code>361bc871</code> (adjust LLM interface), version 771, built 2024-02-28 19:50:26 (UTC).
|
||||
Current build: <code>9b5e8950</code> (new VFS layer), version 815, built 2024-09-03 11:37:01 (UTC).
|
||||
</div>
|
||||
<p>"PotatOS" stands for "PotatOS Otiose Transformative Advanced Technology Or Something".
|
||||
<a href="https://git.osmarks.net/osmarks/potatOS">This repository</a> contains the source code for the latest version of PotatOS, "PotatOS Epenthesis".
|
||||
@@ -132,6 +132,7 @@ 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.
|
||||
|
Reference in New Issue
Block a user