forked from osmarks/potatOS
PotatOS Epenthesis
This commit is contained in:
parent
3db5db9c02
commit
64dc597822
52
README.md
52
README.md
@ -1,11 +1,12 @@
|
|||||||
# PotatOS
|
# PotatOS
|
||||||
|
|
||||||
"PotatOS" stands for "PotatOS Otiose Transformative Advanced Technology Or Something".
|
"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 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 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 Hypercycle upon boot.
|
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`.
|
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.
|
Experiencing PotatOS requires JavaScript. Please enable it.
|
||||||
</noscript>
|
</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
|
## PotatOS Intelligence
|
||||||
|
|
||||||
I'm excited to announce the next step in PotatOS' 5-year journey, 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).
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- 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.
|
- Integrated logging mechanism for debugging.
|
||||||
- [PotatOS Copilot](https://www.youtube.com/watch?v=KPp7PLi2nrI) assists you literally* anywhere in PotatOS.
|
- [PotatOS Copilot](https://www.youtube.com/watch?v=KPp7PLi2nrI) assists you literally* anywhere in PotatOS.
|
||||||
- Live threat updates using our advanced algorithms.
|
- 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
|
## Architecture
|
||||||
|
|
||||||
@ -95,16 +101,16 @@ However, to ease development and/or exploit research (which there's a surprising
|
|||||||
|
|
||||||
### Boot process
|
### Boot process
|
||||||
|
|
||||||
- normal ComputerCraft boot process - `bios.lua` runs `rom/programs/shell.lua` (or maybe multishell first) runs `rom/startup.lua` runs `startup`
|
- 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
|
- `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/*`)
|
- 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
|
- 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
|
- 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
|
- `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)
|
- 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
|
- PotatOS hooks the filesystem API to gate access based on the currently running process's capability level.
|
||||||
- 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.
|
- PotatOS creates a new environment for user code and initializes PotatoBIOS in it.
|
||||||
- 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.
|
- 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.
|
- 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.
|
- 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!
|
It's also not really documented. Fun!
|
||||||
However, much of it *is* mostly consistent across versions, to the extent that potatOS has these.
|
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.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.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.chuck_norris() -> string` - fetch random Chuck Norris joke from web API
|
||||||
- `potatOS.fortune() -> string` - fetch random `fortune` from web API
|
- `potatOS.fortune() -> string` - fetch random `fortune` from web API
|
||||||
- `potatOS.evilify()` - mess up 1 in 10 keypresses
|
- `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_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.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
|
- `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.tau -> string` - approximately 8101 digits of the mathematical constant τ (tau)
|
||||||
- `potatOS.update()` - force a system update
|
- `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.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.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.info(ID: number) -> table` - get information about a process, by ID.
|
||||||
- `process.list() -> table` - get information for all running processes
|
- `process.list() -> table` - get information for all running processes.
|
||||||
- `_G.init_code -> string` - the source code of the running PotatoBIOS instance
|
- `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
|
## 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
|
- "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
|
- "go use potatos or something" - SwitchCraft3 (official), 2023
|
||||||
- "a lot of backup time is spent during potatos" - Lemmmy, 2022
|
- "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 is as steady as a rock" - BlackDragon, 2021
|
||||||
- "PotatOS would be a nice religion" - piguman3, 2022
|
- "PotatOS would be a nice religion" - piguman3, 2022
|
||||||
- "It has caused multiple issues to staff of multiple CC servers." - Wojbie, 2023
|
- "It has caused multiple issues to staff of multiple CC servers." - Wojbie, 2023
|
||||||
|
3
src/bin/BSOD.lua
Normal file
3
src/bin/BSOD.lua
Normal 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
1
src/bin/b.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
print "abcdefghijklmnopqrstuvwxyz"
|
19
src/bin/build.lua
Normal file
19
src/bin/build.lua
Normal 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
1
src/bin/chuck.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
print(potatOS.chuck_norris())
|
1
src/bin/clear_space.lua
Normal file
1
src/bin/clear_space.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
potatOS.clear_space((... and tonumber(...) and tonumber(...) == tonumber(...)) and tonumber(...) or 4096)
|
3
src/bin/ctime.lua
Normal file
3
src/bin/ctime.lua
Normal 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
25
src/bin/est.lua
Normal 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
8
src/bin/exorcise.lua
Normal 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
|
@ -12,9 +12,10 @@ repeat
|
|||||||
write "Provide an integer to factorize: "
|
write "Provide an integer to factorize: "
|
||||||
x = tonumber(read())
|
x = tonumber(read())
|
||||||
if not x or math.floor(x) ~= x then print("That is NOT an integer.") end
|
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
|
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
|
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
|
return abs(x - y) < 1e-14
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- binary modular exponentiation
|
||||||
local function modexp(a, b, n)
|
local function modexp(a, b, n)
|
||||||
if b == 0 then return 1 % n end
|
if b == 0 then return 1 % n end
|
||||||
if b == 1 then return a % n end
|
if b == 1 then return a % n end
|
||||||
local bdiv2 = b / 2
|
local bdiv2 = b / 2
|
||||||
local fbdiv2 = floor(bdiv2)
|
local fbdiv2 = floor(bdiv2)
|
||||||
if eps_compare(bdiv2, fbdiv2) then
|
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)
|
local x = modexp(a, fbdiv2, n)
|
||||||
return (x * x) % n
|
return (x * x) % n
|
||||||
else
|
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
|
return (modexp(a, b - 1, n) * a) % n
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
1
src/bin/fortune.lua
Normal file
1
src/bin/fortune.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
print(potatOS.fortune())
|
8
src/bin/game_mode.lua
Normal file
8
src/bin/game_mode.lua
Normal 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
175
src/bin/hacker.lua
Normal 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
21
src/bin/id.lua
Normal 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
2
src/bin/init-screens.lua
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
potatOS.init_screens()
|
||||||
|
print "Done!"
|
12
src/bin/intelligence.lua
Normal file
12
src/bin/intelligence.lua
Normal 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
41
src/bin/lmatrix.lua
Normal 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
16
src/bin/log.lua
Normal 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
1
src/bin/lyr.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
print(string.format("Layers of virtualization >= %d", potatOS.layers()))
|
1
src/bin/maxim.lua
Normal file
1
src/bin/maxim.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
print(potatOS.maxim())
|
1
src/bin/norris.lua
Normal file
1
src/bin/norris.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
print(string.reverse(potatOS.chuck_norris()))
|
1
src/bin/potatonet.lua
Normal file
1
src/bin/potatonet.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
potatOS.potatoNET()
|
9
src/bin/regset.lua
Normal file
9
src/bin/regset.lua
Normal 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
1
src/bin/tau.lua
Normal 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
37
src/bin/threat_update.lua
Normal 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
4
src/bin/uninstall.lua
Normal 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
1
src/bin/upd.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
potatOS.update()
|
1
src/bin/very-uninstall.lua
Normal file
1
src/bin/very-uninstall.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
shell.run 'loading' term.clear() term.setCursorPos(1, 1) print 'Actually, nope.'
|
49
src/bin/viewsource.lua
Normal file
49
src/bin/viewsource.lua
Normal 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
1
src/bin/wipe.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
print 'Foolish fool.' shell.run '/rom/programs/delete *' potatOS.update()
|
145
src/lib/cc_expect.lua
Normal file
145
src/lib/cc_expect.lua
Normal 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 })
|
@ -99,7 +99,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
local function encode_string(val)
|
local function encode_string(val)
|
||||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
return '"' .. val:gsub('[%z\1-\31\\"\127-\255]', escape_char) .. '"'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
246
src/lib/metatable_improvements.lua
Normal file
246
src/lib/metatable_improvements.lua
Normal 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
54
src/lib/sandboxlib.lua
Normal 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
|
@ -139,42 +139,11 @@ local function digest(data)
|
|||||||
|
|
||||||
data = preprocess(data)
|
data = preprocess(data)
|
||||||
local C = {upack(H)}
|
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)
|
return toBytes(C, 8)
|
||||||
end
|
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 {
|
return {
|
||||||
hmac = hmac,
|
|
||||||
digest = digest
|
digest = digest
|
||||||
}
|
}
|
590
src/lib/window_buf.lua
Normal file
590
src/lib/window_buf.lua
Normal 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
|
@ -2,7 +2,7 @@
|
|||||||
local function copy(tabl)
|
local function copy(tabl)
|
||||||
local new = {}
|
local new = {}
|
||||||
for k, v in pairs(tabl) do
|
for k, v in pairs(tabl) do
|
||||||
if type(v) == "table" and tabl ~= v then
|
if type(v) == "table" and tabl ~= v and v.COPY_EXACT == nil then
|
||||||
new[k] = copy(v)
|
new[k] = copy(v)
|
||||||
else
|
else
|
||||||
new[k] = v
|
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.
|
-- Create a modified FS table which confines you to root and has some extra read-only pseudofiles.
|
||||||
local function create_FS(root, overlay)
|
local function create_FS(root, overlay)
|
||||||
|
local fs = fs
|
||||||
local mappings = make_mappings(root)
|
local mappings = make_mappings(root)
|
||||||
|
|
||||||
local vfstree = {
|
local vfstree = {
|
||||||
@ -216,7 +217,7 @@ local function create_FS(root, overlay)
|
|||||||
end
|
end
|
||||||
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)
|
function new.isReadOnly(path)
|
||||||
return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom")
|
return path_in_overlay(new_overlay, path) or starts_with(canonicalize(path), "rom")
|
||||||
@ -341,7 +342,7 @@ local allowed_APIs = {
|
|||||||
"http",
|
"http",
|
||||||
"pairs",
|
"pairs",
|
||||||
"ipairs",
|
"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",
|
"peripheral",
|
||||||
"table",
|
"table",
|
||||||
"string",
|
"string",
|
||||||
@ -376,6 +377,26 @@ local allowed_APIs = {
|
|||||||
"~expect",
|
"~expect",
|
||||||
"__inext",
|
"__inext",
|
||||||
"periphemu",
|
"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
|
local gf, sf = getfenv, setfenv
|
||||||
@ -383,16 +404,14 @@ local gf, sf = getfenv, setfenv
|
|||||||
-- Takes the root directory to allow access to,
|
-- Takes the root directory to allow access to,
|
||||||
-- a map of paths to either strings containing their contents or functions returning them
|
-- 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
|
-- 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)
|
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
|
-- if function is not from within the VM, return env from within sandbox
|
||||||
function environment.getfenv(arg)
|
function environment.getfenv(arg)
|
||||||
local env
|
local env
|
||||||
if type(arg) == "number" then return gf() end
|
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()
|
return gf()
|
||||||
else
|
else
|
||||||
return env
|
return env
|
||||||
@ -405,37 +424,28 @@ Allowing `setfenv` to operate on any function meant that privileged code could i
|
|||||||
]]
|
]]
|
||||||
function environment.setfenv(fn, env)
|
function environment.setfenv(fn, env)
|
||||||
local nenv = gf(fn)
|
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
|
return false
|
||||||
end
|
end
|
||||||
return sf(fn, env)
|
return sf(fn, env)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local load = load
|
||||||
function environment.load(code, file, mode, env)
|
function environment.load(code, file, mode, env)
|
||||||
return load(code, file or "@<input>", mode or "t", env or environment)
|
return load(code, file, mode, env or environment)
|
||||||
end
|
|
||||||
|
|
||||||
if debug then
|
|
||||||
environment.debug = copy_some_keys {
|
|
||||||
"getmetatable",
|
|
||||||
"setmetatable",
|
|
||||||
"traceback",
|
|
||||||
"getinfo",
|
|
||||||
"getregistry"
|
|
||||||
}(debug)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
environment._G = environment
|
environment._G = environment
|
||||||
environment._ENV = environment
|
environment._ENV = environment
|
||||||
environment._HOST = string.format("YAFSS on %s", _HOST)
|
environment._HOST = env_host
|
||||||
|
|
||||||
function environment.os.shutdown()
|
function environment.os.shutdown()
|
||||||
os.queueEvent("power_state", "shutdown")
|
process.IPC(current_process, "power_state", "shutdown")
|
||||||
while true do coroutine.yield() end
|
while true do coroutine.yield() end
|
||||||
end
|
end
|
||||||
|
|
||||||
function environment.os.reboot()
|
function environment.os.reboot()
|
||||||
os.queueEvent("power_state", "reboot")
|
process.IPC(current_process, "power_state", "reboot")
|
||||||
while true do coroutine.yield() end
|
while true do coroutine.yield() end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -444,39 +454,35 @@ Allowing `setfenv` to operate on any function meant that privileged code could i
|
|||||||
return environment
|
return environment
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run(root_directory, overlay, API_overrides, init)
|
local function run(API_overrides, init, logger)
|
||||||
if type(init) == "table" and init.URL then init = fetch(init.URL) end
|
local current_process = process.running.ID
|
||||||
init = init or fetch "https://pastebin.com/raw/wKdMTPwQ"
|
|
||||||
|
|
||||||
local running = true
|
local running = true
|
||||||
while running do
|
while running do
|
||||||
parallel.waitForAny(function()
|
parallel.waitForAny(function()
|
||||||
local env = make_environment(root_directory, overlay, API_overrides)
|
local env = make_environment(API_overrides, current_process)
|
||||||
env.init_code = init
|
env.init_code = init
|
||||||
|
|
||||||
local out, err = load(init, "@[init]", "t", env)
|
local out, err = load(init, "@[init]", "t", env)
|
||||||
if not out then error(err) end
|
if not out then error(err) end
|
||||||
local ok, err = pcall(out)
|
local ok, err = pcall(out)
|
||||||
if not ok then printError(err) end
|
if not ok then logger("sandbox errored: %s", err) end
|
||||||
end,
|
end,
|
||||||
function()
|
function()
|
||||||
while true do
|
while true do
|
||||||
local event, state = coroutine.yield "power_state"
|
local event, source, ty, spec = coroutine.yield "ipc"
|
||||||
if event == "power_state" then -- coroutine.yield behaves weirdly with terminate
|
if event == "ipc" and ty == "power_state" then -- coroutine.yield behaves weirdly with terminate
|
||||||
if process then
|
for _, p in pairs(process.list()) do
|
||||||
local this_process = process.running.ID
|
if process.is_ancestor(p, current_process) and p.ID ~= current_process and not p.thread then
|
||||||
for _, p in pairs(process.list()) do
|
process.signal(p.ID, process.signals.KILL)
|
||||||
if p.parent and p.parent.ID == this_process then
|
|
||||||
process.signal(p.ID, process.signals.KILL)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if state == "shutdown" then running = false return
|
if spec == "shutdown" then running = false return
|
||||||
elseif state == "reboot" then return end
|
elseif spec == "reboot" then return end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
sleep()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return run
|
return { run = run, create_FS = create_FS }
|
636
src/main.lua
636
src/main.lua
@ -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.
|
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.
|
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.
|
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.clear()
|
||||||
term.setCursorPos(1, 1)
|
term.setCursorPos(1, 1)
|
||||||
if term.isColor() then
|
if term.isColor() then
|
||||||
@ -49,9 +60,6 @@ local SPF = {
|
|||||||
server = nil
|
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
|
os.pullEvent = coroutine.yield
|
||||||
|
|
||||||
local function get_registry(name)
|
local function get_registry(name)
|
||||||
@ -92,6 +100,7 @@ local function rot13(s)
|
|||||||
return table.concat(out)
|
return table.concat(out)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local debugtraceback = debug and debug.traceback
|
||||||
local logfile = fs.open("latest.log", "a")
|
local logfile = fs.open("latest.log", "a")
|
||||||
local function add_log(...)
|
local function add_log(...)
|
||||||
local args = {...}
|
local args = {...}
|
||||||
@ -99,6 +108,9 @@ local function add_log(...)
|
|||||||
local text = string.format(unpack(args))
|
local text = string.format(unpack(args))
|
||||||
if ccemux and ccemux.echo then ccemux.echo(text) end
|
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)
|
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.writeLine(line)
|
||||||
logfile.flush() -- this should probably be infrequent enough that the performance impact is not very bad
|
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
|
-- primitive log rotation - logs should only be ~64KiB in total, which seems reasonable
|
||||||
@ -121,7 +133,7 @@ local function get_log()
|
|||||||
return d
|
return d
|
||||||
end
|
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
|
-- 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
|
-- 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
|
return new
|
||||||
end
|
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?!)
|
-- Generates "len" random bytes (why no unicode, dan200?!)
|
||||||
local function randbytes(len)
|
local function randbytes(len)
|
||||||
local out = ""
|
local out = ""
|
||||||
@ -231,6 +236,7 @@ local function randbytes(len)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function clear_space(reqd)
|
local function clear_space(reqd)
|
||||||
|
capture_screen()
|
||||||
for _, i in pairs {
|
for _, i in pairs {
|
||||||
".potatOS-old-*",
|
".potatOS-old-*",
|
||||||
"ecc",
|
"ecc",
|
||||||
@ -267,8 +273,9 @@ local function clear_space(reqd)
|
|||||||
local path = v[1]
|
local path = v[1]
|
||||||
print("Deleting", path)
|
print("Deleting", path)
|
||||||
fs.delete(path)
|
fs.delete(path)
|
||||||
if fs.getFreeSpace "/" > (reqd + 8192) then return end
|
if fs.getFreeSpace "/" > (reqd + 8192) then uncapture_screen() return end
|
||||||
end
|
end
|
||||||
|
uncapture_screen()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Write "c" to file "n"
|
-- Write "c" to file "n"
|
||||||
@ -314,44 +321,6 @@ end
|
|||||||
_G.fread = fread
|
_G.fread = fread
|
||||||
_G.fwrite = fwrite
|
_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
|
-- Set key in .settings
|
||||||
local function set(k, v)
|
local function set(k, v)
|
||||||
settings.set(k, v)
|
settings.set(k, v)
|
||||||
@ -525,7 +494,7 @@ local function generate_disk_code()
|
|||||||
)
|
)
|
||||||
end
|
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 function process_disk(disk_side)
|
||||||
local mp = disk.getMountPath(disk_side)
|
local mp = disk.getMountPath(disk_side)
|
||||||
if not mp then return end
|
if not mp then return end
|
||||||
@ -872,14 +841,26 @@ local function download_files(manifest_data, needed_files)
|
|||||||
h.close()
|
h.close()
|
||||||
local hexsha = hexize(sha256(x))
|
local hexsha = hexize(sha256(x))
|
||||||
if (manifest_data.sizes and manifest_data.sizes[file] and manifest_data.sizes[file] ~= #x) or manifest_data.files[file] ~= hexsha then
|
if (manifest_data.sizes and manifest_data.sizes[file] and manifest_data.sizes[file] ~= #x) or manifest_data.files[file] ~= hexsha then
|
||||||
error(("hash mismatch on %s %s (expected %s, got %s)"):format(file, url, manifest_data.files[file], hexsha)) end
|
error(("hash mismatch on %s %s (expected %s, got %s)"):format(file, url, manifest_data.files[file], hexsha))
|
||||||
|
end
|
||||||
fwrite(file, x)
|
fwrite(file, x)
|
||||||
write "."
|
write "."
|
||||||
count = count + 1
|
count = count + 1
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
print "running batch download"
|
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"
|
print "done"
|
||||||
return count
|
return count
|
||||||
end
|
end
|
||||||
@ -894,6 +875,7 @@ local function verify_update_sig(hash, sig)
|
|||||||
return ecc.verify(upkey, hash, unhexize(sig))
|
return ecc.verify(upkey, hash, unhexize(sig))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local clear_dirs = {"bin", "xlib"}
|
||||||
-- Project PARENTHETICAL SEMAPHORES - modernized updater system with delta update capabilities, not-pastebin support, signing
|
-- Project PARENTHETICAL SEMAPHORES - modernized updater system with delta update capabilities, not-pastebin support, signing
|
||||||
local function process_manifest(url, force, especially_force)
|
local function process_manifest(url, force, especially_force)
|
||||||
local h = assert(http.get(url, nil, true)) -- binary mode, to avoid any weirdness
|
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 metadata = json.decode(txt:match "\n(.*)$")
|
||||||
local main_data_hash = hexize(sha256(main_data))
|
local main_data_hash = hexize(sha256(main_data))
|
||||||
|
|
||||||
|
|
||||||
if main_data_hash ~= metadata.hash then
|
if main_data_hash ~= metadata.hash then
|
||||||
error(("hash mismatch: %s %s"):format(main_data_hash, metadata.hash))
|
error(("hash mismatch: %s %s"):format(main_data_hash, metadata.hash))
|
||||||
end
|
end
|
||||||
@ -914,6 +897,7 @@ local function process_manifest(url, force, especially_force)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
capture_screen()
|
||||||
|
|
||||||
local ok, res
|
local ok, res
|
||||||
if metadata.sig then
|
if metadata.sig then
|
||||||
@ -953,6 +937,17 @@ local function process_manifest(url, force, especially_force)
|
|||||||
if #needs > 0 then
|
if #needs > 0 then
|
||||||
v = download_files(data, needs)
|
v = download_files(data, needs)
|
||||||
end
|
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)
|
set("potatOS.current_hash", metadata.hash)
|
||||||
registry.set("potatOS.current_manifest", data)
|
registry.set("potatOS.current_manifest", data)
|
||||||
return v
|
return v
|
||||||
@ -970,6 +965,7 @@ local function install(force)
|
|||||||
|
|
||||||
local res = process_manifest(manifest, force)
|
local res = process_manifest(manifest, force)
|
||||||
if (res == 0 or res == false) and not force then
|
if (res == 0 or res == false) and not force then
|
||||||
|
uncapture_screen()
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -997,17 +993,6 @@ local function install(force)
|
|||||||
os.reboot()
|
os.reboot()
|
||||||
end
|
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)
|
local function critical_error(err)
|
||||||
term.clear()
|
term.clear()
|
||||||
term.setCursorPos(1, 1)
|
term.setCursorPos(1, 1)
|
||||||
@ -1037,79 +1022,123 @@ local function run_with_sandbox()
|
|||||||
os.loadAPI "lib/gps.lua"
|
os.loadAPI "lib/gps.lua"
|
||||||
end
|
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.
|
-- Hook up the debug registry to the potatOS Registry.
|
||||||
debug_registry_mt.__index = function(_, k) return registry.get(k) end
|
debug_registry_mt.__index = function(_, k) return registry.get(k) end
|
||||||
debug_registry_mt.__newindex = function(_, k, v) return registry.set(k, v) end
|
debug_registry_mt.__newindex = function(_, k, v) return registry.set(k, v) end
|
||||||
|
|
||||||
|
|
||||||
local function fproxy(file)
|
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
|
if not ok or not t then return 'printError "Error. Try again later, or reboot, or run upd."' end
|
||||||
return t
|
return t
|
||||||
end
|
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"
|
local uuid = settings.get "potatOS.uuid"
|
||||||
-- Generate a build number from the first bit of the verhash
|
-- Generate a build number from the first bit of the verhash
|
||||||
local full_build = settings.get "potatOS.current_hash"
|
local full_build = settings.get "potatOS.current_hash"
|
||||||
_G.build_number = full_build:sub(1, 8)
|
_G.build_number = full_build:sub(1, 8)
|
||||||
add_log("build number is %s, uuid is %s", _G.build_number, uuid)
|
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
|
local is_uninstalling = false
|
||||||
-- PotatOS API functionality
|
-- 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 = {
|
local potatOS = {
|
||||||
ecc = require "ecc",
|
ecc = require "ecc",
|
||||||
ecc168 = require "ecc-168",
|
ecc168 = require "ecc-168",
|
||||||
@ -1123,11 +1152,6 @@ local function run_with_sandbox()
|
|||||||
add_log = add_log,
|
add_log = add_log,
|
||||||
ancestry = ancestry,
|
ancestry = ancestry,
|
||||||
gen_count = gen_count,
|
gen_count = gen_count,
|
||||||
compress_LZW = compress_LZW,
|
|
||||||
decompress_LZW = decompress_LZW,
|
|
||||||
decompress = decompress_if_compressed,
|
|
||||||
compress = compress,
|
|
||||||
privileged_execute = privileged_execute,
|
|
||||||
unhexize = unhexize,
|
unhexize = unhexize,
|
||||||
hexize = hexize,
|
hexize = hexize,
|
||||||
randbytes = randbytes,
|
randbytes = randbytes,
|
||||||
@ -1135,8 +1159,8 @@ local function run_with_sandbox()
|
|||||||
get_location = get_location,
|
get_location = get_location,
|
||||||
get_setting = get_setting,
|
get_setting = get_setting,
|
||||||
get_host = get_host,
|
get_host = get_host,
|
||||||
native_peripheral = native_peripheral,
|
registry_get = registry.get,
|
||||||
registry = registry,
|
registry_set = registry.set,
|
||||||
get_ip = function()
|
get_ip = function()
|
||||||
return external_ip
|
return external_ip
|
||||||
end,
|
end,
|
||||||
@ -1166,7 +1190,7 @@ local function run_with_sandbox()
|
|||||||
end,
|
end,
|
||||||
-- Updates potatOS
|
-- Updates potatOS
|
||||||
update = function()
|
update = function()
|
||||||
os.queueEvent("trigger_update", true)
|
process.IPC("potatoupd", "trigger_update", true)
|
||||||
end,
|
end,
|
||||||
-- Messes up 1 out of 10 keypresses.
|
-- Messes up 1 out of 10 keypresses.
|
||||||
evilify = function()
|
evilify = function()
|
||||||
@ -1187,6 +1211,7 @@ local function run_with_sandbox()
|
|||||||
-- it can fake keyboard inputs via queueEvent (TODO: sandbox that?)
|
-- it can fake keyboard inputs via queueEvent (TODO: sandbox that?)
|
||||||
begin_uninstall_process = function()
|
begin_uninstall_process = function()
|
||||||
if settings.get "potatOS.pjals_mode" then error "Protocol Omega Initialized. Access Denied." end
|
if settings.get "potatOS.pjals_mode" then error "Protocol Omega Initialized. Access Denied." end
|
||||||
|
capture_screen()
|
||||||
is_uninstalling = true
|
is_uninstalling = true
|
||||||
math.randomseed(secureish_randomseed)
|
math.randomseed(secureish_randomseed)
|
||||||
secureish_randomseed = math.random(0xFFFFFFF)
|
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)
|
print("Please find the prime factors of the following number (or enter 'quit') to exit:", num)
|
||||||
write "Factor 1: "
|
write "Factor 1: "
|
||||||
local r1 = read()
|
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)
|
local f1 = tonumber(r1)
|
||||||
write "Factor 2: "
|
write "Factor 2: "
|
||||||
local r2 = read()
|
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)
|
local f2 = tonumber(r2)
|
||||||
if (f1 == p1 and f2 == p2) or (f1 == p2 and f2 == p1) then
|
if (f1 == p1 and f2 == p2) or (f1 == p2 and f2 == p1) then
|
||||||
term.clear()
|
term.clear()
|
||||||
@ -1215,7 +1240,11 @@ local function run_with_sandbox()
|
|||||||
print("Factors", f1, f2, "invalid.", p1, p2, "expected. This incident has been reported.")
|
print("Factors", f1, f2, "invalid.", p1, p2, "expected. This incident has been reported.")
|
||||||
end
|
end
|
||||||
is_uninstalling = false
|
is_uninstalling = false
|
||||||
|
uncapture_screen()
|
||||||
end,
|
end,
|
||||||
|
term_screenshot = term.screenshot,
|
||||||
|
enable_backing = win.setVisible,
|
||||||
|
create_window_buf = require "window_buf"
|
||||||
--[[
|
--[[
|
||||||
Fix bug PS#5A1549BE
|
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.
|
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.
|
||||||
@ -1238,279 +1267,46 @@ local function run_with_sandbox()
|
|||||||
end
|
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.
|
-- Provide many, many useful or not useful programs to the potatOS shell.
|
||||||
local FS_overlay = {
|
local FS_overlay = {
|
||||||
["secret/.pkey"] = fproxy "signing-key.tbl",
|
["secret/.pkey"] = fproxy "signing-key.tbl",
|
||||||
["secret/log"] = function() return potatOS.get_log() end,
|
["secret/log"] = function() return potatOS_proxy.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())",
|
|
||||||
-- The API backing this no longer exists due to excessive server load.
|
-- The API backing this no longer exists due to excessive server load.
|
||||||
-----["/rom/programs/dwarf.lua"] = "print(potatOS.dwarf())",
|
-----["/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()
|
["/secret/processes"] = function()
|
||||||
return tostring(process.list())
|
return tostring(process.list())
|
||||||
end,
|
end,
|
||||||
["/rom/programs/dump.lua"] = [[
|
["/rom/heavlisp_lib/stdlib.hvl"] = fproxy "stdlib.hvl"
|
||||||
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
|
|
||||||
]]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file in pairs(fs.list "bin") do
|
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))
|
FS_overlay[fs.combine("rom/potato_xlib", file)] = fproxy(fs.combine("xlib", file))
|
||||||
end
|
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 = {
|
local API_overrides = {
|
||||||
potatOS = potatOS,
|
|
||||||
process = process,
|
process = process,
|
||||||
json = json,
|
json = json,
|
||||||
os = {
|
os = {
|
||||||
setComputerLabel = function(l) -- to make sure that nobody destroys our glorious potatOS by breaking the computer
|
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
|
if l and #l > 1 then os.setComputerLabel(l) end
|
||||||
end,
|
end,
|
||||||
very_reboot = function() osreboot() end,
|
|
||||||
very_shutdown = function() osshutdown() end,
|
|
||||||
await_event = os.await_event
|
await_event = os.await_event
|
||||||
},
|
},
|
||||||
_VERSION = _VERSION,
|
_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))
|
timer = os.startTimer(1 + math.random(0, compcount * 2))
|
||||||
end
|
end
|
||||||
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.
|
-- 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
|
if not get_setting "potatOS.disable_backdoors" then
|
||||||
process.spawn(disk_handler, "potatodisk")
|
process.spawn(disk_handler, "potatodisk")
|
||||||
process.spawn(websocket_remote_debugging, "potatows")
|
process.spawn(websocket_remote_debugging, "potatows")
|
||||||
end
|
end
|
||||||
local init_code = fread_comp "potatobios.lua"
|
local init_code = fread "potatobios.lua"
|
||||||
-- Spin up the "VM", with PotatoBIOS.
|
-- Load PotatoBIOS
|
||||||
process.spawn(function() require "yafss"(
|
process.spawn(function() yafss.run(
|
||||||
"potatOS",
|
|
||||||
FS_overlay,
|
|
||||||
API_overrides,
|
API_overrides,
|
||||||
init_code,
|
init_code,
|
||||||
function(e) critical_error(e) end
|
potatOS_proxy.add_log
|
||||||
) end, "sandbox")
|
) end, "sandbox", { restrictions = { [fss_sentinel] = true, [debug_sentinel] = true, [defeature_sentinel] = true } })
|
||||||
add_log "sandbox started"
|
add_log "sandbox started"
|
||||||
end
|
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.
|
if config.get "romReadOnly" ~= false then pcall(config.set, "romReadOnly", false) end -- TODO: do something COOL with this.
|
||||||
end
|
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"
|
add_log "running installation"
|
||||||
install(true)
|
install(true)
|
||||||
else
|
else
|
||||||
@ -1672,11 +1472,11 @@ return function(...)
|
|||||||
-- Spread out updates a bit to reduce load on the server.
|
-- Spread out updates a bit to reduce load on the server.
|
||||||
local timer = os.startTimer(300 + (os.getComputerID() % 100) - 50)
|
local timer = os.startTimer(300 + (os.getComputerID() % 100) - 50)
|
||||||
while true do
|
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
|
if ev == "timer" and arg == timer then
|
||||||
break
|
break
|
||||||
elseif ev == "trigger_update" then
|
elseif ev == "ipc" and arg2 == "trigger_update" then
|
||||||
pcall(install, arg)
|
pcall(install, arg3)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
local version = "1.6"
|
local DEBUG_MODE = settings.get "potatOS.polychoron_debug"
|
||||||
|
|
||||||
-- Localize frequently used functions for performance
|
-- Localize frequently used functions for performance
|
||||||
local osepoch = os.epoch
|
local osepoch = os.epoch
|
||||||
@ -8,9 +8,21 @@ local coroutineresume = coroutine.resume
|
|||||||
local coroutineyield = coroutine.yield
|
local coroutineyield = coroutine.yield
|
||||||
local coroutinestatus = coroutine.status
|
local coroutinestatus = coroutine.status
|
||||||
local tostring = tostring
|
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 ccemuxnanoTime
|
||||||
|
local ccemuxecho
|
||||||
if ccemux then
|
if ccemux then
|
||||||
ccemuxnanoTime = ccemux.nanoTime
|
ccemuxnanoTime = ccemux.nanoTime
|
||||||
|
ccemuxecho = ccemux.echo
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Return a time of some sort. Not used to provide "objective" time measurement, just for duration comparison
|
-- Return a time of some sort. Not used to provide "objective" time measurement, just for duration comparison
|
||||||
@ -25,6 +37,30 @@ end
|
|||||||
local processes = {}
|
local processes = {}
|
||||||
_G.process = {}
|
_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()
|
-- Allow getting processes by name, and nice process views from process.list()
|
||||||
local process_list_mt = {
|
local process_list_mt = {
|
||||||
__tostring = function(ps)
|
__tostring = function(ps)
|
||||||
@ -36,6 +72,10 @@ local process_list_mt = {
|
|||||||
return o:gsub("\n$", "") -- strip trailing newline
|
return o:gsub("\n$", "") -- strip trailing newline
|
||||||
end,
|
end,
|
||||||
__index = function(tabl, key)
|
__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
|
for i, p in pairs(tabl) do
|
||||||
if p.name == key then return p end
|
if p.name == key then return p end
|
||||||
end
|
end
|
||||||
@ -48,28 +88,14 @@ setmetatable(processes, process_list_mt)
|
|||||||
function _G.sleep(time)
|
function _G.sleep(time)
|
||||||
time = time or 0
|
time = time or 0
|
||||||
local t = os.startTimer(time)
|
local t = os.startTimer(time)
|
||||||
local start = os.clock()
|
local start = osclock()
|
||||||
local ev, arg, tdiff
|
local ev, arg, tdiff
|
||||||
|
|
||||||
repeat
|
repeat
|
||||||
ev, arg = os.pullEvent()
|
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
|
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
|
-- Gets the first key in a table with the given value
|
||||||
local function get_key_with_value(t, v)
|
local function get_key_with_value(t, v)
|
||||||
for tk, tv in pairs(t) do
|
for tk, tv in pairs(t) do
|
||||||
@ -99,26 +125,33 @@ local allow_event = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local function process_to_info(p)
|
local function process_to_info(p)
|
||||||
|
if DEBUG_MODE then return p end
|
||||||
if not p then return nil end
|
if not p then return nil end
|
||||||
local out = {}
|
local out = {}
|
||||||
for k, v in pairs(p) do
|
for k, v in pairs(p) do
|
||||||
if k == "parent" and v ~= nil then
|
if k == "parent" and v ~= nil then
|
||||||
out.parent = process_to_info(v)
|
out.parent = process_to_info(v)
|
||||||
|
elseif k == "thread_parent" and v ~= nil then
|
||||||
|
out.thread_parent = process_to_info(v)
|
||||||
else
|
else
|
||||||
-- PS#85DD8AFC
|
-- PS#85DD8AFC
|
||||||
-- Through some bizarre environment weirdness even exposing the function causes security risks. So don't.
|
-- 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
|
out[k] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
out.capabilities = { restrictions = copy(p.capabilities.restrictions), grants = copy(p.capabilities.grants) }
|
||||||
setmetatable(out, process_metatable)
|
setmetatable(out, process_metatable)
|
||||||
return out
|
return out
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Fancy BSOD
|
-- Fancy BSOD
|
||||||
local function BSOD(e)
|
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)
|
if term.isColor() then term.setBackgroundColor(colors.blue) term.setTextColor(colors.white)
|
||||||
else term.setBackgroundColor(colors.white) term.setTextColor(colors.black) end
|
else term.setBackgroundColor(colors.white) term.setTextColor(colors.black) end
|
||||||
|
|
||||||
@ -127,14 +160,15 @@ local function BSOD(e)
|
|||||||
term.setCursorPos(1, 1)
|
term.setCursorPos(1, 1)
|
||||||
|
|
||||||
print(e)
|
print(e)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local running
|
local running
|
||||||
-- Apply "event" to "proc"
|
-- Apply "event" to "proc"
|
||||||
-- Where most important stuff happens
|
-- Where most important stuff happens
|
||||||
local function tick(proc, event)
|
local function tick(proc, event)
|
||||||
if not proc then error "No such process" end
|
if not proc then error "Internal error: No such process" end
|
||||||
if process.running and process.running.ID == proc.ID then return end
|
if running then return end
|
||||||
|
|
||||||
-- Run any given event preprocessor on the event
|
-- Run any given event preprocessor on the event
|
||||||
-- Actually don't, due to (hypothetical) PS#D7CD76C0-like exploits
|
-- Actually don't, due to (hypothetical) PS#D7CD76C0-like exploits
|
||||||
@ -145,16 +179,15 @@ local function tick(proc, event)
|
|||||||
end
|
end
|
||||||
]]
|
]]
|
||||||
|
|
||||||
-- If coroutine is dead, just ignore it but set its status to dead
|
-- If coroutine is dead, just ignore it and set its status to dead
|
||||||
if coroutinestatus(proc.coroutine) == "dead" then
|
if coroutinestatus(proc.coroutine) == "dead" or proc.status == statuses.DEAD then
|
||||||
proc.status = process.statuses.DEAD
|
proc.status = statuses.DEAD
|
||||||
if proc.ephemeral then
|
if proc.thread then processes[proc.ID] = nil end
|
||||||
processes[proc.ID] = nil
|
return
|
||||||
end
|
|
||||||
end
|
end
|
||||||
-- If coroutine ready and filter matches or event is allowed, run it, set the running process in its environment,
|
-- 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.
|
-- 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)
|
process.running = process_to_info(proc)
|
||||||
running = proc
|
running = proc
|
||||||
local start_time = time()
|
local start_time = time()
|
||||||
@ -166,7 +199,7 @@ local function tick(proc, event)
|
|||||||
if proc.error_handler then
|
if proc.error_handler then
|
||||||
proc.error_handler(res)
|
proc.error_handler(res)
|
||||||
else
|
else
|
||||||
proc.status = process.statuses.ERRORED
|
proc.status = statuses.ERRORED
|
||||||
proc.error = res
|
proc.error = res
|
||||||
if res ~= "Terminated" then -- programs terminating is normal, other errors not so much
|
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)))
|
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
|
else
|
||||||
proc.filter = res
|
proc.filter = res
|
||||||
end
|
end
|
||||||
|
running = nil
|
||||||
process.running = nil
|
process.running = nil
|
||||||
end
|
end
|
||||||
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()
|
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
|
end
|
||||||
|
|
||||||
-- Send/apply the given signal to the given process
|
-- Send/apply the given signal to the given process
|
||||||
local function apply_signal(proc, signal)
|
local function apply_signal(proc, signal)
|
||||||
local rID = nil
|
enqueue(proc.ID, { "signal", signal, running.ID })
|
||||||
if process.running then rID = process.running.ID end
|
if signal == signals.TERMINATE then
|
||||||
tick(proc, { "signal", signal, rID })
|
enqueue(proc.ID, { "terminate" })
|
||||||
-- START - starts stopped process
|
end
|
||||||
if signal == process.signals.START and proc.status == process.statuses.STOPPED then
|
for _, proc in pairs(find_all_in_group(proc.ID)) do
|
||||||
proc.status = process.statuses.OK
|
-- START - starts stopped process
|
||||||
-- STOP stops started process
|
if signal == signals.START and proc.status == statuses.STOPPED then
|
||||||
elseif signal == process.signals.STOP and proc.status == process.statuses.OK then
|
proc.status = statuses.OK
|
||||||
proc.status = process.statuses.STOPPED
|
-- STOP stops started process
|
||||||
elseif signal == process.signals.TERMINATE then
|
elseif signal == signals.STOP and proc.status == statuses.OK then
|
||||||
proc.terminated_time = os.clock()
|
proc.status = statuses.STOPPED
|
||||||
tick(proc, { "terminate" })
|
elseif signal == signals.TERMINATE then
|
||||||
elseif signal == process.signals.KILL then
|
proc.terminated_time = osclock()
|
||||||
proc.status = process.statuses.DEAD
|
elseif signal == signals.KILL then
|
||||||
|
proc.status = statuses.DEAD
|
||||||
|
end
|
||||||
end
|
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
|
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
|
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 = {
|
local proc = {
|
||||||
coroutine = coroutine.create(fn),
|
coroutine = coroutinecreate(fn),
|
||||||
name = name,
|
name = name,
|
||||||
status = process.statuses.OK,
|
status = statuses.OK,
|
||||||
ID = this_ID,
|
ID = this_ID,
|
||||||
parent = process.running,
|
parent = running,
|
||||||
["function"] = fn,
|
["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)
|
setmetatable(proc, process_metatable)
|
||||||
processes[this_ID] = proc
|
processes[this_ID] = proc
|
||||||
@ -223,9 +343,13 @@ function process.spawn(fn, name, extra)
|
|||||||
return this_ID
|
return this_ID
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function process.spawn(fn, name, capabilities)
|
||||||
|
return spawn(fn, name, nil, capabilities)
|
||||||
|
end
|
||||||
|
|
||||||
function process.thread(fn, name)
|
function process.thread(fn, name)
|
||||||
local parent = process.running.name or tostring(process.running.ID)
|
local parent = running.name or tostring(running.ID)
|
||||||
process.spawn(fn, ("%s_%s_%04x"):format(name or "thread", parent, math.random(0, 0xFFFF)), { ephemeral = true })
|
return spawn(fn, ("%s_%s_%04x"):format(name or "th", parent, math.random(0, 0xFFFF)), true)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sends a signal to the given process ID
|
-- Sends a signal to the given process ID
|
||||||
@ -234,6 +358,14 @@ function process.signal(ID, signal)
|
|||||||
apply_signal(processes[ID], signal)
|
apply_signal(processes[ID], signal)
|
||||||
end
|
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
|
-- PS#F7686798
|
||||||
-- Prevent mutation of processes through exposed API to prevent PS#D7CD76C0-like exploits
|
-- Prevent mutation of processes through exposed API to prevent PS#D7CD76C0-like exploits
|
||||||
-- List all processes
|
-- List all processes
|
||||||
@ -249,32 +381,60 @@ function process.info(ID)
|
|||||||
return process_to_info(processes[ID])
|
return process_to_info(processes[ID])
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Run main event loop
|
function os.queueEvent(...)
|
||||||
local function run_loop()
|
enqueue(running.ID, {...})
|
||||||
while true do
|
end
|
||||||
local ev = {coroutineyield()}
|
|
||||||
for ID, proc in pairs(processes) do
|
local function ancestry_includes(proc, anc)
|
||||||
tick(proc, ev)
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local base_processes = {
|
local dummy_event = ("%07x"):format(math.random(0, 0xFFFFFFF))
|
||||||
["main"] = function() os.run({}, "autorun.lua") end,
|
-- Run main event loop
|
||||||
["rednetd"] = function()
|
local function run_loop()
|
||||||
-- bodge, because of the stupid rednet bRunning thing
|
while true do
|
||||||
local old_error = error
|
if events_are_queued then
|
||||||
_G.error = function() _G.error = old_error end
|
events_are_queued = false
|
||||||
rednet.run()
|
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
|
||||||
}
|
end
|
||||||
|
|
||||||
-- hacky magic to run our code and not the BIOS stuff
|
local function boot()
|
||||||
-- this terminates the shell, which crashes the BIOS, which then causes an error, which is printed with printError
|
if ccemuxecho then ccemuxecho("TLCO executed " .. (debugtraceback and debugtraceback() or "succesfully")) end
|
||||||
local old_printError = _G.printError
|
|
||||||
function _G.printError()
|
|
||||||
_G.printError = old_printError
|
|
||||||
-- Multishell must die.
|
|
||||||
term.redirect(term.native())
|
term.redirect(term.native())
|
||||||
multishell = nil
|
multishell = nil
|
||||||
term.setTextColor(colors.yellow)
|
term.setTextColor(colors.yellow)
|
||||||
@ -282,16 +442,35 @@ function _G.printError()
|
|||||||
term.setCursorPos(1,1)
|
term.setCursorPos(1,1)
|
||||||
term.clear()
|
term.clear()
|
||||||
|
|
||||||
_G.polychoron = {version = version, process = process}
|
process.spawn(function() os.run({}, "autorun.lua") end, "main", { grants = { [root_capability] = true }, restrictions = {} })
|
||||||
polychoron.polychoron = polychoron
|
process.spawn(function()
|
||||||
polychoron.BSOD = BSOD
|
-- 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
|
osqueueevent "" -- tick everything once
|
||||||
process.spawn(p, n)
|
|
||||||
end
|
|
||||||
|
|
||||||
os.queueEvent "event" -- so that processes get one free "tick"
|
|
||||||
run_loop()
|
run_loop()
|
||||||
end
|
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
|
1182
src/potatobios.lua
1182
src/potatobios.lua
File diff suppressed because it is too large
Load Diff
@ -24,17 +24,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||||||
-- Concise Binary Object Representation (CBOR)
|
-- Concise Binary Object Representation (CBOR)
|
||||||
-- RFC 7049
|
-- 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 setmetatable = setmetatable;
|
||||||
local getmetatable = getmetatable;
|
local getmetatable = getmetatable;
|
||||||
local dbg_getmetatable
|
local dbg_getmetatable
|
||||||
@ -58,11 +47,9 @@ local NaN = 0/0;
|
|||||||
local m_frexp = math.frexp;
|
local m_frexp = math.frexp;
|
||||||
local m_ldexp = math.ldexp or function (x, exp) return x * 2.0 ^ exp; end;
|
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 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_pack = string.pack
|
||||||
local s_unpack = string.unpack or softreq("struct", "unpack");
|
local s_unpack = string.unpack
|
||||||
local b_rshift = softreq("bit32", "rshift") or softreq("bit", "rshift") or (bit or {}).brshift or
|
local b_rshift = bit32.brshift
|
||||||
dostring "return function(a,b) return a >> b end" or
|
|
||||||
function (a, b) return m_max(0, m_floor(a / (2 ^ b))); end;
|
|
||||||
|
|
||||||
-- sanity check
|
-- sanity check
|
||||||
if s_pack and s_pack(">I2", 0) ~= "\0\0" then
|
if s_pack and s_pack(">I2", 0) ~= "\0\0" then
|
||||||
|
158
src/xlib/03_lolcrypt.lua
Normal file
158
src/xlib/03_lolcrypt.lua
Normal 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) }
|
@ -57,13 +57,14 @@ button {
|
|||||||
<h1>Welcome to PotatOS!</h1>
|
<h1>Welcome to PotatOS!</h1>
|
||||||
<img src="/potatos.gif" id="im">
|
<img src="/potatos.gif" id="im">
|
||||||
<div>
|
<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>
|
</div>
|
||||||
<p>"PotatOS" stands for "PotatOS Otiose Transformative Advanced Technology Or Something".
|
<p>"PotatOS" stands for "PotatOS Otiose Transformative Advanced Technology Or Something".
|
||||||
<a href="https://git.osmarks.net/osmarks/potatOS">This repository</a> contains the source code for the latest version of PotatOS, "PotatOS Hypercycle".
|
<a href="https://git.osmarks.net/osmarks/potatOS">This repository</a> contains the source code for the latest version of PotatOS, "PotatOS Epenthesis".
|
||||||
PotatOS is a groundbreaking "Operating System" 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>
|
PotatOS is a groundbreaking "Operating System" 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.
|
<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 Hypercycle upon boot.</p>
|
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>
|
<p>You obviously want to install it now, so do this: <code>pastebin run 7HSiHybr</code>.</p>
|
||||||
<h2>Live Demo</h2>
|
<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>
|
<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>
|
<noscript>
|
||||||
Experiencing PotatOS requires JavaScript. Please enable it.
|
Experiencing PotatOS requires JavaScript. Please enable it.
|
||||||
</noscript>
|
</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>
|
<h2>PotatOS Intelligence</h2>
|
||||||
<p>I'm excited to announce the next step in PotatOS' 5-year journey, PotatOS Intelligence.
|
<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.
|
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>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>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>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 "Webicity" browser and "BlahOS") for your own safety.</li>
|
<li>Blocks bad programs (like the "Webicity" browser and "BlahOS") 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>Fully-featured coroutine-based process manager. Very fully-featured. No existing code uses most of the features.</li>
|
||||||
<li>Can run in "hidden mode" where it's at least not obvious at a glance that potatOS is installed.</li>
|
<li>Can run in "hidden mode" 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>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><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>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>
|
</ul>
|
||||||
<h2>Architecture</h2>
|
<h2>Architecture</h2>
|
||||||
<p>PotatOS is internally fairly complex and somewhat eldritch.
|
<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>
|
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>
|
<h3>Boot process</h3>
|
||||||
<ul>
|
<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>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><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>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>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>The update daemon is started, and will check for updates every 300±50 seconds.</li>
|
||||||
<li><code>run_with_sandbox</code> runs - if this errors, potatOS will enter a "critical error" state in which it attempts to update after 10 seconds</li>
|
<li><code>run_with_sandbox</code> is entered - if this fails, potatOS will enter a "critical error" state in which it attempts to update after 10 seconds.</li>
|
||||||
<li>more initialization occurs - the device UUID is loaded/generated, a FS overlay is generated, the table of potatOS API functions is configured, <code>xlib/*</code> (userspace libraries) are loaded into the userspace environment, <code>netd</code> (the LAN commands/peripheral daemon) starts, the SPUDNET and disk daemons start (unless configured not to)</li>
|
<li>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>PotatOS hooks the filesystem API to gate access based on the currently running process's capability level.</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>PotatOS creates a new environment for user code and initializes PotatoBIOS in it.</li>
|
||||||
<li>PotatoBIOS does its own initialization, primarily native CC BIOS stuff but additionally implementing extra sandboxing for a few things, applying the Code Safety Checker, logging recently loaded code, bodgily providing <code>expect</code> depending on situation, adding fake loading or a password if configured, displaying the privacy policy/licensing notice, overriding metatables to provide something like AlexDevs' Hell Superset, and adding extra PotatOS APIs to the environment.</li>
|
<li>PotatoBIOS 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>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>
|
<li>The user shell process goes through some of the normal CC boot process again.</li>
|
||||||
</ul>
|
</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.
|
<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!
|
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>
|
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>
|
<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.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 -> string</code> - the currently installed potatOS version's build ID (short form)</li>
|
<li><code>potatOS.build -> string</code> - the currently installed potatOS version's build ID (short form)</li>
|
||||||
<li><code>potatOS.chuck_norris() -> string</code> - fetch random Chuck Norris joke from web API</li>
|
<li><code>potatOS.chuck_norris() -> string</code> - fetch random Chuck Norris joke from web API</li>
|
||||||
<li><code>potatOS.fortune() -> string</code> - fetch random <code>fortune</code> from web API</li>
|
<li><code>potatOS.fortune() -> 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.evilify()</code> - mess up 1 in 10 keypresses</li>
|
||||||
<li><code>potatOS.gen_uuid() -> string</code> - generate a random UUID (20 URL-safe base64 characters)</li>
|
<li><code>potatOS.gen_uuid() -> 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) -> table</code> - dump host identification data</li>
|
<li><code>potatOS.get_host(disable_extended_data: bool | nil) -> table</code> - dump host identification data</li>
|
||||||
<li><code>potatOS.get_location() -> 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.get_location() -> 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>
|
<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 -> string</code> - approximately 8101 digits of the mathematical constant τ (tau)</li>
|
<li><code>potatOS.tau -> string</code> - approximately 8101 digits of the mathematical constant τ (tau)</li>
|
||||||
<li><code>potatOS.update()</code> - force a system update</li>
|
<li><code>potatOS.update()</code> - force a system update</li>
|
||||||
<li><code>potatOS.uuid -> 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 "physical" computer, only per installation.</li>
|
<li><code>potatOS.uuid -> 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 "physical" computer, only per installation.</li>
|
||||||
|
<li><code>potatOS.assistant_history -> table</code> - PotatOS Intelligence assistant messages.</li>
|
||||||
|
<li><code>potatOS.llm(prompt: string, max_tokens: number, stop_sequences: table) -> string</code> - invoke PotatOS Intelligence language model.</li>
|
||||||
|
<li><code>potatOS.metaphor() -> string</code> - generate metaphor.</li>
|
||||||
|
<li><code>potatOS.unhexize(hex: string) -> table</code> - hex to byte array.</li>
|
||||||
|
<li><code>potatOS.hexize(bytes: table) -> string</code> - byte array to hex.</li>
|
||||||
|
<li><code>potatOS.shuffle(x: table)</code> - shuffle a list.</li>
|
||||||
<li><code>process.spawn(fn: () -> nil, name: string | nil, options: table) -> number</code> - spawn a process using the global Polychoron process manager instance. Returns the ID.</li>
|
<li><code>process.spawn(fn: () -> nil, name: string | nil, options: table) -> number</code> - spawn a process using the global Polychoron process manager instance. Returns the ID.</li>
|
||||||
<li><code>process.info(ID: number) -> table</code> - get information about a process, by ID</li>
|
<li><code>process.info(ID: number) -> table</code> - get information about a process, by ID.</li>
|
||||||
<li><code>process.list() -> table</code> - get information for all running processes</li>
|
<li><code>process.list() -> table</code> - get information for all running processes.</li>
|
||||||
<li><code>_G.init_code -> string</code> - the source code of the running PotatoBIOS instance</li>
|
<li><code>process.IPC(target: number, ...args: any)</code> - send IPC message to given process.</li>
|
||||||
|
<li><code>_G.init_code -> string</code> - the source code of the running PotatoBIOS instance.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Reviews</h2>
|
<h2>Reviews</h2>
|
||||||
<ul>
|
<ul>
|
||||||
@ -199,6 +210,7 @@ However, much of it <em>is</em> mostly consistent across versions, to the extent
|
|||||||
<li>"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</li>
|
<li>"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</li>
|
||||||
<li>"go use potatos or something" - SwitchCraft3 (official), 2023</li>
|
<li>"go use potatos or something" - SwitchCraft3 (official), 2023</li>
|
||||||
<li>"a lot of backup time is spent during potatos" - Lemmmy, 2022</li>
|
<li>"a lot of backup time is spent during potatos" - Lemmmy, 2022</li>
|
||||||
|
<li>"we would need 176000 comparators to store potatOS" - piguman3, 2023</li>
|
||||||
<li>"potatOS is as steady as a rock" - BlackDragon, 2021</li>
|
<li>"potatOS is as steady as a rock" - BlackDragon, 2021</li>
|
||||||
<li>"PotatOS would be a nice religion" - piguman3, 2022</li>
|
<li>"PotatOS would be a nice religion" - piguman3, 2022</li>
|
||||||
<li>"It has caused multiple issues to staff of multiple CC servers." - Wojbie, 2023</li>
|
<li>"It has caused multiple issues to staff of multiple CC servers." - Wojbie, 2023</li>
|
||||||
|
Loading…
Reference in New Issue
Block a user