PotatOS Epenthesis

This commit is contained in:
osmarks 2023-12-10 15:40:47 +00:00
parent 3db5db9c02
commit 64dc597822
45 changed files with 2435 additions and 1623 deletions

View File

@ -1,11 +1,12 @@
# PotatOS
"PotatOS" stands for "PotatOS Otiose Transformative Advanced Technology Or Something".
[This repository](https://git.osmarks.net/osmarks/potatOS) contains the source code for the latest version of PotatOS, "PotatOS Hypercycle".
[This repository](https://git.osmarks.net/osmarks/potatOS) contains the source code for the latest version of PotatOS, "PotatOS Epenthesis".
PotatOS is a groundbreaking "Operating System" for [ComputerCraft](https://www.computercraft.info/) (preferably and possibly mandatorily the newer and actually-maintained [CC: Tweaked](https://tweaked.cc/)).
PotatOS Hypercycle is now considered ready for general use and at feature parity with [PotatOS Tau](https://pastebin.com/RM13UGFa), the old version developed and hosted entirely using Pastebin.
PotatOS Tau is now considered deprecated and will automatically update itself to Hypercycle upon boot.
PotatOS Epenthesis is now considered ready for general use and at feature parity with [PotatOS Tau](https://pastebin.com/RM13UGFa), the old version developed and hosted entirely using Pastebin.
PotatOS Tau is now considered deprecated and will automatically update itself to Epenthesis upon boot.
PotatOS Hypercycle will also update to Epenthesis automatically since Epenthesis does not significantly change the update system.
You obviously want to install it now, so do this: `pastebin run 7HSiHybr`.
@ -20,6 +21,10 @@ Thanks to technology, we're able to offer a live PotatOS instance in your browse
Experiencing PotatOS requires JavaScript. Please enable it.
</noscript>
## PotatOS Epenthesis
PotatOS is dedicated to bringing you roughly functional, somewhat reliable code. Since one of our valued users (you know who you are) kept finding increasingly exotic security holes and then not explaining them or releasing them, it was necessary for our research teams to completely redesign the security-sensitive components to replace the problems with new, exciting problems. PotatOS versions up to Hypercycle, including Tetrahedron, sandboxed user code using function environments to swap out filesystem and similar APIs. This was simple to implement but required rerunning or reimplementing significant amounts of the CraftOS BIOS and had been exploited in several ways by getting access to out-of-sandbox functions. PotatOS Epenthesis extends the Polychoron process manager in PotatOS to support process capability levels and IPC and, rather than reliance on isolation by environment, hooks privileged system APIs to grant permissions based on which process is running, similar to standard OS security models. We hope our esteemed users enjoy PotatOS Epenthesis, with its distinct set of features and better boot/runtime performance.
## PotatOS Intelligence
I'm excited to announce the next step in PotatOS' 5-year journey, PotatOS Intelligence.
@ -59,7 +64,6 @@ Unlike most "OS"es for CC (primarily excluding Opus OS, which is actually useful
- Remote debugging capabilities for development and stuff (highly* secured, via ECC signing on debugging disks and SPUDNET's security features).
- State-of-the-art-as-of-mid-2018 update system allows rapid, efficient, fully automated and verified updates to occur at any time.
- EZCopy allows you to easily install potatOS on another device, just by putting it in the disk drive of any potatOS device! EZCopy is unfortunately disabled on some servers.
- Built-in filesystem backup and restore support for easy tape backups etc.
- Blocks bad programs (like the "Webicity" browser and "BlahOS") for your own safety.
- Fully-featured coroutine-based process manager. Very fully-featured. No existing code uses most of the features.
- Can run in "hidden mode" where it's at least not obvious at a glance that potatOS is installed.
@ -87,6 +91,8 @@ Unlike most "OS"es for CC (primarily excluding Opus OS, which is actually useful
- Integrated logging mechanism for debugging.
- [PotatOS Copilot](https://www.youtube.com/watch?v=KPp7PLi2nrI) assists you literally* anywhere in PotatOS.
- Live threat updates using our advanced algorithms.
- PotatOS Epenthesis' rewritten security model fixes many exploits and adds others while reducing boot times.
- IPC mechanism.
## Architecture
@ -95,16 +101,16 @@ However, to ease development and/or exploit research (which there's a surprising
### Boot process
- normal ComputerCraft boot process - `bios.lua` runs `rom/programs/shell.lua` (or maybe multishell first) runs `rom/startup.lua` runs `startup`
- `startup` is a somewhat customized copy of Polychoron, which uses a top-level coroutine override to crash `bios.lua`'s `parallel.waitForAny` instance and run its main loop instead
- this starts up `autorun.lua` (which is a compiled bundle of `main.lua` and `lib/*`)
- 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
- The update daemon is started, and will check for updates every 300±50 seconds
- `run_with_sandbox` runs - if this errors, potatOS will enter a "critical error" state in which it attempts to update after 10 seconds
- more initialization occurs - the device UUID is loaded/generated, a FS overlay is generated, the table of potatOS API functions is configured, `xlib/*` (userspace libraries) are loaded into the userspace environment, `netd` (the LAN commands/peripheral daemon) starts, the SPUDNET and disk daemons start (unless configured not to)
- the main sandbox process starts up
- 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 `load`, along with the PotatoBIOS code.
- 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 `expect` 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.
- Normal ComputerCraft boot process - `bios.lua` runs `rom/programs/shell.lua` (or maybe multishell first) runs `rom/startup.lua` runs `startup`.
- `startup` contains the PotatOS process manager, Polychoron, which uses a top-level coroutine override to crash `bios.lua`'s `parallel.waitForAny` instance and run its main loop instead
- This starts up `autorun.lua` (which is a compiled bundle of `main.lua` and `lib/*`).
- Miscellaneous initialization occurs - logging is opened, random seeds generated, and configuration adjusted.
- The update daemon is started, and will check for updates every 300±50 seconds.
- `run_with_sandbox` is entered - if this fails, potatOS will enter a "critical error" state in which it attempts to update after 10 seconds.
- More initialization occurs - the device UUID is loaded/generated, a FS overlay is generated, the table of potatOS API functions is configured, `xlib/*` (userspace libraries) are loaded into the userspace environment, `netd` (the LAN commands/peripheral daemon) starts, the SPUDNET and disk daemons start (unless configured not to)
- PotatOS hooks the filesystem API to gate access based on the currently running process's capability level.
- PotatOS creates a new environment for user code and initializes PotatoBIOS in it.
- PotatoBIOS does its own initialization - primarily that of the native CC BIOS, as well as the Code Safety Checker, logging of recently loaded code, bodgily providing `expect` 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.
- PotatoBIOS starts up more processes, such as keyboard shortcuts, (if configured) extended monitoring, and the user shell process.
- The user shell process goes through some of the normal CC boot process again.
@ -114,14 +120,14 @@ The PotatOS userspace API, mostly accessible from `_G.potatOS`, has absolutely n
It's also not really documented. Fun!
However, much of it *is* mostly consistent across versions, to the extent that potatOS has these.
Here's a list of some of the more useful and/or consistently available functions:
Here's a list of some of the more useful and/or consistently available functions (TODO UPDATE):
- `potatOS.add_log(message: string, ...formattingArgs: any)` - add a line to the log file - supports `string.format`-style formatting
- `potatOS.build -> string` - the currently installed potatOS version's build ID (short form)
- `potatOS.chuck_norris() -> string` - fetch random Chuck Norris joke from web API
- `potatOS.fortune() -> string` - fetch random `fortune` from web API
- `potatOS.evilify()` - mess up 1 in 10 keypresses
- `potatOS.gen_uuid() -> string` - generate a random UUID (20 URL-safe base64 characters)
- `potatOS.gen_uuid() -> string` - generate a random UUID (20 URL-safe base64 characters) (not actually a spec-compliant UUID)
- `potatOS.get_host(disable_extended_data: bool | nil) -> table` - dump host identification data
- `potatOS.get_location() -> number, number, number | nil` - get GPS location, if available. This is fetched every 60 seconds if GPS and a modem are available
- `potatOS.init_screens()` - reset palettes to default
@ -137,10 +143,17 @@ Here's a list of some of the more useful and/or consistently available functions
- `potatOS.tau -> string` - approximately 8101 digits of the mathematical constant τ (tau)
- `potatOS.update()` - force a system update
- `potatOS.uuid -> string` - get the system's 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.
- `potatOS.assistant_history -> table` - PotatOS Intelligence assistant messages.
- `potatOS.llm(prompt: string, max_tokens: number, stop_sequences: table) -> string` - invoke PotatOS Intelligence language model.
- `potatOS.metaphor() -> string` - generate metaphor.
- `potatOS.unhexize(hex: string) -> table` - hex to byte array.
- `potatOS.hexize(bytes: table) -> string` - byte array to hex.
- `potatOS.shuffle(x: table)` - shuffle a list.
- `process.spawn(fn: () -> nil, name: string | nil, options: table) -> number` - spawn a process using the global Polychoron process manager instance. Returns the ID.
- `process.info(ID: number) -> table` - get information about a process, by ID
- `process.list() -> table` - get information for all running processes
- `_G.init_code -> string` - the source code of the running PotatoBIOS instance
- `process.info(ID: number) -> table` - get information about a process, by ID.
- `process.list() -> table` - get information for all running processes.
- `process.IPC(target: number, ...args: any)` - send IPC message to given process.
- `_G.init_code -> string` - the source code of the running PotatoBIOS instance.
## Reviews
@ -162,6 +175,7 @@ Here's a list of some of the more useful and/or consistently available functions
- "PotatOS is many, varied, ever-changing, and eternal. Fighting it is like fighting a many-headed monster, which, each time a neck is severed, sprouts a head even fiercer and cleverer than before. You are fighting that which is unfixed, mutating, indestructible." - someone
- "go use potatos or something" - SwitchCraft3 (official), 2023
- "a lot of backup time is spent during potatos" - Lemmmy, 2022
- "we would need 176000 comparators to store potatOS" - piguman3, 2023
- "potatOS is as steady as a rock" - BlackDragon, 2021
- "PotatOS would be a nice religion" - piguman3, 2022
- "It has caused multiple issues to staff of multiple CC servers." - Wojbie, 2023

File diff suppressed because one or more lines are too long

3
src/bin/BSOD.lua Normal file
View File

@ -0,0 +1,3 @@
local w, h = term.getSize()
polychoron.BSOD(potatOS.randbytes(math.random(0, w * h)))
os.pullEvent "key"

1
src/bin/b.lua Normal file
View File

@ -0,0 +1 @@
print "abcdefghijklmnopqrstuvwxyz"

19
src/bin/build.lua Normal file
View File

@ -0,0 +1,19 @@
print("Short hash", potatOS.build)
print("Full hash", potatOS.full_build)
local mfst = potatOS.registry.get "potatOS.current_manifest"
if mfst then
print("Counter", mfst.build)
print("Built at (local time)", os.date("%Y-%m-%d %X", mfst.timestamp))
print("Downloaded from", mfst.manifest_URL)
local verified = mfst.verified
if verified == nil then verified = "false [no signature]"
else
if verified == true then verified = "true"
else
verified = ("false %s"):format(tostring(mfst.verification_error))
end
end
print("Signature verified", verified)
else
print "Manifest not found in registry. Extended data unavailable."
end

1
src/bin/chuck.lua Normal file
View File

@ -0,0 +1 @@
print(potatOS.chuck_norris())

1
src/bin/clear_space.lua Normal file
View File

@ -0,0 +1 @@
potatOS.clear_space((... and tonumber(...) and tonumber(...) == tonumber(...)) and tonumber(...) or 4096)

3
src/bin/ctime.lua Normal file
View File

@ -0,0 +1,3 @@
for _, info in pairs(process.list()) do
textutils.pagedPrint(("%s %f %f"):format(info.name or info.ID, info.execution_time, info.ctime))
end

25
src/bin/est.lua Normal file
View File

@ -0,0 +1,25 @@
-- edit reality to match typo in docs
function Safe_SerializeWithtextutilsDotserialize(Valuje)
local _, __ = pcall(textutils.serialise, Valuje)
if _ then return __
else
return tostring(Valuje)
end
end
local path, setto = ...
path = path or ""
if setto ~= nil then
local x, jo, jx = textutils.unserialise(setto), pcall(json.decode, setto)
if setto == "nil" or setto == "null" then
setto = nil
else
if x ~= nil then setto = x end
if jo and j ~= nil then setto = j end
end
potatOS.registry.set(path, setto)
print(("Value of registry entry %s set to:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(setto)))
else
textutils.pagedPrint(("Value of registry entry %s is:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(potatOS.registry.get(path))))
end

8
src/bin/exorcise.lua Normal file
View File

@ -0,0 +1,8 @@
-- like delete but COOLER and LATIN
for _, wcard in pairs{...} do
for _, path in pairs(fs.find(wcard)) do
fs.ultradelete(path)
local n = potatOS.lorem():gsub("%.", " " .. path .. ".")
print(n)
end
end

View File

@ -12,9 +12,10 @@ repeat
write "Provide an integer to factorize: "
x = tonumber(read())
if not x or math.floor(x) ~= x then print("That is NOT an integer.") end
if x and x < 2 then print("I forgot to mention this, but also don't use 1, 0 or negative integers.") end
until x
if x > (2^40) then print("WARNING: Number is quite big. Due to Lua floating point limitations, draconic entities MAY be present. If this runs for several seconds, it's probably frozen due to this.") end
if x > (2^40) then print("WARNING: Number is quite big. Due to Lua floating point limitations, draconic entities MAY be present and results may be blatantly wrong. If this runs for several seconds, it's probably frozen due to this.") end
local floor, abs, random, log, pow = math.floor, math.abs, math.random, math.log, math.pow
@ -28,17 +29,16 @@ local function eps_compare(x, y)
return abs(x - y) < 1e-14
end
-- binary modular exponentiation
local function modexp(a, b, n)
if b == 0 then return 1 % n end
if b == 1 then return a % n end
local bdiv2 = b / 2
local fbdiv2 = floor(bdiv2)
if eps_compare(bdiv2, fbdiv2) then
-- b is even, so it is possible to just modexp with HALF the exponent and square it (mod n)
local x = modexp(a, fbdiv2, n)
return (x * x) % n
else
-- not even, so subtract 1 (this is even), modexp that, and multiply by a again (mod n)
return (modexp(a, b - 1, n) * a) % n
end
end

1
src/bin/fortune.lua Normal file
View File

@ -0,0 +1 @@
print(potatOS.fortune())

8
src/bin/game_mode.lua Normal file
View File

@ -0,0 +1,8 @@
potatOS.evilify()
print "GAME KEYBOARD enabled."
potatOS.init_screens()
print "GAME SCREEN enabled."
print "Activated GAME MODE."
--bigfont.bigWrite "GAME MODE."
--local x, y = term.getCursorPos()
--term.setCursorPos(1, y + 3)

175
src/bin/hacker.lua Normal file
View File

@ -0,0 +1,175 @@
local mat = term.current()
mat.setPaletteColor(colors.black, 0)
mat.setPaletteColor(colors.green, 0x0cff0c)
mat.setPaletteColor(colors.red, 0xff0000)
if mat.setTextScale then mat.setTextScale(0.5) end
mat.setTextColor(colors.green)
term.redirect(mat)
local jargonWords = {
acronyms =
{"TCP", "HTTP", "SDD", "RAM", "GB", "CSS", "SSL", "AGP", "SQL", "FTP", "PCI", "AI", "ADP",
"RSS", "XML", "EXE", "COM", "HDD", "THX", "SMTP", "SMS", "USB", "PNG", "PHP", "UDP",
"TPS", "RX", "ASCII", "CD-ROM", "CGI", "CPU", "DDR", "DHCP", "BIOS", "IDE", "IP", "MAC",
"MP3", "AAC", "PPPoE", "SSD", "SDRAM", "VGA", "XHTML", "Y2K", "GUI", "EPS", "SATA", "SAS",
"VM", "LAN", "DRAM", "L3", "L2", "DNS", "UEFI", "UTF-8", "DDOS", "HDMI", "GPU", "RSA", "AES",
"L7", "ISO", "HTTPS", "SSH", "SIMD", "GNU", "PDF", "LPDDR5", "ARM", "RISC", "CISC", "802.11",
"5G", "LTE", "3GPP", "MP4", "2FA", "RCE", "JBIG2", "ISA", "PCIe", "NVMe", "SHA", "QR", "CUDA",
"IPv4", "IPv6", "ARP", "DES", "IEEE", "NoSQL", "UTF-16", "ADSL", "ABI", "TX", "HEVC", "AVC",
"AV1", "ASLR", "ECC", "HBA", "HAL", "SMT", "RPC", "JIT", "LCD", "LED", "MIME", "MIMO", "LZW",
"LGA", "OFDM", "ORM", "PCRE", "POP3", "SMTP", "802.3", "PSU", "RGB", "VLIW", "VPS", "VPN",
"XMPP", "IRC", "GNSS"},
adjectives =
{"auxiliary", "primary", "back-end", "digital", "open-source", "virtual", "cross-platform",
"redundant", "online", "haptic", "multi-byte", "Bluetooth", "wireless", "1080p", "neural",
"optical", "solid state", "mobile", "unicode", "backup", "high speed", "56k", "analog",
"fiber optic", "central", "visual", "ethernet", "Griswold", "binary", "ternary",
"secondary", "web-scale", "persistent", "Java", "cloud", "hyperscale", "seconday", "cloudscale",
"software-defined", "hyperconverged", "x86", "Ethernet", "WiFi", "4k", "gigabit", "neuromorphic",
"sparse", "machine learning", "authentication", "multithreaded", "statistical", "nonlinear",
"photonic", "streaming", "concurrent", "memory-safe", "C", "electromagnetic", "nanoscale",
"high-level", "low-level", "distributed", "accelerated", "base64", "purely functional",
"serial", "parallel", "compute", "graphene", "recursive", "denormalized", "orbital",
"networked", "autonomous", "applicative", "acausal", "hardened", "category-theoretic",
"ultrasonic"},
nouns =
{"driver", "protocol", "bandwidth", "panel", "microchip", "program", "port", "card",
"array", "interface", "system", "sensor", "firewall", "hard drive", "pixel", "alarm",
"feed", "monitor", "application", "transmitter", "bus", "circuit", "capacitor", "matrix",
"address", "form factor", "array", "mainframe", "processor", "antenna", "transistor",
"virus", "malware", "spyware", "network", "internet", "field", "acutator", "tetryon",
"beacon", "resonator", "diode", "oscillator", "vertex", "shader", "cache", "platform",
"hyperlink", "device", "encryption", "node", "headers", "botnet", "applet", "satellite",
"Unix", "byte", "Web 3", "metaverse", "microservice", "ultrastructure", "subsystem",
"call stack", "gate", "filesystem", "file", "database", "bitmap", "Bloom filter", "tensor",
"hash table", "tree", "optics", "silicon", "hardware", "uplink", "script", "tunnel",
"server", "barcode", "exploit", "vulnerability", "backdoor", "computer", "page",
"regex", "socket", "platform", "IP", "compiler", "interpreter", "nanochip", "certificate",
"API", "bitrate", "acknowledgement", "layout", "satellite", "shell", "MAC", "PHY", "VLAN",
"SoC", "assembler", "interrupt", "directory", "display", "functor", "bits", "logic",
"sequence", "procedure", "subnet", "invariant", "monad", "endofunctor", "borrow checker"},
participles =
{"backing up", "bypassing", "hacking", "overriding", "compressing", "copying", "navigating",
"indexing", "connecting", "generating", "quantifying", "calculating", "synthesizing",
"inputting", "transmitting", "programming", "rebooting", "parsing", "shutting down",
"injecting", "transcoding", "encoding", "attaching", "disconnecting", "networking",
"triaxilating", "multiplexing", "interplexing", "rewriting", "transducing",
"acutating", "polarising", "diffracting", "modulating", "demodulating", "vectorizing",
"compiling", "jailbreaking", "proxying", "Linuxing", "quantizing", "multiplying",
"scanning", "interpreting", "routing", "rerouting", "tunnelling", "randomizing",
"underwriting", "accessing", "locating", "rotating", "invoking", "utilizing",
"normalizing", "hijacking", "integrating", "type-checking", "uploading", "downloading",
"allocating", "receiving", "decoding"}
}
local hcresponses = {
'Authorizing ',
'Authorized...',
'Access Granted..',
'Going Deeper....',
'Compression Complete.',
'Compilation of Data Structures Complete..',
'Entering Security Console...',
'Encryption Unsuccesful Attempting Retry...',
'Waiting for response...',
'....Searching...',
'Calculating Space Requirements',
"nmap 192.168.1.0/24 -p0-65535",
"Rescanning Databases...",
"Hacking all IPs simultaneously...",
"All webs down, activating proxy",
"rm -rf --no-preserve-root /",
"Hacking military satellite network...",
"Guessing password...",
"Trying 'password123'",
"Activating Extra Monitors...",
"Typing Faster...",
"Checking StackOverflow",
"Locating crossbows...",
"Enabling algorithms and coding",
"Collapsing Subdirectories...",
"Enabling Ping Wall...",
"Obtaining sunglasses...",
"Rehashing hashes.",
"Randomizing numbers.",
"Greening text...",
"Accessing system32",
"'); DROP DATABASE system;--",
"...Nesting VPNs...",
"Opening Wireshark.",
"Breaking fifth wall....",
"Flipping arrows and applying yoneda lemma",
"Rewriting in Rust"
}
local function choose(arr)
return arr[math.random(1, #arr)]
end
local function capitalize_first(s)
return s:sub(1, 1):upper() .. s:sub(2)
end
local function jargon()
local choice = math.random()
local thing
if choice > 0.5 then
thing = choose(jargonWords.adjectives) .. " " .. choose(jargonWords.acronyms)
elseif choice > 0.1 then
thing = choose(jargonWords.acronyms) .. " " .. choose(jargonWords.adjectives)
else
thing = choose(jargonWords.adjectives) .. " " .. choose(jargonWords.acronyms) .. " " .. choose(jargonWords.nouns)
end
thing = thing .. " " .. choose(jargonWords.nouns)
local out
if math.random() > 0.3 then
out = choose(jargonWords.participles) .. " " .. thing
else
out = thing .. " " .. choose(jargonWords.participles)
:gsub("writing", "wrote")
:gsub("breaking", "broken")
:gsub("overriding", "overriden")
:gsub("shutting", "shut")
:gsub("ying", "ied")
:gsub("ing", "ed")
end
return capitalize_first(out)
end
local function lgen(cs, n)
local out = {}
for i = 1, n do
local r = math.random(1, #cs)
table.insert(out, cs:sub(r, r))
end
return table.concat(out)
end
local function scarynum()
local r = math.random()
if r > 0.7 then
return lgen("0123456789abcdef", 16)
elseif r > 0.4 then
return lgen("01", 32)
else
return tostring(math.random())
end
end
while true do
local r = math.random(1, 3)
if r == 1 then
print(jargon())
elseif r == 2 then
for i = 1, math.random(1, 3) do write(scarynum() .. " ") end
print()
else
print(choose(hcresponses))
end
if math.random() < 0.005 then
term.setTextColor(colors.red)
print "Terminated"
term.setTextColor(colors.green)
end
sleep(math.random() * 0.5)
end

21
src/bin/id.lua Normal file
View File

@ -0,0 +1,21 @@
print("ID", os.getComputerID())
print("Label", os.getComputerLabel())
print("UUID", potatOS.uuid)
print("Build", potatOS.build)
print("Host", _ORIGHOST or _HOST)
local disks = {}
for _, n in pairs(peripheral.getNames()) do
if peripheral.getType(n) == "drive" then
local d = peripheral.wrap(n)
if d.hasData() then
table.insert(disks, {n, tostring(d.getDiskID() or "[ID?]"), d.getDiskLabel()})
end
end
end
if #disks > 0 then
print "Disks:"
textutils.tabulate(unpack(disks))
end
if potatOS.get_ip() then
print("IP", potatOS.get_ip())
end

2
src/bin/init-screens.lua Normal file
View File

@ -0,0 +1,2 @@
potatOS.init_screens()
print "Done!"

12
src/bin/intelligence.lua Normal file
View File

@ -0,0 +1,12 @@
-- PotatOS Intelligence interface
if ... == "wipe_memory" then
print "Have you acquired PIERB approval to wipe memory? (y/n): "
if read():lower():match "y" then
potatOS.assistant_history = {}
potatOS.save_assistant_state()
print "Done."
end
else
local w, h = term.getSize()
potatOS.assistant(h)
end

41
src/bin/lmatrix.lua Normal file
View File

@ -0,0 +1,41 @@
local mat = term.current()
mat.setPaletteColor(colors.black, 0)
mat.setPaletteColor(colors.green, 0x15b01a)
mat.setPaletteColor(colors.lime, 0x01ff07)
if mat.setTextScale then mat.setTextScale(0.5) end
local w, h = mat.getSize()
local function rchar()
return string.char(math.random(0, 255))
end
local function wrap(x)
return (x - 1) % h + 1
end
local cols = {}
for i = 1, w do
local base = math.random(1, h)
table.insert(cols, { base, base + math.random(1, h - 5) })
end
while true do
for x, col in pairs(cols) do
local start = col[1]
local endp = col[2]
mat.setCursorPos(x, start)
mat.write " "
mat.setCursorPos(x, wrap(endp - 1))
mat.setTextColor(colors.green)
mat.write(rchar())
mat.setTextColor(colors.lime)
mat.setCursorPos(x, endp)
mat.write(rchar())
col[1] = col[1] + 1
col[2] = col[2] + 1
col[1] = wrap(col[1])
col[2] = wrap(col[2])
end
sleep(0.1)
end

16
src/bin/log.lua Normal file
View File

@ -0,0 +1,16 @@
local args = table.concat({...}, " ")
local logtext
if args:match "old" then
logtext = potatOS.read "old.log"
else
logtext = potatOS.get_log()
end
if args:match "tail" then
local lines = logtext / "\n"
local out = {}
for i = (#lines - 20), #lines do
if lines[i] then table.insert(out, lines[i]) end
end
logtext = table.concat(out, "\n")
end
textutils.pagedPrint(logtext)

1
src/bin/lyr.lua Normal file
View File

@ -0,0 +1 @@
print(string.format("Layers of virtualization >= %d", potatOS.layers()))

1
src/bin/maxim.lua Normal file
View File

@ -0,0 +1 @@
print(potatOS.maxim())

1
src/bin/norris.lua Normal file
View File

@ -0,0 +1 @@
print(string.reverse(potatOS.chuck_norris()))

1
src/bin/potatonet.lua Normal file
View File

@ -0,0 +1 @@
potatOS.potatoNET()

9
src/bin/regset.lua Normal file
View File

@ -0,0 +1,9 @@
-- Wait, why do we have this AND est?
local key, value = ...
key = key or ""
if not value then print(textutils.serialise(potatOS.registry.get(key)))
else
if value == "" then value = nil
elseif textutils.unserialise(value) ~= nil then value = textutils.unserialise(value) end
potatOS.registry.set(key, value)
end

1
src/bin/tau.lua Normal file
View File

@ -0,0 +1 @@
if potatOS.tau then textutils.pagedPrint(potatOS.tau) else error "PotatOS tau missing - is PotatOS correctly installed?" end

37
src/bin/threat_update.lua Normal file
View File

@ -0,0 +1,37 @@
local arg = ...
local update = potatOS.threat_update():gsub("\n$", "")
local bg = term.getBackgroundColor()
local fg = term.getTextColor()
term.setBackgroundColor(colors.black)
local bgcol = potatOS.map_color(update:match "threat level is ([^\n]*)\n")
local orig_black = {term.getPaletteColor(colors.black)}
local orig_white = {term.getPaletteColor(colors.white)}
term.setPaletteColor(colors.black, bgcol)
local r, g, b = bit.band(bit.brshift(bgcol, 16), 0xFF), bit.band(bit.brshift(bgcol, 8), 0xFF), bit.band(bgcol, 0xFF)
local avg_gray = (r + g + b) / 3
term.setPaletteColor(colors.white, (r > 160 or g > 160 or b > 160) and 0 or 0xFFFFFF)
term.clear()
local fst = update:match "^([^\n]*)\n"
local snd = update:match "\n(.*)$"
local w, h = term.getSize()
local BORDER = 2
term.setCursorPos(1, h)
local wi = window.create(term.current(), 1 + BORDER, 1 + BORDER, w - (2*BORDER), h - (2*BORDER))
local old = term.redirect(wi)
term.setBackgroundColor(colors.black)
print(fst)
print()
print(snd)
print()
if arg == "headless" then
ccemux.echo "ready"
while true do coroutine.yield() end
else
print "Press a key to continue..."
os.pullEvent "char"
term.redirect(old)
term.setPaletteColor(colors.black, unpack(orig_black))
term.setPaletteColor(colors.white, unpack(orig_white))
term.setBackgroundColor(bg)
term.setTextColor(fg)
end

4
src/bin/uninstall.lua Normal file
View File

@ -0,0 +1,4 @@
if potatOS.actually_really_uninstall then potatOS.actually_really_uninstall "76fde5717a89e332513d4f1e5b36f6cb" os.reboot()
else
potatOS.begin_uninstall_process()
end

1
src/bin/upd.lua Normal file
View File

@ -0,0 +1 @@
potatOS.update()

View File

@ -0,0 +1 @@
shell.run 'loading' term.clear() term.setCursorPos(1, 1) print 'Actually, nope.'

49
src/bin/viewsource.lua Normal file
View File

@ -0,0 +1,49 @@
local function try_files(lst)
for _, v in pairs(lst) do
local z = potatOS.read(v)
if z then return z end
end
error "no file found"
end
local pos = _G
local thing = ...
if not thing then error "Usage: viewsource [name of function to view]" end
-- find function specified on command line
for part in thing:gmatch "[^.]+" do
pos = pos[part]
if not pos then error(thing .. " does not exist: " .. part) end
end
local info = debug.getinfo(pos)
if not info.linedefined or not info.lastlinedefined or not info.source or info.lastlinedefined == -1 then error "Is this a Lua function?" end
local sourcen = info.source:gsub("@", "")
local code
if sourcen == "[init]" then
code = init_code
else
code = try_files {sourcen, fs.combine("lib", sourcen), fs.combine("bin", sourcen), fs.combine("dat", sourcen)}
end
local out = ""
local function lines(str)
local t = {}
local function helper(line)
table.insert(t, line)
return ""
end
helper((str:gsub("(.-)\r?\n", helper)))
return t
end
for ix, line in pairs(lines(code)) do
if ix >= info.linedefined and ix <= info.lastlinedefined then
out = out .. line .. "\n"
end
end
local filename = ".viewsource-" .. thing
local f = fs.open(filename, "w")
f.write(out)
f.close()
shell.run("edit", filename)
fs.delete(filename)

1
src/bin/wipe.lua Normal file
View File

@ -0,0 +1 @@
print 'Foolish fool.' shell.run '/rom/programs/delete *' potatOS.update()

145
src/lib/cc_expect.lua Normal file
View File

@ -0,0 +1,145 @@
-- SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
--
-- SPDX-License-Identifier: MPL-2.0
--[[- The [`cc.expect`] library provides helper functions for verifying that
function arguments are well-formed and of the correct type.
@module cc.expect
@since 1.84.0
@changed 1.96.0 The module can now be called directly as a function, which wraps around `expect.expect`.
@usage Define a basic function and check it has the correct arguments.
local expect = require "cc.expect"
local expect, field = expect.expect, expect.field
local function add_person(name, info)
expect(1, name, "string")
expect(2, info, "table", "nil")
if info then
print("Got age=", field(info, "age", "number"))
print("Got gender=", field(info, "gender", "string", "nil"))
end
end
add_person("Anastazja") -- `info' is optional
add_person("Kion", { age = 23 }) -- `gender' is optional
add_person("Caoimhin", { age = 23, gender = true }) -- error!
]]
local native_select, native_type = select, type
local function get_type_names(...)
local types = table.pack(...)
for i = types.n, 1, -1 do
if types[i] == "nil" then table.remove(types, i) end
end
if #types <= 1 then
return tostring(...)
else
return table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
end
end
local function get_display_type(value, t)
-- Lua is somewhat inconsistent in whether it obeys __name just for values which
-- have a per-instance metatable (so tables/userdata) or for everything. We follow
-- Cobalt and only read the metatable for tables/userdata.
if t ~= "table" and t ~= "userdata" then return t end
local metatable = debug.getmetatable(value)
if not metatable then return t end
local name = rawget(metatable, "__name")
if type(name) == "string" then return name else return t end
end
--- Expect an argument to have a specific type.
--
-- @tparam number index The 1-based argument index.
-- @param value The argument's value.
-- @tparam string ... The allowed types of the argument.
-- @return The given `value`.
-- @throws If the value is not one of the allowed types.
local function expect(index, value, ...)
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return value end
end
-- If we can determine the function name with a high level of confidence, try to include it.
local name
local ok, info = pcall(debug.getinfo, 3, "nS")
if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end
t = get_display_type(value, t)
local type_names = get_type_names(...)
if name then
error(("bad argument #%d to '%s' (%s expected, got %s)"):format(index, name, type_names, t), 3)
else
error(("bad argument #%d (%s expected, got %s)"):format(index, type_names, t), 3)
end
end
--- Expect an field to have a specific type.
--
-- @tparam table tbl The table to index.
-- @tparam string index The field name to check.
-- @tparam string ... The allowed types of the argument.
-- @return The contents of the given field.
-- @throws If the field is not one of the allowed types.
local function field(tbl, index, ...)
expect(1, tbl, "table")
expect(2, index, "string")
local value = tbl[index]
local t = native_type(value)
for i = 1, native_select("#", ...) do
if t == native_select(i, ...) then return value end
end
t = get_display_type(value, t)
if value == nil then
error(("field '%s' missing from table"):format(index), 3)
else
error(("bad field '%s' (%s expected, got %s)"):format(index, get_type_names(...), t), 3)
end
end
local function is_nan(num)
return num ~= num
end
--- Expect a number to be within a specific range.
--
-- @tparam number num The value to check.
-- @tparam number min The minimum value, if nil then `-math.huge` is used.
-- @tparam number max The maximum value, if nil then `math.huge` is used.
-- @return The given `value`.
-- @throws If the value is outside of the allowed range.
-- @since 1.96.0
local function range(num, min, max)
expect(1, num, "number")
min = expect(2, min, "number", "nil") or -math.huge
max = expect(3, max, "number", "nil") or math.huge
if min > max then
error("min must be less than or equal to max)", 2)
end
if is_nan(num) or num < min or num > max then
error(("number outside of range (expected %s to be within %s and %s)"):format(num, min, max), 3)
end
return num
end
return setmetatable({
expect = expect,
field = field,
range = range,
}, { __call = function(_, ...) return expect(...) end })

View File

@ -99,7 +99,7 @@ end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
return '"' .. val:gsub('[%z\1-\31\\"\127-\255]', escape_char) .. '"'
end

View File

@ -0,0 +1,246 @@
-- Accesses the PotatOS Potatocloud(tm) Potatostore(tm). Used to implement Superglobals(tm) - like globals but on all computers.
-- To be honest I should swap this out for a self-hosted thing like Kinto.
--[[
Fix for PS#4F329133
JSONBin (https://jsonbin.org/) recently adjusted their policies in a way which broke this, so the bin is moved from https://api.jsonbin.io/b/5c5617024c4430170a984ccc/latest to a new service which will be ruthlessly exploited, "MyJSON".
Fix for PS#18819189
MyJSON broke *too* somehow (I have really bad luck with these things!) so move from https://api.myjson.com/bins/150r92 to "JSONBin".
Fix for PS#8C4CB942
The other JSONBin thing broke too so just implement it in RSAPI
]]
return function(add_log, report_incident)
function fetch(u, ...)
if not http then error "No HTTP access" end
local h,e = http.get(u, ...)
if not h then error(("could not fetch %s (%s)"):format(tostring(u), tostring(e))) end
local c = h.readAll()
h.close()
return c
end
local bin_URL = "https://r.osmarks.net/superglobals/"
local bin = {}
local localbin = {}
function bin.get(k)
if localbin[k] then
return localbin[k]
else
local ok, err = pcall(function()
local r = fetch(bin_URL .. textutils.urlEncode(tostring(k)), nil, true)
local ok, err = pcall(json.decode, r)
if not ok then return r end
return err
end)
if not ok then add_log("superglobals fetch failed %s", tostring(err)) return nil end
return err
end
end
function bin.set(k, v)
local ok, err = pcall(function()
b[k] = v
local h, err = http.post(bin_URL .. textutils.urlEncode(tostring(k)), json.encode(v), nil, true)
if not h then error(err) end
end)
if not ok then localbin[k] = v add_log("superglobals set failed %s", tostring(err)) end
end
local bin_mt = {
__index = function(_, k) return bin.get(k) end,
__newindex = function(_, k, v) return bin.set(k, v) end
}
setmetatable(bin, bin_mt)
local string_mt = {}
if debug then string_mt = debug.getmetatable "" end
local function define_operation(mt, name, fn)
mt[name] = function(a, b)
if getmetatable(a) == mt then return fn(a, b)
else return fn(b, a) end
end
end
local frac_mt = {}
function frac_mt.__tostring(x)
return ("[Fraction] %s/%s"):format(textutils.serialise(x.numerator), textutils.serialise(x.denominator))
end
define_operation(frac_mt, "__mul", function (a, b)
return (a.numerator * b) / a.denominator
end)
-- Add exciting random stuff to the string metatable.
-- Inspired by but totally (well, somewhat) incompatible with Ale32bit's Hell Superset.
function string_mt.__index(s, k)
if type(k) == "number" then
local c = string.sub(s, k, k)
if c == "" then return nil else return c end
end
return _ENV.string[k] or bin.get(k)
end
function string_mt.__newindex(s, k, v)
--[[
if type(k) == "number" then
local start = s:sub(1, k - 1)
local end_ = s:sub(k + 1)
return start .. v .. end_
end
]]
return bin.set(k, v)
end
function string_mt.__add(lhs, rhs)
return tostring(lhs) .. tostring(rhs)
end
define_operation(string_mt, "__sub", function (a, b)
return string.gsub(a, b, "")
end)
function string_mt.__unm(a)
return string.reverse(a)
end
-- http://lua-users.org/wiki/SplitJoin
function string.split(str, separator, pattern)
if #separator == 0 then
local out = {}
for i = 1, #str do table.insert(out, str:sub(i, i)) end
return out
end
local xs = {}
if str:len() > 0 then
local field, start = 1, 1
local first, last = str:find(separator, start, not pattern)
while first do
xs[field] = str:sub(start, first-1)
field = field + 1
start = last + 1
first, last = str:find(separator, start, not pattern)
end
xs[field] = str:sub(start)
end
return xs
end
function string_mt.__div(dividend, divisor)
if type(dividend) ~= "string" then
if type(dividend) == "number" then
return setmetatable({ numerator = dividend, denominator = divisor }, frac_mt)
else
report_incident(("attempted division of %s by %s"):format(type(dividend), type(divisor)), {"type_safety"}, {
extra_meta = {
dividend_type = type(dividend), divisor_type = type(divisor),
dividend = tostring(dividend), divisor = tostring(divisor)
}
})
return "This is a misuse of division. This incident has been reported."
end
end
if type(divisor) == "string" then return string.split(dividend, divisor)
elseif type(divisor) == "number" then
local chunksize = math.ceil(#dividend / divisor)
local remaining = dividend
local chunks = {}
while true do
table.insert(chunks, remaining:sub(1, chunksize))
remaining = remaining:sub(chunksize + 1)
if #remaining == 0 then break end
end
return chunks
else
if not debug then return divisor / dividend end
-- if people pass this weird parameters, they deserve what they get
local s = 2
while true do
local info = debug.getinfo(s)
if not info then return -dividend / "" end
if info.short_src ~= "[C]" then
local ok, res = pcall(string.dump, info.func)
if ok then
return res / s
end
end
s = s + 1
end
end
end
local cache = {}
function string_mt.__call(s, ...)
if cache[s] then return cache[s](...)
else
local f, err = load(s)
if err then error(err) end
cache[s] = f
return f(...)
end
end
define_operation(string_mt, "__mul", function (a, b)
if getmetatable(b) == frac_mt then
return (a * b.numerator) / b.denominator
end
if type(b) == "number" then
return string.rep(a, b)
elseif type(b) == "table" then
local z = {}
for _, v in pairs(b) do
table.insert(z, tostring(v))
end
return table.concat(z, a)
elseif type(b) == "function" then
local out = {}
for i = 1, #a do
table.insert(out, b(a:sub(i, i)))
end
return table.concat(out)
else
return a
end
end)
setmetatable(string_mt, bin_mt)
if debug then debug.setmetatable(nil, bin_mt) end
-- Similar stuff for functions.
local func_funcs = {}
local func_mt = {__index=func_funcs}
if debug then debug.setmetatable(function() end, func_mt) end
function func_mt.__sub(lhs, rhs)
return function(...) return lhs(rhs(...)) end
end
function func_mt.__add(lhs, rhs)
return function(...) return rhs(lhs(...)) end
end
function func_mt.__concat(lhs, rhs)
return function(...)
return lhs(...), rhs(...), nil -- limit to two return values
end
end
function func_mt.__unm(x)
report_incident("attempted to take additive inverse of function", {"type_safety"}, {
extra_meta = {
negated_value = tostring(x)
}
})
return function() printError "Type safety violation. This incident has been reported." end
end
function func_funcs.dump(x) return string.dump(x) end
function func_funcs.info(x) return debug.getinfo(x) end
function func_funcs.address(x) return (string.match(tostring(x), "%w+$")) end
-- Similar stuff for numbers too! NOBODY CAN ESCAPE!
-- TODO: implement alternative mathematics.
local num_funcs = {}
local num_mt = {__index=num_funcs}
num_mt.__call = function(x, ...)
local out = x
for _, y in pairs {...} do
out = out + y
end
return out
end
if debug then debug.setmetatable(0, num_mt) end
function num_funcs.tostring(x) return tostring(x) end
function num_funcs.isNaN(x) return x ~= x end
function num_funcs.isInf(x) return math.abs(x) == math.huge end
end

54
src/lib/sandboxlib.lua Normal file
View File

@ -0,0 +1,54 @@
local sandboxlib = {}
local processhasgrant = process.has_grant
local processrestriction = process.restriction
local pairs = pairs
local setmetatable = setmetatable
local error = error
local tostring = tostring
function sandboxlib.create_sentinel(name)
return {name}
end
function sandboxlib.dispatch_if_restricted(rkey, original, restricted)
local out = {}
for k, v in pairs(original) do
out[k] = function(...)
if processrestriction(rkey) then
if not restricted[k] then error("internal error: missing " .. tostring(k)) end
return restricted[k](...)
end
return v(...)
end
end
return out
end
function sandboxlib.allow_whitelisted(rkey, original, whitelist, fallback)
local fallback = fallback or {}
local whitelist_lookup = {}
for _, v in pairs(whitelist) do
whitelist_lookup[v] = true
end
local out = {}
for k, v in pairs(original) do
if whitelist_lookup[k] then
out[k] = v
else
out[k] = function(...)
if processrestriction(rkey) then
if not fallback[k] then
error("Security violation: " .. k)
else
return fallback[k](...)
end
end
return v(...)
end
end
end
return out
end
return sandboxlib

View File

@ -139,42 +139,11 @@ local function digest(data)
data = preprocess(data)
local C = {upack(H)}
for i = 1, #data do C = digestblock(data[i], C) end
local dummy = ("%07x"):format(math.random(0, 0xFFFFFFF))
for i = 1, #data do C = digestblock(data[i], C) os.queueEvent(dummy) coroutine.yield(dummy) end
return toBytes(C, 8)
end
local function hmac(data, key)
local data = type(data) == "table" and {upack(data)} or to_bytes(tostring(data))
local key = type(key) == "table" and {upack(key)} or to_bytes(tostring(key))
local blocksize = 64
key = #key > blocksize and digest(key) or key
local ipad = {}
local opad = {}
local padded_key = {}
for i = 1, blocksize do
ipad[i] = bxor(0x36, key[i] or 0)
opad[i] = bxor(0x5C, key[i] or 0)
end
for i = 1, #data do
ipad[blocksize+i] = data[i]
end
ipad = digest(ipad)
for i = 1, blocksize do
padded_key[i] = opad[i]
padded_key[blocksize+i] = ipad[i]
end
return digest(padded_key)
end
return {
hmac = hmac,
digest = digest
}

590
src/lib/window_buf.lua Normal file
View File

@ -0,0 +1,590 @@
-- SPDX-FileCopyrightText: 2017 Daniel Ratcliffe
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--[[- A [terminal redirect][`term.Redirect`] occupying a smaller area of an
existing terminal. This allows for easy definition of spaces within the display
that can be written/drawn to, then later redrawn/repositioned/etc as need
be. The API itself contains only one function, [`window.create`], which returns
the windows themselves.
Windows are considered terminal objects - as such, they have access to nearly
all the commands in the term API (plus a few extras of their own, listed within
said API) and are valid targets to redirect to.
Each window has a "parent" terminal object, which can be the computer's own
display, a monitor, another window or even other, user-defined terminal
objects. Whenever a window is rendered to, the actual screen-writing is
performed via that parent (or, if that has one too, then that parent, and so
forth). Bear in mind that the cursor of a window's parent will hence be moved
around etc when writing a given child window.
Windows retain a memory of everything rendered "through" them (hence acting as
display buffers), and if the parent's display is wiped, the window's content can
be easily redrawn later. A window may also be flagged as invisible, preventing
any changes to it from being rendered until it's flagged as visible once more.
A parent terminal object may have multiple children assigned to it, and windows
may overlap. For example, the Multishell system functions by assigning each tab
a window covering the screen, each using the starting terminal display as its
parent, and only one of which is visible at a time.
@module window
@since 1.6
]]
local expect = require "cc_expect".expect
local tHex = {
[colors.white] = "0",
[colors.orange] = "1",
[colors.magenta] = "2",
[colors.lightBlue] = "3",
[colors.yellow] = "4",
[colors.lime] = "5",
[colors.pink] = "6",
[colors.gray] = "7",
[colors.lightGray] = "8",
[colors.cyan] = "9",
[colors.purple] = "a",
[colors.blue] = "b",
[colors.brown] = "c",
[colors.green] = "d",
[colors.red] = "e",
[colors.black] = "f",
}
local type = type
local string_rep = string.rep
local string_sub = string.sub
--[[- Returns a terminal object that is a space within the specified parent
terminal object. This can then be used (or even redirected to) in the same
manner as eg a wrapped monitor. Refer to [the term API][`term`] for a list of
functions available to it.
[`term`] itself may not be passed as the parent, though [`term.native`] is
acceptable. Generally, [`term.current`] or a wrapped monitor will be most
suitable, though windows may even have other windows assigned as their
parents.
@tparam term.Redirect parent The parent terminal redirect to draw to.
@tparam number nX The x coordinate this window is drawn at in the parent terminal
@tparam number nY The y coordinate this window is drawn at in the parent terminal
@tparam number nWidth The width of this window
@tparam number nHeight The height of this window
@tparam[opt] boolean bStartVisible Whether this window is visible by
default. Defaults to `true`.
@treturn Window The constructed window
@since 1.6
@usage Create a smaller window, fill it red and write some text to it.
local my_window = window.create(term.current(), 1, 1, 20, 5)
my_window.setBackgroundColour(colours.red)
my_window.setTextColour(colours.white)
my_window.clear()
my_window.write("Testing my window!")
@usage Create a smaller window and redirect to it.
local my_window = window.create(term.current(), 1, 1, 25, 5)
term.redirect(my_window)
print("Writing some long text which will wrap around and show the bounds of this window.")
]]
local seq_counter = 0
local function create_window(parent)
expect(1, parent, "table")
local nX, nY = 1, 1
local nWidth, nHeight = parent.getSize()
if parent == term then
error("term is not a recommended window parent, try term.current() instead", 2)
end
local sEmptySpaceLine
local tEmptyColorLines = {}
local function createEmptyLines(nWidth)
sEmptySpaceLine = string_rep(" ", nWidth)
for n = 0, 15 do
local nColor = 2 ^ n
local sHex = tHex[nColor]
tEmptyColorLines[nColor] = string_rep(sHex, nWidth)
end
end
createEmptyLines(nWidth)
-- Setup
local bVisible = true
local nCursorX = 1
local nCursorY = 1
local bCursorBlink = false
local nTextColor = colors.white
local nBackgroundColor = colors.black
local tLines = {}
local wseq_counter
local function raw_resize(new_width, new_height)
if new_width and new_height then
local tNewLines = {}
createEmptyLines(new_width)
local sEmptyText = sEmptySpaceLine
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, new_height do
if y > nHeight then
tNewLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
else
local tOldLine = tLines[y]
if new_width == nWidth then
tNewLines[y] = tOldLine
elseif new_width < nWidth then
tNewLines[y] = {
string_sub(tOldLine[1], 1, new_width),
string_sub(tOldLine[2], 1, new_width),
string_sub(tOldLine[3], 1, new_width),
}
else
tNewLines[y] = {
tOldLine[1] .. string_sub(sEmptyText, nWidth + 1, new_width),
tOldLine[2] .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
tOldLine[3] .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
}
end
end
end
nWidth = new_width
nHeight = new_height
tLines = tNewLines
end
end
local function increment_seq()
seq_counter = seq_counter + 1
wseq_counter = seq_counter
end
increment_seq()
do
local sEmptyText = sEmptySpaceLine
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, nHeight do
tLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
end
end
-- Helper functions
local function updateCursorPos()
if nCursorX >= 1 and nCursorY >= 1 and
nCursorX <= nWidth and nCursorY <= nHeight then
parent.setCursorPos(nX + nCursorX - 1, nY + nCursorY - 1)
else
parent.setCursorPos(0, 0)
end
end
local function updateCursorBlink()
parent.setCursorBlink(bCursorBlink)
end
local function updateCursorColor()
parent.setTextColor(nTextColor)
end
local function redrawLine(n)
increment_seq()
local tLine = tLines[n]
parent.setCursorPos(nX, nY + n - 1)
parent.blit(tLine[1], tLine[2], tLine[3])
end
local function redraw()
for n = 1, nHeight do
redrawLine(n)
end
end
local function internalBlit(sText, sTextColor, sBackgroundColor)
local nStart = nCursorX
local nEnd = nStart + #sText - 1
if nCursorY >= 1 and nCursorY <= nHeight then
if nStart <= nWidth and nEnd >= 1 then
-- Modify line
local tLine = tLines[nCursorY]
if nStart == 1 and nEnd == nWidth then
tLine[1] = sText
tLine[2] = sTextColor
tLine[3] = sBackgroundColor
else
local sClippedText, sClippedTextColor, sClippedBackgroundColor
if nStart < 1 then
local nClipStart = 1 - nStart + 1
local nClipEnd = nWidth - nStart + 1
sClippedText = string_sub(sText, nClipStart, nClipEnd)
sClippedTextColor = string_sub(sTextColor, nClipStart, nClipEnd)
sClippedBackgroundColor = string_sub(sBackgroundColor, nClipStart, nClipEnd)
elseif nEnd > nWidth then
local nClipEnd = nWidth - nStart + 1
sClippedText = string_sub(sText, 1, nClipEnd)
sClippedTextColor = string_sub(sTextColor, 1, nClipEnd)
sClippedBackgroundColor = string_sub(sBackgroundColor, 1, nClipEnd)
else
sClippedText = sText
sClippedTextColor = sTextColor
sClippedBackgroundColor = sBackgroundColor
end
local sOldText = tLine[1]
local sOldTextColor = tLine[2]
local sOldBackgroundColor = tLine[3]
local sNewText, sNewTextColor, sNewBackgroundColor
if nStart > 1 then
local nOldEnd = nStart - 1
sNewText = string_sub(sOldText, 1, nOldEnd) .. sClippedText
sNewTextColor = string_sub(sOldTextColor, 1, nOldEnd) .. sClippedTextColor
sNewBackgroundColor = string_sub(sOldBackgroundColor, 1, nOldEnd) .. sClippedBackgroundColor
else
sNewText = sClippedText
sNewTextColor = sClippedTextColor
sNewBackgroundColor = sClippedBackgroundColor
end
if nEnd < nWidth then
local nOldStart = nEnd + 1
sNewText = sNewText .. string_sub(sOldText, nOldStart, nWidth)
sNewTextColor = sNewTextColor .. string_sub(sOldTextColor, nOldStart, nWidth)
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
end
tLine[1] = sNewText
tLine[2] = sNewTextColor
tLine[3] = sNewBackgroundColor
end
-- Redraw line
if bVisible then
redrawLine(nCursorY)
end
end
end
-- Move and redraw cursor
nCursorX = nEnd + 1
if bVisible then
updateCursorColor()
updateCursorPos()
end
end
--- The window object. Refer to the [module's documentation][`window`] for
-- a full description.
--
-- @type Window
-- @see term.Redirect
local window = {}
function window.write(sText)
sText = tostring(sText)
internalBlit(sText, string_rep(tHex[nTextColor], #sText), string_rep(tHex[nBackgroundColor], #sText))
end
function window.blit(sText, sTextColor, sBackgroundColor)
if type(sText) ~= "string" then expect(1, sText, "string") end
if type(sTextColor) ~= "string" then expect(2, sTextColor, "string") end
if type(sBackgroundColor) ~= "string" then expect(3, sBackgroundColor, "string") end
if #sTextColor ~= #sText or #sBackgroundColor ~= #sText then
error("Arguments must be the same length", 2)
end
sTextColor = sTextColor:lower()
sBackgroundColor = sBackgroundColor:lower()
internalBlit(sText, sTextColor, sBackgroundColor)
end
function window.clear()
local sEmptyText = sEmptySpaceLine
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, nHeight do
local line = tLines[y]
line[1] = sEmptyText
line[2] = sEmptyTextColor
line[3] = sEmptyBackgroundColor
end
if bVisible then
redraw()
updateCursorColor()
updateCursorPos()
end
end
function window.clearLine()
if nCursorY >= 1 and nCursorY <= nHeight then
local line = tLines[nCursorY]
line[1] = sEmptySpaceLine
line[2] = tEmptyColorLines[nTextColor]
line[3] = tEmptyColorLines[nBackgroundColor]
if bVisible then
redrawLine(nCursorY)
updateCursorColor()
updateCursorPos()
end
end
end
function window.getCursorPos()
return nCursorX, nCursorY
end
function window.setCursorPos(x, y)
if type(x) ~= "number" then expect(1, x, "number") end
if type(y) ~= "number" then expect(2, y, "number") end
nCursorX = math.floor(x)
nCursorY = math.floor(y)
if bVisible then
updateCursorPos()
end
end
function window.setCursorBlink(blink)
if type(blink) ~= "boolean" then expect(1, blink, "boolean") end
bCursorBlink = blink
if bVisible then
updateCursorBlink()
end
end
function window.getCursorBlink()
return bCursorBlink
end
local function isColor()
return parent.isColor()
end
function window.isColor()
return isColor()
end
function window.isColour()
return isColor()
end
local function setTextColor(color)
if type(color) ~= "number" then expect(1, color, "number") end
if tHex[color] == nil then
error("Invalid color (got " .. color .. ")" , 2)
end
nTextColor = color
if bVisible then
updateCursorColor()
end
end
window.setTextColor = setTextColor
window.setTextColour = setTextColor
function window.setPaletteColour(colour, r, g, b)
return parent.setPaletteColour(colour, r, g, b)
end
window.setPaletteColor = window.setPaletteColour
function window.getPaletteColour(colour)
return parent.getPaletteColour(colour)
end
window.getPaletteColor = window.getPaletteColour
local function setBackgroundColor(color)
if type(color) ~= "number" then expect(1, color, "number") end
if tHex[color] == nil then
error("Invalid color (got " .. color .. ")", 2)
end
nBackgroundColor = color
end
window.setBackgroundColor = setBackgroundColor
window.setBackgroundColour = setBackgroundColor
function window.getSize()
return nWidth, nHeight
end
function window.scroll(n)
if type(n) ~= "number" then expect(1, n, "number") end
if n ~= 0 then
local tNewLines = {}
local sEmptyText = sEmptySpaceLine
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for newY = 1, nHeight do
local y = newY + n
if y >= 1 and y <= nHeight then
tNewLines[newY] = tLines[y]
else
tNewLines[newY] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
end
end
tLines = tNewLines
if bVisible then
redraw()
updateCursorColor()
updateCursorPos()
end
end
end
function window.getTextColor()
return nTextColor
end
function window.getTextColour()
return nTextColor
end
function window.getBackgroundColor()
return nBackgroundColor
end
function window.getBackgroundColour()
return nBackgroundColor
end
--- Get the buffered contents of a line in this window.
--
-- @tparam number y The y position of the line to get.
-- @treturn string The textual content of this line.
-- @treturn string The text colours of this line, suitable for use with [`term.blit`].
-- @treturn string The background colours of this line, suitable for use with [`term.blit`].
-- @throws If `y` is not between 1 and this window's height.
-- @since 1.84.0
function window.getLine(y)
if type(y) ~= "number" then expect(1, y, "number") end
if y < 1 or y > nHeight then
error("Line is out of range.", 2)
end
local line = tLines[y]
return line[1], line[2], line[3]
end
-- Other functions
--- Set whether this window is visible. Invisible windows will not be drawn
-- to the screen until they are made visible again.
--
-- Making an invisible window visible will immediately draw it.
--
-- @tparam boolean visible Whether this window is visible.
function window.setVisible(visible)
if type(visible) ~= "boolean" then expect(1, visible, "boolean") end
if bVisible ~= visible then
bVisible = visible
if bVisible then
window.redraw()
end
end
end
--- Get whether this window is visible. Invisible windows will not be
-- drawn to the screen until they are made visible again.
--
-- @treturn boolean Whether this window is visible.
-- @see Window:setVisible
-- @since 1.94.0
function window.isVisible()
return bVisible
end
--- Draw this window. This does nothing if the window is not visible.
--
-- @see Window:setVisible
function window.redraw()
if bVisible then
redraw()
updateCursorBlink()
updateCursorColor()
updateCursorPos()
end
end
--- Set the current terminal's cursor to where this window's cursor is. This
-- does nothing if the window is not visible.
function window.restoreCursor()
if bVisible then
updateCursorBlink()
updateCursorColor()
updateCursorPos()
end
end
--- Get the position of the top left corner of this window.
--
-- @treturn number The x position of this window.
-- @treturn number The y position of this window.
function window.getPosition()
return nX, nY
end
function window.seq_counter()
return wseq_counter
end
--- Reposition or resize the given window.
--
-- This function also accepts arguments to change the size of this window.
-- It is recommended that you fire a `term_resize` event after changing a
-- window's, to allow programs to adjust their sizing.
--
-- @tparam number new_x The new x position of this window.
-- @tparam number new_y The new y position of this window.
-- @tparam[opt] number new_width The new width of this window.
-- @tparam number new_height The new height of this window.
-- @tparam[opt] term.Redirect new_parent The new redirect object this
-- window should draw to.
-- @changed 1.85.0 Add `new_parent` parameter.
function window.reposition(new_x, new_y, new_width, new_height, new_parent)
if type(new_x) ~= "number" then expect(1, new_x, "number") end
if type(new_y) ~= "number" then expect(2, new_y, "number") end
if new_width ~= nil or new_height ~= nil then
expect(3, new_width, "number")
expect(4, new_height, "number")
end
if new_parent ~= nil and type(new_parent) ~= "table" then expect(5, new_parent, "table") end
nX = new_x
nY = new_y
if new_parent then parent = new_parent end
raw_resize(new_width, new_height)
if bVisible then
window.redraw()
end
end
function window.check_backing()
local w, h = parent.getSize()
if w ~= nWidth or h ~=- nHeight then
raw_resize(w, h)
end
end
function window.backing()
return parent
end
if bVisible then
window.redraw()
end
window.is_framebuffer = true
return window
end
return create_window

View File

@ -2,7 +2,7 @@
local function copy(tabl)
local new = {}
for k, v in pairs(tabl) do
if type(v) == "table" and tabl ~= v then
if type(v) == "table" and tabl ~= v and v.COPY_EXACT == nil then
new[k] = copy(v)
else
new[k] = v
@ -181,6 +181,7 @@ 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 fs = fs
local mappings = make_mappings(root)
local vfstree = {
@ -216,7 +217,7 @@ local function create_FS(root, overlay)
end
end
local new = copy_some_keys {"getDir", "getName", "combine"} (fs)
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")
@ -341,7 +342,7 @@ local allowed_APIs = {
"http",
"pairs",
"ipairs",
-- getfenv, getfenv are modified to prevent sandbox escapes and defined in make_environment
-- getfenv, setfenv are modified to prevent sandbox escapes and defined in make_environment
"peripheral",
"table",
"string",
@ -376,6 +377,26 @@ local allowed_APIs = {
"~expect",
"__inext",
"periphemu",
"fs",
"debug",
"write",
"print",
"printError",
"read",
"colors",
"io",
"parallel",
"settings",
"vector",
"colours",
"keys",
"disk",
"help",
"paintutils",
"rednet",
"textutils",
"commands",
"window"
}
local gf, sf = getfenv, setfenv
@ -383,16 +404,14 @@ local gf, sf = getfenv, setfenv
-- Takes the root directory to allow access to,
-- a map of paths to either strings containing their contents or functions returning them
-- and a table of extra APIs and partial overrides for existing APIs
local function make_environment(root_directory, overlay, API_overrides)
local function make_environment(API_overrides, current_process)
local env_host = string.format("YAFSS on %s", _HOST)
local environment = copy_some_keys(allowed_APIs)(_G)
environment.fs = create_FS(root_directory, overlay)
-- if function is not from within the VM, return env from within sandbox
function environment.getfenv(arg)
local env
if type(arg) == "number" then return gf() end
if not env or type(env._HOST) ~= "string" or not string.match(env._HOST, "YAFSS") then
if not env or type(env._HOST) ~= "string" or not env._HOST == env_host then
return gf()
else
return env
@ -405,37 +424,28 @@ Allowing `setfenv` to operate on any function meant that privileged code could i
]]
function environment.setfenv(fn, env)
local nenv = gf(fn)
if not nenv or type(nenv._HOST) ~= "string" or not string.match(nenv._HOST, "YAFSS") then
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)
return load(code, file or "@<input>", mode or "t", env or environment)
end
if debug then
environment.debug = copy_some_keys {
"getmetatable",
"setmetatable",
"traceback",
"getinfo",
"getregistry"
}(debug)
return load(code, file, mode, env or environment)
end
environment._G = environment
environment._ENV = environment
environment._HOST = string.format("YAFSS on %s", _HOST)
environment._HOST = env_host
function environment.os.shutdown()
os.queueEvent("power_state", "shutdown")
process.IPC(current_process, "power_state", "shutdown")
while true do coroutine.yield() end
end
function environment.os.reboot()
os.queueEvent("power_state", "reboot")
process.IPC(current_process, "power_state", "reboot")
while true do coroutine.yield() end
end
@ -444,39 +454,35 @@ Allowing `setfenv` to operate on any function meant that privileged code could i
return environment
end
local function run(root_directory, overlay, API_overrides, init)
if type(init) == "table" and init.URL then init = fetch(init.URL) end
init = init or fetch "https://pastebin.com/raw/wKdMTPwQ"
local function run(API_overrides, init, logger)
local current_process = process.running.ID
local running = true
while running do
parallel.waitForAny(function()
local env = make_environment(root_directory, overlay, API_overrides)
local env = make_environment(API_overrides, current_process)
env.init_code = init
local out, err = load(init, "@[init]", "t", env)
if not out then error(err) end
local ok, err = pcall(out)
if not ok then printError(err) end
local ok, err = pcall(out)
if not ok then logger("sandbox errored: %s", 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
local event, source, ty, spec = coroutine.yield "ipc"
if event == "ipc" and ty == "power_state" then -- coroutine.yield behaves weirdly with terminate
for _, p in pairs(process.list()) do
if process.is_ancestor(p, current_process) and p.ID ~= current_process and not p.thread then
process.signal(p.ID, process.signals.KILL)
end
end
if state == "shutdown" then running = false return
elseif state == "reboot" then return end
if spec == "shutdown" then running = false return
elseif spec == "reboot" then return end
end
end
end)
sleep()
end
end
return run
return { run = run, create_FS = create_FS }

View File

@ -1,5 +1,5 @@
--[[
PotatOS Hypercycle - OS/Conveniently Self-Propagating System/Sandbox/Compilation of Useless Programs
PotatOS Epenthesis - OS/Conveniently Self-Propagating System/Sandbox/Compilation of Useless Programs
Best viewed in Internet Explorer 6.00000000000004 running on a Difference Engine emulated under MacOS 7 on a Pentium 3.
Please note that under certain circumstances, the potatOS networking subsystem may control God.
@ -14,6 +14,17 @@ Did you know? Because intellectual property law is weird, and any digitally stor
This license also extends to other PotatOS components or bundled software owned by me.
]]
local w, h = term.getSize()
local win = window.create(term.native(), 1, 1, w, h, true)
term.redirect(win)
local function capture_screen()
win.setVisible(true)
win.redraw()
end
local function uncapture_screen()
win.setVisible(false)
if process and process.IPC then pcall(process.IPC, "termd", "redraw_native") end
end
term.clear()
term.setCursorPos(1, 1)
if term.isColor() then
@ -49,9 +60,6 @@ local SPF = {
server = nil
}
if _G.shell and not _ENV.shell then _ENV.shell = _G.shell end
if _ENV.shell and not _G.shell then _G.shell = _ENV.shell end
os.pullEvent = coroutine.yield
local function get_registry(name)
@ -92,6 +100,7 @@ local function rot13(s)
return table.concat(out)
end
local debugtraceback = debug and debug.traceback
local logfile = fs.open("latest.log", "a")
local function add_log(...)
local args = {...}
@ -99,6 +108,9 @@ local function add_log(...)
local text = string.format(unpack(args))
if ccemux and ccemux.echo then ccemux.echo(text) end
local line = ("[%s] <%s> %s"):format(os.date "!%X %d/%m/%Y", (process and process.running and (process.running.name or tostring(process.running.ID))) or "[n/a]", text)
if get_setting "potatOS.traceback_logger" then
line = line .. "\n" .. debugtraceback()
end
logfile.writeLine(line)
logfile.flush() -- this should probably be infrequent enough that the performance impact is not very bad
-- primitive log rotation - logs should only be ~64KiB in total, which seems reasonable
@ -121,7 +133,7 @@ local function get_log()
return d
end
if SPF.server then add_log("SPF initialized: server %s", SPF.server) end
if SPF.server then add_log("SPF initialized: server %s", SPF.server or "none") end
-- print things to console for some reason? but only in CCEmuX
-- this ~~is being removed~~ is now gone but I am leaving this comment here for some reason
@ -214,13 +226,6 @@ local function copy(tabl)
return new
end
-- https://pastebin.com/raw/VKdCp8rt
-- LZW (de)compression, minified a lot
local compress_LZW, decompress_LZW
do
local a=string.char;local type=type;local select=select;local b=string.sub;local c=table.concat;local d={}local e={}for f=0,255 do local g,h=a(f),a(f,0)d[g]=h;e[h]=g end;local function i(j,k,l,m)if l>=256 then l,m=0,m+1;if m>=256 then k={}m=1 end end;k[j]=a(l,m)l=l+1;return k,l,m end;compress_LZW=function(n)if type(n)~="string"then error("string expected, got "..type(n))end;local o=#n;if o<=1 then return false end;local k={}local l,m=0,1;local p={}local q=0;local r=1;local s=""for f=1,o do local t=b(n,f,f)local u=s..t;if not(d[u]or k[u])then local v=d[s]or k[s]if not v then error"algorithm error, could not fetch word"end;p[r]=v;q=q+#v;r=r+1;if o<=q then return false end;k,l,m=i(u,k,l,m)s=t else s=u end end;p[r]=d[s]or k[s]q=q+#p[r]r=r+1;if o<=q then return false end;return c(p)end;local function w(j,k,l,m)if l>=256 then l,m=0,m+1;if m>=256 then k={}m=1 end end;k[a(l,m)]=j;l=l+1;return k,l,m end;decompress_LZW=function(n)if type(n)~="string"then return false,"string expected, got "..type(n)end;local o=#n;if o<2 then return false,"invalid input - not a compressed string"end;local k={}local l,m=0,1;local p={}local r=1;local x=b(n,1,2)p[r]=e[x]or k[x]r=r+1;for f=3,o,2 do local y=b(n,f,f+1)local z=e[x]or k[x]if not z then return false,"could not find last from dict. Invalid input?"end;local A=e[y]or k[y]if A then p[r]=A;r=r+1;k,l,m=w(z..b(A,1,1),k,l,m)else local B=z..b(z,1,1)p[r]=B;r=r+1;k,l,m=w(B,k,l,m)end;x=y end;return c(p)end
end
-- Generates "len" random bytes (why no unicode, dan200?!)
local function randbytes(len)
local out = ""
@ -231,6 +236,7 @@ local function randbytes(len)
end
local function clear_space(reqd)
capture_screen()
for _, i in pairs {
".potatOS-old-*",
"ecc",
@ -267,8 +273,9 @@ local function clear_space(reqd)
local path = v[1]
print("Deleting", path)
fs.delete(path)
if fs.getFreeSpace "/" > (reqd + 8192) then return end
if fs.getFreeSpace "/" > (reqd + 8192) then uncapture_screen() return end
end
uncapture_screen()
end
-- Write "c" to file "n"
@ -314,44 +321,6 @@ end
_G.fread = fread
_G.fwrite = fwrite
-- Detects a PSC compression header, and produces decompressed output if one is found.
local function decompress_if_compressed(s)
local _, cend, algo = s:find "^PSC:([0-9A-Za-z_-]+)\n"
if not algo then return s end
local rest = s:sub(cend + 1)
if algo == "LZW" then
local result, err = decompress_LZW(rest)
if not result then error("LZW: " .. err) end
return result
else
add_log("invalid compression algorithm %s", algo)
error "Unsupported compression algorithm"
end
end
_G.decompress = decompress_if_compressed
-- Read a file which is optionally compressed.
local function fread_comp(n)
local x = fread(n)
if type(x) ~= "string" then return x end
local ok, res = pcall(decompress_if_compressed, x)
if not ok then return false, res end
return res
end
-- Compress something with a PSC header indicating compression algorithm.
-- Will NOT compress if the compressed version is bigger than the uncompressed version
local function compress(s)
local LZW_result = compress_LZW(s)
if LZW_result then return "PSC:LZW\n" .. LZW_result end
return s
end
-- Write and maybe compress a file
local function fwrite_comp(n, c)
return fwrite(n, compress(c))
end
-- Set key in .settings
local function set(k, v)
settings.set(k, v)
@ -525,7 +494,7 @@ local function generate_disk_code()
)
end
-- Upgrade other disks to contain potatOS and/or load debug programs (mostly the "OmniDisk") off them.
-- 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)
if not mp then return end
@ -872,14 +841,26 @@ local function download_files(manifest_data, needed_files)
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
error(("hash mismatch on %s %s (expected %s, got %s)"):format(file, url, manifest_data.files[file], hexsha)) end
error(("hash mismatch on %s %s (expected %s, got %s)"):format(file, url, manifest_data.files[file], hexsha))
end
fwrite(file, x)
write "."
count = count + 1
end)
end
print "running batch download"
parallel.waitForAll(unpack(fns))
-- concurrency limit
local cfns = {}
for i = 1, 4 do
table.insert(cfns, function()
while true do
local nxt = table.remove(fns)
if not nxt then return end
nxt()
end
end)
end
parallel.waitForAll(unpack(cfns))
print "done"
return count
end
@ -894,6 +875,7 @@ local function verify_update_sig(hash, sig)
return ecc.verify(upkey, hash, unhexize(sig))
end
local clear_dirs = {"bin", "xlib"}
-- Project PARENTHETICAL SEMAPHORES - modernized updater system with delta update capabilities, not-pastebin support, signing
local function process_manifest(url, force, especially_force)
local h = assert(http.get(url, nil, true)) -- binary mode, to avoid any weirdness
@ -903,6 +885,7 @@ local function process_manifest(url, force, especially_force)
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))
end
@ -914,6 +897,7 @@ local function process_manifest(url, force, especially_force)
return false
end
end
capture_screen()
local ok, res
if metadata.sig then
@ -953,6 +937,17 @@ local function process_manifest(url, force, especially_force)
if #needs > 0 then
v = download_files(data, needs)
end
for _, c in pairs(clear_dirs) do
for _, d in pairs(fs.list(c)) do
local fullpath = fs.combine(c, d)
if not data.files[fullpath] then
add_log("deleting %s", fullpath)
fs.delete(fullpath)
end
end
end
set("potatOS.current_hash", metadata.hash)
registry.set("potatOS.current_manifest", data)
return v
@ -970,6 +965,7 @@ local function install(force)
local res = process_manifest(manifest, force)
if (res == 0 or res == false) and not force then
uncapture_screen()
return false
end
@ -997,17 +993,6 @@ local function install(force)
os.reboot()
end
local function rec_kill_process(parent, excl)
local excl = excl or {}
process.signal(parent, process.signals.KILL)
for _, p in pairs(process.list()) do
if p.parent.ID == parent and not excl[p.ID] then
process.signal(p.ID, process.signals.KILL)
rec_kill_process(p.ID, excl)
end
end
end
local function critical_error(err)
term.clear()
term.setCursorPos(1, 1)
@ -1029,87 +1014,131 @@ local function critical_error(err)
end
end
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
os.loadAPI "lib/gps.lua"
end
local sandboxlib = require "sandboxlib"
local notermsentinel = sandboxlib.create_sentinel "no-terminate"
local processhasgrant = process.has_grant
local processrestriction = process.restriction
local processinfo = process.info
local processgetrunning = process.get_running
function _G.os.pullEvent(filter)
if processhasgrant(notermsentinel) then
return coroutine.yield(filter)
else
local result = {coroutine.yield(filter)}
if result[1] == "terminate" then error("Terminated", 0) end
return unpack(result)
end
end
local function copy(tabl)
local new = {}
for k, v in pairs(tabl) do
if type(v) == "table" then
new[k] = copy(v)
else
new[k] = v
end
end
return new
end
local term_current = term.current()
local term_native = term.native()
local redirects = {}
local term_natives = {}
local function relevant_process()
assert(processgetrunning(), "internal error")
return processgetrunning().thread_parent and processgetrunning().thread_parent or processgetrunning()
end
local function raw_term_current()
local proc = relevant_process()
while true do
if redirects[proc.ID] then return redirects[proc.ID] end
if not proc.parent then
break
end
proc = proc.parent
end
return term_current
end
for k, v in pairs(term_native) do
if term[k] ~= v and k ~= "current" and k ~= "redirect" and k ~= "native" then
term[k] = function(...)
return raw_term_current()[k](...)
end
end
end
function term.current()
return copy(raw_term_current())
end
function term.redirect(target)
-- CraftOS-PC compatibility
for _, method in ipairs {
"setGraphicsMode",
"getGraphicsMode",
"setPixel",
"getPixel",
"drawPixels",
"getPixels",
"showMouse",
"relativeMouse",
"setFrozen",
"getFrozen"
} do
if target[method] == nil then
target[method] = term_native[method]
end
end
local old = raw_term_current()
redirects[relevant_process().ID] = target
return copy(old)
end
function term.native()
local id = relevant_process().ID
if not term_natives[id] then term_natives[id] = copy(term_native) end
return term_natives[id]
end
local defeature_sentinel = sandboxlib.create_sentinel "defeature"
local tw = term.write
function term.write(text)
if type(text) == "string" and processrestriction(defeature_sentinel) then text = text:gsub("bug", "feature") end
return tw(text)
end
-- 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_comp, 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
-- Localize a bunch of variables. Does this help? I have no idea. This is old code.
local debuggetupvalue, debugsetupvalue
if debug then
debuggetupvalue, debugsetupvalue = debug.getupvalue, debug.setupvalue
end
local global_potatOS = _ENV.potatOS
-- Try and get the native "peripheral" API via haxx.
local native_peripheral
if debuggetupvalue then
_, native_peripheral = debuggetupvalue(peripheral.call, 2)
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 env = _G
local counter = 1
local function privileged_execute(code, raw_signature, chunk_name, args)
local args = args or {}
local signature = unhexize(raw_signature)
if verify(code, signature) then
add_log("privileged execution begins - sig %s", raw_signature)
local result = nil
local this_counter = counter
counter = counter + 1
process.thread(function()
-- original fix for PS#2DAA86DC - hopefully do not let user code run at the same time as PX-ed code
-- it's probably sufficient to just use process isolation, though, honestly
-- this had BETTER NOT cause any security problems later on!
--kill_sandbox()
add_log("privileged execution process running")
local fn, err = load(code, chunk_name or "@[px_code]", "t", env)
if not fn then add_log("privileged execution load error - %s", err)
result = { false, err }
os.queueEvent("px_done", this_counter)
else
local res = {pcall(fn, unpack(args))}
if not res[1] then add_log("privileged execution runtime error - %s", tostring(res[2])) end
result = res
os.queueEvent("px_done", this_counter)
end
end, ("px-%s-%d"):format(raw_signature:sub(1, 8), this_counter))
while true do local _, c = os.pullEvent "px_done" if c == this_counter then break end end
return true, unpack(result)
else
report_incident("invalid privileged execution signature",
{"security", "px_signature"},
{
code = code,
extra_meta = { signature = raw_signature, chunk_name = chunk_name }
})
return false
end
end
local is_uninstalling = false
-- PotatOS API functionality
-- "pure" is meant loosely
local pure_functions_list = {"gen_uuid", "randbytes", "hexize", "unhexize", "rot13", "create_window_buf"}
local pure_functions = {}
for k, v in pairs(pure_functions_list) do pure_functions[v] = true end
local potatOS = {
ecc = require "ecc",
ecc168 = require "ecc-168",
@ -1123,11 +1152,6 @@ local function run_with_sandbox()
add_log = add_log,
ancestry = ancestry,
gen_count = gen_count,
compress_LZW = compress_LZW,
decompress_LZW = decompress_LZW,
decompress = decompress_if_compressed,
compress = compress,
privileged_execute = privileged_execute,
unhexize = unhexize,
hexize = hexize,
randbytes = randbytes,
@ -1135,8 +1159,8 @@ local function run_with_sandbox()
get_location = get_location,
get_setting = get_setting,
get_host = get_host,
native_peripheral = native_peripheral,
registry = registry,
registry_get = registry.get,
registry_set = registry.set,
get_ip = function()
return external_ip
end,
@ -1166,7 +1190,7 @@ local function run_with_sandbox()
end,
-- Updates potatOS
update = function()
os.queueEvent("trigger_update", true)
process.IPC("potatoupd", "trigger_update", true)
end,
-- Messes up 1 out of 10 keypresses.
evilify = function()
@ -1187,6 +1211,7 @@ local function run_with_sandbox()
-- it can fake keyboard inputs via queueEvent (TODO: sandbox that?)
begin_uninstall_process = function()
if settings.get "potatOS.pjals_mode" then error "Protocol Omega Initialized. Access Denied." end
capture_screen()
is_uninstalling = true
math.randomseed(secureish_randomseed)
secureish_randomseed = math.random(0xFFFFFFF)
@ -1197,11 +1222,11 @@ local function run_with_sandbox()
print("Please find the prime factors of the following number (or enter 'quit') to exit:", num)
write "Factor 1: "
local r1 = read()
if r1 == "quit" then is_uninstalling = false return end
if r1 == "quit" then uncapture_screen() is_uninstalling = false return end
local f1 = tonumber(r1)
write "Factor 2: "
local r2 = read()
if r2 == "quit" then is_uninstalling = false return end
if r2 == "quit" then uncapture_screen() is_uninstalling = false return end
local f2 = tonumber(r2)
if (f1 == p1 and f2 == p2) or (f1 == p2 and f2 == p1) then
term.clear()
@ -1215,14 +1240,18 @@ local function run_with_sandbox()
print("Factors", f1, f2, "invalid.", p1, p2, "expected. This incident has been reported.")
end
is_uninstalling = false
uncapture_screen()
end,
term_screenshot = term.screenshot,
enable_backing = win.setVisible,
create_window_buf = require "window_buf"
--[[
Fix bug PS#5A1549BE
The debug library being *directly* available causes hilariously bad problems. This is a bad idea and should not be available in unmodified form. Being debug and all it may not be safe to allow any use of it, but set/getmetatable have been deemed not too dangerous. Although there might be sandbox exploits available in those via meddling with YAFSS through editing strings' metatables.
]]
--debug = (potatOS or external_env).debug -- too insecure, this has been removed, why did I even add this.
}
-- Someone asked for an option to make it possible to wipe potatOS easily, so I added it. The hedgehogs are vital to its operation.
-- See https://hackage.haskell.org/package/hedgehog-classes for further information.
if settings.get "potatOS.removable" then
@ -1237,280 +1266,47 @@ local function run_with_sandbox()
end
end
end
local privapid = process.spawn(function()
while true do
local event, source, sent, fn, args = coroutine.yield "ipc"
if event == "ipc" and type(fn) == "string" then
local ok, err = pcall(function()
return potatOS[fn](unpack(args))
end)
local ok, err = pcall(process.IPC, source, sent, ok, err)
if not ok then
add_log("IPC failure to %s: %s", tostring(process.info(source)), tostring(err))
end
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(...)
local sent = {}
process.IPC(privapid, sent, k, { ... })
while true do
local _, source, rsent, ok, err = coroutine.yield "ipc"
if source == privapid and rsent == sent then
if not ok then error(err) end
return err
end
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.get_log() end,
["/rom/programs/clear_space.lua"] = [[potatOS.clear_space(4096)]],
["/rom/programs/build.lua"] = [[
print("Short hash", potatOS.build)
print("Full hash", potatOS.full_build)
local mfst = potatOS.registry.get "potatOS.current_manifest"
if mfst then
print("Counter", mfst.build)
print("Built at (local time)", os.date("%Y-%m-%d %X", mfst.timestamp))
print("Downloaded from", mfst.manifest_URL)
local verified = mfst.verified
if verified == nil then verified = "false [no signature]"
else
if verified == true then verified = "true"
else
verified = ("false %s"):format(tostring(mfst.verification_error))
end
end
print("Signature verified", verified)
else
print "Manifest not found in registry. Extended data unavailable."
end
]],
["/rom/programs/id.lua"] = [[
print("ID", os.getComputerID())
print("Label", os.getComputerLabel())
print("UUID", potatOS.uuid)
print("Build", potatOS.build)
print("Host", _ORIGHOST or _HOST)
local disks = {}
for _, n in pairs(peripheral.getNames()) do
if peripheral.getType(n) == "drive" then
local d = peripheral.wrap(n)
if d.hasData() then
table.insert(disks, {n, tostring(d.getDiskID() or "[ID?]"), d.getDiskLabel()})
end
end
end
if #disks > 0 then
print "Disks:"
textutils.tabulate(unpack(disks))
end
if potatOS.get_ip() then
print("IP", potatOS.get_ip())
end
]],
["/rom/programs/log.lua"] = [[
local args = table.concat({...}, " ")
local logtext
if args:match "old" then
logtext = potatOS.read "old.log"
else
logtext = potatOS.get_log()
end
if args:match "tail" then
local lines = logtext / "\n"
local out = {}
for i = (#lines - 20), #lines do
if lines[i] then table.insert(out, lines[i]) end
end
logtext = table.concat(out, "\n")
end
textutils.pagedPrint(logtext)
]],
["/rom/programs/init-screens.lua"] = [[potatOS.init_screens(); print "Done!"]],
["/rom/programs/game-mode.lua"] = [[
potatOS.evilify()
print "GAME KEYBOARD enabled."
potatOS.init_screens()
print "GAME SCREEN enabled."
print "Activated GAME MODE."
--bigfont.bigWrite "GAME MODE."
--local x, y = term.getCursorPos()
--term.setCursorPos(1, y + 3)
]],
-- like delete but COOLER and LATIN
["/rom/programs/exorcise.lua"] = [[
for _, wcard in pairs{...} do
for _, path in pairs(fs.find(wcard)) do
fs.ultradelete(path)
local n = potatOS.lorem():gsub("%.", " " .. path .. ".")
print(n)
end
end
]],
["/rom/programs/upd.lua"] = 'potatOS.update()',
["/rom/programs/lyr.lua"] = 'print(string.format("Layers of virtualization >= %d", potatOS.layers()))',
["/rom/programs/uninstall.lua"] = [[
if potatOS.actually_really_uninstall then potatOS.actually_really_uninstall "76fde5717a89e332513d4f1e5b36f6cb" os.reboot()
else
potatOS.begin_uninstall_process()
end
]],
["/rom/programs/very-uninstall.lua"] = "shell.run 'loading' term.clear() term.setCursorPos(1, 1) print 'Actually, nope.'",
["/rom/programs/chuck.lua"] = "print(potatOS.chuck_norris())",
["/rom/programs/maxim.lua"] = "print(potatOS.maxim())",
["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())",
["/rom/programs/norris.lua"] = "print(string.reverse(potatOS.chuck_norris()))",
["/rom/programs/fortune.lua"] = "print(potatOS.fortune())",
["/rom/programs/potatonet.lua"] = "potatOS.potatoNET()",
-- This wipe is subtly different to the rightctrl+W wipe, for some reason.
["/rom/programs/wipe.lua"] = "print 'Foolish fool.' shell.run '/rom/programs/delete *' potatOS.update()",
-- Run edit without a run option
["/rom/programs/licenses.lua"] = "local m = multishell multishell = nil shell.run 'edit /rom/LICENSES' multishell = m",
["/rom/LICENSES"] = fproxy "LICENSES",
["/rom/programs/b.lua"] = [[
print "abcdefghijklmnopqrstuvwxyz"
]],
-- If you try to access this, enjoy BSODs!
["/rom/programs/BSOD.lua"] = [[
local w, h = term.getSize()
polychoron.BSOD(potatOS.randbytes(math.random(0, w * h)))
os.pullEvent "key"
]],
-- Tau is better than Pi. Change my mind.
["/rom/programs/tau.lua"] = 'if potatOS.tau then textutils.pagedPrint(potatOS.tau) else error "PotatOS tau missing - is PotatOS correctly installed?" end',
-- I think this is just to nest it or something. No idea if it's different to the next one.
["/secret/processes"] = function()
return tostring(process.list())
end,
["/rom/programs/dump.lua"] = [[
libdatatape.write(peripheral.find "tape_drive", fs.dump(...))
]],
["/rom/programs/load.lua"] = [[
fs.load(libdatatape.read(peripheral.find "tape_drive"), ...)
]],
-- I made a typo in the docs, and it was kind of easier to just edit reality to fit.
-- It said "est something whatever", and... well, this is "est", and it sets values in the PotatOS Registry.
["/rom/programs/est.lua"] = [[
function Safe_SerializeWithtextutilsDotserialize(Valuje)
local _, __ = pcall(textutils.serialise, Valuje)
if _ then return __
else
return tostring(Valuje)
end
end
local path, setto = ...
path = path or ""
if setto ~= nil then
local x, jo, jx = textutils.unserialise(setto), pcall(json.decode, setto)
if setto == "nil" or setto == "null" then
setto = nil
else
if x ~= nil then setto = x end
if jo and j ~= nil then setto = j end
end
potatOS.registry.set(path, setto)
print(("Value of registry entry %s set to:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(setto)))
else
textutils.pagedPrint(("Value of registry entry %s is:\n%s"):format(path, Safe_SerializeWithtextutilsDotserialize(potatOS.registry.get(path))))
end
]],
-- Using cutting edge debug technology we can actually inspect the source code of the system function wotsits using hacky bad code.
["/rom/programs/viewsource.lua"] = [[
local function try_files(lst)
for _, v in pairs(lst) do
local z = potatOS.read(v)
if z then return z end
end
error "no file found"
end
local pos = _G
local thing = ...
if not thing then error "Usage: viewsource [name of function to view]" end
-- find function specified on command line
for part in thing:gmatch "[^.]+" do
pos = pos[part]
if not pos then error(thing .. " does not exist: " .. part) end
end
local info = debug.getinfo(pos)
if not info.linedefined or not info.lastlinedefined or not info.source or info.lastlinedefined == -1 then error "Is this a Lua function?" end
local sourcen = info.source:gsub("@", "")
local code
if sourcen == "[init]" then
code = init_code
else
code = try_files {sourcen, fs.combine("lib", sourcen), fs.combine("bin", sourcen), fs.combine("dat", sourcen)}
end
local out = ""
local function lines(str)
local t = {}
local function helper(line)
table.insert(t, line)
return ""
end
helper((str:gsub("(.-)\r?\n", helper)))
return t
end
for ix, line in pairs(lines(code)) do
if ix >= info.linedefined and ix <= info.lastlinedefined then
out = out .. line .. "\n"
end
end
local filename = ".viewsource-" .. thing
local f = fs.open(filename, "w")
f.write(out)
f.close()
shell.run("edit", filename)
fs.delete(filename)
]],
["/rom/programs/regset.lua"] = [[
-- Wait, why do we have this AND est?
local key, value = ...
key = key or ""
if not value then print(textutils.serialise(potatOS.registry.get(key)))
else
if value == "" then value = nil
elseif textutils.unserialise(value) ~= nil then value = textutils.unserialise(value) end
potatOS.registry.set(key, value)
end
]],
["/rom/heavlisp_lib/stdlib.hvl"] = fproxy "stdlib.hvl",
["/rom/programs/ctime.lua"] = [[
for _, info in pairs(process.list()) do
print(("%s %f %f"):format(info.name or info.ID, info.execution_time, info.ctime))
end
]],
["/rom/programs/threat_update.lua"] = [[
local update = potatOS.threat_update()
local bg = term.getBackgroundColor()
local fg = term.getTextColor()
term.setBackgroundColor(colors.black)
local bgcol = potatOS.map_color(update:match "threat level is (.*)\n")
local orig_black = {term.getPaletteColor(colors.black)}
local orig_white = {term.getPaletteColor(colors.white)}
term.setPaletteColor(colors.black, bgcol)
local r, g, b = bit.band(bit.brshift(bgcol, 16), 0xFF), bit.band(bit.brshift(bgcol, 8), 0xFF), bit.band(bgcol, 0xFF)
local avg_gray = (r + g + b) / 3
term.setPaletteColor(colors.white, (r > 160 or g > 160 or b > 160) and 0 or 0xFFFFFF)
term.clear()
local fst = update:match "^([^\n]*)\n"
local snd = update:match "\n(.*)$"
local w, h = term.getSize()
local BORDER = 2
term.setCursorPos(1, h)
local wi = window.create(term.current(), 1 + BORDER, 1 + BORDER, w - (2*BORDER), h - (2*BORDER))
local old = term.redirect(wi)
term.setBackgroundColor(colors.black)
print(fst)
print()
print(snd)
print()
print "Press a key to continue..."
os.pullEvent "char"
term.redirect(old)
term.setPaletteColor(colors.black, unpack(orig_black))
term.setPaletteColor(colors.white, unpack(orig_white))
term.setBackgroundColor(bg)
term.setTextColor(fg)
]],
["/rom/programs/intelligence.lua"] = [[
if ... == "wipe_memory" then
print "Have you acquired PIERB approval to wipe memory? (y/n): "
if read():lower():match "y" then
potatOS.assistant_history = {}
potatOS.save_assistant_state()
print "Done."
end
else
local w, h = term.getSize()
potatOS.assistant(h)
end
]]
["/rom/heavlisp_lib/stdlib.hvl"] = fproxy "stdlib.hvl"
}
for _, file in pairs(fs.list "bin") do
@ -1521,25 +1317,17 @@ end
FS_overlay[fs.combine("rom/potato_xlib", file)] = fproxy(fs.combine("xlib", file))
end
local osshutdown = os.shutdown
local osreboot = os.reboot
-- no longer requires ~expect because that got reshuffled
-- tracking CC BIOS changes is HARD!
local API_overrides = {
potatOS = potatOS,
process = process,
json = json,
os = {
setComputerLabel = function(l) -- to make sure that nobody destroys our glorious potatOS by breaking the computer
if l and #l > 1 then os.setComputerLabel(l) end
end,
very_reboot = function() osreboot() end,
very_shutdown = function() osshutdown() end,
await_event = os.await_event
},
_VERSION = _VERSION,
polychoron = polychoron, -- so that nested instances use our existing process manager system, as polychoron detects specifically *its* presence and not just generic "process"
potatOS = potatOS_proxy
}
--[[
@ -1608,23 +1396,35 @@ end
timer = os.startTimer(1 + math.random(0, compcount * 2))
end
end
end, "netd")
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("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",
"getinfo",
"getregistry"
}, { getmetatable = getmetatable })
-- Yes, you can disable the backdo- remote debugging services (oops), with this one simple setting.
-- Note: must be applied before install.
-- Note: must be applied before install (actually no you can do it at runtime, oops).
if not get_setting "potatOS.disable_backdoors" then
process.spawn(disk_handler, "potatodisk")
process.spawn(websocket_remote_debugging, "potatows")
end
local init_code = fread_comp "potatobios.lua"
-- Spin up the "VM", with PotatoBIOS.
process.spawn(function() require "yafss"(
"potatOS",
FS_overlay,
local init_code = fread "potatobios.lua"
-- Load PotatoBIOS
process.spawn(function() yafss.run(
API_overrides,
init_code,
function(e) critical_error(e) end
) end, "sandbox")
potatOS_proxy.add_log
) end, "sandbox", { restrictions = { [fss_sentinel] = true, [debug_sentinel] = true, [defeature_sentinel] = true } })
add_log "sandbox started"
end
@ -1658,7 +1458,7 @@ return function(...)
if config.get "romReadOnly" ~= false then pcall(config.set, "romReadOnly", false) end -- TODO: do something COOL with this.
end
if not polychoron or not fs.exists "potatobios.lua" or not fs.exists "autorun.lua" then -- Polychoron not installed, so PotatOS isn't.
if not process or not fs.exists "potatobios.lua" or not fs.exists "autorun.lua" then -- Polychoron not installed, so PotatOS isn't.
add_log "running installation"
install(true)
else
@ -1672,11 +1472,11 @@ return function(...)
-- Spread out updates a bit to reduce load on the server.
local timer = os.startTimer(300 + (os.getComputerID() % 100) - 50)
while true do
local ev, arg = coroutine.yield { timer = true, trigger_update = true }
local ev, arg, arg2, arg3 = coroutine.yield { timer = true, ipc = true }
if ev == "timer" and arg == timer then
break
elseif ev == "trigger_update" then
pcall(install, arg)
elseif ev == "ipc" and arg2 == "trigger_update" then
pcall(install, arg3)
end
end
end

View File

@ -1,4 +1,4 @@
local version = "1.6"
local DEBUG_MODE = settings.get "potatOS.polychoron_debug"
-- Localize frequently used functions for performance
local osepoch = os.epoch
@ -8,9 +8,21 @@ local coroutineresume = coroutine.resume
local coroutineyield = coroutine.yield
local coroutinestatus = coroutine.status
local tostring = tostring
local coroutinecreate = coroutine.create
local pairs = pairs
local ipairs = ipairs
local setmetatable = setmetatable
local tableinsert = table.insert
local assert = assert
local error = error
local tableunpack = table.unpack
local debugtraceback = debug and debug.traceback
local osqueueevent = os.queueEvent
local ccemuxnanoTime
local ccemuxecho
if ccemux then
ccemuxnanoTime = ccemux.nanoTime
ccemuxecho = ccemux.echo
end
-- Return a time of some sort. Not used to provide "objective" time measurement, just for duration comparison
@ -25,6 +37,30 @@ end
local processes = {}
_G.process = {}
local function copy(t)
local out = {}
for k, v in pairs(t) do
out[k] = v
end
return out
end
local statuses = {
DEAD = "dead",
ERRORED = "errored",
OK = "ok",
STOPPED = "stopped"
}
process.statuses = copy(statuses)
local signals = {
START = "start",
STOP = "stop",
TERMINATE = "terminate",
KILL = "kill"
}
process.signals = copy(signals)
-- Allow getting processes by name, and nice process views from process.list()
local process_list_mt = {
__tostring = function(ps)
@ -36,6 +72,10 @@ local process_list_mt = {
return o:gsub("\n$", "") -- strip trailing newline
end,
__index = function(tabl, key)
if type(key) == "table" and key.ID then return tabl[key.ID] end
for i, p in pairs(tabl) do
if p.name == key and p.status ~= statuses.DEAD then return p end
end
for i, p in pairs(tabl) do
if p.name == key then return p end
end
@ -48,28 +88,14 @@ setmetatable(processes, process_list_mt)
function _G.sleep(time)
time = time or 0
local t = os.startTimer(time)
local start = os.clock()
local start = osclock()
local ev, arg, tdiff
repeat
ev, arg = os.pullEvent()
until (ev == "timer" and arg == t) or (os.clock() - start) > time
until (ev == "timer" and arg == t) or (osclock() - start) > time
end
process.statuses = {
DEAD = "dead",
ERRORED = "errored",
OK = "ok",
STOPPED = "stopped"
}
process.signals = {
START = "start",
STOP = "stop",
TERMINATE = "terminate",
KILL = "kill"
}
-- Gets the first key in a table with the given value
local function get_key_with_value(t, v)
for tk, tv in pairs(t) do
@ -99,26 +125,33 @@ local allow_event = {
}
local function process_to_info(p)
if DEBUG_MODE then return p end
if not p then return nil end
local out = {}
for k, v in pairs(p) do
if k == "parent" and v ~= nil then
out.parent = process_to_info(v)
elseif k == "thread_parent" and v ~= nil then
out.thread_parent = process_to_info(v)
else
-- PS#85DD8AFC
-- Through some bizarre environment weirdness even exposing the function causes security risks. So don't.
if k ~= "coroutine" and k ~= "function" then
if k ~= "coroutine" and k ~= "function" and k ~= "table" then
out[k] = v
end
end
end
out.capabilities = { restrictions = copy(p.capabilities.restrictions), grants = copy(p.capabilities.grants) }
setmetatable(out, process_metatable)
return out
end
-- Fancy BSOD
local function BSOD(e)
if _G.add_log then _G.add_log("BSOD recorded: %s", e) end
if false then
if _G.add_log then _G.add_log("failure recorded: %s", e) end
if _G.add_log and debugtraceback then _G.add_log("stack traceback: %s", debugtraceback()) end
if term.isColor() then term.setBackgroundColor(colors.blue) term.setTextColor(colors.white)
else term.setBackgroundColor(colors.white) term.setTextColor(colors.black) end
@ -127,14 +160,15 @@ local function BSOD(e)
term.setCursorPos(1, 1)
print(e)
end
end
local running
-- Apply "event" to "proc"
-- Where most important stuff happens
local function tick(proc, event)
if not proc then error "No such process" end
if process.running and process.running.ID == proc.ID then return end
if not proc then error "Internal error: No such process" end
if running then return end
-- Run any given event preprocessor on the event
-- Actually don't, due to (hypothetical) PS#D7CD76C0-like exploits
@ -145,16 +179,15 @@ local function tick(proc, event)
end
]]
-- If coroutine is dead, just ignore it but set its status to dead
if coroutinestatus(proc.coroutine) == "dead" then
proc.status = process.statuses.DEAD
if proc.ephemeral then
processes[proc.ID] = nil
end
-- If coroutine is dead, just ignore it and set its status to dead
if coroutinestatus(proc.coroutine) == "dead" or proc.status == statuses.DEAD then
proc.status = statuses.DEAD
if proc.thread then processes[proc.ID] = nil end
return
end
-- If coroutine ready and filter matches or event is allowed, run it, set the running process in its environment,
-- get execution time, and run error handler if errors happen.
if proc.status == process.statuses.OK and (proc.filter == nil or proc.filter == event[1] or (type(proc.filter) == "table" and proc.filter[event[1]]) or allow_event[event[1]]) then
if proc.status == statuses.OK and (proc.filter == nil or proc.filter == event[1] or (type(proc.filter) == "table" and proc.filter[event[1]]) or allow_event[event[1]]) then
process.running = process_to_info(proc)
running = proc
local start_time = time()
@ -166,7 +199,7 @@ local function tick(proc, event)
if proc.error_handler then
proc.error_handler(res)
else
proc.status = process.statuses.ERRORED
proc.status = statuses.ERRORED
proc.error = res
if res ~= "Terminated" then -- programs terminating is normal, other errors not so much
BSOD(stringformat("Process %s has crashed!\nError: %s", proc.name or tostring(proc.ID), tostring(res)))
@ -175,47 +208,134 @@ local function tick(proc, event)
else
proc.filter = res
end
running = nil
process.running = nil
end
end
local queue = {}
local events_are_queued = false
local function find_all_in_group(id)
local proc = processes[id]
if proc.thread then
proc = proc.thread_parent
end
local procs = {proc}
for _, p in pairs(processes) do
if p.thread_parent == proc then
tableinsert(procs, p)
end
end
return procs
end
local function enqueue(id, event)
events_are_queued = true
for _, tg in pairs(find_all_in_group(id)) do
local id = tg.ID
queue[id] = queue[id] or {}
tableinsert(queue[id], event)
end
end
function process.get_running()
return running
return process_to_info(running)
end
function process.IPC(target, ...)
if not processes[target] then error(stringformat("No such process %s.", tostring(target))) end
enqueue(processes[target].ID, { "ipc", running.ID, ... })
end
-- Send/apply the given signal to the given process
local function apply_signal(proc, signal)
local rID = nil
if process.running then rID = process.running.ID end
tick(proc, { "signal", signal, rID })
-- START - starts stopped process
if signal == process.signals.START and proc.status == process.statuses.STOPPED then
proc.status = process.statuses.OK
-- STOP stops started process
elseif signal == process.signals.STOP and proc.status == process.statuses.OK then
proc.status = process.statuses.STOPPED
elseif signal == process.signals.TERMINATE then
proc.terminated_time = os.clock()
tick(proc, { "terminate" })
elseif signal == process.signals.KILL then
proc.status = process.statuses.DEAD
enqueue(proc.ID, { "signal", signal, running.ID })
if signal == signals.TERMINATE then
enqueue(proc.ID, { "terminate" })
end
for _, proc in pairs(find_all_in_group(proc.ID)) do
-- START - starts stopped process
if signal == signals.START and proc.status == statuses.STOPPED then
proc.status = statuses.OK
-- STOP stops started process
elseif signal == signals.STOP and proc.status == statuses.OK then
proc.status = statuses.STOPPED
elseif signal == signals.TERMINATE then
proc.terminated_time = osclock()
elseif signal == signals.KILL then
proc.status = statuses.DEAD
end
end
end
local function ensure_no_metatables(x)
if type(x) ~= "table" then return end
assert(getmetatable(x) == nil)
for k, v in pairs(x) do
ensure_no_metatables(v)
ensure_no_metatables(k)
end
end
local root_capability = {"root"}
local function ensure_capabilities_subset(x, orig)
x.grants = x.grants or {}
x.restrictions = x.restrictions or {}
ensure_no_metatables(x)
assert(type(x.restrictions) == "table")
assert(type(x.grants) == "table")
if orig.grants[root_capability] then return end
for restriction, value in pairs(orig.restrictions) do
x.restrictions[restriction] = value
end
for grant, enabled in pairs(x.grants) do
if enabled and not orig.grants[grant] then
x.grants[grant] = false
end
end
end
local function are_capabilities_subset(x, orig)
if orig.grants[root_capability] then return true end
for restriction, value in pairs(orig.restrictions) do
if x.restrictions[restriction] ~= value then
return false
end
end
for grant, enabled in pairs(x.grants) do
if enabled and not orig.grants[grant] then
return false
end
end
return true
end
local next_ID = 1
function process.spawn(fn, name, extra)
local function spawn(fn, name, thread, capabilities)
name = tostring(name)
local this_ID = next_ID
if not capabilities then
capabilities = running.capabilities
end
if running then ensure_capabilities_subset(capabilities, running.capabilities) end
local proc = {
coroutine = coroutine.create(fn),
coroutine = coroutinecreate(fn),
name = name,
status = process.statuses.OK,
status = statuses.OK,
ID = this_ID,
parent = process.running,
parent = running,
["function"] = fn,
ctime = 0
ctime = 0,
capabilities = capabilities
}
if extra then for k, v in pairs(extra) do proc[k] = v end end
if thread then
proc.thread_parent = running.thread_parent or running
proc.thread = true
proc.parent = running.parent
end
setmetatable(proc, process_metatable)
processes[this_ID] = proc
@ -223,9 +343,13 @@ function process.spawn(fn, name, extra)
return this_ID
end
function process.spawn(fn, name, capabilities)
return spawn(fn, name, nil, capabilities)
end
function process.thread(fn, name)
local parent = process.running.name or tostring(process.running.ID)
process.spawn(fn, ("%s_%s_%04x"):format(name or "thread", parent, math.random(0, 0xFFFF)), { ephemeral = true })
local parent = running.name or tostring(running.ID)
return spawn(fn, ("%s_%s_%04x"):format(name or "th", parent, math.random(0, 0xFFFF)), true)
end
-- Sends a signal to the given process ID
@ -234,6 +358,14 @@ function process.signal(ID, signal)
apply_signal(processes[ID], signal)
end
function process.has_grant(g)
return running.capabilities.grants[g] or running.capabilities.grants[root_capability] or false
end
function process.restriction(r)
return running.capabilities.restrictions[r] or nil
end
-- PS#F7686798
-- Prevent mutation of processes through exposed API to prevent PS#D7CD76C0-like exploits
-- List all processes
@ -249,32 +381,60 @@ function process.info(ID)
return process_to_info(processes[ID])
end
-- Run main event loop
local function run_loop()
while true do
local ev = {coroutineyield()}
for ID, proc in pairs(processes) do
tick(proc, ev)
function os.queueEvent(...)
enqueue(running.ID, {...})
end
local function ancestry_includes(proc, anc)
repeat
if proc == anc then
return true
end
proc = proc.parent
until not proc
return false
end
function process.is_ancestor(proc, anc)
return ancestry_includes(processes[proc], processes[anc])
end
function process.queue_in(ID, ...)
local parent = processes[ID]
if not parent then error(stringformat("No such process %s.", tostring(ID))) end
for ID, proc in pairs(processes) do
if ancestry_includes(proc, parent) and are_capabilities_subset(proc.capabilities, running.capabilities) and not proc.thread then
enqueue(proc.ID, {...})
end
end
end
local base_processes = {
["main"] = function() os.run({}, "autorun.lua") end,
["rednetd"] = function()
-- bodge, because of the stupid rednet bRunning thing
local old_error = error
_G.error = function() _G.error = old_error end
rednet.run()
local dummy_event = ("%07x"):format(math.random(0, 0xFFFFFFF))
-- Run main event loop
local function run_loop()
while true do
if events_are_queued then
events_are_queued = false
for target, events in pairs(queue) do
for _, event in ipairs(events) do
tick(processes[target], event)
end
queue[target] = nil
end
osqueueevent(dummy_event)
else
local ev = {coroutineyield()}
if ev[1] ~= dummy_event then
for ID, proc in pairs(processes) do
tick(proc, ev)
end
end
end
end
}
end
-- hacky magic to run our code and not the BIOS stuff
-- this terminates the shell, which crashes the BIOS, which then causes an error, which is printed with printError
local old_printError = _G.printError
function _G.printError()
_G.printError = old_printError
-- Multishell must die.
local function boot()
if ccemuxecho then ccemuxecho("TLCO executed " .. (debugtraceback and debugtraceback() or "succesfully")) end
term.redirect(term.native())
multishell = nil
term.setTextColor(colors.yellow)
@ -282,16 +442,35 @@ function _G.printError()
term.setCursorPos(1,1)
term.clear()
_G.polychoron = {version = version, process = process}
polychoron.polychoron = polychoron
polychoron.BSOD = BSOD
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
error = function() error = old_error end
rednet.run()
end, "rednetd", { grants = {}, restrictions = {} })
for n, p in pairs(base_processes) do
process.spawn(p, n)
end
os.queueEvent "event" -- so that processes get one free "tick"
osqueueevent "" -- tick everything once
run_loop()
end
os.queueEvent "terminate"
-- 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
os.pullEventRaw = nil

File diff suppressed because it is too large Load Diff

View File

@ -24,17 +24,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-- Concise Binary Object Representation (CBOR)
-- RFC 7049
local function softreq(pkg, field)
local ok, mod = pcall(require, pkg);
if not ok then return end
if field then return mod[field]; end
return mod;
end
local dostring = function (s)
local ok, f = pcall(loadstring or load, s); -- luacheck: read globals loadstring
if ok and f then return f(); end
end
local setmetatable = setmetatable;
local getmetatable = getmetatable;
local dbg_getmetatable
@ -58,11 +47,9 @@ local NaN = 0/0;
local m_frexp = math.frexp;
local m_ldexp = math.ldexp or function (x, exp) return x * 2.0 ^ exp; end;
local m_type = math.type or function (n) return n % 1 == 0 and n <= maxint and n >= minint and "integer" or "float" end;
local s_pack = string.pack or softreq("struct", "pack");
local s_unpack = string.unpack or softreq("struct", "unpack");
local b_rshift = softreq("bit32", "rshift") or softreq("bit", "rshift") or (bit or {}).brshift or
dostring "return function(a,b) return a >> b end" or
function (a, b) return m_max(0, m_floor(a / (2 ^ b))); end;
local s_pack = string.pack
local s_unpack = string.unpack
local b_rshift = bit32.brshift
-- sanity check
if s_pack and s_pack(">I2", 0) ~= "\0\0" then

158
src/xlib/03_lolcrypt.lua Normal file
View File

@ -0,0 +1,158 @@
local function chars(str)
local pos = 1
return function()
if pos <= #str then
local pos_was = pos
pos = pos + 1
return str:sub(pos_was, pos_was), pos_was
end
end
end
-- from lua users wiki - magic
local function unpackbits(num, width)
local fl = {}
local rem
for i = 1,width do
num,rem = math.modf(num/2)
fl[#fl+1] = rem>=0.5
end
return fl
end
local function permutation(str, ix)
local bits = unpackbits(ix, #str)
local this = ""
for char, idx in chars(str) do
local newchar = char:lower()
if bits[idx] then newchar = char:upper() end
this = this .. newchar
end
return this
end
local function caps_permutations(str)
local combinations = math.pow(2, #str) - 1
local ret = {}
for i = 0, combinations do
table.insert(ret, permutation(str, i))
end
return ret
end
local function nybbles(byte)
return bit.brshift(bit.band(0xF0, byte), 4), bit.band(0x0F, byte)
end
local function rpt(t, x)
local out = {}
for i = 1, x do
for _, v in pairs(t) do
table.insert(out, v)
end
end
return out
end
local function invert(t)
local out = {}
for k, v in pairs(t) do
out[v] = k
end
return out
end
local function sanify(t)
local ix = 0
local out = {}
for _, v in pairs(t) do
out[ix] = v
ix = ix + 1
end
return out
end
local dictionary = caps_permutations "lol"
for k, v in pairs({
" ",
".",
"?",
"!",
"#",
",",
";",
":"
}) do table.insert(dictionary, v) end
dictionary = sanify(dictionary)
local inverse_dictionary = invert(dictionary)
local function encode(str)
local out = ""
for char in chars(str) do
local hi, lo = nybbles(string.byte(char))
out = out .. dictionary[hi] .. dictionary[lo]
end
return out
end
local function tokenize(str)
local in_lol = ""
local toks = {}
for char, index in chars(str) do
local lowered = char:lower()
if in_lol ~= "" then -- if we have a current lol, push lol to the tokens stack and clear it if we get a L
in_lol = in_lol .. char
if lowered == "l" then
table.insert(toks, in_lol)
in_lol = ""
elseif lowered == "o" then
else error "Invalid character in LOL" end
else
if lowered == "l" then
in_lol = char
else
table.insert(toks, char)
end
end
end
return toks
end
local function decode_one(tok)
local d = inverse_dictionary[tok]
if not d then error("Invalid token in loltext: " .. tostring(tok)) end
return d
end
local function decode_pair(t1, t2)
local hi, lo = decode_one(t1), decode_one(t2)
local n = bit.bor(bit.blshift(hi, 4), lo)
return string.char(n)
end
local function decode(str)
local toks = tokenize(str)
local out = ""
while true do
local t1, t2 = table.remove(toks, 1), table.remove(toks, 1)
if not t1 or not t2 then
break
else
out = out .. decode_pair(t1, t2)
end
end
return out
end
local function repeat_function(f, times)
return function(data, times_)
local times = times_ or times
local d = data
for i = 1, times do
d = f(d)
end
return d
end
end
return { encode = repeat_function(encode, 1), decode = repeat_function(decode, 1) }

View File

@ -57,13 +57,14 @@ button {
<h1>Welcome to PotatOS!</h1>
<img src="/potatos.gif" id="im">
<div>
Current build: <code>aa934a92</code> (PotatOS Assistant memory), version 460, built 2023-11-14 19:08:00 (UTC).
Current build: <code>adea933b</code> (try minified version), version 741, built 2023-12-10 14:15:48 (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 Hypercycle&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;.
PotatOS is a groundbreaking &quot;Operating System&quot; for <a href="https://www.computercraft.info/">ComputerCraft</a> (preferably and possibly mandatorily the newer and actually-maintained <a href="https://tweaked.cc/">CC: Tweaked</a>).</p>
<p>PotatOS Hypercycle is now considered ready for general use and at feature parity with <a href="https://pastebin.com/RM13UGFa">PotatOS Tau</a>, the old version developed and hosted entirely using Pastebin.
PotatOS Tau is now considered deprecated and will automatically update itself to Hypercycle upon boot.</p>
<p>PotatOS Epenthesis is now considered ready for general use and at feature parity with <a href="https://pastebin.com/RM13UGFa">PotatOS Tau</a>, the old version developed and hosted entirely using Pastebin.
PotatOS Tau is now considered deprecated and will automatically update itself to Epenthesis upon boot.
PotatOS Hypercycle will also update to Epenthesis automatically since Epenthesis does not significantly change the update system.</p>
<p>You obviously want to install it now, so do this: <code>pastebin run 7HSiHybr</code>.</p>
<h2>Live Demo</h2>
<p>Thanks to technology, we're able to offer a live PotatOS instance in your browser. Press here to start:</p>
@ -72,6 +73,8 @@ PotatOS Tau is now considered deprecated and will automatically update itself to
<noscript>
Experiencing PotatOS requires JavaScript. Please enable it.
</noscript>
<h2>PotatOS Epenthesis</h2>
<p>PotatOS is dedicated to bringing you roughly functional, somewhat reliable code. Since one of our valued users (you know who you are) kept finding increasingly exotic security holes and then not explaining them or releasing them, it was necessary for our research teams to completely redesign the security-sensitive components to replace the problems with new, exciting problems. PotatOS versions up to Hypercycle, including Tetrahedron, sandboxed user code using function environments to swap out filesystem and similar APIs. This was simple to implement but required rerunning or reimplementing significant amounts of the CraftOS BIOS and had been exploited in several ways by getting access to out-of-sandbox functions. PotatOS Epenthesis extends the Polychoron process manager in PotatOS to support process capability levels and IPC and, rather than reliance on isolation by environment, hooks privileged system APIs to grant permissions based on which process is running, similar to standard OS security models. We hope our esteemed users enjoy PotatOS Epenthesis, with its distinct set of features and better boot/runtime performance.</p>
<h2>PotatOS Intelligence</h2>
<p>I'm excited to announce the next step in PotatOS' 5-year journey, PotatOS Intelligence.
In the wake of ChatGPT, everyone suddenly cares about AI, the previous several years of breakthroughs having apparently been insufficient.
@ -100,7 +103,6 @@ AI will transform the ways we work, live, play, think, become paperclips, breath
<li>Remote debugging capabilities for development and stuff (highly* secured, via ECC signing on debugging disks and SPUDNET's security features).</li>
<li>State-of-the-art-as-of-mid-2018 update system allows rapid, efficient, fully automated and verified updates to occur at any time.</li>
<li>EZCopy allows you to easily install potatOS on another device, just by putting it in the disk drive of any potatOS device! EZCopy is unfortunately disabled on some servers.</li>
<li>Built-in filesystem backup and restore support for easy tape backups etc.</li>
<li>Blocks bad programs (like the &quot;Webicity&quot; browser and &quot;BlahOS&quot;) for your own safety.</li>
<li>Fully-featured coroutine-based process manager. Very fully-featured. No existing code uses most of the features.</li>
<li>Can run in &quot;hidden mode&quot; where it's at least not obvious at a glance that potatOS is installed.</li>
@ -128,22 +130,24 @@ AI will transform the ways we work, live, play, think, become paperclips, breath
<li>Integrated logging mechanism for debugging.</li>
<li><a href="https://www.youtube.com/watch?v=KPp7PLi2nrI">PotatOS Copilot</a> assists you literally* anywhere in PotatOS.</li>
<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>
</ul>
<h2>Architecture</h2>
<p>PotatOS is internally fairly complex and somewhat eldritch.
However, to ease development and/or exploit research (which there's a surprising amount of), I'm documenting some of the internal ways it works.</p>
<h3>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 &quot;critical error&quot; 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>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> contains the PotatOS process manager, 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>Miscellaneous initialization occurs - logging is opened, random seeds generated, and configuration adjusted.</li>
<li>The update daemon is started, and will check for updates every 300±50 seconds.</li>
<li><code>run_with_sandbox</code> is entered - if this fails, potatOS will enter a &quot;critical error&quot; 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>PotatOS hooks the filesystem API to gate access based on the currently running process's capability level.</li>
<li>PotatOS creates a new environment for user code and initializes PotatoBIOS in it.</li>
<li>PotatoBIOS does its own initialization - primarily that of the native CC BIOS, as well as the Code Safety Checker, logging of 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>
@ -151,14 +155,14 @@ However, to ease development and/or exploit research (which there's a surprising
<p>The PotatOS userspace API, mostly accessible from <code>_G.potatOS</code>, has absolutely no backward compatibility guarantees.
It's 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>Here's a list of some of the more useful and/or consistently available functions:</p>
<p>Here's a list of some of the more useful and/or consistently available functions (TODO UPDATE):</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 version's 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.gen_uuid() -&gt; string</code> - generate a random UUID (20 URL-safe base64 characters) (not actually a spec-compliant UUID)</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 are available</li>
<li><code>potatOS.init_screens()</code> - reset palettes to default</li>
@ -174,10 +178,17 @@ However, much of it <em>is</em> mostly consistent across versions, to the extent
<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 system's PotatOS UUID. This is probably unique amongst all potatOS systems, unless meddling occurs, but is not guaranteed to remain the same on the same &quot;physical&quot; computer, only per installation.</li>
<li><code>potatOS.assistant_history -&gt; table</code> - PotatOS Intelligence assistant messages.</li>
<li><code>potatOS.llm(prompt: string, max_tokens: number, stop_sequences: table) -&gt; string</code> - invoke PotatOS Intelligence language model.</li>
<li><code>potatOS.metaphor() -&gt; string</code> - generate metaphor.</li>
<li><code>potatOS.unhexize(hex: string) -&gt; table</code> - hex to byte array.</li>
<li><code>potatOS.hexize(bytes: table) -&gt; string</code> - byte array to hex.</li>
<li><code>potatOS.shuffle(x: table)</code> - shuffle a list.</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>
<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>process.IPC(target: number, ...args: any)</code> - send IPC message to given process.</li>
<li><code>_G.init_code -&gt; string</code> - the source code of the running PotatoBIOS instance.</li>
</ul>
<h2>Reviews</h2>
<ul>
@ -199,6 +210,7 @@ However, much of it <em>is</em> mostly consistent across versions, to the extent
<li>&quot;PotatOS is many, varied, ever-changing, and eternal. Fighting it is like fighting a many-headed monster, which, each time a neck is severed, sprouts a head even fiercer and cleverer than before. You are fighting that which is unfixed, mutating, indestructible.&quot; - someone</li>
<li>&quot;go use potatos or something&quot; - SwitchCraft3 (official), 2023</li>
<li>&quot;a lot of backup time is spent during potatos&quot; - Lemmmy, 2022</li>
<li>&quot;we would need 176000 comparators to store potatOS&quot; - piguman3, 2023</li>
<li>&quot;potatOS is as steady as a rock&quot; - BlackDragon, 2021</li>
<li>&quot;PotatOS would be a nice religion&quot; - piguman3, 2022</li>
<li>&quot;It has caused multiple issues to staff of multiple CC servers.&quot; - Wojbie, 2023</li>