Compare commits

..

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

43 changed files with 809 additions and 1482 deletions

View File

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

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
*.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,13 +1,12 @@
#!/bin/bash
LUA=${LUA:-lua}
KVAR=${1:-base}
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
$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/
rm target/version.lua
rm -r doc/ &>/dev/null
$LUA finddesc.lua doc/ $(find lib/ module/ -type f|sort)

1
cfg/rc.cfg Normal file
View File

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

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

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

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

@ -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))

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

@ -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.."_")
local px = rpc.proxy(host,rpath.."_")
function px.getLabel()
return host..":"..rpath
end
@ -15,18 +16,10 @@ end
function netutil.exportfs(path) -- string -- boolean -- 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

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")
@ -24,17 +23,16 @@ local function saveConfig()
end
function rc.load(name,force) -- string boolean -- table -- Attempts to load service *name*, and if *force* is true, replaces the current instance.
if 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")
if force then
rc.stop(name)
service[name] = nil
end
end
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.

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 = {}
@ -56,13 +39,10 @@ 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)
if table.remove(rt,1) == rv then
return table.unpack(rt)
end
error(rt[3])
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*.
filter=(filter or "").."(.+)"
@ -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

@ -21,16 +21,12 @@ 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")
else
local input = io.read()
if input:sub(1,1) == "=" then
input = "return "..input:sub(2)
end
@ -38,7 +34,7 @@ function shell.interactive()
if not f then
print("\27[31m"..r)
else
local rt = {xpcall(f,debug.traceback)}
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
@ -46,7 +42,6 @@ function shell.interactive()
end
end
end
end
end
return shell

View File

@ -83,7 +83,7 @@ function shutil.df() -- Prints free disk space.
local fstr = "%-"..tostring(ml).."s %5s %5s"
print("fs"..(" "):rep(ml-2).." size used")
for k,v in pairs(mt) do
local st, su = fs.spaceTotal("/"..v), fs.spaceUsed("/"..v)
local st, su = fs.spaceTotal(v), fs.spaceUsed(v)
print(string.format(fstr,v,wrapUnits(st),wrapUnits(su)))
end
end
@ -119,38 +119,9 @@ function shutil.free() -- Displays used and free memory.
print(string.format("%5s %5s %5s",wrapUnits(computer.totalMemory()),wrapUnits(computer.totalMemory()-computer.freeMemory()),wrapUnits(computer.freeMemory())))
end
function shutil.which(name)
local fpath
for _,dir in ipairs(os.getenv("PATH")) do
fpath = fpath or fs.exists(string.format("%s/%s.lua",dir,name)) and string.format("%s/%s.lua",dir,name) or fs.exists(string.format("%s/%s",dir,name)) and string.format("%s/%s",dir,name)
end
return fpath
end
shutil.cd = os.chdir
shutil.mkdir = fs.makeDirectory
shutil.cp = fs.copy
shutil.rm = fs.remove
return 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(...) -- string -- table -- Returns a unionfs object of the directories specified in *...*.
local paths,fids,fc = {...}, {}, 0
for k,v in pairs(paths) do
paths[k] = "/"..normalise(v)
end
local proxy = {}
local function realpath(path)
path = path or ""
for k,v in pairs(paths) do
if fs.exists(v.."/"..path) then
return v.."/"..path
end
end
return paths[1].."/"..path
end
function proxy.setLabel()
return false
end
function proxy.spaceUsed()
return fs.spaceUsed(paths[1])
end
function proxy.spaceTotal()
return fs.spaceTotal(paths[1])
end
function proxy.isReadOnly()
return fs.isReadOnly(paths[1])
end
function proxy.isDirectory(path)
return fs.isDirectory(realpath(path))
end
function proxy.lastModified(path)
return fs.lastModified(realpath(path))
end
function proxy.getLabel()
return fs.getLabel(paths[1])
end
function proxy.exists(path)
return fs.exists(realpath(path))
end
function proxy.remove(path)
return fs.remove(realpath(path))
end
function proxy.size(path)
return fs.size(realpath(path))
end
function proxy.list(path)
local nt,rt = {},{}
if #fs.segments(path) < 1 then
for k,v in pairs(paths) do
for l,m in ipairs(fs.list(v.."/"..path)) do
nt[m] = true
end
end
for k,v in pairs(nt) do
rt[#rt+1] = k
end
table.sort(rt)
return rt
else
return fs.list(realpath(path))
end
end
function proxy.open(path,mode)
local fh, r = fs.open(realpath(path),mode)
if not fh then return fh, r end
fids[fc] = fh
fc = fc + 1
return fc - 1
end
function proxy.close(fid)
if not fids[fid] then
return false, "file not open"
end
local rfh = fids[fid]
fids[fid] = nil
return rfh:close()
end
function proxy.write(fid,d)
if not fids[fid] then
return false, "file not open"
end
return fids[fid]:write(d)
end
function proxy.read(fid,d)
if not fids[fid] then
return false, "file not open"
end
local rb = fids[fid]:read(d)
if rb == "" then rb = nil end
return rb
end
function proxy.seek(fid,d)
if not fids[fid] then
return false, "file not open"
end
return fids[fid]:seek(d)
end
return proxy
end
return unionfs

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) -- table -- function -- 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
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) -- string string -- table -- 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
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
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
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/%i]",gpua:sub(1,8),scra:sub(1,8),tonumber(bn) or 0))
end)) end,string.format("ttyd[%s:%s]",gpua:sub(1,8),scra:sub(1,8)))
local function bread(n)
local r
if lbuf then
while not buf:find("\n") do
coroutine.yield()
local r = buf
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

