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
*.af
apidoc.md
/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 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
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")
./luapreproc.lua module/init.lua kernel.lua
### The boot filesystem
A boot filesystem contains several things:
- The kernel, as init.lua
- The exec/ directory, as this contains all executables
- The lib/ directory, containing libraries
- The service/ directory, containing system services
- The exec/ directory, containing single-shot executable files
This has been automated in the form of build.sh, pending a real makefile.
## Documentation
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
LUA=${LUA:-lua}
KVAR=${1:-base}
#!/bin/sh
rm -r target/*
mkdir -p target/doc &>/dev/null
$LUA build.lua kcfg/$KVAR.cfg target/init.lua
echo _OSVERSION=\"PsychOS 2.0a3-$(git rev-parse --short HEAD)-$KVAR\" > target/version.lua
mkdir target &>/dev/null
lua luapreproc.lua module/init.lua target/init.lua
echo _OSVERSION=\"PsychOS 2.0a2-$(git rev-parse --short HEAD)\" > target/version.lua
cat target/version.lua target/init.lua > target/tinit.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 -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 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"
local docfiles = {}
for _,file in pairs(tA) do
docfiles[file] = {}
local f = io.open(file)
local lines = {}
for l in f:read("*a"):gmatch("[^\n]+") do
if l:find("function") and not l:find("local") then
lines[#lines+1] = l
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
os.execute("sh -c 'mkdir -p "..outpath .. "'")
if outpath:sub(#outpath) == "/" then outpath = outpath:sub(1, #outpath - 1) end
local ad = io.open(outpath.."/apidoc.md","w")
for k,v in pairs(tA) do
local fd = doc.parsefile(v)
local ds = formatDocs(fd)
print(string.format("%s: %i",v,ds:len()))
if ds and ds:len() > 0 then
os.execute("sh -c 'mkdir -p $(dirname \""..outpath.."/"..v.."\")'")
local f = io.open(outpath.."/"..v:gsub("%.lua$",".md"),"wb")
f:write(string.format("# %s\n\n",v))
f:write(ds)
f:write("\n\n")
f:close()
ad:write(string.format("# %s\n\n",v))
ad:write(ds)
ad:write("\n\n")
for k,v in pairs(lines) do
local name, args, desc = v:match("function%s+(.+)%s*%((.*)%)%s*%-%-%s*(.+)")
if name and args and desc then
docfiles[file][#docfiles[file]+1] = string.format("##%s(%s)\n%s",name,args,desc)
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)
local path = fpath or self.path
if not path then return false, "no path" end
self.path = path
self.path = path
local f = io.open(path,"wb")
if not f then return false, "unable to open file" end
for k,v in ipairs(self) do
@ -112,7 +112,6 @@ function ed.newBuffer()
end
function ed.open(buffer)
local bpath = buffer
if ed.buffers[buffer] then
buffer = ed.buffers[buffer]
end
@ -121,9 +120,7 @@ function ed.open(buffer)
nb:load(buffer)
buffer = nb
end
if type(buffer) ~= "table" then buffer = ed.newBuffer() end
buffer[1] = buffer[1] or ""
buffer.path = buffer.path or "/"..((bpath:sub(1,1) == "/" and table.concat(fs.segments(path),"/")) or table.concat(fs.segments(os.getenv("PWD").."/"..bpath),"/"))
if type(buffer) ~= "table" then buffer = ed.newBuffer() buffer[1] = "" end
return buffer
end
@ -163,10 +160,10 @@ function ed.visual(buffer)
if cx ~= ox or cy ~= oy or force then
io.write("\27[2J\27[H")
for i = cy, cy+my do
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
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
io.write(string.format("\27[1;%iH\27[0;36;%im\27[2K[%s] ced visual: %i,%i/%i, %iK free %i",my+2,(mode == "c" and 7) or 0, mode, buffer.x, buffer.y, #buffer, computer.freeMemory()//1024,mult))
io.write(string.format("\27[%i;%iH\27[0m",buffer.x+6-cx,buffer.y-cy+1))

View File

@ -1,5 +1,5 @@
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 = {...}
if type(t) == "string" then
table.insert(tA,1,t)
@ -26,7 +26,7 @@ function event.pull(t,...) -- number -- -- return an event, optionally with time
return nil
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
local tEv = {coroutine.yield()}
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
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))
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.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 = ""
for i = 1, 16 do
npID = npID .. string.char(math.random(32,126))
@ -20,11 +20,11 @@ function net.genPacketID() -- -- string -- generate a random 16-character string
return npID
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)
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
computer.pushSignal("net_send",1,to,port,data,pid)
if block then return false end
@ -37,7 +37,7 @@ end
-- ordered packet delivery, layer 4?
function net.send(to,port,ldata) -- 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 = {}
if ldata:len() > net.mtu then
for i = 1, ldata:len(), net.mtu do
@ -112,7 +112,7 @@ local function socket(addr,port,sclose)
return conn
end
function net.open(to,port) -- 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
local st = computer.uptime()+net.streamdelay
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)
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
_, from, rport, data = event.pull("net_msg")
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)
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)
if rport == port and data == "openstream" then
local nport = math.random(net.minport,net.maxport)

View File

@ -1,11 +1,12 @@
local computer = require "computer"
local minitel = require "minitel"
local event = require "event"
local ufs = require "unionfs"
local rpc = require "rpc"
local netutil = {}
function netutil.importfs(host,rpath,lpath) -- string string string -- boolean -- Import filesystem *rpath* from *host* and attach it to *lpath*.
local px = rpc.proxy(host,"fs_"..rpath.."_")
function netutil.importfs(host,rpath,lpath) -- import filesystem *rpath* from *host* and attach it to *lpath*
local px = rpc.proxy(host,rpath.."_")
function px.getLabel()
return host..":"..rpath
end
@ -13,25 +14,17 @@ function netutil.importfs(host,rpath,lpath) -- string string string -- boolean -
return fs.mount(lpath,px)
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 px = require("unionfs").create(path)
function px.dirstat(p)
local rt = {}
for k,v in ipairs(px.list(p)) do
local fp = p.."/"..v
rt[v] = {px.isDirectory(fp), px.size(fp), px.lastModified(fp)}
end
return rt
end
local px = ufs.create(path)
for k,v in pairs(px) do
rpc.register("fs_"..path.."_"..k,v)
print("fs_"..path.."_"..k)
rpc.register(path.."_"..k,v)
print(path.."_"..k)
end
return true
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 success, fail, time, avg = 0, 0, 0, 0
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
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
local socket = minitel.open(host,port)
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")
end
until socket.state ~= "open"
return true
end
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 rc = {}
rc.paths = "/boot/service\n/pkg/service"
rc.pids = {}
local service = {}
local cfg = {}
cfg.enabled = {"getty","minitel","fsmanager"}
cfg.enabled = {"getty","minitel"}
local function loadConfig()
local f = io.open("/boot/cfg/rc.cfg","rb")
@ -23,21 +22,20 @@ local function saveConfig()
return true
end
function rc.load(name,force) -- string boolean -- table -- Attempts to load service *name*, and if *force* is true, replaces the current instance.
if not service[name] or force then
for d in rc.paths:gmatch("[^\n]+") do
if fs.exists(d.."/"..name..".lua") then
service[name] = runfile(d.."/"..name..".lua")
end
end
function rc.load(name,force)
if force then
rc.stop(name)
service[name] = nil
end
if service[name] then
return service[name]
end
return false, "unable to load service "..name
if service[name] then return true end
service[name] = setmetatable({},{__index=_G})
local f = io.open("/boot/service/"..name..".lua","rb")
local res = load(f:read("*a"),name,"t",service[name])()
f:close()
return res
end
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 service[name].stop then
service[name].stop(...)
@ -49,7 +47,7 @@ function rc.stop(name,...) -- string -- boolean string -- Stops service *name*,
rc.pids[name] = nil
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)
if not service[name] then return false, "service not found" end
local rv = {service[name].start(...)}
@ -58,19 +56,19 @@ function rc.start(name,...) -- string -- boolean string -- Stops service *name*,
end
end
function rc.restart(name) -- string -- -- Restarts service *name* using rc.stop and rc.start.
function rc.restart(name)
rc.stop(name)
rc.start(name)
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
if v == name then return false end
end
cfg.enabled[#cfg.enabled+1] = name
saveConfig()
end
function rc.disable(name) -- string -- -- Disables service *name* being started on startup.
function rc.disable(name)
local disabled = false
for k,v in pairs(cfg.enabled) do
if v == name then table.remove(cfg.enabled,k) disabled = true break end

View File

@ -5,36 +5,19 @@ local rpc = {}
_G.rpcf = {}
rpc.port = 111
local function setacl(self, fname, host)
self[fname] = self[fname] or {}
self[fname][host] = true
end
-- function rpc.allow(fn, host) -- string string -- -- Enable the allow list for function *fname* and add *host* to it.
-- function rpc.deny(fn, host) -- string string -- -- Enable the deny list for function *fname* and add *host* to it.
rpc.allow = setmetatable({},{__call=setacl})
rpc.deny = setmetatable({},{__call=setacl})
local function isPermitted(host,fn)
if rpc.allow[fn] then
return rpc.allow[fn][host] or false
end
if rpc.deny[fn] and rpc.deny[fn][host] then
return false
end
return true
end
local function rpcexec(_, from, port, data)
if port ~= rpc.port then return false end
os.spawn(function()
local rpcrq = serial.unserialize(data) or {}
if rpcf[rpcrq[1]] and isPermitted(from,rpcrq[1]) then
os.setenv("RPC_CLIENT",from)
minitel.send(from,port,serial.serialize({rpcrq[2],pcall(rpcf[rpcrq[1]],table.unpack(rpcrq,3))}))
elseif type(rpcrq[2]) == "string" then
minitel.send(from,port,serial.serialize({rpcrq[2],false,"function unavailable"}))
if port == rpc.port then
local rpcrq = serial.unserialize(data)
local rpcn, rpcid = table.remove(rpcrq,1), table.remove(rpcrq,1)
if rpcf[rpcn] then
local rt = {pcall(rpcf[rpcn],table.unpack(rpcrq))}
if rt[1] == true then
table.remove(rt,1)
end
minitel.send(from,port,serial.serialize({rpcid,table.unpack(rt)}))
else
end
end
end,"rpc worker for "..tostring(from))
end
function rpcf.list()
local rt = {}
@ -44,7 +27,7 @@ function rpcf.list()
return rt
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
return rpcf[fn](...)
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)
rt = serial.unserialize(tostring(data)) or {}
until (type(rt) == "table" and rt[1] == rv) or computer.uptime() > st + 30
if rt[1] == rv then
if rt[2] then
return table.unpack(rt,3)
end
error(rt[3])
if table.remove(rt,1) == rv then
return table.unpack(rt)
end
error("timed out")
return false
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 "").."(.+)"
local fnames = rpc.call(hostname,"list")
if not fnames then return false end
@ -79,7 +59,7 @@ function rpc.proxy(hostname,filter) -- string string -- table -- Returns a compo
end
return rt
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
for k,v in pairs(os.tasks()) do
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
os.spawn(function()
while true do
pcall(rpcexec,event.pull("net_msg"))
rpcexec(event.pull("net_msg"))
end
end,"rpc daemon")
end
rpcf[name] = fn
end
function rpc.unregister(name) -- string -- -- Removes a function from the RPC function registry, clearing any ACL rules.
rpcf[name] = nil
rpc.allow[name] = nil
rpc.deny[name] = nil
end
return rpc

View File

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

View File

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

View File

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

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

View File

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

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

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.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
end

View File

@ -3,14 +3,14 @@ fs = {}
local fsmounts = {}
-- basics
function fs.segments(path) -- string -- table -- Splits *path* on each /
function fs.segments(path) -- splits *path* on each /
local segments = {}
for segment in path:gmatch("[^/]+") do
segments[#segments+1] = segment
end
return segments
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 path:sub(1,1) ~= "/" then path=(os.getenv("PWD") or "").."/"..path end
local segments, rpath, rfs= fs.segments(path)
@ -44,7 +44,7 @@ local function fclose(self)
return fsmounts[self.fs].close(self.fid)
end
function fs.open(path,mode) -- 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"
local fsi,path = fs.resolve(path)
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
fobj.read = fread
end
if mode:find("w") or mode:find("a") then
if mode:find("w") then
fobj.write = fwrite
end
return fobj
@ -62,67 +62,61 @@ function fs.open(path,mode) -- string string -- table -- Opens file *path* with
return false
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 df = fs.open(to,"wb")
if not of or not df then
return false
end
local tmp
repeat
tmp = of:read(2048)
df:write(tmp or "")
until not tmp
df:write(of:read("*a"))
df:close()
of:close()
return true
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 dfsi, dpath = fs.resolve(to)
if ofsi == dfsi then
fsmounts[ofsi].rename(opath,dpath)
return true
end
if not fs.copy(from,to) then return false end
if not fs.remove(from) then return false end
fs.copy(from,to)
fs.remove(from)
return true
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
fsmounts[table.concat(fs.segments(path),"/")] = proxy
return true
end
return false, "path is not a directory"
end
function fs.umount(path) -- string -- -- Unmounts filesystem from *path*.
function fs.umount(path)
local fsi,_ = fs.resolve(path)
fsmounts[fsi] = nil
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 = {}
for k,v in pairs(fsmounts) do
rt[#rt+1] = k,v.address or "unknown"
end
table.sort(rt)
return rt
end
function fs.address(path) -- 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)
return fsmounts[fsi].address
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)
return fsmounts[fsi].fstype or fsmounts[fsi].type or "filesystem"
return fsmounts[fsi].type or "filesystem"
end
fsmounts["/"] = component.proxy(computer.tmpAddress())
fs.makeDirectory("temp")
if computer.getBootAddress and component.type(computer.getBootAddress()) == "filesystem" then
if computer.getBootAddress then
fs.makeDirectory("boot")
fs.mount("boot",component.proxy(computer.getBootAddress()))
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.setenv("PWD","/boot")
@ -12,9 +20,6 @@ os.spawn(function()
end
os.setenv("HOSTNAME",hostname)
syslog(string.format("Hostname set to %s",hostname))
if fs.exists("/boot/pkg") then
pcall(require,"pkgfs")
end
local pids = {}
local rc = require "rc"
for k,v in pairs(rc.cfg.enabled) do

View File

@ -1,21 +1,21 @@
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)
if not f then return false, e end
return buffer.new(mode,f)
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
fd=io.open(fd,"rbt")
fd=io.open(fd,"rb")
end
if fd then
os.setenv("STDIN",fd)
end
return os.getenv("STDIN")
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
fd=io.open(fd,"wb")
end
@ -32,9 +32,8 @@ function io.write(...) -- Writes its arguments to the default output stream.
io.output():write(...)
end
function print(...) -- Writes each argument to the default output stream, separated by space.
function print(...) -- Writes each argument to the default output stream, separated by newlines.
for k,v in ipairs({...}) do
io.write((k>1 and "\t" or "")..tostring(v))
io.write(tostring(v).."\n")
end
io.write("\n")
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 c = f:read("*a")
f:close()
return load(c,p,"t")
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)(...)
end
function os.spawnfile(p,n,...) -- spawns a new process from file *p* with name *n*, with arguments following *n*.
local tA = {...}
return os.spawn(function() local res={pcall(loadfile(p), table.unpack(tA))} computer.pushSignal("process_finished", os.pid(), table.unpack(res)) dprint(table.concat(res)) end,n or p)
end
_G.package = {}
package.path="/boot/lib/?.lua;/pkg/lib/?.lua;/boot/lib/?/init.lua;/pkg/lib/?/init.lua;./?;./?.lua;./?/init.lua"
package.loaded = {buffer=buffer, component=component, computer=computer, coroutine=coroutine, fs=fs, math=math, os=os, package=package, string=string, table=table}
package.alias = {filesystem="fs"}
function require(f,force) -- string boolean -- table -- searches for a library with name *f* and returns what the library returns, if possible. if *force* is set, loads the library even if it is cached
f=package.alias[f] or f
package.loaded = {computer=computer,component=component,fs=fs,buffer=buffer}
function require(f,force) -- searches for a library with name *f* and returns what the library returns, if possible. if *force* is set, loads the library even if it is cached
if not package.loaded[f] or force then
local ln = f:gsub("%.","/")
for d in package.path:gmatch("[^;]+") do
local p = d:gsub("%?",ln)
if fs.exists(p) and not fs.isDirectory(p) then
package.loaded[f] = runfile(p)
break
local lib = os.getenv("LIB") or "/boot/lib"
for d in lib:gmatch("[^\n]+") do
if fs.exists(d.."/"..f) then
package.loaded[f] = runfile(d.."/"..f)
elseif fs.exists(d.."/"..f..".lua") then
package.loaded[f] = runfile(d.."/"..f..".lua")
end
end
end
@ -29,6 +29,6 @@ function require(f,force) -- string boolean -- table -- searches for a library w
end
error("library not found: "..f)
end
function reload(f) -- string -- table -- Reloads library *f* from disk into memory.
function reload(f)
return require(f,true)
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
local np = {}
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
local tTasks,nPid,nTimeout,cPid = {},1,0.25,0 -- table of tasks, next process ID, default event timeout, current PID
function os.spawn(f,n) -- function string -- number -- creates a process from function *f* with name *n*
local tTasks,nPid,nTimeout,cPid = {},1,0.25,0 -- table of tasks, next process ID, event timeout, current PID
function os.spawn(f,n) -- creates a process from function *f* with name *n*
tTasks[nPid] = {
c=coroutine.create(function()
local rt = {pcall(f)}
if not rt[1] then
syslog(rt[2])
end
computer.pushSignal("process_finished",os.pid(),table.unpack(rt))
end), -- actual coroutine
c=coroutine.create(f), -- actual coroutine
n=n, -- process name
p=nPid, -- process PID
P=cPid, -- parent PID
t=0, -- CPU time
T=0, -- total uptime
E=nTimeout, -- event timeout
e={} -- environment variables
}
if tTasks[cPid] then
@ -25,37 +16,32 @@ function os.spawn(f,n) -- function string -- number -- creates a process from fu
nPid = nPid + 1
return nPid - 1
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
end
function os.pid() -- -- number -- returns the current process' PID
function os.pid() -- returns the current process' PID
return cPid
end
function os.tasks() -- -- table -- returns a table of process IDs
function os.tasks() -- returns a table of process IDs
local rt = {}
for k,v in pairs(tTasks) do
rt[#rt+1] = k
end
return rt
end
function os.taskInfo(pid) -- 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()
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
function os.sched() -- the actual scheduler function
os.sched = nil
local sTimeout = nTimeout
while #tTasks > 0 do
local tEv = {computer.pullSignal(sTimeout)}
sTimeout = nTimeout
local tEv = {computer.pullSignal(nTimeout)}
for k,v in pairs(tTasks) do
if coroutine.status(v.c) ~= "dead" then
cPid = k
local sT, sC = os.clock(), computer.uptime()
coroutine.resume(v.c,table.unpack(tEv))
v.t, v.T = v.t + os.clock() - sT, v.T + computer.uptime() - sC
sTimeout=math.min(sTimeout, v.E)
else
tTasks[k] = nil
end
@ -72,9 +58,11 @@ function os.getenv(k) -- gets a process' *k* environment variable
return tTasks[cPid].e[k]
end
end
function os.setTimeout(n,pid)
assert(type(n) == "number" and n >= 0)
tTasks[pid or cPid].E = n
return true
function os.setTimeout(n)
if type(n) == "number" and n >= 0 then
nTimeout = n
return true
end
return false
end
end

View File

@ -9,7 +9,6 @@ syslog.notice = 5
syslog.info = 6
syslog.debug = 7
-- function syslog(msg, level, service) -- string number string -- -- Output *msg* to the system log, with severity *level*, from *service*.
local rdprint=dprint or function() end
setmetatable(syslog,{__call = function(_,msg, level, service)
level, service = level or syslog.info, service or (os.taskInfo(os.pid()) or {}).name or "unknown"

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

View File

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

View File

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

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}