Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

58 changed files with 1247 additions and 1838 deletions

View File

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

4
.gitignore vendored
View File

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

View File

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

View File

@ -6,31 +6,23 @@ A lightweight, multi-user operating system for OpenComputers
### The kernel ### The kernel
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. The kernel can be built using luapreproc:
#### Unix-like systems ./luapreproc.lua module/init.lua kernel.lua
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 ### The boot filesystem
A boot filesystem contains several things: A boot filesystem contains several things:
- The kernel, as init.lua - The kernel, as init.lua
- The exec/ directory, as this contains all executables
- The lib/ directory, containing libraries - The lib/ directory, containing libraries
- The service/ directory, containing system services - 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. This has been automated in the form of build.sh, pending a real makefile.
## Documentation ## Documentation
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. To generate function documentation, run:
./finddesc.lua module/* lib/* > apidoc.md

View File

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

View File

@ -1,15 +1,10 @@
#!/bin/bash #!/bin/sh
LUA=${LUA:-lua}
KVAR=${1:-base}
rm -r target/* rm -r target/*
mkdir -p target/doc &>/dev/null mkdir target &>/dev/null
$LUA build.lua kcfg/$KVAR.cfg target/init.lua lua luapreproc.lua module/init.lua target/init.lua
echo _OSVERSION=\"PsychOS 2.0a3-$(git rev-parse --short HEAD)-$KVAR\" > target/version.lua echo _OSVERSION=\"PsychOS 2.0a2-$(git rev-parse --short HEAD)\" > target/version.lua
cat target/version.lua target/init.lua > target/tinit.lua cat target/version.lua target/init.lua > target/tinit.lua
mv target/tinit.lua target/init.lua mv target/tinit.lua target/init.lua
cp -r service/ lib/ cfg/ exec/ target/ cp -r service/ lib/ cfg/ target/
lua finddesc.lua $(find module/ -type f) $(find lib/ -type f) > apidoc.md
rm target/version.lua 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
cfg/rc.cfg Normal file
View File

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

View File

@ -1,3 +0,0 @@
---
title: 'PsychOS API Documentation'
---

View File

@ -1,60 +1,27 @@
#!/usr/bin/env lua
local doc = require "lib/doc"
local tA = {...} local tA = {...}
local docfiles = {}
local outpath = table.remove(tA,1) for _,file in pairs(tA) do
docfiles[file] = {}
print(outpath) local f = io.open(file)
local lines = {}
local function formatDocs(fd) for l in f:read("*a"):gmatch("[^\n]+") do
local rs = "" if l:find("function") and not l:find("local") then
for name,finfo in pairs(fd) do lines[#lines+1] = l
if rs:len() > 0 then
rs = rs .. "\n\n"
end 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 end
return rs for k,v in pairs(lines) do
end local name, args, desc = v:match("function%s+(.+)%s*%((.*)%)%s*%-%-%s*(.+)")
if name and args and desc then
os.execute("sh -c 'mkdir -p "..outpath .. "'") docfiles[file][#docfiles[file]+1] = string.format("##%s(%s)\n%s",name,args,desc)
if outpath:sub(#outpath) == "/" then outpath = outpath:sub(1, #outpath - 1) end 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
end end
ad:close() 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

View File

@ -1,51 +0,0 @@
#!/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()

View File

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

View File

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

View File

@ -1,154 +0,0 @@
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})

View File

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

View File

@ -17,7 +17,7 @@ end
function ed.bfunc:save(fpath) function ed.bfunc:save(fpath)
local path = fpath or self.path local path = fpath or self.path
if not path then return false, "no path" end if not path then return false, "no path" end
self.path = path self.path = path
local f = io.open(path,"wb") local f = io.open(path,"wb")
if not f then return false, "unable to open file" end if not f then return false, "unable to open file" end
for k,v in ipairs(self) do for k,v in ipairs(self) do
@ -112,7 +112,6 @@ function ed.newBuffer()
end end
function ed.open(buffer) function ed.open(buffer)
local bpath = buffer
if ed.buffers[buffer] then if ed.buffers[buffer] then
buffer = ed.buffers[buffer] buffer = ed.buffers[buffer]
end end
@ -121,9 +120,7 @@ function ed.open(buffer)
nb:load(buffer) nb:load(buffer)
buffer = nb buffer = nb
end end
if type(buffer) ~= "table" then buffer = ed.newBuffer() end if type(buffer) ~= "table" then buffer = ed.newBuffer() buffer[1] = "" 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 return buffer
end end
@ -163,10 +160,10 @@ function ed.visual(buffer)
if cx ~= ox or cy ~= oy or force then if cx ~= ox or cy ~= oy or force then
io.write("\27[2J\27[H") io.write("\27[2J\27[H")
for i = cy, cy+my do for i = cy, cy+my do
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))) print(string.format("\27[31m%4i \27[0m%s",i,(buffer[i] or "\27[36m~"):sub(cx,cx+mx-6)))
end end
elseif mode == "i" then elseif mode == "i" then
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))) 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)))
end 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[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)) io.write(string.format("\27[%i;%iH\27[0m",buffer.x+6-cx,buffer.y-cy+1))

View File

@ -1,5 +1,5 @@
local event = {} local event = {}
function event.pull(t,...) -- number -- -- return an event, optionally with timeout *t* and filter *...*. function event.pull(t,...) -- return an event, optionally with timeout *t* and filter *...*.
local tA = {...} local tA = {...}
if type(t) == "string" then if type(t) == "string" then
table.insert(tA,1,t) table.insert(tA,1,t)
@ -26,7 +26,7 @@ function event.pull(t,...) -- number -- -- return an event, optionally with time
return nil return nil
end end
function event.listen(e,f) -- string function -- -- run function *f* for every occurance of event *e* function event.listen(e,f) -- run function *f* for every occurance of event *e*
os.spawn(function() while true do os.spawn(function() while true do
local tEv = {coroutine.yield()} local tEv = {coroutine.yield()}
if tEv[1] == e then if tEv[1] == e then
@ -36,7 +36,7 @@ function event.listen(e,f) -- string function -- -- run function *f* for every o
end end,string.format("[%d] %s listener",os.pid(),e)) end end,string.format("[%d] %s listener",os.pid(),e))
end end
function event.ignore(e,f) -- string function -- -- stop function *f* running for every occurance of event *e* function event.ignore(e,f) -- stop function *f* running for every occurance of event *e*
computer.pushSignal("unlisten",e,tostring(f)) computer.pushSignal("unlisten",e,tostring(f))
end end

63
lib/interminitel.lua Normal file
View File

@ -0,0 +1,63 @@
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

View File

@ -1,53 +0,0 @@
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

View File

@ -1,49 +0,0 @@
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

View File

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

View File

@ -12,7 +12,7 @@ net.minport = 32768
net.maxport = 65535 net.maxport = 65535
net.openports = {} net.openports = {}
function net.genPacketID() -- -- string -- generate a random 16-character string, for use in packet IDs function net.genPacketID() -- generate a random 16-character string, for use in packet IDs
local npID = "" local npID = ""
for i = 1, 16 do for i = 1, 16 do
npID = npID .. string.char(math.random(32,126)) npID = npID .. string.char(math.random(32,126))
@ -20,11 +20,11 @@ function net.genPacketID() -- -- string -- generate a random 16-character string
return npID return npID
end end
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* 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*
computer.pushSignal("net_send",0,to,port,data,npID) computer.pushSignal("net_send",0,to,port,data,npID)
end end
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 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
local pid, stime = net.genPacketID(), computer.uptime() + net.streamdelay local pid, stime = net.genPacketID(), computer.uptime() + net.streamdelay
computer.pushSignal("net_send",1,to,port,data,pid) computer.pushSignal("net_send",1,to,port,data,pid)
if block then return false end if block then return false end
@ -37,7 +37,7 @@ end
-- ordered packet delivery, layer 4? -- ordered packet delivery, layer 4?
function net.send(to,port,ldata) -- string number string -- boolean -- send arbitrary data *ldata* reliably to host *to* on port *port* function net.send(to,port,ldata) -- send arbitrary data *ldata* reliably to host *to* on port *port*
local tdata = {} local tdata = {}
if ldata:len() > net.mtu then if ldata:len() > net.mtu then
for i = 1, ldata:len(), net.mtu do for i = 1, ldata:len(), net.mtu do
@ -112,7 +112,7 @@ local function socket(addr,port,sclose)
return conn return conn
end end
function net.open(to,port) -- string number -- buffer -- open a socket to host *to* on port *port* function net.open(to,port) -- open a socket to host *to* on port *port*
if not net.rsend(to,port,"openstream") then return false, "no ack from host" end if not net.rsend(to,port,"openstream") then return false, "no ack from host" end
local st = computer.uptime()+net.streamdelay local st = computer.uptime()+net.streamdelay
local est = false local est = false
@ -139,7 +139,7 @@ function net.open(to,port) -- string number -- buffer -- open a socket to host *
return socket(to,data,sclose) return socket(to,data,sclose)
end end
function net.listen(port) -- number -- buffer -- listen for connections on port *port* in a blocking manner function net.listen(port) -- listen for connections on port *port* in a blocking manner
repeat repeat
_, from, rport, data = event.pull("net_msg") _, from, rport, data = event.pull("net_msg")
until rport == port and data == "openstream" until rport == port and data == "openstream"
@ -150,7 +150,7 @@ function net.listen(port) -- number -- buffer -- listen for connections on port
return socket(from,nport,sclose) return socket(from,nport,sclose)
end end
function net.flisten(port,listener) -- number function -- function -- run function *listener* on a connection to *port* function net.flisten(port,listener) -- run function *listener* on a connection to *port*
local function helper(_,from,rport,data) local function helper(_,from,rport,data)
if rport == port and data == "openstream" then if rport == port and data == "openstream" then
local nport = math.random(net.minport,net.maxport) local nport = math.random(net.minport,net.maxport)

View File

@ -1,11 +1,12 @@
local computer = require "computer" local computer = require "computer"
local minitel = require "minitel" local minitel = require "minitel"
local event = require "event" local event = require "event"
local ufs = require "unionfs"
local rpc = require "rpc" local rpc = require "rpc"
local netutil = {} local netutil = {}
function netutil.importfs(host,rpath,lpath) -- string string string -- boolean -- Import filesystem *rpath* from *host* and attach it to *lpath*. function netutil.importfs(host,rpath,lpath) -- import filesystem *rpath* from *host* and attach it to *lpath*
local px = rpc.proxy(host,"fs_"..rpath.."_") local px = rpc.proxy(host,rpath.."_")
function px.getLabel() function px.getLabel()
return host..":"..rpath return host..":"..rpath
end end
@ -13,25 +14,17 @@ function netutil.importfs(host,rpath,lpath) -- string string string -- boolean -
return fs.mount(lpath,px) return fs.mount(lpath,px)
end end
function netutil.exportfs(path) -- string -- boolean -- Export the directory *path* over RPC. function netutil.exportfs(path) -- export the directory *path* over RPC
local path = "/"..table.concat(fs.segments(path),"/") local path = "/"..table.concat(fs.segments(path),"/")
local px = require("unionfs").create(path) local px = ufs.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 for k,v in pairs(px) do
rpc.register("fs_"..path.."_"..k,v) rpc.register(path.."_"..k,v)
print("fs_"..path.."_"..k) print(path.."_"..k)
end end
return true return true
end end
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. 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.
local times, timeout = times or 5, timeout or 30 local times, timeout = times or 5, timeout or 30
local success, fail, time, avg = 0, 0, 0, 0 local success, fail, time, avg = 0, 0, 0, 0
for i = 1, times do for i = 1, times do
@ -53,7 +46,7 @@ function netutil.ping(addr,times,timeout,silent) -- string number number boolean
return success > 0, success, fail, avg return success > 0, success, fail, avg
end end
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. function netutil.nc(host,port)
port = port or 22 port = port or 22
local socket = minitel.open(host,port) local socket = minitel.open(host,port)
if not socket then return false end if not socket then return false end
@ -73,7 +66,6 @@ function netutil.nc(host,port) -- string number -- boolean -- Starts an interact
socket:write(b.."\n") socket:write(b.."\n")
end end
until socket.state ~= "open" until socket.state ~= "open"
return true
end end
return netutil return netutil

View File

@ -1,146 +0,0 @@
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

View File

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

View File

@ -1,11 +1,10 @@
local serial = require "serialization" local serial = require "serialization"
local rc = {} local rc = {}
rc.paths = "/boot/service\n/pkg/service"
rc.pids = {} rc.pids = {}
local service = {} local service = {}
local cfg = {} local cfg = {}
cfg.enabled = {"getty","minitel","fsmanager"} cfg.enabled = {"getty","minitel"}
local function loadConfig() local function loadConfig()
local f = io.open("/boot/cfg/rc.cfg","rb") local f = io.open("/boot/cfg/rc.cfg","rb")
@ -23,21 +22,20 @@ local function saveConfig()
return true return true
end end
function rc.load(name,force) -- string boolean -- table -- Attempts to load service *name*, and if *force* is true, replaces the current instance. function rc.load(name,force)
if not service[name] or force then if force then
for d in rc.paths:gmatch("[^\n]+") do rc.stop(name)
if fs.exists(d.."/"..name..".lua") then service[name] = nil
service[name] = runfile(d.."/"..name..".lua")
end
end
end end
if service[name] then if service[name] then return true end
return service[name] service[name] = setmetatable({},{__index=_G})
end local f = io.open("/boot/service/"..name..".lua","rb")
return false, "unable to load service "..name local res = load(f:read("*a"),name,"t",service[name])()
f:close()
return res
end end
function rc.stop(name,...) -- string -- boolean string -- Stops service *name*, supplying *...* to the stop function. Returns false and a reason if this fails. function rc.stop(name,...)
if not service[name] then return false, "service not found" end if not service[name] then return false, "service not found" end
if service[name].stop then if service[name].stop then
service[name].stop(...) service[name].stop(...)
@ -49,7 +47,7 @@ function rc.stop(name,...) -- string -- boolean string -- Stops service *name*,
rc.pids[name] = nil rc.pids[name] = nil
end end
function rc.start(name,...) -- string -- boolean string -- Stops service *name*, supplying *...* to the stop function. Returns false and a reason if this fails. function rc.start(name,...)
rc.load(name) rc.load(name)
if not service[name] then return false, "service not found" end if not service[name] then return false, "service not found" end
local rv = {service[name].start(...)} local rv = {service[name].start(...)}
@ -58,19 +56,19 @@ function rc.start(name,...) -- string -- boolean string -- Stops service *name*,
end end
end end
function rc.restart(name) -- string -- -- Restarts service *name* using rc.stop and rc.start. function rc.restart(name)
rc.stop(name) rc.stop(name)
rc.start(name) rc.start(name)
end end
function rc.enable(name) -- string -- -- Enables service *name* being started on startup. function rc.enable(name)
for k,v in pairs(cfg.enabled) do for k,v in pairs(cfg.enabled) do
if v == name then return false end if v == name then return false end
end end
cfg.enabled[#cfg.enabled+1] = name cfg.enabled[#cfg.enabled+1] = name
saveConfig() saveConfig()
end end
function rc.disable(name) -- string -- -- Disables service *name* being started on startup. function rc.disable(name)
local disabled = false local disabled = false
for k,v in pairs(cfg.enabled) do for k,v in pairs(cfg.enabled) do
if v == name then table.remove(cfg.enabled,k) disabled = true break end if v == name then table.remove(cfg.enabled,k) disabled = true break end

View File

@ -5,36 +5,19 @@ local rpc = {}
_G.rpcf = {} _G.rpcf = {}
rpc.port = 111 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) local function rpcexec(_, from, port, data)
if port ~= rpc.port then return false end if port == rpc.port then
os.spawn(function() local rpcrq = serial.unserialize(data)
local rpcrq = serial.unserialize(data) or {} local rpcn, rpcid = table.remove(rpcrq,1), table.remove(rpcrq,1)
if rpcf[rpcrq[1]] and isPermitted(from,rpcrq[1]) then if rpcf[rpcn] then
os.setenv("RPC_CLIENT",from) local rt = {pcall(rpcf[rpcn],table.unpack(rpcrq))}
minitel.send(from,port,serial.serialize({rpcrq[2],pcall(rpcf[rpcrq[1]],table.unpack(rpcrq,3))})) if rt[1] == true then
elseif type(rpcrq[2]) == "string" then table.remove(rt,1)
minitel.send(from,port,serial.serialize({rpcrq[2],false,"function unavailable"})) end
minitel.send(from,port,serial.serialize({rpcid,table.unpack(rt)}))
else
end
end end
end,"rpc worker for "..tostring(from))
end end
function rpcf.list() function rpcf.list()
local rt = {} local rt = {}
@ -44,7 +27,7 @@ function rpcf.list()
return rt return rt
end end
function rpc.call(hostname,fn,...) -- string string -- boolean -- Calls exported function *fn* on host *hostname*, with parameters *...*, returning whatever the function returns, or false. function rpc.call(hostname,fn,...)
if hostname == "localhost" then if hostname == "localhost" then
return rpcf[fn](...) return rpcf[fn](...)
end end
@ -56,15 +39,12 @@ function rpc.call(hostname,fn,...) -- string string -- boolean -- Calls exported
local _, from, port, data = event.pull(30, "net_msg", hostname, rpc.port) local _, from, port, data = event.pull(30, "net_msg", hostname, rpc.port)
rt = serial.unserialize(tostring(data)) or {} rt = serial.unserialize(tostring(data)) or {}
until (type(rt) == "table" and rt[1] == rv) or computer.uptime() > st + 30 until (type(rt) == "table" and rt[1] == rv) or computer.uptime() > st + 30
if rt[1] == rv then if table.remove(rt,1) == rv then
if rt[2] then return table.unpack(rt)
return table.unpack(rt,3)
end
error(rt[3])
end end
error("timed out") return false
end end
function rpc.proxy(hostname,filter) -- string string -- table -- Returns a component.proxy()-like table from the functions on *hostname* with names matching *filter*. function rpc.proxy(hostname,filter)
filter=(filter or "").."(.+)" filter=(filter or "").."(.+)"
local fnames = rpc.call(hostname,"list") local fnames = rpc.call(hostname,"list")
if not fnames then return false end if not fnames then return false end
@ -79,7 +59,7 @@ function rpc.proxy(hostname,filter) -- string string -- table -- Returns a compo
end end
return rt return rt
end end
function rpc.register(name,fn) -- string function -- -- Registers a function to be exported by the RPC library. function rpc.register(name,fn)
local rpcrunning = false local rpcrunning = false
for k,v in pairs(os.tasks()) do for k,v in pairs(os.tasks()) do
if os.taskInfo(v).name == "rpc daemon" then if os.taskInfo(v).name == "rpc daemon" then
@ -89,17 +69,11 @@ function rpc.register(name,fn) -- string function -- -- Registers a function to
if not rpcrunning then if not rpcrunning then
os.spawn(function() os.spawn(function()
while true do while true do
pcall(rpcexec,event.pull("net_msg")) rpcexec(event.pull("net_msg"))
end end
end,"rpc daemon") end,"rpc daemon")
end end
rpcf[name] = fn rpcf[name] = fn
end 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 return rpc

View File

@ -3,7 +3,7 @@ local local_pairs=function(tbl)
local mt=getmetatable(tbl) local mt=getmetatable(tbl)
return (mt and mt.__pairs or pairs)(tbl) return (mt and mt.__pairs or pairs)(tbl)
end end
function serial.serialize(value,af) -- boolean -- string -- serialize *value* into a string. If *af* is true, allow functions. This breaks unserialization. function serial.serialize(value,af) -- 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 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 id="^[%a_][%w_]*$"
local ts={} local ts={}
@ -40,7 +40,7 @@ function serial.serialize(value,af) -- boolean -- string -- serialize *value* in
else error("ut "..t) end end else error("ut "..t) end end
return s(value, 1) return s(value, 1)
end end
function serial.unserialize(data) -- string -- -- return *data*, but unserialized function serial.unserialize(data) -- return *data*, but unserialized
checkArg(1, data, "string") checkArg(1, data, "string")
local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}}) local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}})
if not result then return nil, reason end if not result then return nil, reason end

View File

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

View File

@ -2,7 +2,6 @@ local component = require "component"
local fs = require "fs" local fs = require "fs"
local shell = require "shell" local shell = require "shell"
local ed = require "ed" local ed = require "ed"
local doc = require "doc"
local shutil = {} local shutil = {}
shutil.ed = ed.interactive shutil.ed = ed.interactive
shutil.vi = ed.visual shutil.vi = ed.visual
@ -19,7 +18,7 @@ local function wrapUnits(n)
return tostring(math.floor(n))..(scale[count] or "") return tostring(math.floor(n))..(scale[count] or "")
end end
function shutil.import(lib) -- string -- boolean -- Imports the functions from library *lib* into the shell environment. function shutil.import(lib)
local cE = os.getenv("INCLUDE") or shell.include local cE = os.getenv("INCLUDE") or shell.include
local nE = {} local nE = {}
for k,v in pairs(cE) do for k,v in pairs(cE) do
@ -31,7 +30,7 @@ function shutil.import(lib) -- string -- boolean -- Imports the functions from l
return true return true
end end
function shutil.unimport(lib) -- string -- boolean -- Removes the functions from *lib* from the shell environment. function shutil.unimport(lib)
local cE = os.getenv("INCLUDE") or shell.include local cE = os.getenv("INCLUDE") or shell.include
local nE = {} local nE = {}
for k,v in pairs(cE) do for k,v in pairs(cE) do
@ -43,7 +42,7 @@ function shutil.unimport(lib) -- string -- boolean -- Removes the functions from
return true return true
end end
function shutil.ls(...) -- string -- -- Prints contents of directories specified as *...*. function shutil.ls(...)
local tA = {...} local tA = {...}
if not tA[1] then tA[1] = "." end if not tA[1] then tA[1] = "." end
for _,d in ipairs(tA) do for _,d in ipairs(tA) do
@ -56,7 +55,7 @@ function shutil.ls(...) -- string -- -- Prints contents of directories specified
end end
end end
function shutil.cat(...) -- string -- -- Outputs the contents of files specified in *...* to the standard output. function shutil.cat(...)
for _,fn in ipairs({...}) do for _,fn in ipairs({...}) do
local f = io.open(fn,"rb") local f = io.open(fn,"rb")
io.write(f:read("*a")) io.write(f:read("*a"))
@ -64,7 +63,7 @@ function shutil.cat(...) -- string -- -- Outputs the contents of files specified
end end
end end
function shutil.ps() -- Prints the processes running on the system. function shutil.ps()
print("PID# Parent | Name") print("PID# Parent | Name")
for k,v in pairs(os.tasks()) do for k,v in pairs(os.tasks()) do
local t = os.taskInfo(v) local t = os.taskInfo(v)
@ -72,7 +71,7 @@ function shutil.ps() -- Prints the processes running on the system.
end end
end end
function shutil.df() -- Prints free disk space. function shutil.df()
local mt = fs.mounts() local mt = fs.mounts()
local ml = 0 local ml = 0
for k,v in pairs(mt) do for k,v in pairs(mt) do
@ -83,12 +82,12 @@ function shutil.df() -- Prints free disk space.
local fstr = "%-"..tostring(ml).."s %5s %5s" local fstr = "%-"..tostring(ml).."s %5s %5s"
print("fs"..(" "):rep(ml-2).." size used") print("fs"..(" "):rep(ml-2).." size used")
for k,v in pairs(mt) do 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))) print(string.format(fstr,v,wrapUnits(st),wrapUnits(su)))
end end
end end
function shutil.mount(addr,path) -- string string -- boolean string -- Mounts filesystem component with address *addr* to *path* in the filesystem. function shutil.mount(addr,path)
if not addr then if not addr then
local mt = fs.mounts() local mt = fs.mounts()
for k,v in pairs(mt) do for k,v in pairs(mt) do
@ -114,43 +113,14 @@ function shutil.mount(addr,path) -- string string -- boolean string -- Mounts fi
end end
end end
function shutil.free() -- Displays used and free memory. function shutil.free()
print("Total Used Free") print("Total Used Free")
print(string.format("%5s %5s %5s",wrapUnits(computer.totalMemory()),wrapUnits(computer.totalMemory()-computer.freeMemory()),wrapUnits(computer.freeMemory()))) print(string.format("%5s %5s %5s",wrapUnits(computer.totalMemory()),wrapUnits(computer.totalMemory()-computer.freeMemory()),wrapUnits(computer.freeMemory())))
end 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.cd = os.chdir
shutil.mkdir = fs.makeDirectory shutil.mkdir = fs.makeDirectory
shutil.cp = fs.copy shutil.cp = fs.copy
shutil.rm = fs.remove shutil.rm = fs.remove
return setmetatable({}, {__index = function(t,k) return shutil
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 Normal file
View File

@ -0,0 +1,114 @@
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

197
lib/vcomponent.lua Normal file
View File

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

View File

@ -1,35 +1,7 @@
local vtansi = {} local vtansi = {}
local keyboardIgnore = {} function vtansi.vtemu(gpu) -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
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 colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
local mx, my = gpu.maxResolution() local mx, my = gpu.maxResolution()
local buffer = nil
local cx, cy = 1, 1 local cx, cy = 1, 1
local pc = " " local pc = " "
local lc = "" local lc = ""
@ -40,19 +12,8 @@ function vtansi.vtemu(gpu,bn) -- table number -- function -- takes GPU component
local bg, fg = 0, 0xFFFFFF local bg, fg = 0, 0xFFFFFF
-- setup -- setup
if gpu.getActiveBuffer then gpu.setResolution(mx,my)
buffer = bn or gpu.allocateBuffer(mx,my) gpu.fill(1,1,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() local function checkCursor()
if cx > mx and lw then if cx > mx and lw then
cx, cy = 1, cy+1 cx, cy = 1, cy+1
@ -67,13 +28,6 @@ function vtansi.vtemu(gpu,bn) -- table number -- function -- takes GPU component
end end
local function termwrite(s) 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 wb = ""
local lb, ec = nil, nil local lb, ec = nil, nil
local function flushwb() local function flushwb()
@ -96,9 +50,8 @@ function vtansi.vtemu(gpu,bn) -- table number -- function -- takes GPU component
if cc == "\n" then if cc == "\n" then
flushwb() flushwb()
cx,cy = 1, cy+1 cx,cy = 1, cy+1
checkCursor()
elseif cc == "\t" then elseif cc == "\t" then
wb=wb..(" "):rep((((cx//8)+1) * 8) - cx + 1) wb=wb..(" "):rep(8*((cx+9)//8))
elseif cc == "\27" then elseif cc == "\27" then
flushwb() flushwb()
mode = 1 mode = 1
@ -123,31 +76,20 @@ function vtansi.vtemu(gpu,bn) -- table number -- function -- takes GPU component
if cc == "H" then if cc == "H" then
cx, cy = math.min(mx,tA[1] or 1), math.min(my,tA[2] or 1) cx, cy = math.min(mx,tA[1] or 1), math.min(my,tA[2] or 1)
elseif cc == "A" then elseif cc == "A" then
for i = 1, (tA[1] or 1) do cy = cy - (tA[1] or 1)
cy = cy - 1
checkCursor()
end
elseif cc == "B" then elseif cc == "B" then
for i = 1, (tA[1] or 1) do cy = cy + (tA[1] or 1)
cy = cy + 1
checkCursor()
end
elseif cc == "C" then elseif cc == "C" then
for i = 1, (tA[1] or 1) do cx = cx + (tA[1] or 1)
cx = cx + 1
checkCursor()
end
elseif cc == "D" then elseif cc == "D" then
for i = 1, (tA[1] or 1) do cx = cx - (tA[1] or 1)
cx = cx - 1
checkCursor()
end
elseif cc == "s" then elseif cc == "s" then
sx, sy = cx, cy sx, sy = cx, cy
elseif cc == "u" then elseif cc == "u" then
cx, cy = sx, sy cx, cy = sx, sy
elseif cc == "n" and tA[1] == 6 then elseif cc == "n" and tA[1] == 6 then
rs = string.format("%s\27[%d;%dR",rs,cx,cy) 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 elseif cc == "K" and tA[1] == 1 then
gpu.fill(1,cy,cx,1," ") gpu.fill(1,cy,cx,1," ")
elseif cc == "K" and tA[1] == 2 then elseif cc == "K" and tA[1] == 2 then
@ -164,7 +106,7 @@ function vtansi.vtemu(gpu,bn) -- table number -- function -- takes GPU component
elseif cc == "m" then elseif cc == "m" then
for _,num in ipairs(tA) do for _,num in ipairs(tA) do
if num == 0 then if num == 0 then
fg,bg,ec,lb = 0xFFFFFF,0,false,true fg,bg,ec,lb = 0xFFFFFF,0,true,true
elseif num == 7 then elseif num == 7 then
local nfg,nbg = bg, fg local nfg,nbg = bg, fg
fg, bg = nfg, nbg fg, bg = nfg, nbg
@ -197,65 +139,48 @@ function vtansi.vtemu(gpu,bn) -- table number -- function -- takes GPU component
return rs, lb, ec return rs, lb, ec
end end
return termwrite, buffer return termwrite
end end
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. function vtansi.vtsession(gpua,scra) -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions.
local modifiers = {}
local gpu = component.proxy(gpua) local gpu = component.proxy(gpua)
-- gpu.bind(scra) gpu.bind(scra)
local write, bn = vtansi.vtemu(gpu,bn) local write = vtansi.vtemu(gpu)
local kba = {} local kba = {}
for k,v in ipairs(component.invoke(scra,"getKeyboards")) do for k,v in ipairs(component.invoke(scra,"getKeyboards")) do
kba[v]=true kba[v]=true
end end
local buf, lbuf, echo = "", false, false local buf, lbuf, echo = "", true, true
os.spawn(function() os.spawn(function() dprint(pcall(function()
while true do while true do
local ty,ka,ch,kc = coroutine.yield() local ty,ka,ch = coroutine.yield()
if kba[ka] and keyboardIgnore[ka] == bn then if ty == "key_down" and kba[ka] then
keyboardIgnore[ka] = nil if ch == 13 then ch = 10 end
end if ch == 8 and lbuf then
if kba[ka] and vtansi.keys[kc] then if buf:len() > 0 then
modifiers[vtansi.keys[kc]] = ty == "key_down" if echo then write("\8 \8") end
end buf = buf:sub(1,-2)
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
else
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
elseif ch > 0 then
if echo then write(string.char(ch)) end
buf=buf..string.char(ch)
end end
end end
end end
end,string.format("ttyd[%s:%s/%i]",gpua:sub(1,8),scra:sub(1,8),tonumber(bn) or 0)) end)) end,string.format("ttyd[%s:%s]",gpua:sub(1,8),scra:sub(1,8)))
local function bread(n) local function bread(n)
coroutine.yield() local r
local r = buf if lbuf then
buf = "" 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 return r
end end
local function bwrite(d) local function bwrite(d)

View File

@ -179,9 +179,7 @@ local env = {code = ""}
setmetatable(env, {__index=_env}) setmetatable(env, {__index=_env})
env:process(arg[1]) env:process(arg[1])
local tmpfile = os.tmpname() local tmpfile = os.tmpname()
if tmpfile:sub(#tmpfile) == "." then tmpfile = tmpfile:sub(1, #tmpfile - 1) end
local tmpf = io.open(tmpfile, "wb") local tmpf = io.open(tmpfile, "wb")
env.code = env.code:gsub("%-%-.-\n","\n"):gsub("\n\n","\n")
tmpf:write(env.code) tmpf:write(env.code)
tmpf:close() tmpf:close()
--if (os.execute("lua minify.lua "..tmpfile.." > "..arg[2])) then --if (os.execute("lua minify.lua "..tmpfile.." > "..arg[2])) then

View File

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

View File

@ -2,7 +2,7 @@
buffer = {} buffer = {}
function buffer.new(mode, stream) -- string table -- table -- create a new buffer in mode *mode* backed by stream object *stream* function buffer.new(mode, stream) -- create a new buffer in mode *mode* backed by stream object *stream*
local result = { local result = {
mode = {}, mode = {},
stream = stream, stream = stream,
@ -234,114 +234,26 @@ function buffer:read(...)
end end
local function readLine(chop) local function readLine(chop)
if not self.mode.t then local start = 1
local start = 1 while true do
while true do local l = self.bufferRead:find("\n", start, true)
local l = self.bufferRead:find("\n", start, true) if l then
if l then local result = self.bufferRead:sub(1, l + (chop and -1 or 0))
local result = self.bufferRead:sub(1, l + (chop and -1 or 0)) self.bufferRead = self.bufferRead:sub(l + 1)
self.bufferRead = self.bufferRead:sub(l + 1) return result
return result else
else start = #self.bufferRead
start = #self.bufferRead local result, reason = readChunk()
local result, reason = readChunk() if not result then
if not result then if reason then
if reason then return nil, reason
return nil, reason else -- eof
else -- eof local result = #self.bufferRead > 0 and self.bufferRead or nil
local result = #self.bufferRead > 0 and self.bufferRead or nil self.bufferRead = ""
self.bufferRead = "" return result
return result
end
end end
end 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
end end

View File

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

View File

@ -64,7 +64,7 @@ end
devfs.component.address = "devfs" devfs.component.address = "devfs"
devfs.component.type = "devfs" devfs.component.type = "devfs"
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. 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.
devfs.files[fname] = fopen devfs.files[fname] = fopen
end end

View File

@ -3,14 +3,14 @@ fs = {}
local fsmounts = {} local fsmounts = {}
-- basics -- basics
function fs.segments(path) -- string -- table -- Splits *path* on each / function fs.segments(path) -- splits *path* on each /
local segments = {} local segments = {}
for segment in path:gmatch("[^/]+") do for segment in path:gmatch("[^/]+") do
segments[#segments+1] = segment segments[#segments+1] = segment
end end
return segments return segments
end end
function fs.resolve(path) -- string -- string string -- Resolves *path* to a specific filesystem mount and path function fs.resolve(path) -- resolves *path* to a specific filesystem mount and path
if not path or path == "." then path = os.getenv("PWD") end if not path or path == "." then path = os.getenv("PWD") end
if path:sub(1,1) ~= "/" then path=(os.getenv("PWD") or "").."/"..path end if path:sub(1,1) ~= "/" then path=(os.getenv("PWD") or "").."/"..path end
local segments, rpath, rfs= fs.segments(path) local segments, rpath, rfs= fs.segments(path)
@ -44,7 +44,7 @@ local function fclose(self)
return fsmounts[self.fs].close(self.fid) return fsmounts[self.fs].close(self.fid)
end end
function fs.open(path,mode) -- string string -- table -- Opens file *path* with mode *mode*, returning a file object. function fs.open(path,mode) -- opens file *path* with mode *mode*
mode = mode or "rb" mode = mode or "rb"
local fsi,path = fs.resolve(path) local fsi,path = fs.resolve(path)
if not fsmounts[fsi] then return false end if not fsmounts[fsi] then return false end
@ -54,7 +54,7 @@ function fs.open(path,mode) -- string string -- table -- Opens file *path* with
if mode:find("r") then if mode:find("r") then
fobj.read = fread fobj.read = fread
end end
if mode:find("w") or mode:find("a") then if mode:find("w") then
fobj.write = fwrite fobj.write = fwrite
end end
return fobj return fobj
@ -62,67 +62,61 @@ function fs.open(path,mode) -- string string -- table -- Opens file *path* with
return false return false
end end
function fs.copy(from,to) -- string string -- boolean -- copies a file from *from* to *to* function fs.copy(from,to) -- copies a file from *from* to *to*
local of = fs.open(from,"rb") local of = fs.open(from,"rb")
local df = fs.open(to,"wb") local df = fs.open(to,"wb")
if not of or not df then if not of or not df then
return false return false
end end
local tmp df:write(of:read("*a"))
repeat
tmp = of:read(2048)
df:write(tmp or "")
until not tmp
df:close() df:close()
of:close() of:close()
return true
end end
function fs.rename(from,to) -- string string -- boolean -- Moves file *from* to *to* function fs.rename(from,to) -- moves file *from* to *to*
local ofsi, opath = fs.resolve(from) local ofsi, opath = fs.resolve(from)
local dfsi, dpath = fs.resolve(to) local dfsi, dpath = fs.resolve(to)
if ofsi == dfsi then if ofsi == dfsi then
fsmounts[ofsi].rename(opath,dpath) fsmounts[ofsi].rename(opath,dpath)
return true return true
end end
if not fs.copy(from,to) then return false end fs.copy(from,to)
if not fs.remove(from) then return false end fs.remove(from)
return true return true
end end
function fs.mount(path,proxy) -- string table -- boolean -- Mounts the filesystem *proxy* to the mount point *path* if it is a directory. BYO proxy. function fs.mount(path,proxy) -- 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 if fs.isDirectory(path) and not fsmounts[table.concat(fs.segments(path),"/")] then
fsmounts[table.concat(fs.segments(path),"/")] = proxy fsmounts[table.concat(fs.segments(path),"/")] = proxy
return true return true
end end
return false, "path is not a directory" return false, "path is not a directory"
end end
function fs.umount(path) -- string -- -- Unmounts filesystem from *path*. function fs.umount(path)
local fsi,_ = fs.resolve(path) local fsi,_ = fs.resolve(path)
fsmounts[fsi] = nil fsmounts[fsi] = nil
end end
function fs.mounts() -- -- table -- Returns a table containing the mount points of all mounted filesystems function fs.mounts() -- returns a table containing the mount points of all mounted filesystems
local rt = {} local rt = {}
for k,v in pairs(fsmounts) do for k,v in pairs(fsmounts) do
rt[#rt+1] = k,v.address or "unknown" rt[#rt+1] = k,v.address or "unknown"
end end
table.sort(rt)
return rt return rt
end end
function fs.address(path) -- string -- string -- Returns the address of the filesystem at a given path, if applicable; do not expect a sensical response function fs.address(path) -- returns the address of the filesystem at a given path, if applicable; do not expect a sensical response
local fsi,_ = fs.resolve(path) local fsi,_ = fs.resolve(path)
return fsmounts[fsi].address return fsmounts[fsi].address
end end
function fs.type(path) -- string -- string -- Returns the component type of the filesystem at a given path, if applicable function fs.type(path) -- returns the component type of the filesystem at a given path, if applicable
local fsi,_ = fs.resolve(path) local fsi,_ = fs.resolve(path)
return fsmounts[fsi].fstype or fsmounts[fsi].type or "filesystem" return fsmounts[fsi].type or "filesystem"
end end
fsmounts["/"] = component.proxy(computer.tmpAddress()) fsmounts["/"] = component.proxy(computer.tmpAddress())
fs.makeDirectory("temp") fs.makeDirectory("temp")
if computer.getBootAddress and component.type(computer.getBootAddress()) == "filesystem" then if computer.getBootAddress then
fs.makeDirectory("boot") fs.makeDirectory("boot")
fs.mount("boot",component.proxy(computer.getBootAddress())) fs.mount("boot",component.proxy(computer.getBootAddress()))
end end

View File

@ -1,4 +1,12 @@
_OSVERSION=_OSVERSION or "PsychOS 2" --#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"
os.spawn(function() os.spawn(function()
os.setenv("PWD","/boot") os.setenv("PWD","/boot")
@ -12,9 +20,6 @@ os.spawn(function()
end end
os.setenv("HOSTNAME",hostname) os.setenv("HOSTNAME",hostname)
syslog(string.format("Hostname set to %s",hostname)) syslog(string.format("Hostname set to %s",hostname))
if fs.exists("/boot/pkg") then
pcall(require,"pkgfs")
end
local pids = {} local pids = {}
local rc = require "rc" local rc = require "rc"
for k,v in pairs(rc.cfg.enabled) do for k,v in pairs(rc.cfg.enabled) do

View File

@ -1,21 +1,21 @@
io = {} io = {}
function io.open(path,mode) -- string string -- table -- Open file *path* in *mode*. Returns a buffer object. function io.open(path,mode) -- Open file *path* in *mode*. Returns a buffer object.
local f,e = fs.open(path, mode) local f,e = fs.open(path, mode)
if not f then return false, e end if not f then return false, e end
return buffer.new(mode,f) return buffer.new(mode,f)
end end
function io.input(fd) -- table -- table -- Sets the default input stream to *fd* if provided, either as a buffer as a path. Returns the default input stream. 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.
if type(fd) == "string" then if type(fd) == "string" then
fd=io.open(fd,"rbt") fd=io.open(fd,"rb")
end end
if fd then if fd then
os.setenv("STDIN",fd) os.setenv("STDIN",fd)
end end
return os.getenv("STDIN") return os.getenv("STDIN")
end end
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. 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.
if type(fd) == "string" then if type(fd) == "string" then
fd=io.open(fd,"wb") fd=io.open(fd,"wb")
end end
@ -32,9 +32,8 @@ function io.write(...) -- Writes its arguments to the default output stream.
io.output():write(...) io.output():write(...)
end end
function print(...) -- Writes each argument to the default output stream, separated by space. function print(...) -- Writes each argument to the default output stream, separated by newlines.
for k,v in ipairs({...}) do for k,v in ipairs({...}) do
io.write((k>1 and "\t" or "")..tostring(v)) io.write(tostring(v).."\n")
end end
io.write("\n")
end end

View File

@ -1,26 +1,26 @@
function loadfile(p) -- string -- function -- reads file *p* and returns a function if possible function loadfile(p) -- reads file *p* and returns a function if possible
local f = io.open(p,"rb") local f = io.open(p,"rb")
local c = f:read("*a") local c = f:read("*a")
f:close() f:close()
return load(c,p,"t") return load(c,p,"t")
end end
function runfile(p,...) -- string -- -- runs file *p* with arbitrary arguments in the current thread function runfile(p,...) -- runs file *p* with arbitrary arguments in the current thread
return loadfile(p)(...) return loadfile(p)(...)
end 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 = {} _G.package = {}
package.path="/boot/lib/?.lua;/pkg/lib/?.lua;/boot/lib/?/init.lua;/pkg/lib/?/init.lua;./?;./?.lua;./?/init.lua" package.loaded = {computer=computer,component=component,fs=fs,buffer=buffer}
package.loaded = {buffer=buffer, component=component, computer=computer, coroutine=coroutine, fs=fs, math=math, os=os, package=package, string=string, table=table} 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.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 if not package.loaded[f] or force then
local ln = f:gsub("%.","/") local lib = os.getenv("LIB") or "/boot/lib"
for d in package.path:gmatch("[^;]+") do for d in lib:gmatch("[^\n]+") do
local p = d:gsub("%?",ln) if fs.exists(d.."/"..f) then
if fs.exists(p) and not fs.isDirectory(p) then package.loaded[f] = runfile(d.."/"..f)
package.loaded[f] = runfile(p) elseif fs.exists(d.."/"..f..".lua") then
break package.loaded[f] = runfile(d.."/"..f..".lua")
end end
end end
end end
@ -29,6 +29,6 @@ function require(f,force) -- string boolean -- table -- searches for a library w
end end
error("library not found: "..f) error("library not found: "..f)
end end
function reload(f) -- string -- table -- Reloads library *f* from disk into memory. function reload(f)
return require(f,true) return require(f,true)
end end

141
module/nvt100.lua Normal file
View File

@ -0,0 +1,141 @@
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

View File

@ -1,4 +1,4 @@
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 function os.chdir(p) -- 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 if not (p:sub(1,1) == "/") then
local np = {} local np = {}
for k,v in pairs(fs.segments(os.getenv("PWD").."/"..p)) do for k,v in pairs(fs.segments(os.getenv("PWD").."/"..p)) do

View File

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

View File

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

View File

@ -9,7 +9,6 @@ syslog.notice = 5
syslog.info = 6 syslog.info = 6
syslog.debug = 7 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 local rdprint=dprint or function() end
setmetatable(syslog,{__call = function(_,msg, level, service) setmetatable(syslog,{__call = function(_,msg, level, service)
level, service = level or syslog.info, service or (os.taskInfo(os.pid()) or {}).name or "unknown" level, service = level or syslog.info, service or (os.taskInfo(os.pid()) or {}).name or "unknown"

5
module/term.lua Normal file
View File

@ -0,0 +1,5 @@
--#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

58
module/vt-task.lua Normal file
View File

@ -0,0 +1,58 @@
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 Normal file
View File

@ -0,0 +1,148 @@
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

View File

@ -1,31 +0,0 @@
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

View File

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

View File

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

95
service/fserv.lua Normal file
View File

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

View File

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

View File

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

View File

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

162
service/vtunnel.lua Normal file
View File

@ -0,0 +1,162 @@
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

View File

@ -1,46 +0,0 @@
\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}