@ -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

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

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

@ -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
@ -68,11 +68,7 @@ function fs.copy(from,to) -- string string -- boolean -- copies a file from *fro
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
@ -107,7 +103,6 @@ function fs.mounts() -- -- table -- Returns a table containing the mount points
for k,v in pairs(fsmounts) do
rt[#rt+1] = k,v.address or "unknown"
end
table.sort(rt)
return rt
end
@ -117,12 +112,12 @@ function fs.address(path) -- string -- string -- Returns the address of the file
end
function fs.type(path) -- string -- string -- Returns the component type of the filesystem at a given path, if applicable
local fsi,_ = fs.resolve(path)
return fsmounts[fsi].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

@ -8,7 +8,7 @@ end
function io.input(fd) -- table -- table -- Sets the default input stream to *fd* if provided, either as a buffer as a path. Returns the default input stream.
if type(fd) == "string" then
fd=io.open(fd,"rbt")
fd=io.open(fd,"rb")
end
if fd then
os.setenv("STDIN",fd)
@ -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

@ -7,20 +7,20 @@ end
function runfile(p,...) -- string -- -- runs file *p* with arbitrary arguments in the current thread
return loadfile(p)(...)
end
function os.spawnfile(p,n,...) -- string string -- number -- spawns a new process from file *p* with name *n*, with arguments following *n*.
local tA = {...}
return os.spawn(function() local res={pcall(loadfile(p), table.unpack(tA))} computer.pushSignal("process_finished", os.pid(), table.unpack(res)) dprint(table.concat(res)) end,n or p)
end
_G.package = {}
package.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"}
package.loaded = {computer=computer,component=component,fs=fs,buffer=buffer}
function require(f,force) -- string boolean -- table -- searches for a library with name *f* and returns what the library returns, if possible. if *force* is set, loads the library even if it is cached
f=package.alias[f] or f
if not package.loaded[f] or force then
local 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

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
local tTasks,nPid,nTimeout,cPid = {},1,0.25,0 -- table of tasks, next process ID, event timeout, current PID
function os.spawn(f,n) -- function string -- number -- creates a process from function *f* with name *n*
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
@ -41,21 +32,16 @@ end
function os.taskInfo(pid) -- number -- table -- returns info on process *pid* as a table with name and parent values
pid = pid or os.pid()
if not tTasks[pid] then return false end
return {name=tTasks[pid].n,parent=tTasks[pid].P,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
function os.setTimeout(n)
if type(n) == "number" and n >= 0 then
nTimeout = n
return true
end
return false
end
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
while true do
local eT = {coroutine.yield()}
if eT[1] == "clipboard" then
if computer.uptime() > lc + 5 then
_G.clip = ""
end
lc = computer.uptime()
_G.clip = _G.clip .. eT[3]
end
end
end)
end

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