Compare commits

..

114 Commits
skye ... master

Author SHA1 Message Date
1c647c76fe fix timeout accounting in the scheduler 2023-11-12 23:31:56 +10:00
5f56c74e6f cleanup RPC library, make it possible to detect the client hostname when running RPC requests 2023-11-12 23:31:09 +10:00
0aa574ea7a early work on OpenOS bootstrapping 2023-10-13 11:52:01 +10:00
c52a0ee2ac start of an install document 2023-10-08 12:17:17 +10:00
fe8107bf7f sort the fs.mounts table 2023-10-08 11:42:49 +10:00
35f3c6f89e implement the dirstat extension in exportfs, greatly improving perf for OpenOS clients 2023-10-08 11:41:44 +10:00
638322c157 skip mounting any already mounted filesystems 2023-10-07 11:35:15 +10:00
91d9ce33c8 remove a debug log that spammed syslog 2023-10-01 13:41:34 +10:00
0935d15864 update rtfsboot module to pull in the correct library paths 2023-10-01 13:41:01 +10:00
39e9b1765d immediately unsave the cursor position, as a neat trick 2023-09-29 15:35:29 +10:00
f83b8c999c fix lines longer than the screen causing weird scrolling artifacts, and avoid superfluous history entries 2023-09-29 10:15:25 +10:00
88bce6cd96 add fs_ prefix to exported filesystems to match OpenOS version, for cross-compatibility 2023-09-20 00:01:44 +10:00
a7708b9e47 address RPC loop issues as described in https://github.com/ShadowKatStudios/OC-Minitel/pull/40 plus implement ACLs like the OpenOS version of the RPC library 2023-09-20 00:01:21 +10:00
026f2524e6 this one works, I tested it 2023-08-07 13:26:42 +10:00
e20123b527 clean up print tabbing 2023-08-07 13:26:20 +10:00
efdb01328f I don't know why it was looking at package.loaded but it isn't now. 2023-08-07 13:25:57 +10:00
a05e19b545 I'm stupid sometimes 2023-08-07 12:56:59 +10:00
00d4472f91 make fsmanager's mount function externally accessible 2023-08-07 12:54:25 +10:00
82decfabf9 pkgman updates to simplify distribution with different kernels 2023-08-05 16:22:59 +10:00
ae41cc48dd fix tabs 2023-08-05 15:15:03 +10:00
429c9e2aa7 fix support for booting from rtfs on tape 2023-08-05 10:06:05 +10:00
ff7ec50a94 add some simple minification filters to preproc, which can shrink the kernel significantly 2023-08-04 20:32:42 +10:00
ad99c438b3 I keep forgetting that's there >.> 2023-08-04 20:31:22 +10:00
2e021ff6d5 remove old rc.load implementation 2023-08-04 20:29:09 +10:00
e9e824b42d remove unused include 2023-08-04 20:28:53 +10:00
c0f8b9b900 attempt to load pkgfs if there's a package archive directory 2023-08-04 20:28:26 +10:00
a33476cf00 fix edge case where io.open(whatever):read() returns nil 2023-08-04 20:24:18 +10:00
c2bbd7d2ca update to match latest diskpart 2023-08-03 12:15:03 +10:00
2edbb42aa4 have pkgman create the directories it needs rather than fail for cryptic reasons 2023-08-01 20:48:19 +10:00
7849fca4a0 stamp the kernel variant onto the version string 2023-08-01 20:44:14 +10:00
ae634bc52e turns out this was already packaged. whoops. 2023-08-01 18:26:14 +10:00
904fdce527 moved fserv to its own package 2023-08-01 18:21:08 +10:00
e85b948f4d add module for booting from rtfs volumes 2023-08-01 18:02:14 +10:00
30db695c4e oops forgot this one 2023-08-01 18:01:22 +10:00
ef2c01b1d4 refactor build system to allow pulling libraries from packages, assuming directories are set up correctly 2023-08-01 17:58:27 +10:00
eb98acc232 and another one. oops. 2023-08-01 15:58:16 +10:00
3d6017f87e clean up some less than ideal commits 2023-08-01 15:55:16 +10:00
86149d7e85 steal gamax92's vcomponent library, which apparently works without modification 2023-07-31 08:14:21 +10:00
36a73b892a implement component.get for convenience 2023-07-31 08:11:24 +10:00
7fa61e115e cleanup and sanity checks 2023-07-30 17:35:00 +10:00
87f8bd2149 shell-related cleanup and fixes. 2023-07-30 15:04:41 +10:00
b88134b70e force position of cursor before drawing lines in ced vi. might be slower, but stops line wrapping breaking things. 2023-07-30 15:03:58 +10:00
265681c61c left a debug syslog in there. oops. 2023-07-28 22:04:43 +10:00
395ade429a assorted filesystem-related improvements 2023-07-28 21:34:56 +10:00
6d96109217 misc accounting features 2023-07-04 18:25:11 +10:00
f311063a42 cleanup my mess 2023-06-07 00:19:09 +10:00
eb95f9715e virtual terminal support with vtansi and getty, for machines with VRAM available 2023-06-07 00:16:56 +10:00
2df878f3e8 standalone executable support. again. 2023-06-07 00:16:26 +10:00
a533748d55 ^C now terminates line mode input with an error 2023-06-07 00:12:50 +10:00
ddc2445104 actually handle append file modes 2023-06-07 00:12:15 +10:00
1ef6d5db96 updated fsmanager to new service semantics 2021-06-22 16:39:10 +10:00
6f1b673bec made liblz16 happier on OpenOS when decompressing smaller files 2021-05-26 17:46:17 +10:00
2707ecc155 added significantly larger file support to libmtar, as well as versioning support 2021-05-26 17:07:10 +10:00
8e84eb0c67 switched to using string.(un)pack in libmtar and liblz16, meaning it works on both big and little endian machines, and should be smaller and faster 2021-05-26 16:15:24 +10:00
c5f304380e one more try 2020-08-21 10:38:18 +10:00
0aaf4acd52 apply the right separator for concat 2020-08-21 10:37:19 +10:00
d15a841316 made ed.open use the full path for the file 2020-08-21 10:35:48 +10:00
d12ec38016 made vi not choke when opening a new file, as well as actually preserve the file name 2020-08-21 10:28:51 +10:00
0db31a2e27 added some more keyboard shortcuts to io.read linemode 2020-08-21 10:18:43 +10:00
8865768576 made libmtar cope with lower memory systems at the expense of speed 2020-07-13 00:58:19 +10:00
5db20adefd more standard package stuff, plus package.alias 2020-07-01 14:31:00 +10:00
d40ce731ef made rc enforce the new service semantics 2020-06-29 15:31:32 +10:00
417856ebd6 modify the two services people actually use to work with the new service semantics 2020-06-29 15:31:12 +10:00
216e0a15c6 removed os.spawnfile because nothing used it and it doesn't align with the system's design 2020-06-29 15:26:27 +10:00
5938f75f4c added a process_finished event and added syslog error reporting 2020-06-29 15:25:58 +10:00
be3d3c207f rewrote require() to support package.path and submodules 2020-06-25 17:19:50 +10:00
522d456433 add basic submodule support to require 2020-06-25 16:29:12 +10:00
5e9baee9fa stole some code from AmandaC to handle an undocumented condition with 404 errors returned by the internet card 2020-06-25 10:29:22 +10:00
4a5d9bcee2 better type annotation for genHeader 2020-06-25 09:39:31 +10:00
f0fb5ff776 removed unarchive from the mtar library, wasn't meant to be in there anyway 2020-06-25 09:33:22 +10:00
22bd6982d0 made cursor movement and newlines work properly when they hit screen borders 2020-06-21 21:31:19 +10:00
8bb123f198 added a component documentation searcher 2020-06-21 19:16:50 +10:00
3f82d96b8e buffer history now: can't go out of bounds, can go to an empty line once you go past the most recent 2020-06-21 18:26:10 +10:00
0421034ff7 added history support to buffer:read() in terminal mode, do provide feedback 2020-06-20 17:24:10 +10:00
e3069f94a3 make unionfs a more ... optional dependency of netutil 2020-06-11 16:18:32 +10:00
f9749ac181 more draw call optimisation 2020-06-11 14:03:54 +10:00
642eb9adf1 reduced redraw to two draw calls, four colour calls, and one get call: draw line, get character, invert colours for cell 2020-06-11 13:34:59 +10:00
b4db6c7226 remove debugging syslog calls from buffer library, reduce draw calls for readline 2020-06-11 13:20:49 +10:00
f95124996c moved all readline analogs into the buffer module 2020-06-11 12:56:07 +10:00
1cc220d38e made io.input open buffers with mode t, for use with readline coming soonTM 2020-06-11 12:55:19 +10:00
87596c8834 add os.getTimeout() function to ask how long the scheduler waits between running processes 2020-06-11 12:46:00 +10:00
4e64a55169 cleaner default source list 2020-06-08 10:36:36 +10:00
322cb837bb add main repo as default 2020-06-08 10:33:57 +10:00
8ae4d7b57c made rc load and search pkgfs if a service isn't found 2020-06-07 23:35:24 +10:00
2fbee483b2 move actual preproc lib into folder rather than symlinking to the version in lib/, a better solution will be considered later 2020-06-07 23:29:26 +10:00
b82bb2a853 move libraries out of main dist and into PsychOSPackages 2020-06-07 23:24:04 +10:00
92084e8c90 remove vcomponent and vtunnel because they are now included in the PsychOSPackages repo 2020-06-07 22:44:46 +10:00
21f40b3f3c make pkgman remove system packages 2020-06-07 22:43:19 +10:00
9983acb267 clean up pkgman.upgrade 2020-06-06 20:24:15 +10:00
6a39fe1743 made rc search for services in the pkgfs 2020-06-06 19:50:28 +10:00
45c70cbaa6 made require search the pkgfs if available 2020-06-06 19:49:26 +10:00
89ab49faf6 made pkgman cleanly remove packages before updating them 2020-06-06 19:48:21 +10:00
f86f7d54ad add pkgfs.component.exists and do automounting of archives 2020-06-06 19:45:40 +10:00
4e3df481cc attempt to force the internet card to work (ha\!) 2020-06-06 19:44:21 +10:00
360bb88ac3 added the pkgman package manager library 2020-06-06 14:58:15 +10:00
c1fcfd652e fserv now sends the correct status messages for HTTP(S) proxying 2020-06-06 14:42:58 +10:00
8b4d0e4eb8 fixed file downloading with the HTTP(S) proxies in the download library 2020-06-06 14:42:30 +10:00
edbe787ea6 added AmandaC's download library, with some modifications - including HTTP(S) support 2020-06-06 14:24:54 +10:00
999d8e0387 rewrote the whole index thing and made it possible to remove packages from the pkgfs index 2020-06-06 13:35:59 +10:00
3266c66fc4 updated fserv to fit the new (and next) service system 2020-06-06 12:55:02 +10:00
3fed8a5985 fixed fs.copy
oops
2020-06-06 12:54:10 +10:00
f132c2349f made pkgfs auto-mount when loaded 2020-06-06 12:50:50 +10:00
97e559f26f added type annotations and function documentation to liblz16 and pkgfs 2020-06-05 23:07:06 +10:00
2ce2692aee updated the README to represent the current state of the system 2020-06-05 23:06:37 +10:00
fb0c740b39 add liblz16 and lzss for use with pkgfs, and eventually mtar probably 2020-06-05 22:54:51 +10:00
21d71b29ba detect absolute/relative paths in pkgfs 2020-06-05 22:45:31 +10:00
ffc6c8915a pkgfs now stores absolute paths to archive files, oops 2020-06-05 12:33:03 +10:00
0f1b324cc4 initial pkgfs work, seems functional 2020-06-05 12:10:48 +10:00
58c9a5492e added a fallback _OSVERSION string 2020-06-03 12:38:04 +10:00
bc5f24f1e6 bumped the version number because we selfhosting now 2020-06-03 09:56:41 +10:00
29cdcb1809 added the preproc library, meaning PsychOS is nominally self-hosting 2020-06-03 09:55:22 +10:00
7dda36fd1d Merge pull request 'clipboard: Make the clipboard service work under the new service sys' (#4) from Amanda/OC-PsychOS2:fix-clipboard-editor-cleanup into master
Looks good.
2020-05-27 02:21:05 +10:00
Amanda Cameron
5213835970 clipboard: Make the clipboard service work under the new service sys
This also adds a [.editorconfig](https://editorconfig.org/) and adds
apidoc.md to the gitignore.
2020-05-21 15:02:46 -04:00
124b39c96d Merge pull request 'Fix document generation on Windows (using Git Bash)' (#3) from Skye/OC-PsychOS2:git-windows-bash-fixes into master
Windows is infinitely cursed.

Greatly appreciated.
2020-05-14 14:11:23 +10:00
43 changed files with 1483 additions and 810 deletions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
root = true
[*.lua]
indent_style = space
indent_size = 1

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
*.cpio
*.af
apidoc.md
/target
/doc

82
INSTALL.md Normal file
View File

@ -0,0 +1,82 @@
# Installing PsychOS
## From OpenOS
### Requirements
In general:
- oppm
- mtar
For installing to an unmanaged drive or tape
- slicer
- partman
- rtfs
- boopu
There are two easy methods to get these packages.
1. With oppm, if available, you can run `oppm install mtar partman rtfs boopu`.
2. You can use the `obootstrap.lua` script to set up a temporary environment for installing PsychOS. This can even be used from the OpenOS installer disk.
```
# wget https://git.shadowkat.net/izaya/OC-PsychOS2/raw/branch/master/obootstrap.lua /tmp/obootstrap.lua
# /tmp/obootstrap.lua
```
### Preparing the target disk
#### Managed filesystem
Preparing a managed filesystem is extremely simple: attach it to your computer, and make sure there's nothing that you want to keep in the following locations on the filesystem:
- init.lua
- lib/
- service/
- doc/
- pkg/
- cfg/
#### Unmanaged drive or tape
##### Creating partitions
First, you'll need to find out how many sectors your target device has. `slicer <addr> show` will show you something like this:
```
# slicer 9f7 show
Drive 9f755736 - 1024KiB, 2048 sectors:
# Name Type Start Len End
```
The usable space on the disk is going to be two sectors less than the total size - sectors 2 through 2047, in this case, as you will want to leave space for the OSDI partition table at the start, and the MTPT partition table at the end.
First, we'll create the boot partition. 64KiB is the recommended size, though 48KiB may be enough. OC disks use 512 byte sectors, so that will work out to 128 sectors.
```
# slicer 9f7 add init.lua boot 2 128
Drive 9f755736 - 1024KiB, 2048 sectors:
# Name Type Start Len End
1: 9f755736 mtpt 0 0 -1
2: init.lua boot 2 128 129
```
Next, we need to create an rtfs partition, for the boot filesystem. This can use the rest of the space on the disk, but should be named `<first 8 characters of computer address>-boot`.
```
# slicer 9f7 add ffa5c282-boot rtfs 130 1918
Drive 9f755736 - 1024KiB, 2048 sectors:
# Name Type Start Len End
1: 9f755736 mtpt 0 0 -1
2: init.lua boot 2 128 129
3: ffa5c282-boot rtfs 130 1918 2047
```
Once you're all done, you can restart partman and it should recognise the new partitions.
```
# rc partman restart
# components part
partition 9f755736-a739-4f45-8c5c-35a66a7f5dbe/2
```
##### Formatting the filesystem
Next, we'll use the mkfs.rtfs utility to format the filesystem partition we just created. Do note that the order of components is not fixed, so using a shortened version can result in unreliable behavior, like, for example, formatting the boot partition.
```
# mkfs.rtfs 9f755736-a739-4f45-8c5c-35a66a7f5dbe/2 ffa5c282-boot
9f755736-a739-4f45-8c5c-35a66a7f5dbe/2
```
To make OpenOS mount the filesystem, the simplest way is to restart partman again, as described in the previous section.

View File

@ -6,23 +6,31 @@ A lightweight, multi-user operating system for OpenComputers
### The kernel
The kernel can be built using luapreproc:
The kernel is composed of a number of modules, found in the *module/* directory, as specified by a file in the *kcfg* directory, `base` by default. Which modules are included can be customised by changing the include statements in the kernel configuration file; copying it and customizing that is recommended, so you can *git pull* later without having to stash or reset your changes.
./luapreproc.lua module/init.lua kernel.lua
#### Unix-like systems
The kernel can be built using the preproc library and provided scripts:
lua build.lua kcfg/base.cfg kernel.lua
#### PsychOS
The kernel can be built from inside PsychOS using the preproc library, assuming you have the kernel source available:
preproc("kcfg/base.cfg","kernel.lua")
### The boot filesystem
A boot filesystem contains several things:
- The kernel, as init.lua
- The exec/ directory, as this contains all executables
- The lib/ directory, containing libraries
- The service/ directory, containing system services
- The exec/ directory, containing single-shot executable files
This has been automated in the form of build.sh, pending a real makefile.
## Documentation
To generate function documentation, run:
./finddesc.lua module/* lib/* > apidoc.md
Documentation is generated as the system is built with build.sh; a set of markdown files will be placed into *doc/*, as well as an all-in-one *apidoc.md*. If pandoc is installed, an *apidoc.pdf* will also be generated.

4
build.lua Normal file
View File

@ -0,0 +1,4 @@
local preproc = require "preproc"
preproc.minify = true
preproc(...)

View File

@ -1,12 +1,13 @@
#!/bin/bash
LUA=${LUA:-lua}
KVAR=${1:-base}
rm -r target/*
mkdir -p target/doc &>/dev/null
$LUA luapreproc.lua module/init.lua target/init.lua
echo _OSVERSION=\"PsychOS 2.0a2-$(git rev-parse --short HEAD)\" > target/version.lua
$LUA build.lua kcfg/$KVAR.cfg target/init.lua
echo _OSVERSION=\"PsychOS 2.0a3-$(git rev-parse --short HEAD)-$KVAR\" > target/version.lua
cat target/version.lua target/init.lua > target/tinit.lua
mv target/tinit.lua target/init.lua
cp -r service/ lib/ cfg/ target/
cp -r service/ lib/ cfg/ exec/ target/
rm target/version.lua
rm -r doc/ &>/dev/null
$LUA finddesc.lua doc/ $(find lib/ module/ -type f|sort)

View File

@ -1 +0,0 @@
{enabled={"getty","minitel"}}

2
kcfg/base.cfg Normal file
View File

@ -0,0 +1,2 @@
--#include "module/base.lua"
--#include "module/init.lua"

3
kcfg/full.cfg Normal file
View File

@ -0,0 +1,3 @@
--#include "module/base.lua"
--#include "module/rtfsboot.lua"
--#include "module/init.lua"

View File

@ -4,6 +4,7 @@ doc.searchers = {}
doc.tctab = {
["string"] = 31,
["table"] = 32,
["userdata"] = 32,
["number"] = 33,
["boolean"] = 35,
["function"] = 36
@ -111,6 +112,31 @@ function doc.searchers.cdoc(topic) -- string -- string string -- Searches for do
end
end
end
function doc.searchers.component(name)
local dt = {}
local addr = component.list(name)()
if addr then
for fname,_ in pairs(component.methods(addr)) do
fd = {args={},outtypes={},atypes={}}
local ds = component.doc(addr,fname)
local ins, outs, desc = ds:match("%((.-)%)") or "", ds:match("%):(.*)%-%-") or "", ds:match("%-%-(.+)") or ""
for arg in ins:gmatch("[^,%s%[%]]+") do
local an,at = arg:match("(.-):(.+)")
at = at:match("(.-)=") or at
fd.args[#fd.args+1] = {an,at}
fd.atypes[an] = at
end
for out in outs:gmatch("[^,]+") do
fd.outtypes[#fd.outtypes+1] = out:match("^%s*(.-)%s*$")
end
fd.description = desc or ""
dt[name.."."..fname] = fd
end
else
return
end
return doc.format(dt)
end
function doc.docs(topic) -- string -- boolean -- Displays the documentation for *topic*, returning true, or errors. Also callable as just doc().
local lib = os.getenv("LIB") or "/boot/lib"

133
lib/download.lua Normal file
View File

@ -0,0 +1,133 @@
local net = require "minitel"
local dl = {}
dl.protos = {}
-- Stolen from the old exec/fget
local function parseURL(url)
local proto,addr = url:match("(.-)://(.+)")
addr = addr or url
local hp, path = addr:match("(.-)(/.*)")
hp, path = hp or addr, path or "/"
local host, port = hp:match("(.+):(.+)")
host = host or hp
return proto, host, port, path
end
function dl.protos.fget(host, optPort, path, dest) -- string string string number -- boolean -- Downloads path from host (on optPort or 70), printing the directory listing, or saving the file to dest.
local socket = assert(net.open(host, optPort or 70))
socket:write(string.format("t%s\n", path))
local status
repeat
coroutine.yield()
status = socket:read(1)
until status ~= ""
if status == "d" then
io.write("Directory Listing:\n")
local tmp = ""
repeat
coroutine.yield()
tmp = socket:read("*a")
io.write(tmp)
until socket.state == "closed" and tmp == ""
return true
elseif status == "y" then
if not dest then
error("Must provide local path to save remote files.")
end
io.write(string.format("Saving %s to %s...\n", path, dest))
local f = assert(io.open(dest, "wb"))
local tmp = ""
repeat
coroutine.yield()
tmp = socket:read("*a")
f:write(tmp)
until socket.state == "closed" and tmp == ""
f:close()
print("Done.")
return true
else
local err, tmp = "", ""
repeat
coroutine.yield()
tmp = socket:read("*a")
err = err .. tmp
until socket.state == "closed" and tmp == ""
error(string.format("Got error from remote host: %s", err))
end
end
function dl.protos.http(host, optPort, path, dest, url) -- string string string number -- boolean -- Downloads *url* to *dest* via the internet card, if available.
if not component.list("internet")() then
local proto,host,sPort,path = parseURL(url)
local proxy = os.getenv(proto:upper().."_PROXY")
if not proxy and fs.exists("/boot/cfg/"..proto.."_proxy") then
local f = io.open("/boot/cfg/"..proto.."_proxy","rb")
proxy = f:read()
f:close()
end
if not proxy then error("No internet card or HTTP(S) proxy available") end
print("Internet card unavailable, falling back to proxy "..proxy)
if optPort then host=string.format("%s:%i",host,optPort) end
return dl.wget(string.format("%s/%s%s",proxy,host,path),dest)
end
if not dest then
error("Must provide local path to save remote files.")
end
local R,r=component.invoke(component.list("internet")(),"request",url)
if not R then error(r) end
repeat
ok, err = R.finishConnect()
if type(ok) ~= "boolean" then
if err == url then
return 404, "This is a bug in OC, I think?"
end
return -1, err or "Connection Error"
end
coroutine.yield()
until ok
local code, messsage, headers
repeat
coroutine.yield()
code, message, headers = R.response()
until code or message
if code > 299 or code < 200 then
return false, code, message
end
local f=io.open(dest,"wb")
if not f then error("Unable to open file "..dest) end
io.write(string.format("Saving %s to %s...\n", url, dest))
repeat
coroutine.yield()
ns = R.read()
f:write(ns or "")
until not ns
f:close()
print("Done.")
return true
end
dl.protos.https = dl.protos.http
function dl.wget(remotePath, dest) -- string string -- -- Downloads from remote *remotePath* to *dest*
local proto, host, sPort, path = parseURL(remotePath)
if dl.protos[proto] then
local port
if sPort then
port = tonumber(sPort)
end
dl.protos[proto](host, port, path, dest, remotePath)
else
error("Unsupported protocol: " .. tostring(proto))
end
end
return setmetatable(dl,{__call=function(_,path,dest) return dl.wget(path,dest) end})

View File

@ -112,6 +112,7 @@ function ed.newBuffer()
end
function ed.open(buffer)
local bpath = buffer
if ed.buffers[buffer] then
buffer = ed.buffers[buffer]
end
@ -120,7 +121,9 @@ function ed.open(buffer)
nb:load(buffer)
buffer = nb
end
if type(buffer) ~= "table" then buffer = ed.newBuffer() buffer[1] = "" end
if type(buffer) ~= "table" then buffer = ed.newBuffer() end
buffer[1] = buffer[1] or ""
buffer.path = buffer.path or "/"..((bpath:sub(1,1) == "/" and table.concat(fs.segments(path),"/")) or table.concat(fs.segments(os.getenv("PWD").."/"..bpath),"/"))
return buffer
end
@ -160,10 +163,10 @@ function ed.visual(buffer)
if cx ~= ox or cy ~= oy or force then
io.write("\27[2J\27[H")
for i = cy, cy+my do
print(string.format("\27[31m%4i \27[0m%s",i,(buffer[i] or "\27[36m~"):sub(cx,cx+mx-6)))
io.write(string.format("\27[1;%iH\27[31m%4i \27[0m%s",(i-cy+1),i,(buffer[i] or "\27[36m~"):sub(cx,cx+mx-6)))
end
elseif mode == "i" then
print(string.format("\27[2K\27[999D\27[31m%4i \27[0m%s",buffer.y,(buffer[buffer.y] or "\27[36m~"):sub(cx,cx+mx-6)))
io.write(string.format("\27[2K\27[999D\27[31m%4i \27[0m%s",buffer.y,(buffer[buffer.y] or "\27[36m~"):sub(cx,cx+mx-6)))
end
io.write(string.format("\27[1;%iH\27[0;36;%im\27[2K[%s] ced visual: %i,%i/%i, %iK free %i",my+2,(mode == "c" and 7) or 0, mode, buffer.x, buffer.y, #buffer, computer.freeMemory()//1024,mult))
io.write(string.format("\27[%i;%iH\27[0m",buffer.x+6-cx,buffer.y-cy+1))

View File

@ -1,63 +0,0 @@
local imt = {}
imt.ttypes = {}
imt.ttypes.string=1
imt.ttypes.number=2
imt.ftypes = {tostring,tonumber}
function imt.to16bn(n)
return string.char(math.floor(n/256))..string.char(math.floor(n%256))
end
function imt.from16bn(s)
return (string.byte(s,1,1)*256)+string.byte(s,2,2)
end
function imt.encodePacket(...)
local tArgs = {...}
local packet = string.char(#tArgs%256)
for _,segment in ipairs(tArgs) do
local segtype = type(segment)
segment = tostring(segment)
packet = packet .. imt.to16bn(segment:len()) .. string.char(imt.ttypes[segtype]) .. tostring(segment)
end
packet = imt.to16bn(packet:len()) .. packet
return packet
end
function imt.decodePacket(s)
local function getfirst(n)
local ns = s:sub(1,n)
s=s:sub(n+1)
return ns
end
if s:len() < 2 then return false end
local plen = imt.from16bn(getfirst(2))
local segments = {}
if s:len() < plen then return false end
local nsegments = string.byte(getfirst(1))
--print(tostring(plen).." bytes, "..tostring(nsegments).." segments")
for i = 1, nsegments do
local seglen = imt.from16bn(getfirst(2))
local segtype = imt.ftypes[string.byte(getfirst(1))]
local segment = segtype(getfirst(seglen))
--print(seglen,segtype,segment,type(segment))
segments[#segments+1] = segment
end
return table.unpack(segments)
end
function imt.getRemainder(s)
local function getfirst(n)
local ns = s:sub(1,n)
s=s:sub(n+1)
return ns
end
local plen = imt.from16bn(getfirst(2))
if s:len() > plen then
getfirst(plen)
return s
end
return nil
end
return imt

53
lib/liblz16.lua Normal file
View File

@ -0,0 +1,53 @@
local lz = require "lzss"
local buffer = require "buffer"
lz16 = {}
local function readBuffer(fi)
local stream = {}
if fi:read(4) ~= "lz16" then
return false, "not an lz16 archive"
end
function stream.read()
local len = string.unpack(">I2", fi:read(2) or "\0\0")
if len < 1 then
return nil
end
if os.sleep then os.sleep(0) else coroutine.yield() end
return lz.decompress(fi:read(len))
end
function stream.close()
fi:close()
end
return buffer.new("rb",stream)
end
local function writeBuffer(fo)
local stream = {}
function stream:write(data)
local cblock = lz.compress(data)
fo:write(string.pack(">I2", cblock:len()) .. cblock)
return cblock:len()+2
end
function stream.close()
fo:close()
end
fo:write("lz16") -- write header
return buffer.new("wb",stream)
end
function lz16.buffer(stream) -- table -- table -- Wrap a stream to read or write LZ16.
if stream.mode.w then
return writeBuffer(stream)
end
return readBuffer(stream)
end
function lz16.open(fname, mode) -- string string -- table -- Open file *fname* to read or write LZ16-compressed data depending on *mode*
local f = io.open(fname, mode)
if not f then return false end
f.mode.b = true
return lz16.buffer(f)
end
return lz16

49
lib/libmtar.lua Normal file
View File

@ -0,0 +1,49 @@
local mtar = {}
local versions = {}
versions[0] = {nlf = ">I2", flf = ">I2"} -- original version format
versions[1] = {nlf = ">I2", flf = ">I8"} -- extended file size format
mtar.versions = versions
local function cleanPath(path)
local pt = {}
for segment in path:gmatch("[^/]+") do
if segment == ".." then
pt[#pt] = nil
elseif segment ~= "." then
pt[#pt+1] = segment
end
end
return table.concat(pt,"/")
end
function mtar.genHeader(fname,len,version) -- string number -- string -- generate a header for file *fname* when provided with file length *len*
version=version or 1
return string.format("\255\255%s%s%s%s", string.char(version), string.pack(versions[version].nlf,fname:len()), fname, string.pack(versions[version].flf,len))
end
function mtar.iter(stream) -- table -- function -- Given buffer *stream*, returns an iterator suitable for use with *for* that returns, for each iteration, the file name, a function to read from the file, and the length of the file.
local remain = 0
local function read(n)
local rb = stream:read(math.min(n,remain)) or ""
remain = remain - rb:len()
return rb
end
return function()
while remain > 0 do
remain=remain-(#stream:read(math.min(remain,2048)) or "")
end
local version = 0
local nlen = string.unpack(">I2", stream:read(2) or "\0\0")
if nlen == 0 then
return
elseif nlen == 65535 then -- versioned header
version = string.byte(stream:read(1))
nlen = string.unpack(versions[version].nlf, stream:read(string.packsize(versions[version].nlf)))
end
local name = cleanPath(stream:read(nlen))
remain = string.unpack(versions[version].flf, stream:read(string.packsize(versions[version].flf)))
return name, read, remain
end
end
return mtar

123
lib/lzss.lua Normal file
View File

@ -0,0 +1,123 @@
--[[----------------------------------------------------------------------------
LZSS - encoder / decoder
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
--]]----------------------------------------------------------------------------
--------------------------------------------------------------------------------
local M = {}
local string, table = string, table
--------------------------------------------------------------------------------
local POS_BITS = 12
local LEN_BITS = 16 - POS_BITS
local POS_SIZE = 1 << POS_BITS
local LEN_SIZE = 1 << LEN_BITS
local LEN_MIN = 3
--------------------------------------------------------------------------------
function M.compress(input)
local offset, output = 1, {}
local window = ''
local function search()
for i = LEN_SIZE + LEN_MIN - 1, LEN_MIN, -1 do
local str = string.sub(input, offset, offset + i - 1)
local pos = string.find(window, str, 1, true)
if pos then
return pos, str
end
end
end
while offset <= #input do
local flags, buffer = 0, {}
for i = 0, 7 do
if offset <= #input then
local pos, str = search()
if pos and #str >= LEN_MIN then
local tmp = ((pos - 1) << LEN_BITS) | (#str - LEN_MIN)
buffer[#buffer + 1] = string.pack('>I2', tmp)
else
flags = flags | (1 << i)
str = string.sub(input, offset, offset)
buffer[#buffer + 1] = str
end
window = string.sub(window .. str, -POS_SIZE)
offset = offset + #str
else
break
end
end
if #buffer > 0 then
output[#output + 1] = string.char(flags)
output[#output + 1] = table.concat(buffer)
end
end
return table.concat(output)
end
--------------------------------------------------------------------------------
function M.decompress(input)
local offset, output = 1, {}
local window = ''
while offset <= #input do
local flags = string.byte(input, offset)
offset = offset + 1
for i = 1, 8 do
local str = nil
if (flags & 1) ~= 0 then
if offset <= #input then
str = string.sub(input, offset, offset)
offset = offset + 1
end
else
if offset + 1 <= #input then
local tmp = string.unpack('>I2', input, offset)
offset = offset + 2
local pos = (tmp >> LEN_BITS) + 1
local len = (tmp & (LEN_SIZE - 1)) + LEN_MIN
str = string.sub(window, pos, pos + len - 1)
end
end
flags = flags >> 1
if str then
output[#output + 1] = str
window = string.sub(window .. str, -POS_SIZE)
end
end
end
return table.concat(output)
end
return M

View File

@ -1,12 +1,11 @@
local computer = require "computer"
local minitel = require "minitel"
local event = require "event"
local ufs = require "unionfs"
local rpc = require "rpc"
local netutil = {}
function netutil.importfs(host,rpath,lpath) -- string string string -- boolean -- Import filesystem *rpath* from *host* and attach it to *lpath*.
local px = rpc.proxy(host,rpath.."_")
local px = rpc.proxy(host,"fs_"..rpath.."_")
function px.getLabel()
return host..":"..rpath
end
@ -16,10 +15,18 @@ end
function netutil.exportfs(path) -- string -- boolean -- Export the directory *path* over RPC.
local path = "/"..table.concat(fs.segments(path),"/")
local px = ufs.create(path)
local px = require("unionfs").create(path)
function px.dirstat(p)
local rt = {}
for k,v in ipairs(px.list(p)) do
local fp = p.."/"..v
rt[v] = {px.isDirectory(fp), px.size(fp), px.lastModified(fp)}
end
return rt
end
for k,v in pairs(px) do
rpc.register(path.."_"..k,v)
print(path.."_"..k)
rpc.register("fs_"..path.."_"..k,v)
print("fs_"..path.."_"..k)
end
return true
end

146
lib/pkgfs.lua Normal file
View File

@ -0,0 +1,146 @@
local mtar = require "libmtar"
local w, lz16 = pcall(require, "liblz16")
if not w then lz16 = nil end
pkgfs = {}
pkgfs.files = {}
local findex = {}
local handles = {}
local hc = 0
local function rfalse()
return false
end
local function rzero()
return 0
end
pkgfs.component = {seek = rfalse, makeDirectory = rfalse, write = rfalse, rename = rfalse, setlabel = rfalse, spaceUsed = rzero, spaceTotal = rzero, lastModified = rzero, address = "pkgfs"}
local function fopen(path,comp)
local f
if comp and lz16 then
f = lz16.open(path,"rb")
else
f = io.open(path,"rb")
end
return f
end
local function fnormalize(s)
return table.concat(fs.segments(s),"/")
end
function pkgfs.component.exists(path)
path = fnormalize(path)
return findex[path] and true
end
function pkgfs.component.list(path)
path = fnormalize(path).."/"
local ft,rt = {},{}
for k,v in pairs(findex) do
k="/"..k
if k:match(path.."([^/]+)/.+") then
ft[k:match(path.."([^/]+)/.+").."/"] = true
elseif k:match(path.."([^/]+)") then
ft[k:match(path.."([^/]+)")] = true
end
end
for k,v in pairs(ft) do
rt[#rt+1] = k
end
return rt
end
function pkgfs.component.isDirectory(path)
path = fnormalize(path).."/"
for k,v in pairs(findex) do
k="/"..k
if k:match(path.."([^/]+)/.+") then
return true
end
end
return false
end
function pkgfs.component.size(path)
path=fnormalize(path)
if not findex[path] then return false end
local f = fopen(findex[path][1], findex[path][2])
for fname, read, fsize in mtar.iter(f) do
if fname == path then
return fsize
end
end
return false
end
function pkgfs.component.open(path,mode)
path=fnormalize(path)
if mode:find("w") or mode:find("a") or not findex[path] then
return false
end
local f = fopen(findex[path][1],findex[path][2])
for fname,read,fsize in mtar.iter(f) do
if fname == path then
hc = hc + 1
handles[hc] = {read, f}
return hc
end
end
end
function pkgfs.component.read(handle, n)
if not handles[handle] then return false end
local rv = handles[handle][1](n)
if not rv then return nil end
if rv:len() < 1 then return nil end
return rv
end
function pkgfs.component.close(handle)
if not handles[handle] then return false end
handles[handle][2]:close()
handles[handle] = nil
return true
end
local function index()
findex = {}
for k,v in pairs(pkgfs.files) do
fname, comp = v[1], v[2]
if fname:sub(1,1) ~= "/" then
fname = "/"..fnormalize(os.getenv("PWD").."/"..fname)
end
local f = fopen(fname,comp)
if not f then error("unable to open file "..fname) end
for name, read, fsize in mtar.iter(f) do
findex[fnormalize(name)] = {fname,comp}
end
f:close()
end
return true
end
function pkgfs.add(fname,comp) -- string boolean -- boolean -- Add a package as specified in *fname* to the pkgfs component. If *comp* is true, read it as a LZ16-compressed package.
pkgfs.files[#pkgfs.files+1] = {fname,comp}
return index()
end
function pkgfs.remove(fname) -- string -- boolean -- Removes the package specified by *fname* from the pkgfs index.
for k,v in pairs(pkgfs.files) do
if v[1] == fname then
table.remove(pkgfs.files,k)
end
end
return index()
end
fs.makeDirectory("/pkg")
fs.mount("/pkg",pkgfs.component)
for _,file in ipairs(fs.list("/boot/pkg/")) do
if file:sub(-5) == ".mtar" then
pcall(pkgfs.add,"/boot/pkg/"..file)
elseif file:sub(-9) == ".mtar.lss" then
pcall(pkgfs.add,"/boot/pkg/"..file,true)
end
end
return pkgfs

193
lib/pkgman.lua Normal file
View File

@ -0,0 +1,193 @@
local serial = require "serialization"
local dl = require "download"
local mtar = require "libmtar"
local pkg = {}
pkg.cfgPath = "/boot/cfg/pkg"
pkg.sourcePath = pkg.cfgPath .. "/sources.cfg"
pkg.installedPath = pkg.cfgPath .. "/installed.cfg"
local w, lz16 = pcall(require,"liblz16")
if not w then lz16 = nil end
fs.makeDirectory("/boot/pkg")
fs.makeDirectory("/boot/cfg/pkg")
require "pkgfs"
local kver,kvar = _OSVERSION:match("(%x+)%-([^-]-)$")
kver,kvar = kver or "unknown", kvar or "base"
local function getSources()
local f = io.open(pkg.sourcePath,"rb")
if not f then return {main={path="https://oc.shadowkat.net/psychos/pkg",cache=true,name="main"}} end
local c = f:read("*a")
f:close()
return serial.unserialize(c)
end
local function saveSources(t)
fs.makeDirectory(pkg.cfgPath)
local f = io.open(pkg.sourcePath,"wb")
f:write(serial.serialize(t))
f:close()
end
local function getInstalled()
local f = io.open(pkg.installedPath,"rb")
if not f then return {psychos={version=kver},["kernel-"..kvar]={version=kver}} end
local c = f:read("*a")
f:close()
return serial.unserialize(c)
end
local function saveInstalled(t)
fs.makeDirectory(pkg.cfgPath)
local f = io.open(pkg.installedPath,"wb")
if not f then return false end
f:write(serial.serialize(t))
f:close()
end
local function getRepoMeta(repo)
if not getSources()[repo].cache or not fs.exists("/boot/cfg/pkg/repo-"..repo..".cfg") then
dl(getSources()[repo].path.."/packages.cfg","/boot/cfg/pkg/repo-"..repo..".cfg")
end
local f = io.open("/boot/cfg/pkg/repo-"..repo..".cfg","rb")
local rt = serial.unserialize(f:read("*a"))
f:close()
if not getSources()[repo].cache then
fs.remove("/boot/cfg/pkg/repo-"..repo..".cfg")
end
return rt
end
local function activatePackage(path,compressed)
require("pkgfs").add(path,compressed)
end
local function deactivatePackage(path)
require("pkgfs").remove(path)
end
local function fnormalize(s)
return table.concat(fs.segments(s),"/")
end
local function installSystemPackage(path,comp)
local f
if comp and lz16 then
f = lz16.open(path,"rb")
else
f = io.open(path,"rb")
end
for fname, read, size in mtar.iter(f) do
local opath = "/boot/"..fnormalize(fname)
print(opath..": "..tostring(size))
fs.makeDirectory(opath:match("(.+)/[^/]+"))
local of = io.open(opath,"wb")
if not of then error("unable to open "..opath.." for writing") end
local tmp
repeat
tmp = read(2048) or ""
of:write(tmp)
until not tmp or tmp:len() < 1
of:close()
end
return true
end
function pkg.addRepo(name,path,cache) -- string string boolean -- boolean -- Adds a repository, referred to as *name*, to the list of package sources, with the remote path *path*. If *cache* is set, keep a local copy of the repository index.
local sources = getSources()
sources[name] = {path=path,cache=cache,name=name}
saveSources(sources)
end
function pkg.delRepo(name) -- string -- boolean -- Removes a repository from the list of repositories.
local sources = getSources()
sources[name] = nil
saveSources(sources)
end
function pkg.update() -- Re-download cached repository indexes.
for repo,meta in pairs(getSources()) do
fs.remove("/boot/cfg/pkg/repo-"..repo..".cfg")
if meta.cache then
getRepoMeta(repo)
end
end
end
function pkg.list(filter,installed) -- string boolean -- -- Print a list of available packages matching *filter*, optionally filtering to only installed packages if *installed* is set.
filter = filter or ""
local pkglist = {}
for repo,_ in pairs(getSources()) do
for pkg,meta in pairs(getRepoMeta(repo)) do
if pkg:find(filter) or (pkg.meta or ""):find(filter) then
meta.repo = repo
pkglist[pkg] = meta
end
end
end
for k,v in pairs(pkglist) do
if v.system then io.write("\27[31m") end
print(string.format("%s/%s: %s\27[0m\n %s\n Authors: %s",v.repo,k,v.name,v.description,v.authors))
end
end
function pkg.getMeta(pkgname) -- string -- table -- Returns the metadata for a the package specified in *pkgname*.
print("Finding package "..pkgname)
for repo,info in pairs(getSources()) do
local pkg = getRepoMeta(repo)[pkgname]
if pkg then
print("Package "..pkgname.." located in repo "..repo.." at "..info.path)
pkg.repository = info
return pkg
end
end
end
function pkg.get(pkgname,auto) -- string boolean -- boolean -- Downloads and mounts a package, identified as *pkgname,* onto the pkgfs. Setting *auto* will flag the package as automatically installed; this is used for dependencies.
local pkginfo = pkg.getMeta(pkgname)
if not pkginfo then error("unable to locate package "..pkgname) end
pkginfo.manual = not auto
fs.makeDirectory("/boot/pkg")
for k,v in ipairs(pkginfo.dependencies or {}) do
if not getInstalled()[v] then
pkg.get(v,true)
end
end
dl(pkginfo.repository.path.."/"..pkginfo.filename,"/boot/pkg/"..pkginfo.filename)
local installed = getInstalled()
installed[pkgname] = pkginfo
saveInstalled(installed)
if pkginfo.system then
local rv = installSystemPackage("/boot/pkg/"..pkginfo.filename,pkginfo.compressed)
fs.remove("/boot/pkg/"..pkginfo.filename)
return rv
end
pcall(activatePackage,"/boot/pkg/"..pkginfo.filename,pkginfo.compressed)
return true
end
function pkg.upgrade(force) -- boolean -- -- Upgrades all packages on the system to the current version stored in the relevant repository. If *force* is set, re-download all packages.
pkg.update()
fs.makeDirectory("/boot/pkg")
local installed = getInstalled()
for repo,info in pairs(getSources()) do
for pkgname,pkginfo in pairs(getRepoMeta(repo)) do
if installed[pkgname] and pkginfo.version ~= installed[pkgname].version or force then
pkg.remove(pkgname)
pkg.get(pkgname,pkginfo.auto)
end
end
end
end
function pkg.remove(pkgname) -- string -- boolean -- Remove the package *pkgname* from the pkgfs and package directory.
local installed = getInstalled()
local pkginfo = installed[pkgname]
if not pkginfo then return true end
pcall(deactivatePackage,"/boot/pkg/"..pkginfo.filename)
fs.remove("/boot/pkg/"..pkginfo.filename)
if pkginfo.system then
for k,v in pairs(pkginfo.files) do
fs.remove(v)
end
end
installed[pkgname] = nil
saveInstalled(installed)
return true
end
return pkg

View File

@ -1,10 +1,11 @@
local serial = require "serialization"
local rc = {}
rc.paths = "/boot/service\n/pkg/service"
rc.pids = {}
local service = {}
local cfg = {}
cfg.enabled = {"getty","minitel"}
cfg.enabled = {"getty","minitel","fsmanager"}
local function loadConfig()
local f = io.open("/boot/cfg/rc.cfg","rb")
@ -23,16 +24,17 @@ local function saveConfig()
end
function rc.load(name,force) -- string boolean -- table -- Attempts to load service *name*, and if *force* is true, replaces the current instance.
if force then
rc.stop(name)
service[name] = nil
if not service[name] or force then
for d in rc.paths:gmatch("[^\n]+") do
if fs.exists(d.."/"..name..".lua") then
service[name] = runfile(d.."/"..name..".lua")
end
if service[name] then return true end
service[name] = setmetatable({},{__index=_G})
local f = io.open("/boot/service/"..name..".lua","rb")
local res = load(f:read("*a"),name,"t",service[name])()
f:close()
return res
end
end
if service[name] then
return service[name]
end
return false, "unable to load service "..name
end
function rc.stop(name,...) -- string -- boolean string -- Stops service *name*, supplying *...* to the stop function. Returns false and a reason if this fails.

View File

@ -5,19 +5,36 @@ local rpc = {}
_G.rpcf = {}
rpc.port = 111
local function setacl(self, fname, host)
self[fname] = self[fname] or {}
self[fname][host] = true
end
-- function rpc.allow(fn, host) -- string string -- -- Enable the allow list for function *fname* and add *host* to it.
-- function rpc.deny(fn, host) -- string string -- -- Enable the deny list for function *fname* and add *host* to it.
rpc.allow = setmetatable({},{__call=setacl})
rpc.deny = setmetatable({},{__call=setacl})
local function isPermitted(host,fn)
if rpc.allow[fn] then
return rpc.allow[fn][host] or false
end
if rpc.deny[fn] and rpc.deny[fn][host] then
return false
end
return true
end
local function rpcexec(_, from, port, data)
if port == rpc.port then
local rpcrq = serial.unserialize(data)
local rpcn, rpcid = table.remove(rpcrq,1), table.remove(rpcrq,1)
if rpcf[rpcn] then
local rt = {pcall(rpcf[rpcn],table.unpack(rpcrq))}
if rt[1] == true then
table.remove(rt,1)
end
minitel.send(from,port,serial.serialize({rpcid,table.unpack(rt)}))
else
end
if port ~= rpc.port then return false end
os.spawn(function()
local rpcrq = serial.unserialize(data) or {}
if rpcf[rpcrq[1]] and isPermitted(from,rpcrq[1]) then
os.setenv("RPC_CLIENT",from)
minitel.send(from,port,serial.serialize({rpcrq[2],pcall(rpcf[rpcrq[1]],table.unpack(rpcrq,3))}))
elseif type(rpcrq[2]) == "string" then
minitel.send(from,port,serial.serialize({rpcrq[2],false,"function unavailable"}))
end
end,"rpc worker for "..tostring(from))
end
function rpcf.list()
local rt = {}
@ -39,10 +56,13 @@ function rpc.call(hostname,fn,...) -- string string -- boolean -- Calls exported
local _, from, port, data = event.pull(30, "net_msg", hostname, rpc.port)
rt = serial.unserialize(tostring(data)) or {}
until (type(rt) == "table" and rt[1] == rv) or computer.uptime() > st + 30
if table.remove(rt,1) == rv then
return table.unpack(rt)
if rt[1] == rv then
if rt[2] then
return table.unpack(rt,3)
end
return false
error(rt[3])
end
error("timed out")
end
function rpc.proxy(hostname,filter) -- string string -- table -- Returns a component.proxy()-like table from the functions on *hostname* with names matching *filter*.
filter=(filter or "").."(.+)"
@ -69,11 +89,17 @@ function rpc.register(name,fn) -- string function -- -- Registers a function to
if not rpcrunning then
os.spawn(function()
while true do
rpcexec(event.pull("net_msg"))
pcall(rpcexec,event.pull("net_msg"))
end
end,"rpc daemon")
end
rpcf[name] = fn
end
function rpc.unregister(name) -- string -- -- Removes a function from the RPC function registry, clearing any ACL rules.
rpcf[name] = nil
rpc.allow[name] = nil
rpc.deny[name] = nil
end
return rpc

View File

@ -21,12 +21,16 @@ end
function shell.interactive()
local shenv = setmetatable({}, {__index=shindex})
local run = true
os.setenv("PATH",{"/boot/exec","/pkg/exec"})
function shenv.quit()
run = false
end
while run do
io.write(string.format("\27[32m%s:%s>\27[0m ",os.getenv("HOSTNAME") or "localhost",(os.getenv("PWD") or _VERSION)))
local input = io.read()
local w,input = pcall(io.read)
if not w then
print("\27[31m^C")
else
if input:sub(1,1) == "=" then
input = "return "..input:sub(2)
end
@ -34,7 +38,7 @@ function shell.interactive()
if not f then
print("\27[31m"..r)
else
local rt = {pcall(f)}
local rt = {xpcall(f,debug.traceback)}
local rs = table.remove(rt,1)
if not rs then io.write("\27[31m") end
for k,v in pairs(rt) do
@ -42,6 +46,7 @@ function shell.interactive()
end
end
end
end
end
return shell

View File

@ -83,7 +83,7 @@ function shutil.df() -- Prints free disk space.
local fstr = "%-"..tostring(ml).."s %5s %5s"
print("fs"..(" "):rep(ml-2).." size used")
for k,v in pairs(mt) do
local st, su = fs.spaceTotal(v), fs.spaceUsed(v)
local st, su = fs.spaceTotal("/"..v), fs.spaceUsed("/"..v)
print(string.format(fstr,v,wrapUnits(st),wrapUnits(su)))
end
end
@ -119,9 +119,38 @@ function shutil.free() -- Displays used and free memory.
print(string.format("%5s %5s %5s",wrapUnits(computer.totalMemory()),wrapUnits(computer.totalMemory()-computer.freeMemory()),wrapUnits(computer.freeMemory())))
end
function shutil.which(name)
local fpath
for _,dir in ipairs(os.getenv("PATH")) do
fpath = fpath or fs.exists(string.format("%s/%s.lua",dir,name)) and string.format("%s/%s.lua",dir,name) or fs.exists(string.format("%s/%s",dir,name)) and string.format("%s/%s",dir,name)
end
return fpath
end
shutil.cd = os.chdir
shutil.mkdir = fs.makeDirectory
shutil.cp = fs.copy
shutil.rm = fs.remove
return shutil
return setmetatable({}, {__index = function(t,k)
if shutil[k] then
return shutil[k]
end
local path = shutil.which(k)
if path then
local fn, e = loadfile(path)
if not fn then error(string.format("\n - %s",e)) end
return function(...)
local tA = {...}
local pid = os.spawn(function() return fn(table.unpack(tA)) end,path)
local ret = {require("event").pull("process_finished",pid)}
if not ret[3] then
error(string.format("\n - %s",ret[4]))
end
for i = 1, 3 do
table.remove(ret,1)
end
return table.unpack(ret)
end
end
end})

View File

@ -1,114 +0,0 @@
local unionfs = {}
local function normalise(path)
return table.concat(fs.segments(path),"/")
end
function unionfs.create(...) -- string -- table -- Returns a unionfs object of the directories specified in *...*.
local paths,fids,fc = {...}, {}, 0
for k,v in pairs(paths) do
paths[k] = "/"..normalise(v)
end
local proxy = {}
local function realpath(path)
path = path or ""
for k,v in pairs(paths) do
if fs.exists(v.."/"..path) then
return v.."/"..path
end
end
return paths[1].."/"..path
end
function proxy.setLabel()
return false
end
function proxy.spaceUsed()
return fs.spaceUsed(paths[1])
end
function proxy.spaceTotal()
return fs.spaceTotal(paths[1])
end
function proxy.isReadOnly()
return fs.isReadOnly(paths[1])
end
function proxy.isDirectory(path)
return fs.isDirectory(realpath(path))
end
function proxy.lastModified(path)
return fs.lastModified(realpath(path))
end
function proxy.getLabel()
return fs.getLabel(paths[1])
end
function proxy.exists(path)
return fs.exists(realpath(path))
end
function proxy.remove(path)
return fs.remove(realpath(path))
end
function proxy.size(path)
return fs.size(realpath(path))
end
function proxy.list(path)
local nt,rt = {},{}
if #fs.segments(path) < 1 then
for k,v in pairs(paths) do
for l,m in ipairs(fs.list(v.."/"..path)) do
nt[m] = true
end
end
for k,v in pairs(nt) do
rt[#rt+1] = k
end
table.sort(rt)
return rt
else
return fs.list(realpath(path))
end
end
function proxy.open(path,mode)
local fh, r = fs.open(realpath(path),mode)
if not fh then return fh, r end
fids[fc] = fh
fc = fc + 1
return fc - 1
end
function proxy.close(fid)
if not fids[fid] then
return false, "file not open"
end
local rfh = fids[fid]
fids[fid] = nil
return rfh:close()
end
function proxy.write(fid,d)
if not fids[fid] then
return false, "file not open"
end
return fids[fid]:write(d)
end
function proxy.read(fid,d)
if not fids[fid] then
return false, "file not open"
end
local rb = fids[fid]:read(d)
if rb == "" then rb = nil end
return rb
end
function proxy.seek(fid,d)
if not fids[fid] then
return false, "file not open"
end
return fids[fid]:seek(d)
end
return proxy
end
return unionfs

View File

@ -1,197 +0,0 @@
local proxylist = {}
local proxyobjs = {}
local typelist = {}
local doclist = {}
local oproxy = component.proxy
function component.proxy(address)
checkArg(1,address,"string")
if proxyobjs[address] ~= nil then
return proxyobjs[address]
end
return oproxy(address)
end
local olist = component.list
function component.list(filter, exact)
checkArg(1,filter,"string","nil")
local result = {}
local data = {}
for k,v in olist(filter, exact) do
data[#data + 1] = k
data[#data + 1] = v
result[k] = v
end
for k,v in pairs(typelist) do
if filter == nil or (exact and v == filter) or (not exact and v:find(filter, nil, true)) then
data[#data + 1] = k
data[#data + 1] = v
result[k] = v
end
end
local place = 1
return setmetatable(result,
{__call=function()
local addr,type = data[place], data[place + 1]
place = place + 2
return addr, type
end}
)
end
local otype = component.type
function component.type(address)
checkArg(1,address,"string")
if typelist[address] ~= nil then
return typelist[address]
end
return otype(address)
end
local odoc = component.doc
function component.doc(address, method)
checkArg(1,address,"string")
checkArg(2,method,"string")
if proxylist[address] ~= nil then
if proxylist[address][method] == nil then
error("no such method",2)
end
if doclist[address] ~= nil then
return doclist[address][method]
end
return nil
end
return odoc(address, method)
end
local oslot = component.slot
function component.slot(address)
checkArg(1,address,"string")
if proxylist[address] ~= nil then
return -1 -- vcomponents do not exist in a slot
end
return oslot(address)
end
local omethods = component.methods
function component.methods(address)
checkArg(1,address,"string")
if proxylist[address] ~= nil then
local methods = {}
for k,v in pairs(proxylist[address]) do
if type(v) == "function" then
methods[k] = true -- All vcomponent methods are direct
end
end
return methods
end
return omethods(address)
end
local oinvoke = component.invoke
function component.invoke(address, method, ...)
checkArg(1,address,"string")
checkArg(2,method,"string")
if proxylist[address] ~= nil then
if proxylist[address][method] == nil then
error("no such method",2)
end
return proxylist[address][method](...)
end
return oinvoke(address, method, ...)
end
local ofields = component.fields
function component.fields(address)
checkArg(1,address,"string")
if proxylist[address] ~= nil then
return {} -- What even is this?
end
return ofields(address)
end
local componentCallback =
{
__call = function(self, ...) return proxylist[self.address][self.name](...) end,
__tostring = function(self) return (doclist[self.address] ~= nil and doclist[self.address][self.name] ~= nil) and doclist[self.address][self.name] or "function" end
}
local vcomponent = {}
function vcomponent.register(address, ctype, proxy, doc)
checkArg(1,address,"string")
checkArg(2,ctype,"string")
checkArg(3,proxy,"table")
if proxylist[address] ~= nil then
return nil, "component already at address"
elseif component.type(address) ~= nil then
return nil, "cannot register over real component"
end
proxy.address = address
proxy.type = ctype
local proxyobj = {}
for k,v in pairs(proxy) do
if type(v) == "function" then
proxyobj[k] = setmetatable({name=k,address=address},componentCallback)
else
proxyobj[k] = v
end
end
proxylist[address] = proxy
proxyobjs[address] = proxyobj
typelist[address] = ctype
doclist[address] = doc
computer.pushSignal("component_added",address,ctype)
return true
end
function vcomponent.unregister(address)
checkArg(1,address,"string")
if proxylist[address] == nil then
if component.type(address) ~= nil then
return nil, "cannot unregister real component"
else
return nil, "no component at address"
end
end
local thetype = typelist[address]
proxylist[address] = nil
proxyobjs[address] = nil
typelist[address] = nil
doclist[address] = nil
computer.pushSignal("component_removed",address,thetype)
return true
end
function vcomponent.list()
local list = {}
for k,v in pairs(proxylist) do
list[#list + 1] = {k,typelist[k],v}
end
return list
end
function vcomponent.resolve(address, componentType)
checkArg(1, address, "string")
checkArg(2, componentType, "string", "nil")
for k,v in pairs(typelist) do
if componentType == nil or v == componentType then
if k:sub(1, #address) == address then
return k
end
end
end
return nil, "no such component"
end
local r = math.random
function vcomponent.uuid()
return string.format("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
r(0,255),r(0,255),r(0,255),r(0,255),
r(0,255),r(0,255),
r(64,79),r(0,255),
r(128,191),r(0,255),
r(0,255),r(0,255),r(0,255),r(0,255),r(0,255),r(0,255))
end
return vcomponent

View File

@ -1,7 +1,35 @@
local vtansi = {}
function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
local keyboardIgnore = {}
vtansi.activeBuffers = {}
vtansi.sequences = {
[28] = "\n", -- newline
[200] = "\27[A", -- up
[203] = "\27[D", -- left
[205] = "\27[C", -- right
[208] = "\27[B", -- down
[201] = "\27[5~", -- page up
[209] = "\27[6~" -- page down
}
vtansi.keys = {}
vtansi.keys[0x38] = "lalt"
vtansi.keys[0xB8] = "ralt"
function vtansi.saveToBuffer(gpu,idx)
gpu.bitblt(idx, nil, nil, nil, nil, 0)
end
function vtansi.loadFromBuffer(gpu,idx)
gpu.bitblt(0, nil, nil, nil, nil, idx)
end
function vtansi.switchToBuffer(gpu,idx)
-- copy screen to the active buffer
vtansi.saveToBuffer(gpu,vtansi.activeBuffers[gpu.address])
-- copy the new buffer to the screen
vtansi.loadFromBuffer(gpu,idx)
vtansi.activeBuffers[gpu.address] = idx
end
function vtansi.vtemu(gpu,bn) -- table number -- function -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal, either allocating a new buffer or using *bn*.
local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
local mx, my = gpu.maxResolution()
local buffer = nil
local cx, cy = 1, 1
local pc = " "
local lc = ""
@ -12,8 +40,19 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp
local bg, fg = 0, 0xFFFFFF
-- setup
if gpu.getActiveBuffer then
buffer = bn or gpu.allocateBuffer(mx,my)
vtansi.activeBuffers[gpu.address] = vtansi.activeBuffers[gpu.address] or buffer
local oldActiveBuffer = vtansi.activeBuffers[gpu.address]
gpu.setActiveBuffer(buffer)
gpu.setResolution(mx,my)
gpu.fill(1,1,mx,my," ")
gpu.setActiveBuffer(oldActiveBuffer)
else
gpu.setResolution(mx,my)
gpu.fill(1,1,mx,my," ")
end
local function checkCursor()
if cx > mx and lw then
cx, cy = 1, cy+1
@ -28,6 +67,13 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp
end
local function termwrite(s)
if buffer then
if vtansi.activeBuffers[gpu.address] == buffer then
gpu.setActiveBuffer(0)
else
gpu.setActiveBuffer(buffer)
end
end
local wb = ""
local lb, ec = nil, nil
local function flushwb()
@ -50,8 +96,9 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp
if cc == "\n" then
flushwb()
cx,cy = 1, cy+1
checkCursor()
elseif cc == "\t" then
wb=wb..(" "):rep(8*((cx+9)//8))
wb=wb..(" "):rep((((cx//8)+1) * 8) - cx + 1)
elseif cc == "\27" then
flushwb()
mode = 1
@ -76,20 +123,31 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp
if cc == "H" then
cx, cy = math.min(mx,tA[1] or 1), math.min(my,tA[2] or 1)
elseif cc == "A" then
cy = cy - (tA[1] or 1)
for i = 1, (tA[1] or 1) do
cy = cy - 1
checkCursor()
end
elseif cc == "B" then
cy = cy + (tA[1] or 1)
for i = 1, (tA[1] or 1) do
cy = cy + 1
checkCursor()
end
elseif cc == "C" then
cx = cx + (tA[1] or 1)
for i = 1, (tA[1] or 1) do
cx = cx + 1
checkCursor()
end
elseif cc == "D" then
cx = cx - (tA[1] or 1)
for i = 1, (tA[1] or 1) do
cx = cx - 1
checkCursor()
end
elseif cc == "s" then
sx, sy = cx, cy
elseif cc == "u" then
cx, cy = sx, sy
elseif cc == "n" and tA[1] == 6 then
rs = string.format("%s\27[%d;%dR",rs,cx,cy)
dprint(string.format("reporting %d;%d as current cursor position",cx,cy))
elseif cc == "K" and tA[1] == 1 then
gpu.fill(1,cy,cx,1," ")
elseif cc == "K" and tA[1] == 2 then
@ -106,7 +164,7 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp
elseif cc == "m" then
for _,num in ipairs(tA) do
if num == 0 then
fg,bg,ec,lb = 0xFFFFFF,0,true,true
fg,bg,ec,lb = 0xFFFFFF,0,false,true
elseif num == 7 then
local nfg,nbg = bg, fg
fg, bg = nfg, nbg
@ -139,48 +197,65 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp
return rs, lb, ec
end
return termwrite
return termwrite, buffer
end
function vtansi.vtsession(gpua,scra) -- string string -- table -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions.
function vtansi.vtsession(gpua,scra,bn) -- string string number -- function function function -- creates a process to handle the GPU and screen address combination *gpua*/*scra*, optionally using buffer number *bn* specifically. Returns read, write and "close" functions.
local modifiers = {}
local gpu = component.proxy(gpua)
gpu.bind(scra)
local write = vtansi.vtemu(gpu)
-- gpu.bind(scra)
local write, bn = vtansi.vtemu(gpu,bn)
local kba = {}
for k,v in ipairs(component.invoke(scra,"getKeyboards")) do
kba[v]=true
end
local buf, lbuf, echo = "", true, true
os.spawn(function() dprint(pcall(function()
local buf, lbuf, echo = "", false, false
os.spawn(function()
while true do
local ty,ka,ch = coroutine.yield()
if ty == "key_down" and kba[ka] then
if ch == 13 then ch = 10 end
if ch == 8 and lbuf then
if buf:len() > 0 then
if echo then write("\8 \8") end
buf = buf:sub(1,-2)
local ty,ka,ch,kc = coroutine.yield()
if kba[ka] and keyboardIgnore[ka] == bn then
keyboardIgnore[ka] = nil
end
elseif ch > 0 then
if echo then write(string.char(ch)) end
buf=buf..string.char(ch)
if kba[ka] and vtansi.keys[kc] then
modifiers[vtansi.keys[kc]] = ty == "key_down"
end
if ty == "key_down" and kba[ka] and (bn == nil or vtansi.activeBuffers[gpua] == bn) then
if bn and ty == "key_down" and kba[ka] and ch == 46 and kc == 52 and (modifiers.lalt or modifiers.ralt) then
-- next buffer
local allBuffers = gpu.buffers()
for k,v in ipairs(allBuffers) do
if v == vtansi.activeBuffers[gpu.address] and allBuffers[k+1] and not keyboardIgnore[ka] then
keyboardIgnore[ka] = bn
vtansi.switchToBuffer(gpu,allBuffers[k+1])
end
end
elseif bn and ty == "key_down" and kba[ka] and ch == 44 and kc == 51 and (modifiers.lalt or modifiers.ralt) then
-- previous buffer
local allBuffers = gpu.buffers()
for k,v in ipairs(allBuffers) do
if v == vtansi.activeBuffers[gpu.address] and allBuffers[k-1] and not keyboardIgnore[ka] then
keyboardIgnore[ka] = bn
vtansi.switchToBuffer(gpu,allBuffers[k-1])
end
end)) end,string.format("ttyd[%s:%s]",gpua:sub(1,8),scra:sub(1,8)))
local function bread(n)
local r
if lbuf then
while not buf:find("\n") do
coroutine.yield()
end
local n = buf:find("\n")
r, buf = buf:sub(1,n), buf:sub(n+1)
else
r = buf
buf = ""
coroutine.yield()
local outs
if ch > 0 then
outs = string.char(ch)
end
outs = vtansi.sequences[kc] or outs
if outs then
if echo then write(outs) end
buf=buf..outs
end
end
end
end
end,string.format("ttyd[%s:%s/%i]",gpua:sub(1,8),scra:sub(1,8),tonumber(bn) or 0))
local function bread(n)
coroutine.yield()
local r = buf
buf = ""
return r
end
local function bwrite(d)

10
module/base.lua Normal file
View File

@ -0,0 +1,10 @@
--#include "module/syslog.lua"
--#include "module/sched.lua"
--#include "module/buffer.lua"
--#include "module/osutil.lua"
--#include "module/fs.lua"
--#include "module/io.lua"
--#include "module/devfs.lua"
--#include "module/devfs/syslog.lua"
--#include "module/component-get.lua"
--#include "module/loadfile.lua"

View File

@ -234,6 +234,7 @@ function buffer:read(...)
end
local function readLine(chop)
if not self.mode.t then
local start = 1
while true do
local l = self.bufferRead:find("\n", start, true)
@ -255,6 +256,93 @@ function buffer:read(...)
end
end
end
else
-- this whole thing is a house of cards. good luck.
io.write("\27[s\27[8m\27[6n")
if not (self.mx or self.my) then
io.write("\27[9999;9999H\27[6n\27[u")
end
local pos, buffer, hIndex, sx, sy = 1, "", 0
self.history = self.history or {}
local function redraw()
-- scroll until the buffer will fit on the screen
while sx and sy and self.mx and self.my and #buffer > (self.mx * ((self.my - sy) + 1)) - sx do
sy = sy - 1
io.write("\27[9999;9999H ")
io.write(string.format("\27[2K\27[%i;%iH\27[s", sx, sy))
end
io.write(string.format("\27[u%s \27[u\27[%iC",buffer,(#buffer-pos)+1))
end
while true do
char = readBytesOrChars(1)
if char == "\27" then
if readBytesOrChars(1) == "[" then
local args = {""}
repeat
char = readBytesOrChars(1)
if char:match("%d") then
args[#args] = args[#args]..char
else
args[#args] = tonumber(args[#args])
args[#args+1] = ""
end
until not char:match("[%d;]")
if char == "C" then -- right
if pos > 1 then
pos = pos - 1
end
elseif char == "D" then -- left
if pos <= #buffer then
pos = pos + 1
end
elseif char == "A" then -- up
hIndex = hIndex + 1
io.write("\27[u"..(" "):rep(buffer:len()+1))
buffer = self.history[1+#self.history-hIndex] or buffer
pos = 1
elseif char == "B" then -- down
hIndex = hIndex - 1
io.write("\27[u"..(" "):rep(buffer:len()+1))
if hIndex == 0 then
hIndex = hIndex - 1
buffer = ""
end
buffer = self.history[1+#self.history-hIndex] or buffer
pos = 1
elseif char == "R" then -- cursor position report
self.mx, self.my = sx and math.max(self.mx or 0, args[1]) or self.mx, sy and math.max(self.my or 0, args[2]) or self.my
sx, sy = sx or args[1], sy or args[2]
end
hIndex = math.max(math.min(hIndex,#self.history),0)
end
elseif char == "\8" then -- backspace
if #buffer > 0 and pos <= #buffer then
buffer = buffer:sub(1, (#buffer - pos)) .. buffer:sub((#buffer - pos) + 2)
end
elseif char == "\3" then -- ^C, error
error("terminated")
elseif char == "\1" then -- ^A, go to start of line
pos = buffer:len()+1
elseif char == "\5" then -- ^E, go to end of line
pos = 1
elseif char == "\2" then -- ^B, back one word
local nc = buffer:reverse():find(" ",pos+1)
pos = nc or #buffer+1
elseif char == "\6" then -- ^F, forward one word
local nc = buffer:find(" ",math.max(#buffer-pos+3,0))
pos = (nc and #buffer-nc+2) or 1
elseif char == "\13" or char == "\10" or char == "\n" then -- return / newline
io.write("\n")
self.history[#self.history+1] = buffer ~= "" and buffer ~= self.history[#self.history] and buffer or nil
if #self.history > (self.maxhistory or 16) then table.remove(self.history,1) end
if chop then buffer = buffer .. "\n" end
return buffer
else
buffer = buffer:sub(1, (#buffer - pos) + 1) .. char .. buffer:sub((#buffer - pos) + 2)
end
redraw()
end
end
end
local function readAll()

8
module/component-get.lua Normal file
View File

@ -0,0 +1,8 @@
function component.get(addr, ctype)
for c in component.list(ctype, true) do
if c:sub(1, addr:len()) == addr then
return c
end
end
return nil, "no such component"
end

View File

@ -54,7 +54,7 @@ function fs.open(path,mode) -- string string -- table -- Opens file *path* with
if mode:find("r") then
fobj.read = fread
end
if mode:find("w") then
if mode:find("w") or mode:find("a") then
fobj.write = fwrite
end
return fobj
@ -68,7 +68,11 @@ function fs.copy(from,to) -- string string -- boolean -- copies a file from *fro
if not of or not df then
return false
end
df:write(of:read("*a"))
local tmp
repeat
tmp = of:read(2048)
df:write(tmp or "")
until not tmp
df:close()
of:close()
return true
@ -103,6 +107,7 @@ function fs.mounts() -- -- table -- Returns a table containing the mount points
for k,v in pairs(fsmounts) do
rt[#rt+1] = k,v.address or "unknown"
end
table.sort(rt)
return rt
end
@ -112,12 +117,12 @@ function fs.address(path) -- string -- string -- Returns the address of the file
end
function fs.type(path) -- string -- string -- Returns the component type of the filesystem at a given path, if applicable
local fsi,_ = fs.resolve(path)
return fsmounts[fsi].type or "filesystem"
return fsmounts[fsi].fstype or fsmounts[fsi].type or "filesystem"
end
fsmounts["/"] = component.proxy(computer.tmpAddress())
fs.makeDirectory("temp")
if computer.getBootAddress then
if computer.getBootAddress and component.type(computer.getBootAddress()) == "filesystem" then
fs.makeDirectory("boot")
fs.mount("boot",component.proxy(computer.getBootAddress()))
end

View File

@ -1,12 +1,4 @@
--#include "module/syslog.lua"
--#include "module/sched.lua"
--#include "module/buffer.lua"
--#include "module/osutil.lua"
--#include "module/fs.lua"
--#include "module/io.lua"
--#include "module/devfs.lua"
--#include "module/devfs/syslog.lua"
--#include "module/loadfile.lua"
_OSVERSION=_OSVERSION or "PsychOS 2"
os.spawn(function()
os.setenv("PWD","/boot")
@ -20,6 +12,9 @@ os.spawn(function()
end
os.setenv("HOSTNAME",hostname)
syslog(string.format("Hostname set to %s",hostname))
if fs.exists("/boot/pkg") then
pcall(require,"pkgfs")
end
local pids = {}
local rc = require "rc"
for k,v in pairs(rc.cfg.enabled) do

View File

@ -8,7 +8,7 @@ end
function io.input(fd) -- table -- table -- Sets the default input stream to *fd* if provided, either as a buffer as a path. Returns the default input stream.
if type(fd) == "string" then
fd=io.open(fd,"rb")
fd=io.open(fd,"rbt")
end
if fd then
os.setenv("STDIN",fd)
@ -32,8 +32,9 @@ function io.write(...) -- Writes its arguments to the default output stream.
io.output():write(...)
end
function print(...) -- Writes each argument to the default output stream, separated by newlines.
function print(...) -- Writes each argument to the default output stream, separated by space.
for k,v in ipairs({...}) do
io.write(tostring(v).."\n")
io.write((k>1 and "\t" or "")..tostring(v))
end
io.write("\n")
end

View File

@ -7,20 +7,20 @@ end
function runfile(p,...) -- string -- -- runs file *p* with arbitrary arguments in the current thread
return loadfile(p)(...)
end
function os.spawnfile(p,n,...) -- string string -- number -- spawns a new process from file *p* with name *n*, with arguments following *n*.
local tA = {...}
return os.spawn(function() local res={pcall(loadfile(p), table.unpack(tA))} computer.pushSignal("process_finished", os.pid(), table.unpack(res)) dprint(table.concat(res)) end,n or p)
end
_G.package = {}
package.loaded = {computer=computer,component=component,fs=fs,buffer=buffer}
package.path="/boot/lib/?.lua;/pkg/lib/?.lua;/boot/lib/?/init.lua;/pkg/lib/?/init.lua;./?;./?.lua;./?/init.lua"
package.loaded = {buffer=buffer, component=component, computer=computer, coroutine=coroutine, fs=fs, math=math, os=os, package=package, string=string, table=table}
package.alias = {filesystem="fs"}
function require(f,force) -- string boolean -- table -- searches for a library with name *f* and returns what the library returns, if possible. if *force* is set, loads the library even if it is cached
f=package.alias[f] or f
if not package.loaded[f] or force then
local lib = os.getenv("LIB") or "/boot/lib"
for d in lib:gmatch("[^\n]+") do
if fs.exists(d.."/"..f) then
package.loaded[f] = runfile(d.."/"..f)
elseif fs.exists(d.."/"..f..".lua") then
package.loaded[f] = runfile(d.."/"..f..".lua")
local ln = f:gsub("%.","/")
for d in package.path:gmatch("[^;]+") do
local p = d:gsub("%?",ln)
if fs.exists(p) and not fs.isDirectory(p) then
package.loaded[f] = runfile(p)
break
end
end
end

20
module/rtfsboot.lua Normal file
View File

@ -0,0 +1,20 @@
--#includepkglib "diskpart" "lib/diskpart.lua" "diskpart"
--#includepkglib "rtfs" "lib/fs/rtfs/init.lua" "fs.rtfs"
--#includepkglib "rtfs" "lib/fs/rtfs/v1.lua" "fs.rtfs.v1"
do
local a = computer.getBootAddress()
if component.type(a) == "drive" or component.type(a) == "tape_drive" then
local diskpart = require "diskpart"
for k,v in ipairs(diskpart.getPartitions(a)) do
if v[2] == "rtfs" and v[1] == computer.address():sub(1,8) .. "-boot" then
syslog("Partition with suitable name detected, attempting to mount...")
local rtfs = require "fs.rtfs"
fs.makeDirectory("boot")
local m = rtfs.mount(diskpart.proxyPartition(a,k))
m.address = string.format("%s/%i/rtfs",a,k)
fs.mount("boot",m)
break
end
end
end
end

View File

@ -1,11 +1,20 @@
do
local tTasks,nPid,nTimeout,cPid = {},1,0.25,0 -- table of tasks, next process ID, event timeout, current PID
local tTasks,nPid,nTimeout,cPid = {},1,0.25,0 -- table of tasks, next process ID, default event timeout, current PID
function os.spawn(f,n) -- function string -- number -- creates a process from function *f* with name *n*
tTasks[nPid] = {
c=coroutine.create(f), -- actual coroutine
c=coroutine.create(function()
local rt = {pcall(f)}
if not rt[1] then
syslog(rt[2])
end
computer.pushSignal("process_finished",os.pid(),table.unpack(rt))
end), -- actual coroutine
n=n, -- process name
p=nPid, -- process PID
P=cPid, -- parent PID
t=0, -- CPU time
T=0, -- total uptime
E=nTimeout, -- event timeout
e={} -- environment variables
}
if tTasks[cPid] then
@ -32,16 +41,21 @@ end
function os.taskInfo(pid) -- number -- table -- returns info on process *pid* as a table with name and parent values
pid = pid or os.pid()
if not tTasks[pid] then return false end
return {name=tTasks[pid].n,parent=tTasks[pid].P}
return {name=tTasks[pid].n,parent=tTasks[pid].P,cputime=tTasks[pid].t,iotime=tTasks[pid].T,timeout=tTasks[pid].E}
end
function os.sched() -- the actual scheduler function
os.sched = nil
local sTimeout = nTimeout
while #tTasks > 0 do
local tEv = {computer.pullSignal(nTimeout)}
local tEv = {computer.pullSignal(sTimeout)}
sTimeout = nTimeout
for k,v in pairs(tTasks) do
if coroutine.status(v.c) ~= "dead" then
cPid = k
local sT, sC = os.clock(), computer.uptime()
coroutine.resume(v.c,table.unpack(tEv))
v.t, v.T = v.t + os.clock() - sT, v.T + computer.uptime() - sC
sTimeout=math.min(sTimeout, v.E)
else
tTasks[k] = nil
end
@ -58,11 +72,9 @@ function os.getenv(k) -- gets a process' *k* environment variable
return tTasks[cPid].e[k]
end
end
function os.setTimeout(n)
if type(n) == "number" and n >= 0 then
nTimeout = n
function os.setTimeout(n,pid)
assert(type(n) == "number" and n >= 0)
tTasks[pid or cPid].E = n
return true
end
return false
end
end

31
obootstrap.lua Normal file
View File

@ -0,0 +1,31 @@
local fs = require "filesystem"
local wdir = "/tmp/psbootstrap"
local dlfiles = {
["/lib/libmtar.lua"] = "https://git.shadowkat.net/izaya/OC-misc/raw/branch/master/mtar/libmtar.lua",
["/lib/fs/rtfs/init.lua"] = "https://git.shadowkat.net/izaya/PsychOSPackages/raw/branch/master/rtfs/lib/fs/rtfs/init.lua",
["/lib/fs/rtfs/v1.lua"] = "https://git.shadowkat.net/izaya/PsychOSPackages/raw/branch/master/rtfs/lib/fs/rtfs/v1.lua",
["/lib/diskpart.lua"] = "https://git.shadowkat.net/izaya/PsychOSPackages/raw/branch/master/diskpart/lib/diskpart.lua",
["/etc/rc.d/partman.lua"] = "https://git.shadowkat.net/izaya/OC-misc/raw/branch/master/partition/OpenOS/etc/rc.d/partman.lua",
["/bin/slicer.lua"] = "https://git.shadowkat.net/izaya/PsychOSPackages/raw/branch/master/slicer/exec/slicer.lua",
["/bin/boopu.lua"] = "https://git.shadowkat.net/izaya/PsychOSPackages/raw/branch/master/boopu/exec/boopu.lua",
}
local function run(cmd)
print(cmd)
os.execute(cmd)
end
print("Downloading and linking files...")
for k,v in pairs(dlfiles) do
local tpath = string.format("%s/%s", wdir, k)
local isdir = fs.isDirectory(fs.path(tpath)) or fs.makeDirectory(fs.path(tpath))
run(string.format("wget '%s' '%s'", v, tpath))
local lt = k
while not fs.isDirectory(fs.path(lt)) and not fs.isLink(fs.path(lt)) do
lt = fs.path(lt)
end
if fs.get(tpath) ~= fs.get(lt) then
run(string.format("ln '%s/%s' '%s'", wdir, lt, lt))
end
end

121
preproc.lua Normal file
View File

@ -0,0 +1,121 @@
local preproc = {}
preproc.directives = {}
function preproc.parsewords(line) -- string -- table -- Returns a table of words from the string *line*, parsing quotes and escapes.
local rt = {""}
local escaped, quoted = false, false
for c in line:gmatch(".") do
if escaped then
rt[#rt] = rt[#rt]..c
elseif c == '"' or c == "'" then
quoted = not quoted
elseif c == "\\" then
escaped = true
elseif c:match("%s") and not quoted and rt[#rt]:len() > 0 then
rt[#rt+1] = ""
else
rt[#rt] = rt[#rt]..c
end
end
return rt
end
function preproc.line(line) -- string -- -- Returns either a function - which can be called to get lines until it returns nil - or a string from processing *line* using preprocessor directives.
if line:match("^%-%-#") then
local directive, args = line:match("^%-%-#(%S+)%s(.+)")
print(directive,args)
local args = preproc.parsewords(args)
if preproc.directives[directive] then
return preproc.directives[directive](table.unpack(args))
else
error("unknown preprocessor directive: "..directive)
end
else
return line
end
end
function preproc.preproc(...) -- string -- string -- Returns the output from preprocessing the files listed in *...*.
local tA = {...}
local output = ""
for _,fname in ipairs(tA) do
local f,e = io.open(fname)
if not f then error("unable to open file "..fname..": "..e) end
for line in f:lines() do
local r = preproc.line(line)
if type(r) == "function" then
while true do
local rs = r()
if not rs then break end
output = output .. rs .. "\n"
end
else
output = output .. r .. "\n"
end
end
end
return output
end
preproc.directives.include = preproc.preproc
function preproc.directives.includelib(file, name) -- string string -- string -- Returns a preprocessed inlined library
return string.format("package.loaded['%s'] = (function()\n%s\nend)()", name, preproc.preproc(file))
end
function preproc.directives.includepkgfile(package, file)
if (_OSVERSION or ""):sub(1,7) == "PsychOS" then
return preproc.preproc(string.format("/pkg/%s", file))
else
for path in (os.getenv("PSYCHOSPACKAGES") or "../PsychOSPackages"):gmatch("[^:]+") do
local f = io.open(string.format("%s/%s/%s", path, package, file), "r")
if f then
f:close()
return preproc.preproc(string.format("%s/%s/%s", path, package, file))
end
end
end
error(string.format("unable to locate file %s from package %s", file, package))
end
function preproc.directives.includepkglib(package, file, name) -- string string -- string -- Returns a preprocessed inlined library
return string.format("package.loaded['%s'] = (function()\n%s\nend)()", name, preproc.directives.includepkgfile(package, file))
end
local minify = true
local minifyFilters = {
{"%-%-%[%[.-%]%]",""},
{"%-%-.-\n","\n"},
{"\n[ \t]+","\n"},
{"%s?%.%.%s?",".."},
{"%s?==%s?","=="},
{"%s?~=%s?","~="},
{"%s?>=%s?",">="},
{"%s?<=%s?","<="},
{"%s?>%s?",">"},
{"%s?<%s?","<"},
{"%s?=%s?","="},
{"%s?,%s?",","},
{",\n",","},
{"\n\n+","\n"},
{"[ \t]\n","\n"},
{"%{%s+","{"},
{"%s+%}","}"}
}
return setmetatable(preproc,{__call=function(_,...)
local tA = {...}
local out = table.remove(tA,#tA)
local f,e = io.open(out,"wb")
if not f then error("unable to open file "..out..": "..e) end
local out = preproc.preproc(table.unpack(tA))
if preproc.minify then
local olen = #out
for k,v in ipairs(minifyFilters) do
out = out:gsub(v[1],v[2])
end
print(olen, #out)
end
f:write(out)
f:close()
end})

View File

@ -1,11 +1,17 @@
local lc = computer.uptime()
_G.clip = ""
while true do
function start()
return os.spawn(function()
local lc
while true do
local eT = {coroutine.yield()}
if eT[1] == "clipboard" then
if computer.uptime() > lc + 5 then
_G.clip = ""
end
lc = computer.uptime()
_G.clip = _G.clip .. eT[3]
end
end
end)
end

View File

@ -1,95 +0,0 @@
local minitel = require "minitel"
local serial = require "serialization"
local cfg = {["path"]="/boot/srv/frequest",["port"]=70}
f=io.open("/boot/cfg/fserv.cfg","rb")
if f then
local ncfg = serial.unserialize(f:read("*a"))
f:close()
for k,v in pairs(ncfg) do
cfg[k] = v
end
end
local function fileHandler(socket,rtype,path)
syslog(string.format("[%s:%d] %s %s",socket.addr,socket.port,rtype,path),syslog.info,"fserv")
if rtype == "t" then
if fs.exists(path) and fs.isDirectory(path) then
socket:write("d")
for _,file in ipairs(fs.list(path)) do
socket:write(file.."\n")
end
elseif fs.exists(path) and not fs.isDirectory(path) then
local f,err = io.open(path,"rb")
if f then
socket:write("y")
while true do
local c = f:read(4096)
if not c or c == "" then break end
socket:write(c)
end
else
socket:write("fFailed to open file: "..err)
end
else
socket:write("nFile not found")
end
elseif rtype == "s" then
if fs.exists(path) then
local ftype = "f"
if fs.isDirectory(path) then
ftype = "d"
end
socket:write(string.format("y%s\n%d",ftype,fs.size(path)))
else
socket:write("nFile not found.")
end
else
socket:write("fUnknown request type")
end
end
local function httpHandler(socket,rtype,path)
local tPath = fs.segments(path)
local proto = table.remove(tPath,1)
local url = string.format("%s://%s",proto,table.concat(tPath,"/"))
local request = component.invoke(component.list("internet")(),"request",url)
repeat
coroutine.yield()
until request.finishConnect()
local code, message, headers = request.response()
if code < 200 or code > 299 then
socket:write(string.format("f%d\n%s",code,message))
else
local data = ""
repeat
coroutine.yield()
data = request.read()
if data then
socket:write(data)
end
until not data
end
end
local function socketHandler(socket)
return function()
local line = nil
repeat
coroutine.yield()
line = socket:read()
until line
local rtype, path = line:match("(.)(.+)")
if fs.segments(path)[1] == "http" or fs.segments(path)[1] == "https" then
httpHandler(socket,rtype,path)
else
path = (cfg.path .. "/" .. path:gsub("../","")):gsub("/+","/")
fileHandler(socket,rtype,path)
end
socket:close()
end
end
while true do
os.spawn(socketHandler(minitel.listen(70)),"fserv worker process")
end

View File

@ -1,26 +1,44 @@
local function mount(addr)
dest = component.invoke(addr,"getLabel") or "mnt/"..addr:sub(1,3)
dest = "/"..dest
local fsmanager = {}
fsmanager.filesystems = {}
local run = true
function fsmanager.mount(addr)
for k,v in ipairs(fs.mounts()) do
if fs.address(v) == addr then
return
end
end
dest = "/" .. (component.invoke(addr,"getLabel") or "mnt/"..addr:sub(1,3))
syslog("Mounting "..addr.." to "..dest)
fs.makeDirectory(dest)
local w,r = fs.mount(dest,component.proxy(addr))
if not w then
syslog("Failed to mount: "..r)
return false
end
end
for addr, _ in component.list("filesystem") do
mount(addr)
fsmanager.filesystems[addr] = dest
end
function start()
function fsmanager.start()
run = true
return os.spawn(function()
while true do
for addr, _ in component.list("filesystem") do
fsmanager.mount(addr)
end
while run do
local tE = {coroutine.yield()}
if tE[1] == "component_added" and tE[3] == "filesystem" then
mount(tE[2])
elseif tE[1] == "component_removed" and tE[3] == "filesystem" then
fs.umount("/mnt/"..tE[2]:sub(1,3))
fsmanager.mount(tE[2])
elseif tE[1] == "component_removed" and fsmanager.filesystems[tE[2]] and tE[3] == "filesystem" then
syslog("Unmounting "..tE[2].." from "..fsmanager.filesystems[tE[2]])
fs.umount(fsmanager.filesystems[tE[2]])
fsmanager.filesystems[tE[2]] = nil
end
end
end,"fsmanager")
end
function fsmanager.stop()
run = false
end
return fsmanager

View File

@ -37,21 +37,32 @@ local function spawnShell(fin,fout)
io.input(fin)
io.output(fout):setvbuf("no")
print(_OSVERSION.." - "..tostring(math.floor(computer.totalMemory()/1024)).."K RAM")
print((os.getenv("HOSTNAME") or "unknown") .. " on " .. fin)
return os.spawn(shell.interactive, "shell: "..tostring(fin))
end
local function allocate()
for k,v in pairs(gpus) do
dprint(k)
dprint("Setting up display "..k)
local sA = nextScreen(v[2])
if v[1] == false and sA then
local terminals = 1
local gpu = component.proxy(k)
gpu.bind(sA)
if gpu.buffers then
gpu.freeAllBuffers()
local mw, mh = gpu.maxResolution()
terminals = math.floor(gpu.freeMemory() / (mw * mh))
end
for i = 1, terminals do
local r,w = vtansi.vtsession(k,sA)
devfs.register("tty"..tostring(ttyn), function() return r,w,function() end end)
gpus[k][1] = true
screens[sA][1] = true
pids["tty"..tostring(ttyn)] = {-1}
ttyn = ttyn + 1
end
gpus[k][1] = true
screens[sA][1] = true
end
end
end
@ -59,14 +70,15 @@ function start()
basepid = os.spawn(function()
scan()
allocate()
dprint("screens ready")
dprint("Display setup complete.")
while true do
coroutine.yield()
for k,v in pairs(pids) do
if not os.taskInfo(v[1]) then
dprint("Spawning new shell for "..k)
pids[k][1] = spawnShell(v[2] or "/dev/"..k, v[3] or "/dev/"..k)
pids[k][2], pids[k][3] = pids[k][2] or io.input(), pids[k][3] or io.output()
pids[k][2], pids[k][3] = pids[k][2] or "/dev/"..k, pids[k][3] or "/dev/"..k
coroutine.yield()
end
end
end
@ -77,3 +89,4 @@ function stop()
os.kill(basepid)
basepid = nil
end
return {start=start,stop=stop}

View File

@ -271,3 +271,4 @@ function del_route(to)
cfg.sroutes[to] = nil
saveconfig()
end
return {start=start,stop=stop,set=set,set_route=set_route,del_route=del_route}

View File

@ -1,162 +0,0 @@
local vcomponent = require "vcomponent"
local serial = require "serialization"
local component = require "component"
local computer = require "computer"
local event = require "event"
local imt = require "interminitel"
local cfg = {}
cfg.peers = {}
cfg.rtimer = 5
cfg.katimer = 30
local listeners = {}
local proxies = {}
local function loadcfg()
local f = io.open("/boot/cfg/vtunnel.cfg","rb")
if not f then return false end
for k,v in pairs(serial.unserialize(f:read("*a")) or {}) do
cfg[k] = v
end
f:close()
end
local function savecfg()
local f = io.open("/boot/cfg/vtunnel.cfg","wb")
if not f then
print("Warning: unable to save configuration.")
return false
end
f:write(serial.serialize(cfg))
f:close()
end
local function createTunnel(host,port,addr,raddr)
local proxy = {address=addr,buffer=""}
function proxy.connect()
if proxy.socket then
proxy.socket.close()
end
proxy.socket = component.invoke(component.list("internet")(),"connect",host,port)
local st = computer.uptime()
repeat
coroutine.yield()
until proxy.socket.finishConnect() or computer.uptime() > st+5
end
function proxy.send(...)
rt = 0
while not proxy.socket.write(imt.encodePacket(...)) and rt < 10 do
proxy.connect()
rt = rt + 1
end
proxy.last = computer.uptime()
end
function proxy.read()
local rb, r
local rt = 0
while true do
rb,r = proxy.socket.read(4096)
if rb or rt > 10 then break end
if type(rb) == "nil" then
proxy.connect()
end
rt = rt + 1
end
proxy.buffer = proxy.buffer .. rb
while imt.decodePacket(proxy.buffer) do
computer.pushSignal("modem_message",addr,raddr,0,0,imt.decodePacket(proxy.buffer))
proxy.buffer = imt.getRemainder(proxy.buffer) or ""
end
if computer.uptime() > proxy.last + cfg.katimer then
proxy.socket.write("\0\1\0")
proxy.last = computer.uptime()
end
end
function proxy.getWakeMessage()
return false
end
proxy.setWakeMessage = proxy.getWakeMessage
function proxy.maxPacketSize()
return 8192
end
function proxy.getChannel()
return host..":"..tostring(port)
end
proxy.connect()
proxy.last = computer.uptime()
return proxy
end
vt = {}
function start()
loadcfg()
for k,v in pairs(cfg.peers) do
print(string.format("Connecting to %s:%d",v.host,v.port))
v.addr = v.addr or vcomponent.uuid()
v.raddr = v.raddr or vcomponent.uuid()
local px = createTunnel(v.host, v.port, v.addr, v.raddr)
vcomponent.register(v.addr, "tunnel", px)
proxies[v.addr] = px
end
for k,v in pairs(os.tasks()) do
if os.taskInfo(v).name:match("minitel") then
os.kill(v)
end
end
end
function vt.stop()
for k,v in pairs(proxies) do
vcomponent.unregister(k)
end
end
function vt.listpeers()
for k,v in pairs(cfg.peers) do
print(string.format("#%d (%s:%d)\n Local address: %s\n Remote address: %s",k,v.host,v.port,v.addr,v.raddr))
end
end
function vt.addpeer(host,port)
port = tonumber(port) or 4096
local t = {}
t.host = host
t.port = port
t.addr = vcomponent.uuid()
t.raddr = vcomponent.uuid()
cfg.peers[#cfg.peers+1] = t
print(string.format("Added peer #%d (%s:%d) to the configuration.\nRestart to apply changes.",#cfg.peers,host,port))
savecfg()
end
function vt.delpeer(n)
n=tonumber(n)
if not n then
print("delpeer requires a number, representing the peer number, as an argument.")
return false
end
local dp = table.remove(cfg.peers, n)
savecfg()
print(string.format("Removed peer %s:%d",dp.host, dp.port))
end
function vt.settimer(time)
time = tonumber(time)
if not time then
print("Timer must be a number.")
return false
end
cfg.rtime = time
savecfg()
end
vt.start = start
_G.libs.vtunnel = vt
start()
local last = computer.uptime()
while true do
local tE = {coroutine.yield()}
if computer.uptime() > last + cfg.rtimer then
for k,v in pairs(proxies) do
v.read()
end
last = computer.uptime()
end
end