forked from izaya/OC-PsychOS2
Compare commits
130 Commits
Author | SHA1 | Date | |
---|---|---|---|
1c647c76fe | |||
5f56c74e6f | |||
0aa574ea7a | |||
c52a0ee2ac | |||
fe8107bf7f | |||
35f3c6f89e | |||
638322c157 | |||
91d9ce33c8 | |||
0935d15864 | |||
39e9b1765d | |||
f83b8c999c | |||
88bce6cd96 | |||
a7708b9e47 | |||
026f2524e6 | |||
e20123b527 | |||
efdb01328f | |||
a05e19b545 | |||
00d4472f91 | |||
82decfabf9 | |||
ae41cc48dd | |||
429c9e2aa7 | |||
ff7ec50a94 | |||
ad99c438b3 | |||
2e021ff6d5 | |||
e9e824b42d | |||
c0f8b9b900 | |||
a33476cf00 | |||
c2bbd7d2ca | |||
2edbb42aa4 | |||
7849fca4a0 | |||
ae634bc52e | |||
904fdce527 | |||
e85b948f4d | |||
30db695c4e | |||
ef2c01b1d4 | |||
eb98acc232 | |||
3d6017f87e | |||
86149d7e85 | |||
36a73b892a | |||
7fa61e115e | |||
87f8bd2149 | |||
b88134b70e | |||
265681c61c | |||
395ade429a | |||
6d96109217 | |||
f311063a42 | |||
eb95f9715e | |||
2df878f3e8 | |||
a533748d55 | |||
ddc2445104 | |||
1ef6d5db96 | |||
6f1b673bec | |||
2707ecc155 | |||
8e84eb0c67 | |||
c5f304380e | |||
0aaf4acd52 | |||
d15a841316 | |||
d12ec38016 | |||
0db31a2e27 | |||
8865768576 | |||
5db20adefd | |||
d40ce731ef | |||
417856ebd6 | |||
216e0a15c6 | |||
5938f75f4c | |||
be3d3c207f | |||
522d456433 | |||
5e9baee9fa | |||
4a5d9bcee2 | |||
f0fb5ff776 | |||
22bd6982d0 | |||
8bb123f198 | |||
3f82d96b8e | |||
0421034ff7 | |||
e3069f94a3 | |||
f9749ac181 | |||
642eb9adf1 | |||
b4db6c7226 | |||
f95124996c | |||
1cc220d38e | |||
87596c8834 | |||
4e64a55169 | |||
322cb837bb | |||
8ae4d7b57c | |||
2fbee483b2 | |||
b82bb2a853 | |||
92084e8c90 | |||
21f40b3f3c | |||
9983acb267 | |||
6a39fe1743 | |||
45c70cbaa6 | |||
89ab49faf6 | |||
f86f7d54ad | |||
4e3df481cc | |||
360bb88ac3 | |||
c1fcfd652e | |||
8b4d0e4eb8 | |||
edbe787ea6 | |||
999d8e0387 | |||
3266c66fc4 | |||
3fed8a5985 | |||
f132c2349f | |||
97e559f26f | |||
2ce2692aee | |||
fb0c740b39 | |||
21d71b29ba | |||
ffc6c8915a | |||
0f1b324cc4 | |||
58c9a5492e | |||
bc5f24f1e6 | |||
29cdcb1809 | |||
7dda36fd1d | |||
|
5213835970 | ||
124b39c96d | |||
56af6d1ade | |||
b89ff14d5c | |||
7ddece288b | |||
cf373668a9 | |||
b3cfeb13ec | |||
b4ee8ed8a6 | |||
4f3bac551e | |||
a917016a66 | |||
d47a0748bd | |||
405ee6408d | |||
e09650276a | |||
daa2975fd6 | |||
d59cc53340 | |||
cae7c916ae | |||
b465aebbdb | |||
fd9e4ad88a |
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@ -0,0 +1,5 @@
|
||||
root = true
|
||||
|
||||
[*.lua]
|
||||
indent_style = space
|
||||
indent_size = 1
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
apidoc.md
|
||||
apidoc.html
|
||||
*.cpio
|
||||
*.af
|
||||
apidoc.md
|
||||
/target
|
||||
/doc
|
||||
|
82
INSTALL.md
Normal file
82
INSTALL.md
Normal 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.
|
20
README.md
20
README.md
@ -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
4
build.lua
Normal file
@ -0,0 +1,4 @@
|
||||
local preproc = require "preproc"
|
||||
|
||||
preproc.minify = true
|
||||
preproc(...)
|
17
build.sh
17
build.sh
@ -1,10 +1,15 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
LUA=${LUA:-lua}
|
||||
KVAR=${1:-base}
|
||||
rm -r target/*
|
||||
mkdir target &>/dev/null
|
||||
lua luapreproc.lua module/init.lua target/init.lua
|
||||
echo _OSVERSION=\"PsychOS 2.0a2-$(git rev-parse --short HEAD)\" > target/version.lua
|
||||
mkdir -p target/doc &>/dev/null
|
||||
$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/
|
||||
lua finddesc.lua $(find module/ -type f) $(find lib/ -type f) > apidoc.md
|
||||
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)
|
||||
$LUA gendoc.lua target/doc/kernel.dict $(find module/ -type f|sort)
|
||||
pandoc doc/apidoc.md docs-metadata.yml --template=template.tex -o doc/apidoc.pdf
|
||||
|
@ -1 +0,0 @@
|
||||
{enabled={"getty","minitel"}}
|
3
docs-metadata.yml
Normal file
3
docs-metadata.yml
Normal file
@ -0,0 +1,3 @@
|
||||
---
|
||||
title: 'PsychOS API Documentation'
|
||||
---
|
73
finddesc.lua
73
finddesc.lua
@ -1,27 +1,60 @@
|
||||
#!/usr/bin/env lua
|
||||
local doc = require "lib/doc"
|
||||
local tA = {...}
|
||||
local docfiles = {}
|
||||
for _,file in pairs(tA) do
|
||||
docfiles[file] = {}
|
||||
local f = io.open(file)
|
||||
local lines = {}
|
||||
for l in f:read("*a"):gmatch("[^\n]+") do
|
||||
if l:find("function") and not l:find("local") then
|
||||
lines[#lines+1] = l
|
||||
|
||||
local outpath = table.remove(tA,1)
|
||||
|
||||
print(outpath)
|
||||
|
||||
local function formatDocs(fd)
|
||||
local rs = ""
|
||||
for name,finfo in pairs(fd) do
|
||||
if rs:len() > 0 then
|
||||
rs = rs .. "\n\n"
|
||||
end
|
||||
local as = ""
|
||||
for k,v in pairs(finfo.args) do
|
||||
if k > 1 then
|
||||
as = as .. ", "
|
||||
end
|
||||
as = as .. v[1]
|
||||
if v[2] then
|
||||
as = as .. "^"..v[2].."^"
|
||||
end
|
||||
end
|
||||
for k,v in pairs(lines) do
|
||||
local name, args, desc = v:match("function%s+(.+)%s*%((.*)%)%s*%-%-%s*(.+)")
|
||||
if name and args and desc then
|
||||
docfiles[file][#docfiles[file]+1] = string.format("##%s(%s)\n%s",name,args,desc)
|
||||
local rt = ""
|
||||
for k,v in pairs(finfo.outtypes or {}) do
|
||||
if rt:len() > 0 then
|
||||
rt = rt .. ", "
|
||||
else
|
||||
rt = ": "
|
||||
end
|
||||
rt = rt .. v
|
||||
end
|
||||
rs = string.format("%s## %s(%s)%s\n%s",rs,name,as,rt,finfo.description)
|
||||
end
|
||||
return rs
|
||||
end
|
||||
|
||||
os.execute("sh -c 'mkdir -p "..outpath .. "'")
|
||||
if outpath:sub(#outpath) == "/" then outpath = outpath:sub(1, #outpath - 1) end
|
||||
local ad = io.open(outpath.."/apidoc.md","w")
|
||||
|
||||
for k,v in pairs(tA) do
|
||||
local fd = doc.parsefile(v)
|
||||
local ds = formatDocs(fd)
|
||||
print(string.format("%s: %i",v,ds:len()))
|
||||
if ds and ds:len() > 0 then
|
||||
os.execute("sh -c 'mkdir -p $(dirname \""..outpath.."/"..v.."\")'")
|
||||
local f = io.open(outpath.."/"..v:gsub("%.lua$",".md"),"wb")
|
||||
f:write(string.format("# %s\n\n",v))
|
||||
f:write(ds)
|
||||
f:write("\n\n")
|
||||
f:close()
|
||||
ad:write(string.format("# %s\n\n",v))
|
||||
ad:write(ds)
|
||||
ad:write("\n\n")
|
||||
end
|
||||
end
|
||||
|
||||
for k,v in pairs(docfiles) do
|
||||
if #v > 0 then
|
||||
print("#"..k)
|
||||
for l,m in pairs(v) do
|
||||
print(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
ad:close()
|
||||
|
51
gendoc.lua
Normal file
51
gendoc.lua
Normal file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env lua
|
||||
local doc = require "lib/doc"
|
||||
local ser = require "lib/serialization"
|
||||
local tA = {...}
|
||||
|
||||
local outpath = table.remove(tA,1)
|
||||
|
||||
print(outpath)
|
||||
|
||||
local function formatDocs(fd)
|
||||
local rs = ""
|
||||
for name,finfo in pairs(fd) do
|
||||
if rs:len() > 0 then
|
||||
rs = rs .. "\n\n"
|
||||
end
|
||||
local as = ""
|
||||
for k,v in pairs(finfo.args) do
|
||||
if k > 1 then
|
||||
as = as .. ", "
|
||||
end
|
||||
as = as .. v[1]
|
||||
if v[2] then
|
||||
as = as .. "^"..v[2].."^"
|
||||
end
|
||||
end
|
||||
local rt = ""
|
||||
for k,v in pairs(finfo.outtypes or {}) do
|
||||
if rt:len() > 0 then
|
||||
rt = rt .. ", "
|
||||
else
|
||||
rt = ": "
|
||||
end
|
||||
rt = rt .. v
|
||||
end
|
||||
rs = string.format("%s## %s(%s)%s\n%s",rs,name,as,rt,finfo.description)
|
||||
end
|
||||
return rs
|
||||
end
|
||||
|
||||
local ad = io.open(outpath,"wb")
|
||||
|
||||
for k,v in pairs(tA) do
|
||||
local fd = doc.parsefile(v)
|
||||
local ds = ser.serialize(fd)
|
||||
local tn = v:match("/(.+)$")
|
||||
if ds:len() > 3 then
|
||||
ad:write(tn.."\t"..ds:gsub("\n",""):gsub(", +",",").."\n")
|
||||
end
|
||||
end
|
||||
|
||||
ad:close()
|
2
kcfg/base.cfg
Normal file
2
kcfg/base.cfg
Normal file
@ -0,0 +1,2 @@
|
||||
--#include "module/base.lua"
|
||||
--#include "module/init.lua"
|
3
kcfg/full.cfg
Normal file
3
kcfg/full.cfg
Normal file
@ -0,0 +1,3 @@
|
||||
--#include "module/base.lua"
|
||||
--#include "module/rtfsboot.lua"
|
||||
--#include "module/init.lua"
|
154
lib/doc.lua
Normal file
154
lib/doc.lua
Normal file
@ -0,0 +1,154 @@
|
||||
local _,serial = pcall(require,"serialization")
|
||||
local doc = {}
|
||||
doc.searchers = {}
|
||||
doc.tctab = {
|
||||
["string"] = 31,
|
||||
["table"] = 32,
|
||||
["userdata"] = 32,
|
||||
["number"] = 33,
|
||||
["boolean"] = 35,
|
||||
["function"] = 36
|
||||
}
|
||||
|
||||
function doc.parsefile(path) -- string -- table -- parses file from *path* to return a documentation table
|
||||
local fdoc = {}
|
||||
local f = io.open(path)
|
||||
local lines = {}
|
||||
for l in f:read("*a"):gmatch("[^\n]+") do
|
||||
if l:find("function") and not l:find("local") then
|
||||
lines[#lines+1] = l
|
||||
end
|
||||
end
|
||||
for k,v in pairs(lines) do
|
||||
local name, args, desc = v:match("function%s+(.+)%s*%((.*)%)%s*%-%-%s*(.+)")
|
||||
if name and args and desc then
|
||||
local fd = {["description"]=desc or desc,["args"]={},["atypes"]={}}
|
||||
for word in args:gmatch("[^%s,]+") do
|
||||
fd.args[#fd.args+1] = {word}
|
||||
fd.atypes[word] = "unknown"
|
||||
end
|
||||
local argtypes, outtypes, description = desc:match("(.-)%-%-(.-)%-%-%s*(.+)")
|
||||
if argtypes and outtypes and description then
|
||||
local wc = 1
|
||||
for word in argtypes:gmatch("%S+") do
|
||||
fd.args[wc][2] = word
|
||||
fd.atypes[fd.args[wc][1]] = word
|
||||
wc = wc + 1
|
||||
end
|
||||
local wc = 1
|
||||
for word in outtypes:gmatch("%S+") do
|
||||
fd.outtypes = fd.outtypes or {}
|
||||
fd.outtypes[#fd.outtypes+1] = word
|
||||
end
|
||||
fd.description = description
|
||||
end
|
||||
fdoc[name] = fd
|
||||
end
|
||||
end
|
||||
return fdoc
|
||||
end
|
||||
|
||||
function doc.format(fdoc) -- table -- string -- returns VT100 formatted documentation from documentation table *fdoc*
|
||||
local rs = "" -- string to return
|
||||
for fname,finfo in pairs(fdoc) do
|
||||
if rs:len() > 0 then rs = rs .. "\n\n" end
|
||||
local as = "" -- string containing arguments for a given function, with colours for type
|
||||
for k,v in ipairs(finfo.args) do
|
||||
local c = doc.tctab[v[2]] or 0
|
||||
if k > 1 then
|
||||
as = as .. ", "
|
||||
end
|
||||
if v[2] then
|
||||
as = string.format("%s%s: \27[%im%s\27[0m",as,v[2],c,v[1])
|
||||
else
|
||||
as = string.format("%s\27[%im%s\27[0m",as,c,v[1])
|
||||
end
|
||||
end
|
||||
local rv = ""
|
||||
if finfo.outtypes then
|
||||
rv = ": "
|
||||
for k,v in ipairs(finfo.outtypes) do
|
||||
if k > 1 then
|
||||
rv = rv .. ", "
|
||||
end
|
||||
local c = doc.tctab[v] or 0
|
||||
rv = string.format("%s\27[%im%s\27[0m",rv,c,v)
|
||||
end
|
||||
end
|
||||
local nd = finfo.description
|
||||
for k,v in pairs(finfo.atypes) do
|
||||
local c = doc.tctab[v] or 7
|
||||
nd=nd:gsub("%*"..k.."%*","\27["..tostring(c).."m"..k.."\27[0m")
|
||||
end
|
||||
rs = string.format("%s\27[36m%s\27[0m(%s)%s\n%s",rs,fname,as,rv,nd)
|
||||
end
|
||||
return rs
|
||||
end
|
||||
|
||||
function doc.searchers.lib(name) -- string -- string string -- Tries to find a documentation from a library with *name*. Returns either a string of documentation, or false and a reason.
|
||||
local lib = os.getenv("LIB") or "/boot/lib"
|
||||
local dt
|
||||
for d in lib:gmatch("[^\n]+") do
|
||||
if fs.exists(d.."/"..name) then
|
||||
dt = doc.parsefile(d.."/"..name)
|
||||
elseif fs.exists(d.."/"..name..".lua") then
|
||||
dt = doc.parsefile(d.."/"..name..".lua")
|
||||
end
|
||||
end
|
||||
if not dt then return false, "unable to find documentation for "..tostring(name) end
|
||||
return doc.format(dt)
|
||||
end
|
||||
function doc.searchers.cdoc(topic) -- string -- string string -- Searches for documentation labelled as *topic* in .dict files under /boot/doc/
|
||||
if not serial then return end
|
||||
for k,v in ipairs(fs.list("/boot/doc")) do
|
||||
if v:sub(-5) == ".dict" then
|
||||
local f=io.open("/boot/doc/"..v,"rb")
|
||||
for line in f:lines() do
|
||||
local mname, docs = line:match("^(.-)\t(.+)$")
|
||||
if mname == topic or mname == topic..".lua" then
|
||||
return doc.format(serial.unserialize(docs))
|
||||
end
|
||||
end
|
||||
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"
|
||||
local dt
|
||||
for k,v in pairs(doc.searchers) do
|
||||
dt=v(topic)
|
||||
if dt then
|
||||
print(dt)
|
||||
return true
|
||||
end
|
||||
end
|
||||
error("unable to find documentation for "..tostring(name))
|
||||
end
|
||||
|
||||
return setmetatable(doc,{__call=function(_,topic) return doc.docs(topic) end})
|
133
lib/download.lua
Normal file
133
lib/download.lua
Normal 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})
|
@ -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))
|
||||
|
@ -1,5 +1,5 @@
|
||||
local event = {}
|
||||
function event.pull(t,...) -- return an event, optionally with timeout *t* and filter *...*.
|
||||
function event.pull(t,...) -- number -- -- return an event, optionally with timeout *t* and filter *...*.
|
||||
local tA = {...}
|
||||
if type(t) == "string" then
|
||||
table.insert(tA,1,t)
|
||||
@ -26,7 +26,7 @@ function event.pull(t,...) -- return an event, optionally with timeout *t* and f
|
||||
return nil
|
||||
end
|
||||
|
||||
function event.listen(e,f) -- run function *f* for every occurance of event *e*
|
||||
function event.listen(e,f) -- string function -- -- run function *f* for every occurance of event *e*
|
||||
os.spawn(function() while true do
|
||||
local tEv = {coroutine.yield()}
|
||||
if tEv[1] == e then
|
||||
@ -36,7 +36,7 @@ function event.listen(e,f) -- run function *f* for every occurance of event *e*
|
||||
end end,string.format("[%d] %s listener",os.pid(),e))
|
||||
end
|
||||
|
||||
function event.ignore(e,f) -- stop function *f* running for every occurance of event *e*
|
||||
function event.ignore(e,f) -- string function -- -- stop function *f* running for every occurance of event *e*
|
||||
computer.pushSignal("unlisten",e,tostring(f))
|
||||
end
|
||||
|
||||
|
@ -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
53
lib/liblz16.lua
Normal 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
49
lib/libmtar.lua
Normal 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
123
lib/lzss.lua
Normal 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
|
@ -12,7 +12,7 @@ net.minport = 32768
|
||||
net.maxport = 65535
|
||||
net.openports = {}
|
||||
|
||||
function net.genPacketID() -- generate a random 16-character string, for use in packet IDs
|
||||
function net.genPacketID() -- -- string -- generate a random 16-character string, for use in packet IDs
|
||||
local npID = ""
|
||||
for i = 1, 16 do
|
||||
npID = npID .. string.char(math.random(32,126))
|
||||
@ -20,11 +20,11 @@ function net.genPacketID() -- generate a random 16-character string, for use in
|
||||
return npID
|
||||
end
|
||||
|
||||
function net.usend(to,port,data,npID) -- send an unreliable packet to host *to* on port *port* with data *data*, optionally with the packet ID *npID*
|
||||
function net.usend(to,port,data,npID) -- string number string string -- -- send an unreliable packet to host *to* on port *port* with data *data*, optionally with the packet ID *npID*
|
||||
computer.pushSignal("net_send",0,to,port,data,npID)
|
||||
end
|
||||
|
||||
function net.rsend(to,port,data,block) -- send a reliable packet to host *to* on port *port* with data *data*, with *block* set to true to disable blocking
|
||||
function net.rsend(to,port,data,block) -- string number string boolean -- boolean -- send a reliable packet to host *to* on port *port* with data *data*, with *block* set to true to disable blocking
|
||||
local pid, stime = net.genPacketID(), computer.uptime() + net.streamdelay
|
||||
computer.pushSignal("net_send",1,to,port,data,pid)
|
||||
if block then return false end
|
||||
@ -37,7 +37,7 @@ end
|
||||
|
||||
-- ordered packet delivery, layer 4?
|
||||
|
||||
function net.send(to,port,ldata) -- send arbitrary data *ldata* reliably to host *to* on port *port*
|
||||
function net.send(to,port,ldata) -- string number string -- boolean -- send arbitrary data *ldata* reliably to host *to* on port *port*
|
||||
local tdata = {}
|
||||
if ldata:len() > net.mtu then
|
||||
for i = 1, ldata:len(), net.mtu do
|
||||
@ -112,7 +112,7 @@ local function socket(addr,port,sclose)
|
||||
return conn
|
||||
end
|
||||
|
||||
function net.open(to,port) -- open a socket to host *to* on port *port*
|
||||
function net.open(to,port) -- string number -- buffer -- open a socket to host *to* on port *port*
|
||||
if not net.rsend(to,port,"openstream") then return false, "no ack from host" end
|
||||
local st = computer.uptime()+net.streamdelay
|
||||
local est = false
|
||||
@ -139,7 +139,7 @@ function net.open(to,port) -- open a socket to host *to* on port *port*
|
||||
return socket(to,data,sclose)
|
||||
end
|
||||
|
||||
function net.listen(port) -- listen for connections on port *port* in a blocking manner
|
||||
function net.listen(port) -- number -- buffer -- listen for connections on port *port* in a blocking manner
|
||||
repeat
|
||||
_, from, rport, data = event.pull("net_msg")
|
||||
until rport == port and data == "openstream"
|
||||
@ -150,7 +150,7 @@ function net.listen(port) -- listen for connections on port *port* in a blocking
|
||||
return socket(from,nport,sclose)
|
||||
end
|
||||
|
||||
function net.flisten(port,listener) -- run function *listener* on a connection to *port*
|
||||
function net.flisten(port,listener) -- number function -- function -- run function *listener* on a connection to *port*
|
||||
local function helper(_,from,rport,data)
|
||||
if rport == port and data == "openstream" then
|
||||
local nport = math.random(net.minport,net.maxport)
|
||||
|
@ -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) -- import filesystem *rpath* from *host* and attach it to *lpath*
|
||||
local px = rpc.proxy(host,rpath.."_")
|
||||
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,"fs_"..rpath.."_")
|
||||
function px.getLabel()
|
||||
return host..":"..rpath
|
||||
end
|
||||
@ -14,17 +13,25 @@ function netutil.importfs(host,rpath,lpath) -- import filesystem *rpath* from *h
|
||||
return fs.mount(lpath,px)
|
||||
end
|
||||
|
||||
function netutil.exportfs(path) -- export the directory *path* over RPC
|
||||
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
|
||||
|
||||
function netutil.ping(addr,times,timeout, silent) -- Request acknowledgment from *addr*, waiting *timeout* seconds each try, and try *times* times. If *silent* is true, don't print status. Returns true if there was at least one successful ping, the number of successes, the number of failures, and the average round trip time.
|
||||
function netutil.ping(addr,times,timeout,silent) -- string number number boolean -- boolean number number number -- Request acknowledgment from *addr*, waiting *timeout* seconds each try, and try *times* times. If *silent* is true, don't print status. Returns true if there was at least one successful ping, the number of successes, the number of failures, and the average round trip time.
|
||||
local times, timeout = times or 5, timeout or 30
|
||||
local success, fail, time, avg = 0, 0, 0, 0
|
||||
for i = 1, times do
|
||||
@ -46,7 +53,7 @@ function netutil.ping(addr,times,timeout, silent) -- Request acknowledgment from
|
||||
return success > 0, success, fail, avg
|
||||
end
|
||||
|
||||
function netutil.nc(host,port)
|
||||
function netutil.nc(host,port) -- string number -- boolean -- Starts an interactive Minitel socket connection to *host* on *port*, primarily for remote login. Returns whether the attempt was successful.
|
||||
port = port or 22
|
||||
local socket = minitel.open(host,port)
|
||||
if not socket then return false end
|
||||
@ -66,6 +73,7 @@ function netutil.nc(host,port)
|
||||
socket:write(b.."\n")
|
||||
end
|
||||
until socket.state ~= "open"
|
||||
return true
|
||||
end
|
||||
|
||||
return netutil
|
||||
|
146
lib/pkgfs.lua
Normal file
146
lib/pkgfs.lua
Normal 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
193
lib/pkgman.lua
Normal 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
|
34
lib/rc.lua
34
lib/rc.lua
@ -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")
|
||||
@ -22,20 +23,21 @@ local function saveConfig()
|
||||
return true
|
||||
end
|
||||
|
||||
function rc.load(name,force)
|
||||
if force then
|
||||
rc.stop(name)
|
||||
service[name] = nil
|
||||
function rc.load(name,force) -- string boolean -- table -- Attempts to load service *name*, and if *force* is true, replaces the current instance.
|
||||
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,...)
|
||||
function rc.stop(name,...) -- string -- boolean string -- Stops service *name*, supplying *...* to the stop function. Returns false and a reason if this fails.
|
||||
if not service[name] then return false, "service not found" end
|
||||
if service[name].stop then
|
||||
service[name].stop(...)
|
||||
@ -47,7 +49,7 @@ function rc.stop(name,...)
|
||||
rc.pids[name] = nil
|
||||
end
|
||||
|
||||
function rc.start(name,...)
|
||||
function rc.start(name,...) -- string -- boolean string -- Stops service *name*, supplying *...* to the stop function. Returns false and a reason if this fails.
|
||||
rc.load(name)
|
||||
if not service[name] then return false, "service not found" end
|
||||
local rv = {service[name].start(...)}
|
||||
@ -56,19 +58,19 @@ function rc.start(name,...)
|
||||
end
|
||||
end
|
||||
|
||||
function rc.restart(name)
|
||||
function rc.restart(name) -- string -- -- Restarts service *name* using rc.stop and rc.start.
|
||||
rc.stop(name)
|
||||
rc.start(name)
|
||||
end
|
||||
|
||||
function rc.enable(name)
|
||||
function rc.enable(name) -- string -- -- Enables service *name* being started on startup.
|
||||
for k,v in pairs(cfg.enabled) do
|
||||
if v == name then return false end
|
||||
end
|
||||
cfg.enabled[#cfg.enabled+1] = name
|
||||
saveConfig()
|
||||
end
|
||||
function rc.disable(name)
|
||||
function rc.disable(name) -- string -- -- Disables service *name* being started on startup.
|
||||
local disabled = false
|
||||
for k,v in pairs(cfg.enabled) do
|
||||
if v == name then table.remove(cfg.enabled,k) disabled = true break end
|
||||
|
62
lib/rpc.lua
62
lib/rpc.lua
@ -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 = {}
|
||||
@ -27,7 +44,7 @@ function rpcf.list()
|
||||
return rt
|
||||
end
|
||||
|
||||
function rpc.call(hostname,fn,...)
|
||||
function rpc.call(hostname,fn,...) -- string string -- boolean -- Calls exported function *fn* on host *hostname*, with parameters *...*, returning whatever the function returns, or false.
|
||||
if hostname == "localhost" then
|
||||
return rpcf[fn](...)
|
||||
end
|
||||
@ -39,12 +56,15 @@ function rpc.call(hostname,fn,...)
|
||||
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)
|
||||
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 "").."(.+)"
|
||||
local fnames = rpc.call(hostname,"list")
|
||||
if not fnames then return false end
|
||||
@ -59,7 +79,7 @@ function rpc.proxy(hostname,filter)
|
||||
end
|
||||
return rt
|
||||
end
|
||||
function rpc.register(name,fn)
|
||||
function rpc.register(name,fn) -- string function -- -- Registers a function to be exported by the RPC library.
|
||||
local rpcrunning = false
|
||||
for k,v in pairs(os.tasks()) do
|
||||
if os.taskInfo(v).name == "rpc daemon" then
|
||||
@ -69,11 +89,17 @@ function rpc.register(name,fn)
|
||||
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
|
||||
|
@ -3,7 +3,7 @@ local local_pairs=function(tbl)
|
||||
local mt=getmetatable(tbl)
|
||||
return (mt and mt.__pairs or pairs)(tbl)
|
||||
end
|
||||
function serial.serialize(value,af) -- serialize *value* into a string. If *af* is true, allow functions. This breaks unserialization.
|
||||
function serial.serialize(value,af) -- boolean -- string -- serialize *value* into a string. If *af* is true, allow functions. This breaks unserialization.
|
||||
local kw={["and"]=true,["break"]=true,["do"]=true,["else"]=true,["elseif"]=true,["end"]=true,["false"]=true,["for"]=true,["function"]=true,["goto"]=true,["if"]=true,["in"]=true,["local"]=true,["nil"]=true,["not"]=true,["or"]=true,["repeat"]=true,["return"]=true,["then"]=true,["true"]=true,["until"]=true,["while"]=true}
|
||||
local id="^[%a_][%w_]*$"
|
||||
local ts={}
|
||||
@ -40,7 +40,7 @@ function serial.serialize(value,af) -- serialize *value* into a string. If *af*
|
||||
else error("ut "..t) end end
|
||||
return s(value, 1)
|
||||
end
|
||||
function serial.unserialize(data) -- return *data*, but unserialized
|
||||
function serial.unserialize(data) -- string -- -- return *data*, but unserialized
|
||||
checkArg(1, data, "string")
|
||||
local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}})
|
||||
if not result then return nil, reason end
|
||||
|
@ -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
|
||||
|
@ -2,6 +2,7 @@ local component = require "component"
|
||||
local fs = require "fs"
|
||||
local shell = require "shell"
|
||||
local ed = require "ed"
|
||||
local doc = require "doc"
|
||||
local shutil = {}
|
||||
shutil.ed = ed.interactive
|
||||
shutil.vi = ed.visual
|
||||
@ -18,7 +19,7 @@ local function wrapUnits(n)
|
||||
return tostring(math.floor(n))..(scale[count] or "")
|
||||
end
|
||||
|
||||
function shutil.import(lib)
|
||||
function shutil.import(lib) -- string -- boolean -- Imports the functions from library *lib* into the shell environment.
|
||||
local cE = os.getenv("INCLUDE") or shell.include
|
||||
local nE = {}
|
||||
for k,v in pairs(cE) do
|
||||
@ -30,7 +31,7 @@ function shutil.import(lib)
|
||||
return true
|
||||
end
|
||||
|
||||
function shutil.unimport(lib)
|
||||
function shutil.unimport(lib) -- string -- boolean -- Removes the functions from *lib* from the shell environment.
|
||||
local cE = os.getenv("INCLUDE") or shell.include
|
||||
local nE = {}
|
||||
for k,v in pairs(cE) do
|
||||
@ -42,7 +43,7 @@ function shutil.unimport(lib)
|
||||
return true
|
||||
end
|
||||
|
||||
function shutil.ls(...)
|
||||
function shutil.ls(...) -- string -- -- Prints contents of directories specified as *...*.
|
||||
local tA = {...}
|
||||
if not tA[1] then tA[1] = "." end
|
||||
for _,d in ipairs(tA) do
|
||||
@ -55,7 +56,7 @@ function shutil.ls(...)
|
||||
end
|
||||
end
|
||||
|
||||
function shutil.cat(...)
|
||||
function shutil.cat(...) -- string -- -- Outputs the contents of files specified in *...* to the standard output.
|
||||
for _,fn in ipairs({...}) do
|
||||
local f = io.open(fn,"rb")
|
||||
io.write(f:read("*a"))
|
||||
@ -63,7 +64,7 @@ function shutil.cat(...)
|
||||
end
|
||||
end
|
||||
|
||||
function shutil.ps()
|
||||
function shutil.ps() -- Prints the processes running on the system.
|
||||
print("PID# Parent | Name")
|
||||
for k,v in pairs(os.tasks()) do
|
||||
local t = os.taskInfo(v)
|
||||
@ -71,7 +72,7 @@ function shutil.ps()
|
||||
end
|
||||
end
|
||||
|
||||
function shutil.df()
|
||||
function shutil.df() -- Prints free disk space.
|
||||
local mt = fs.mounts()
|
||||
local ml = 0
|
||||
for k,v in pairs(mt) do
|
||||
@ -82,12 +83,12 @@ function shutil.df()
|
||||
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
|
||||
|
||||
function shutil.mount(addr,path)
|
||||
function shutil.mount(addr,path) -- string string -- boolean string -- Mounts filesystem component with address *addr* to *path* in the filesystem.
|
||||
if not addr then
|
||||
local mt = fs.mounts()
|
||||
for k,v in pairs(mt) do
|
||||
@ -113,14 +114,43 @@ function shutil.mount(addr,path)
|
||||
end
|
||||
end
|
||||
|
||||
function shutil.free()
|
||||
function shutil.free() -- Displays used and free memory.
|
||||
print("Total Used Free")
|
||||
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})
|
||||
|
114
lib/unionfs.lua
114
lib/unionfs.lua
@ -1,114 +0,0 @@
|
||||
local unionfs = {}
|
||||
|
||||
local function normalise(path)
|
||||
return table.concat(fs.segments(path),"/")
|
||||
end
|
||||
|
||||
function unionfs.create(...)
|
||||
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
|
@ -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
|
145
lib/vtansi.lua
145
lib/vtansi.lua
@ -1,7 +1,35 @@
|
||||
local vtansi = {}
|
||||
function vtansi.vtemu(gpu) -- 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) -- takes GPU component proxy *gpu* and returns a func
|
||||
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) -- takes GPU component proxy *gpu* and returns a func
|
||||
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) -- takes GPU component proxy *gpu* and returns a func
|
||||
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) -- takes GPU component proxy *gpu* and returns a func
|
||||
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) -- takes GPU component proxy *gpu* and returns a func
|
||||
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) -- takes GPU component proxy *gpu* and returns a func
|
||||
return rs, lb, ec
|
||||
end
|
||||
|
||||
return termwrite
|
||||
return termwrite, buffer
|
||||
end
|
||||
|
||||
function vtansi.vtsession(gpua,scra) -- 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)
|
||||
|
@ -179,7 +179,9 @@ local env = {code = ""}
|
||||
setmetatable(env, {__index=_env})
|
||||
env:process(arg[1])
|
||||
local tmpfile = os.tmpname()
|
||||
if tmpfile:sub(#tmpfile) == "." then tmpfile = tmpfile:sub(1, #tmpfile - 1) end
|
||||
local tmpf = io.open(tmpfile, "wb")
|
||||
env.code = env.code:gsub("%-%-.-\n","\n"):gsub("\n\n","\n")
|
||||
tmpf:write(env.code)
|
||||
tmpf:close()
|
||||
--if (os.execute("lua minify.lua "..tmpfile.." > "..arg[2])) then
|
||||
|
10
module/base.lua
Normal file
10
module/base.lua
Normal 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"
|
@ -2,7 +2,7 @@
|
||||
|
||||
buffer = {}
|
||||
|
||||
function buffer.new(mode, stream) -- create a new buffer in mode *mode* backed by stream object *stream*
|
||||
function buffer.new(mode, stream) -- string table -- table -- create a new buffer in mode *mode* backed by stream object *stream*
|
||||
local result = {
|
||||
mode = {},
|
||||
stream = stream,
|
||||
@ -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
8
module/component-get.lua
Normal 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
|
@ -64,7 +64,7 @@ end
|
||||
devfs.component.address = "devfs"
|
||||
devfs.component.type = "devfs"
|
||||
|
||||
function devfs.register(fname,fopen) -- Register a new devfs node with the name *fname* that will run the function *fopen* when opened. This function should return a function for read, a function for write, function for close, and optionally, a function for seek, in that order.
|
||||
function devfs.register(fname,fopen) -- string function -- -- Register a new devfs node with the name *fname* that will run the function *fopen* when opened. This function should return a function for read, a function for write, function for close, and optionally, a function for seek, in that order.
|
||||
devfs.files[fname] = fopen
|
||||
end
|
||||
|
||||
|
@ -3,14 +3,14 @@ fs = {}
|
||||
local fsmounts = {}
|
||||
|
||||
-- basics
|
||||
function fs.segments(path) -- splits *path* on each /
|
||||
function fs.segments(path) -- string -- table -- Splits *path* on each /
|
||||
local segments = {}
|
||||
for segment in path:gmatch("[^/]+") do
|
||||
segments[#segments+1] = segment
|
||||
end
|
||||
return segments
|
||||
end
|
||||
function fs.resolve(path) -- resolves *path* to a specific filesystem mount and path
|
||||
function fs.resolve(path) -- string -- string string -- Resolves *path* to a specific filesystem mount and path
|
||||
if not path or path == "." then path = os.getenv("PWD") end
|
||||
if path:sub(1,1) ~= "/" then path=(os.getenv("PWD") or "").."/"..path end
|
||||
local segments, rpath, rfs= fs.segments(path)
|
||||
@ -44,7 +44,7 @@ local function fclose(self)
|
||||
return fsmounts[self.fs].close(self.fid)
|
||||
end
|
||||
|
||||
function fs.open(path,mode) -- opens file *path* with mode *mode*
|
||||
function fs.open(path,mode) -- string string -- table -- Opens file *path* with mode *mode*, returning a file object.
|
||||
mode = mode or "rb"
|
||||
local fsi,path = fs.resolve(path)
|
||||
if not fsmounts[fsi] then return false end
|
||||
@ -54,7 +54,7 @@ function fs.open(path,mode) -- opens file *path* with mode *mode*
|
||||
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
|
||||
@ -62,61 +62,67 @@ function fs.open(path,mode) -- opens file *path* with mode *mode*
|
||||
return false
|
||||
end
|
||||
|
||||
function fs.copy(from,to) -- copies a file from *from* to *to*
|
||||
function fs.copy(from,to) -- string string -- boolean -- copies a file from *from* to *to*
|
||||
local of = fs.open(from,"rb")
|
||||
local df = fs.open(to,"wb")
|
||||
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
|
||||
end
|
||||
|
||||
function fs.rename(from,to) -- moves file *from* to *to*
|
||||
function fs.rename(from,to) -- string string -- boolean -- Moves file *from* to *to*
|
||||
local ofsi, opath = fs.resolve(from)
|
||||
local dfsi, dpath = fs.resolve(to)
|
||||
if ofsi == dfsi then
|
||||
fsmounts[ofsi].rename(opath,dpath)
|
||||
return true
|
||||
end
|
||||
fs.copy(from,to)
|
||||
fs.remove(from)
|
||||
if not fs.copy(from,to) then return false end
|
||||
if not fs.remove(from) then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
function fs.mount(path,proxy) -- mounts the filesystem *proxy* to the mount point *path* if it is a directory. BYO proxy.
|
||||
function fs.mount(path,proxy) -- string table -- boolean -- Mounts the filesystem *proxy* to the mount point *path* if it is a directory. BYO proxy.
|
||||
if fs.isDirectory(path) and not fsmounts[table.concat(fs.segments(path),"/")] then
|
||||
fsmounts[table.concat(fs.segments(path),"/")] = proxy
|
||||
return true
|
||||
end
|
||||
return false, "path is not a directory"
|
||||
end
|
||||
function fs.umount(path)
|
||||
function fs.umount(path) -- string -- -- Unmounts filesystem from *path*.
|
||||
local fsi,_ = fs.resolve(path)
|
||||
fsmounts[fsi] = nil
|
||||
end
|
||||
|
||||
function fs.mounts() -- returns a table containing the mount points of all mounted filesystems
|
||||
function fs.mounts() -- -- table -- Returns a table containing the mount points of all mounted filesystems
|
||||
local rt = {}
|
||||
for k,v in pairs(fsmounts) do
|
||||
rt[#rt+1] = k,v.address or "unknown"
|
||||
end
|
||||
table.sort(rt)
|
||||
return rt
|
||||
end
|
||||
|
||||
function fs.address(path) -- returns the address of the filesystem at a given path, if applicable; do not expect a sensical response
|
||||
function fs.address(path) -- string -- string -- Returns the address of the filesystem at a given path, if applicable; do not expect a sensical response
|
||||
local fsi,_ = fs.resolve(path)
|
||||
return fsmounts[fsi].address
|
||||
end
|
||||
function fs.type(path) -- returns the component type of the filesystem at a given path, if applicable
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -1,21 +1,21 @@
|
||||
io = {}
|
||||
|
||||
function io.open(path,mode) -- Open file *path* in *mode*. Returns a buffer object.
|
||||
function io.open(path,mode) -- string string -- table -- Open file *path* in *mode*. Returns a buffer object.
|
||||
local f,e = fs.open(path, mode)
|
||||
if not f then return false, e end
|
||||
return buffer.new(mode,f)
|
||||
end
|
||||
|
||||
function io.input(fd) -- Sets the default input stream to *fd* if provided, either as a buffer as a path. Returns the default input stream.
|
||||
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)
|
||||
end
|
||||
return os.getenv("STDIN")
|
||||
end
|
||||
function io.output(fd) -- Sets the default output stream to *fd* if provided, either as a buffer as a path. Returns the default output stream.
|
||||
function io.output(fd) -- table -- table -- Sets the default output stream to *fd* if provided, either as a buffer as a path. Returns the default output stream.
|
||||
if type(fd) == "string" then
|
||||
fd=io.open(fd,"wb")
|
||||
end
|
||||
@ -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
|
||||
|
@ -1,26 +1,26 @@
|
||||
function loadfile(p) -- reads file *p* and returns a function if possible
|
||||
function loadfile(p) -- string -- function -- reads file *p* and returns a function if possible
|
||||
local f = io.open(p,"rb")
|
||||
local c = f:read("*a")
|
||||
f:close()
|
||||
return load(c,p,"t")
|
||||
end
|
||||
function runfile(p,...) -- runs file *p* with arbitrary arguments in the current thread
|
||||
function runfile(p,...) -- string -- -- runs file *p* with arbitrary arguments in the current thread
|
||||
return loadfile(p)(...)
|
||||
end
|
||||
function os.spawnfile(p,n,...) -- 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}
|
||||
function require(f,force) -- 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
|
||||
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
|
||||
@ -29,6 +29,6 @@ function require(f,force) -- searches for a library with name *f* and returns wh
|
||||
end
|
||||
error("library not found: "..f)
|
||||
end
|
||||
function reload(f)
|
||||
function reload(f) -- string -- table -- Reloads library *f* from disk into memory.
|
||||
return require(f,true)
|
||||
end
|
||||
|
@ -1,141 +0,0 @@
|
||||
function vt100emu(gpu) -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
|
||||
local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
|
||||
local mx, my = gpu.maxResolution()
|
||||
local cx, cy = 1, 1
|
||||
local pc = " "
|
||||
local lc = ""
|
||||
local mode = 0 -- 0 normal, 1 escape, 2 command
|
||||
local lw = true
|
||||
local sx, sy = 1,1
|
||||
local cs = ""
|
||||
local bg, fg = 0, 0xFFFFFF
|
||||
|
||||
-- setup
|
||||
gpu.setResolution(mx,my)
|
||||
gpu.fill(1,1,mx,my," ")
|
||||
local function checkCursor()
|
||||
if cx > mx and lw then
|
||||
cx, cy = 1, cy+1
|
||||
end
|
||||
if cy > my then
|
||||
gpu.copy(1,2,mx,my-1,0,-1)
|
||||
gpu.fill(1,my,mx,1," ")
|
||||
cy=my
|
||||
end
|
||||
if cy < 1 then cy = 1 end
|
||||
if cx < 1 then cx = 1 end
|
||||
end
|
||||
|
||||
local function termwrite(s)
|
||||
local wb = ""
|
||||
local lb, ec = nil, nil
|
||||
local function flushwb()
|
||||
while wb:len() > 0 do
|
||||
checkCursor()
|
||||
local wl = wb:sub(1,mx-cx+1)
|
||||
wb = wb:sub(wl:len()+1)
|
||||
gpu.set(cx, cy, wl)
|
||||
cx = cx + wl:len()
|
||||
end
|
||||
end
|
||||
local rs = ""
|
||||
s=s:gsub("\8","\27[D")
|
||||
pc = gpu.get(cx,cy)
|
||||
gpu.setForeground(fg)
|
||||
gpu.setBackground(bg)
|
||||
gpu.set(cx,cy,pc)
|
||||
for cc in s:gmatch(".") do
|
||||
if mode == 0 then
|
||||
if cc == "\n" then
|
||||
flushwb()
|
||||
cx,cy = 1, cy+1
|
||||
elseif cc == "\t" then
|
||||
wb=wb..(" "):rep(8*((cx+9)//8))
|
||||
elseif cc == "\27" then
|
||||
flushwb()
|
||||
mode = 1
|
||||
else
|
||||
wb = wb .. cc
|
||||
end
|
||||
elseif mode == 1 then
|
||||
if cc == "[" then
|
||||
mode = 2
|
||||
else
|
||||
mode = 0
|
||||
end
|
||||
elseif mode == 2 then
|
||||
if cc:match("[%d;]") then
|
||||
cs = cs .. cc
|
||||
else
|
||||
mode = 0
|
||||
local tA = {}
|
||||
for s in cs:gmatch("%d+") do
|
||||
tA[#tA+1] = tonumber(s)
|
||||
end
|
||||
if cc == "H" then
|
||||
cx, cy = tA[1] or 1, tA[2] or 1
|
||||
elseif cc == "A" then
|
||||
cy = cy - (tA[1] or 1)
|
||||
elseif cc == "B" then
|
||||
cy = cy + (tA[1] or 1)
|
||||
elseif cc == "C" then
|
||||
cx = cx + (tA[1] or 1)
|
||||
elseif cc == "D" then
|
||||
cx = cx - (tA[1] or 1)
|
||||
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)
|
||||
elseif cc == "K" and tA[1] == 1 then
|
||||
gpu.fill(1,cy,cx,1," ")
|
||||
elseif cc == "K" and tA[1] == 2 then
|
||||
gpu.fill(cx,cy,mx,1," ")
|
||||
elseif cc == "K" then
|
||||
gpu.fill(1,cy,mx,1," ")
|
||||
elseif cc == "J" and tA[1] == 1 then
|
||||
gpu.fill(1,1,mx,cy," ")
|
||||
elseif cc == "J" and tA[1] == 2 then
|
||||
gpu.fill(1,1,mx,my," ")
|
||||
cx, cy = 1, 1
|
||||
elseif cc == "J" then
|
||||
gpu.fill(1,cy,mx,my," ")
|
||||
elseif cc == "m" then
|
||||
for _,num in ipairs(tA) do
|
||||
if num == 0 then
|
||||
fg,bg,ec,lb = 0xFFFFFF,0,true,true
|
||||
elseif num == 7 then
|
||||
local nfg,nbg = bg, fg
|
||||
fg, bg = nfg, nbg
|
||||
elseif num > 29 and num < 38 then
|
||||
fg = colours[num-29]
|
||||
elseif num > 39 and num < 48 then
|
||||
bg = colours[num-39]
|
||||
elseif num == 100 then -- disable local echo
|
||||
ec = false
|
||||
elseif num == 101 then -- disable line mode
|
||||
lb = false
|
||||
end
|
||||
end
|
||||
gpu.setForeground(fg)
|
||||
gpu.setBackground(bg)
|
||||
end
|
||||
cs = ""
|
||||
checkCursor()
|
||||
end
|
||||
end
|
||||
end
|
||||
flushwb()
|
||||
checkCursor()
|
||||
pc = gpu.get(cx,cy)
|
||||
gpu.setForeground(bg)
|
||||
gpu.setBackground(fg)
|
||||
gpu.set(cx,cy,pc)
|
||||
gpu.setForeground(fg)
|
||||
gpu.setBackground(bg)
|
||||
return rs, lb, ec
|
||||
end
|
||||
|
||||
return termwrite
|
||||
end
|
@ -1,4 +1,4 @@
|
||||
function os.chdir(p) -- changes the current working directory of the calling process to the directory specified in *p*, returning true or false, error
|
||||
function os.chdir(p) -- string -- boolean string -- changes the current working directory of the calling process to the directory specified in *p*, returning true or false, error
|
||||
if not (p:sub(1,1) == "/") then
|
||||
local np = {}
|
||||
for k,v in pairs(fs.segments(os.getenv("PWD").."/"..p)) do
|
||||
|
20
module/rtfsboot.lua
Normal file
20
module/rtfsboot.lua
Normal 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
|
@ -1,11 +1,20 @@
|
||||
do
|
||||
local tTasks,nPid,nTimeout,cPid = {},1,0.25,0 -- table of tasks, next process ID, event timeout, current PID
|
||||
function os.spawn(f,n) -- creates a process from function *f* with name *n*
|
||||
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
|
||||
@ -16,32 +25,37 @@ function os.spawn(f,n) -- creates a process from function *f* with name *n*
|
||||
nPid = nPid + 1
|
||||
return nPid - 1
|
||||
end
|
||||
function os.kill(pid) -- removes process *pid* from the task list
|
||||
function os.kill(pid) -- number -- -- removes process *pid* from the task list
|
||||
tTasks[pid] = nil
|
||||
end
|
||||
function os.pid() -- returns the current process' PID
|
||||
function os.pid() -- -- number -- returns the current process' PID
|
||||
return cPid
|
||||
end
|
||||
function os.tasks() -- returns a table of process IDs
|
||||
function os.tasks() -- -- table -- returns a table of process IDs
|
||||
local rt = {}
|
||||
for k,v in pairs(tTasks) do
|
||||
rt[#rt+1] = k
|
||||
end
|
||||
return rt
|
||||
end
|
||||
function os.taskInfo(pid) -- returns info on process *pid* as a table with name and parent values
|
||||
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
|
||||
|
@ -9,6 +9,7 @@ syslog.notice = 5
|
||||
syslog.info = 6
|
||||
syslog.debug = 7
|
||||
|
||||
-- function syslog(msg, level, service) -- string number string -- -- Output *msg* to the system log, with severity *level*, from *service*.
|
||||
local rdprint=dprint or function() end
|
||||
setmetatable(syslog,{__call = function(_,msg, level, service)
|
||||
level, service = level or syslog.info, service or (os.taskInfo(os.pid()) or {}).name or "unknown"
|
||||
|
@ -1,5 +0,0 @@
|
||||
--#include "module/vt-task.lua"
|
||||
do
|
||||
local r,w = vtemu(component.list("gpu")(),component.list("screen")())
|
||||
devfs.register("tty0", function() return r,w,function() end end)
|
||||
end
|
@ -1,58 +0,0 @@
|
||||
do
|
||||
--#include "module/nvt100.lua"
|
||||
function vtemu(gpua,scra) -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions.
|
||||
local gpu = component.proxy(gpua)
|
||||
gpu.bind(scra)
|
||||
local write = vt100emu(gpu)
|
||||
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()
|
||||
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 then
|
||||
if buf:len() > 0 then
|
||||
if echo then write("\8 \8") end
|
||||
buf = buf:sub(1,-2)
|
||||
end
|
||||
elseif ch > 0 then
|
||||
if echo then write(string.char(ch)) end
|
||||
buf=buf..string.char(ch)
|
||||
end
|
||||
end
|
||||
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()
|
||||
end
|
||||
return r
|
||||
end
|
||||
local function bwrite(d)
|
||||
local ba, lb, ec = write(d)
|
||||
buf = buf .. ba
|
||||
if lb ~= nil then
|
||||
dprint("local buffer mode: "..tostring(lb))
|
||||
lbuf = lb
|
||||
end
|
||||
if ec ~= nil then
|
||||
dprint("echo mode: "..tostring(ec))
|
||||
echo = ec
|
||||
end
|
||||
end
|
||||
return bread, bwrite, function() io.write("\27[2J\27[H") end
|
||||
end
|
||||
end
|
148
module/vt100.lua
148
module/vt100.lua
@ -1,148 +0,0 @@
|
||||
function vt100emu(gpu) -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
|
||||
local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
|
||||
local mx, my = gpu.maxResolution()
|
||||
local cx, cy = 1, 1
|
||||
local pc = " "
|
||||
local lc = ""
|
||||
local mode = "n"
|
||||
local lw = true
|
||||
local sx, sy = 1,1
|
||||
local cs = ""
|
||||
local bg, fg = 0, 0xFFFFFF
|
||||
|
||||
-- setup
|
||||
gpu.setResolution(mx,my)
|
||||
gpu.fill(1,1,mx,my," ")
|
||||
|
||||
local function termwrite(s)
|
||||
local rs = ""
|
||||
s=s:gsub("\8","\27[D")
|
||||
pc = gpu.get(cx,cy)
|
||||
gpu.setForeground(fg)
|
||||
gpu.setBackground(bg)
|
||||
gpu.set(cx,cy,pc)
|
||||
for i = 1, s:len() do
|
||||
local cc = s:sub(i,i)
|
||||
|
||||
if mode == "n" then
|
||||
if cc == "\n" then -- line feed
|
||||
cx, cy = 1, cy+1
|
||||
elseif cc == "\r" then -- cursor home
|
||||
cx = 1
|
||||
elseif cc == "\27" then -- escape
|
||||
mode = "e"
|
||||
elseif cc == "\t" then
|
||||
cx = 8*((cx+9)//8)
|
||||
elseif string.byte(cc) > 31 and string.byte(cc) < 127 then -- printable, I guess
|
||||
gpu.set(cx, cy, cc)
|
||||
cx = cx + 1
|
||||
end
|
||||
|
||||
elseif mode == "e" then
|
||||
if cc == "[" then
|
||||
mode = "v"
|
||||
cs = ""
|
||||
elseif cc == "D" then -- scroll down
|
||||
gpu.copy(1,2,mx,my-1,0,-1)
|
||||
gpu.fill(1,my,mx,1," ")
|
||||
cy=cy+1
|
||||
mode = "n"
|
||||
elseif cc == "M" then -- scroll up
|
||||
gpu.copy(1,1,mx,my-1,0,1)
|
||||
gpu.fill(1,1,mx,1," ")
|
||||
mode = "n"
|
||||
else
|
||||
mode = "n"
|
||||
end
|
||||
|
||||
elseif mode == "v" then
|
||||
mode = "n"
|
||||
if cc == "s" then -- save cursor
|
||||
sx, sy = cx, cy
|
||||
elseif cc == "u" then -- restore cursor
|
||||
cx, cy = sx, sy
|
||||
elseif cc == "H" then -- cursor home or to
|
||||
local tx, ty = cs:match("(%d+);(%d+)")
|
||||
tx, ty = tx or "1", ty or "1"
|
||||
cx, cy = tonumber(tx), tonumber(ty)
|
||||
elseif cc == "A" then -- cursor up
|
||||
cy = cy - (tonumber(cs) or 1)
|
||||
elseif cc == "B" then -- cursor down
|
||||
cy = cy + (tonumber(cs) or 1)
|
||||
elseif cc == "C" then -- cursor right
|
||||
cx = cx + (tonumber(cs) or 1)
|
||||
elseif cc == "D" then -- cursor left
|
||||
cx = cx - (tonumber(cs) or 1)
|
||||
elseif cc == "h" and lc == "7" then -- enable line wrap
|
||||
lw = true
|
||||
elseif cc == "l" and lc == "7" then -- disable line wrap
|
||||
lw = false
|
||||
elseif cc == "c" then
|
||||
rs = string.format("%s\27[%d;%d0c",rs,mx,my)
|
||||
elseif cc == "n" and lc == "6" then
|
||||
rs = string.format("%s\27[%d;%dR",rs,cx,cy)
|
||||
elseif cc == "K" then
|
||||
if lc == "1" then
|
||||
gpu.fill(1,cy,cx,1," ")
|
||||
elseif lc == "2" then
|
||||
gpu.fill(cx,cy,mx,1," ")
|
||||
else
|
||||
gpu.fill(1,cy,mx,1," ")
|
||||
end
|
||||
elseif cc == "J" then
|
||||
if lc == "1" then
|
||||
gpu.fill(1,1,mx,cy," ")
|
||||
elseif lc == "2" then
|
||||
gpu.full(1,1,mx,my," ")
|
||||
cx,cy = 1, 1
|
||||
else
|
||||
gpu.fill(1,cy,mx,my," ")
|
||||
end
|
||||
elseif cc == "m" then
|
||||
for num in cs:gmatch("%d+") do
|
||||
num=tonumber(num)
|
||||
if num == 0 then
|
||||
fg,bg = 0xFFFFFF,0
|
||||
elseif num == 7 then
|
||||
local nfg,nbg = bg, fg
|
||||
fg, bg = nfg, nbg
|
||||
elseif num > 29 and num < 38 then
|
||||
fg = colours[num-29]
|
||||
elseif num > 39 and num < 48 then
|
||||
bg = colours[num-39]
|
||||
end
|
||||
end
|
||||
gpu.setForeground(fg)
|
||||
gpu.setBackground(bg)
|
||||
else
|
||||
cs = cs .. cc
|
||||
if cc:match("[%d;]") then
|
||||
mode = "v"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if cx > mx and lw then
|
||||
cx, cy = 1, cy+1
|
||||
end
|
||||
if cy > my then
|
||||
gpu.copy(1,2,mx,my-1,0,-1)
|
||||
gpu.fill(1,my,mx,1," ")
|
||||
cy=my
|
||||
end
|
||||
if cy < 1 then cy = 1 end
|
||||
if cx < 1 then cx = 1 end
|
||||
|
||||
lc = cc
|
||||
end
|
||||
pc = gpu.get(cx,cy)
|
||||
gpu.setForeground(bg)
|
||||
gpu.setBackground(fg)
|
||||
gpu.set(cx,cy,pc)
|
||||
gpu.setForeground(fg)
|
||||
gpu.setBackground(bg)
|
||||
return rs
|
||||
end
|
||||
|
||||
return termwrite
|
||||
end
|
31
obootstrap.lua
Normal file
31
obootstrap.lua
Normal 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
121
preproc.lua
Normal 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})
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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
|
46
template.tex
Normal file
46
template.tex
Normal file
@ -0,0 +1,46 @@
|
||||
\documentclass[11pt,twoside,a4paper]{article}
|
||||
\usepackage{hyperref}
|
||||
\usepackage{multicol}
|
||||
\usepackage{standalone}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{pdfpages}
|
||||
\usepackage{listings}
|
||||
\usepackage{color}
|
||||
\usepackage{sectsty}
|
||||
\usepackage[cm]{fullpage}
|
||||
\lstset{
|
||||
commentstyle=\color{cyan}, % comment style
|
||||
keywordstyle=\color{cyan}, % keyword style
|
||||
stringstyle=\color{red}, % string literal style
|
||||
numbers=left, % where to put the line-numbers; possible values are (none, left, right)
|
||||
numbersep=5pt, % how far the line-numbers are from the code
|
||||
numberstyle=\tiny\color{gray}, % the style that is used for the line-numbers
|
||||
}
|
||||
\hypersetup{
|
||||
colorlinks=true
|
||||
}
|
||||
|
||||
\subsectionfont{\ttfamily}
|
||||
|
||||
% pandoc stuff
|
||||
\providecommand{\tightlist}{%
|
||||
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
|
||||
|
||||
\let\stdsection\section
|
||||
\renewcommand\section{\newpage\stdsection}
|
||||
|
||||
\lstset{basicstyle=\footnotesize\ttfamily,breaklines=true}
|
||||
\newcommand{\ignore}[1]{}
|
||||
\title{$title$$if(thanks)$\thanks{$thanks$}$endif$}
|
||||
\author{$for(author)$$author$$sep$ \and $endfor$}
|
||||
\date{}
|
||||
\begin{document}
|
||||
\pagenumbering{gobble}
|
||||
\maketitle
|
||||
\newpage
|
||||
\pagenumbering{alph}
|
||||
\tableofcontents
|
||||
\newpage
|
||||
\pagenumbering{arabic}
|
||||
$body$
|
||||
\end{document}
|
Loading…
Reference in New Issue
Block a user