Compare commits

..

130 Commits

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

Greatly appreciated.
2020-05-14 14:11:23 +10:00
56af6d1ade Fix document generation on Windows (using Git Bash)
* Made finddesc.lua execute commands using `sh -c 'command'`, which ensures that Unix-like shell is used instead of cmd.exe
* Made finddesc.lua avoid a situation where it would end up with a double '/' in `outpath`
* Redirect output of deleting the document to null to avoid pointless "No such file or directory" errors.
2020-05-12 16:29:44 +01:00
b89ff14d5c added type annotations and descriptions for functions where appropriate 2020-05-12 17:55:05 +10:00
7ddece288b added doc() support for pre-compiled API documentation, wrote a generator for it, and included that in the build script 2020-05-12 16:59:17 +10:00
cf373668a9 added a title for PDF generation 2020-05-12 16:11:51 +10:00
b3cfeb13ec remove replaced kernel modules 2020-05-12 12:08:01 +10:00
b4ee8ed8a6 point build.sh to the right finddesc 2020-05-12 12:04:47 +10:00
4f3bac551e updated finddesc to use the new doc library, allowing type annotations and building a directory of documentation 2020-05-12 12:01:44 +10:00
a917016a66 fix type annotation in event.ignore 2020-05-12 11:44:05 +10:00
d47a0748bd added type annotations to documentation for various libraries 2020-05-12 10:57:13 +10:00
405ee6408d Merge pull request 'Fix the build process so that it works on the Bash shell for Git on Windows and fix markdown output' (#2) from Skye/OC-PsychOS2:git-windows-bash-fixes into master
Doesn't seem to break anything, though I wish Microsoft would fix their filesystems.

Markdown changes are a nice touch, though they'll be replaced soon.

Might think about using #!/usr/bin/env for Lua at some point.
2020-05-12 10:55:37 +10:00
e09650276a Improve markdown output from finddesc.lua
* It didn't add spaces after the ## or #, which made them not work as headings in some renderers, so I fixed this.
* I then made it add newlines to make it look nicer without being rendered.
2020-05-12 01:49:51 +01:00
daa2975fd6 Fix the build process so that it works on the Bash shell for Git on Windows
1. Made the Lua thing used be an optional variable, so it works for different Lua versions and locations

2. Made it work better with windows filesystems being weird with trailing dots.
2020-05-11 20:09:41 +01:00
d59cc53340 updated netutil to use the documentation library's type signature system 2020-05-11 01:00:13 +10:00
cae7c916ae made the shell include the documentation library automatically 2020-05-11 00:59:50 +10:00
b465aebbdb added a library for retrieving documentation 2020-05-11 00:59:05 +10:00
fd9e4ad88a Merge pull request 'Add support for \27[8m (equiv. to nonstandard \27[100m)' (#1) from Ocawesome101/OC-PsychOS2:master into master 2020-04-26 11:57:38 +10:00
58 changed files with 1839 additions and 1248 deletions

5
.editorconfig Normal file
View File

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

82
INSTALL.md Normal file
View File

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

View File

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

4
build.lua Normal file
View File

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

View File

@ -1,10 +1,15 @@
#!/bin/sh
#!/bin/bash
LUA=${LUA:-lua}
KVAR=${1:-base}
rm -r target/*
mkdir target &>/dev/null
lua luapreproc.lua module/init.lua target/init.lua
echo _OSVERSION=\"PsychOS 2.0a2-$(git rev-parse --short HEAD)\" > target/version.lua
mkdir -p target/doc &>/dev/null
$LUA build.lua kcfg/$KVAR.cfg target/init.lua
echo _OSVERSION=\"PsychOS 2.0a3-$(git rev-parse --short HEAD)-$KVAR\" > target/version.lua
cat target/version.lua target/init.lua > target/tinit.lua
mv target/tinit.lua target/init.lua
cp -r service/ lib/ cfg/ target/
lua finddesc.lua $(find module/ -type f) $(find lib/ -type f) > apidoc.md
cp -r service/ lib/ cfg/ exec/ target/
rm target/version.lua
rm -r doc/ &>/dev/null
$LUA finddesc.lua doc/ $(find lib/ module/ -type f|sort)
$LUA gendoc.lua target/doc/kernel.dict $(find module/ -type f|sort)
pandoc doc/apidoc.md docs-metadata.yml --template=template.tex -o doc/apidoc.pdf

View File

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

3
docs-metadata.yml Normal file
View File

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

View File

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

51
gendoc.lua Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env lua
local doc = require "lib/doc"
local ser = require "lib/serialization"
local tA = {...}
local outpath = table.remove(tA,1)
print(outpath)
local function formatDocs(fd)
local rs = ""
for name,finfo in pairs(fd) do
if rs:len() > 0 then
rs = rs .. "\n\n"
end
local as = ""
for k,v in pairs(finfo.args) do
if k > 1 then
as = as .. ", "
end
as = as .. v[1]
if v[2] then
as = as .. "^"..v[2].."^"
end
end
local rt = ""
for k,v in pairs(finfo.outtypes or {}) do
if rt:len() > 0 then
rt = rt .. ", "
else
rt = ": "
end
rt = rt .. v
end
rs = string.format("%s## %s(%s)%s\n%s",rs,name,as,rt,finfo.description)
end
return rs
end
local ad = io.open(outpath,"wb")
for k,v in pairs(tA) do
local fd = doc.parsefile(v)
local ds = ser.serialize(fd)
local tn = v:match("/(.+)$")
if ds:len() > 3 then
ad:write(tn.."\t"..ds:gsub("\n",""):gsub(", +",",").."\n")
end
end
ad:close()

2
kcfg/base.cfg Normal file
View File

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

3
kcfg/full.cfg Normal file
View File

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

154
lib/doc.lua Normal file
View File

@ -0,0 +1,154 @@
local _,serial = pcall(require,"serialization")
local doc = {}
doc.searchers = {}
doc.tctab = {
["string"] = 31,
["table"] = 32,
["userdata"] = 32,
["number"] = 33,
["boolean"] = 35,
["function"] = 36
}
function doc.parsefile(path) -- string -- table -- parses file from *path* to return a documentation table
local fdoc = {}
local f = io.open(path)
local lines = {}
for l in f:read("*a"):gmatch("[^\n]+") do
if l:find("function") and not l:find("local") then
lines[#lines+1] = l
end
end
for k,v in pairs(lines) do
local name, args, desc = v:match("function%s+(.+)%s*%((.*)%)%s*%-%-%s*(.+)")
if name and args and desc then
local fd = {["description"]=desc or desc,["args"]={},["atypes"]={}}
for word in args:gmatch("[^%s,]+") do
fd.args[#fd.args+1] = {word}
fd.atypes[word] = "unknown"
end
local argtypes, outtypes, description = desc:match("(.-)%-%-(.-)%-%-%s*(.+)")
if argtypes and outtypes and description then
local wc = 1
for word in argtypes:gmatch("%S+") do
fd.args[wc][2] = word
fd.atypes[fd.args[wc][1]] = word
wc = wc + 1
end
local wc = 1
for word in outtypes:gmatch("%S+") do
fd.outtypes = fd.outtypes or {}
fd.outtypes[#fd.outtypes+1] = word
end
fd.description = description
end
fdoc[name] = fd
end
end
return fdoc
end
function doc.format(fdoc) -- table -- string -- returns VT100 formatted documentation from documentation table *fdoc*
local rs = "" -- string to return
for fname,finfo in pairs(fdoc) do
if rs:len() > 0 then rs = rs .. "\n\n" end
local as = "" -- string containing arguments for a given function, with colours for type
for k,v in ipairs(finfo.args) do
local c = doc.tctab[v[2]] or 0
if k > 1 then
as = as .. ", "
end
if v[2] then
as = string.format("%s%s: \27[%im%s\27[0m",as,v[2],c,v[1])
else
as = string.format("%s\27[%im%s\27[0m",as,c,v[1])
end
end
local rv = ""
if finfo.outtypes then
rv = ": "
for k,v in ipairs(finfo.outtypes) do
if k > 1 then
rv = rv .. ", "
end
local c = doc.tctab[v] or 0
rv = string.format("%s\27[%im%s\27[0m",rv,c,v)
end
end
local nd = finfo.description
for k,v in pairs(finfo.atypes) do
local c = doc.tctab[v] or 7
nd=nd:gsub("%*"..k.."%*","\27["..tostring(c).."m"..k.."\27[0m")
end
rs = string.format("%s\27[36m%s\27[0m(%s)%s\n%s",rs,fname,as,rv,nd)
end
return rs
end
function doc.searchers.lib(name) -- string -- string string -- Tries to find a documentation from a library with *name*. Returns either a string of documentation, or false and a reason.
local lib = os.getenv("LIB") or "/boot/lib"
local dt
for d in lib:gmatch("[^\n]+") do
if fs.exists(d.."/"..name) then
dt = doc.parsefile(d.."/"..name)
elseif fs.exists(d.."/"..name..".lua") then
dt = doc.parsefile(d.."/"..name..".lua")
end
end
if not dt then return false, "unable to find documentation for "..tostring(name) end
return doc.format(dt)
end
function doc.searchers.cdoc(topic) -- string -- string string -- Searches for documentation labelled as *topic* in .dict files under /boot/doc/
if not serial then return end
for k,v in ipairs(fs.list("/boot/doc")) do
if v:sub(-5) == ".dict" then
local f=io.open("/boot/doc/"..v,"rb")
for line in f:lines() do
local mname, docs = line:match("^(.-)\t(.+)$")
if mname == topic or mname == topic..".lua" then
return doc.format(serial.unserialize(docs))
end
end
end
end
end
function doc.searchers.component(name)
local dt = {}
local addr = component.list(name)()
if addr then
for fname,_ in pairs(component.methods(addr)) do
fd = {args={},outtypes={},atypes={}}
local ds = component.doc(addr,fname)
local ins, outs, desc = ds:match("%((.-)%)") or "", ds:match("%):(.*)%-%-") or "", ds:match("%-%-(.+)") or ""
for arg in ins:gmatch("[^,%s%[%]]+") do
local an,at = arg:match("(.-):(.+)")
at = at:match("(.-)=") or at
fd.args[#fd.args+1] = {an,at}
fd.atypes[an] = at
end
for out in outs:gmatch("[^,]+") do
fd.outtypes[#fd.outtypes+1] = out:match("^%s*(.-)%s*$")
end
fd.description = desc or ""
dt[name.."."..fname] = fd
end
else
return
end
return doc.format(dt)
end
function doc.docs(topic) -- string -- boolean -- Displays the documentation for *topic*, returning true, or errors. Also callable as just doc().
local lib = os.getenv("LIB") or "/boot/lib"
local dt
for k,v in pairs(doc.searchers) do
dt=v(topic)
if dt then
print(dt)
return true
end
end
error("unable to find documentation for "..tostring(name))
end
return setmetatable(doc,{__call=function(_,topic) return doc.docs(topic) end})

133
lib/download.lua Normal file
View File

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

View File

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

View File

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

View File

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

53
lib/liblz16.lua Normal file
View File

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

49
lib/libmtar.lua Normal file
View File

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

123
lib/lzss.lua Normal file
View File

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

View File

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

View File

@ -1,12 +1,11 @@
local computer = require "computer"
local minitel = require "minitel"
local event = require "event"
local ufs = require "unionfs"
local rpc = require "rpc"
local netutil = {}
function netutil.importfs(host,rpath,lpath) -- import filesystem *rpath* from *host* and attach it to *lpath*
local px = rpc.proxy(host,rpath.."_")
function netutil.importfs(host,rpath,lpath) -- string string string -- boolean -- Import filesystem *rpath* from *host* and attach it to *lpath*.
local px = rpc.proxy(host,"fs_"..rpath.."_")
function px.getLabel()
return host..":"..rpath
end
@ -14,17 +13,25 @@ function netutil.importfs(host,rpath,lpath) -- import filesystem *rpath* from *h
return fs.mount(lpath,px)
end
function netutil.exportfs(path) -- export the directory *path* over RPC
function netutil.exportfs(path) -- string -- boolean -- Export the directory *path* over RPC.
local path = "/"..table.concat(fs.segments(path),"/")
local px = ufs.create(path)
local px = require("unionfs").create(path)
function px.dirstat(p)
local rt = {}
for k,v in ipairs(px.list(p)) do
local fp = p.."/"..v
rt[v] = {px.isDirectory(fp), px.size(fp), px.lastModified(fp)}
end
return rt
end
for k,v in pairs(px) do
rpc.register(path.."_"..k,v)
print(path.."_"..k)
rpc.register("fs_"..path.."_"..k,v)
print("fs_"..path.."_"..k)
end
return true
end
function netutil.ping(addr,times,timeout, silent) -- Request acknowledgment from *addr*, waiting *timeout* seconds each try, and try *times* times. If *silent* is true, don't print status. Returns true if there was at least one successful ping, the number of successes, the number of failures, and the average round trip time.
function netutil.ping(addr,times,timeout,silent) -- string number number boolean -- boolean number number number -- Request acknowledgment from *addr*, waiting *timeout* seconds each try, and try *times* times. If *silent* is true, don't print status. Returns true if there was at least one successful ping, the number of successes, the number of failures, and the average round trip time.
local times, timeout = times or 5, timeout or 30
local success, fail, time, avg = 0, 0, 0, 0
for i = 1, times do
@ -46,7 +53,7 @@ function netutil.ping(addr,times,timeout, silent) -- Request acknowledgment from
return success > 0, success, fail, avg
end
function netutil.nc(host,port)
function netutil.nc(host,port) -- string number -- boolean -- Starts an interactive Minitel socket connection to *host* on *port*, primarily for remote login. Returns whether the attempt was successful.
port = port or 22
local socket = minitel.open(host,port)
if not socket then return false end
@ -66,6 +73,7 @@ function netutil.nc(host,port)
socket:write(b.."\n")
end
until socket.state ~= "open"
return true
end
return netutil

146
lib/pkgfs.lua Normal file
View File

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

193
lib/pkgman.lua Normal file
View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

10
module/base.lua Normal file
View File

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

View File

@ -2,7 +2,7 @@
buffer = {}
function buffer.new(mode, stream) -- create a new buffer in mode *mode* backed by stream object *stream*
function buffer.new(mode, stream) -- string table -- table -- create a new buffer in mode *mode* backed by stream object *stream*
local result = {
mode = {},
stream = stream,
@ -234,26 +234,114 @@ function buffer:read(...)
end
local function readLine(chop)
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
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
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

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

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

View File

@ -64,7 +64,7 @@ end
devfs.component.address = "devfs"
devfs.component.type = "devfs"
function devfs.register(fname,fopen) -- Register a new devfs node with the name *fname* that will run the function *fopen* when opened. This function should return a function for read, a function for write, function for close, and optionally, a function for seek, in that order.
function devfs.register(fname,fopen) -- string function -- -- Register a new devfs node with the name *fname* that will run the function *fopen* when opened. This function should return a function for read, a function for write, function for close, and optionally, a function for seek, in that order.
devfs.files[fname] = fopen
end

View File

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

View File

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

View File

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

View File

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

View File

@ -1,141 +0,0 @@
function vt100emu(gpu) -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
local mx, my = gpu.maxResolution()
local cx, cy = 1, 1
local pc = " "
local lc = ""
local mode = 0 -- 0 normal, 1 escape, 2 command
local lw = true
local sx, sy = 1,1
local cs = ""
local bg, fg = 0, 0xFFFFFF
-- setup
gpu.setResolution(mx,my)
gpu.fill(1,1,mx,my," ")
local function checkCursor()
if cx > mx and lw then
cx, cy = 1, cy+1
end
if cy > my then
gpu.copy(1,2,mx,my-1,0,-1)
gpu.fill(1,my,mx,1," ")
cy=my
end
if cy < 1 then cy = 1 end
if cx < 1 then cx = 1 end
end
local function termwrite(s)
local wb = ""
local lb, ec = nil, nil
local function flushwb()
while wb:len() > 0 do
checkCursor()
local wl = wb:sub(1,mx-cx+1)
wb = wb:sub(wl:len()+1)
gpu.set(cx, cy, wl)
cx = cx + wl:len()
end
end
local rs = ""
s=s:gsub("\8","\27[D")
pc = gpu.get(cx,cy)
gpu.setForeground(fg)
gpu.setBackground(bg)
gpu.set(cx,cy,pc)
for cc in s:gmatch(".") do
if mode == 0 then
if cc == "\n" then
flushwb()
cx,cy = 1, cy+1
elseif cc == "\t" then
wb=wb..(" "):rep(8*((cx+9)//8))
elseif cc == "\27" then
flushwb()
mode = 1
else
wb = wb .. cc
end
elseif mode == 1 then
if cc == "[" then
mode = 2
else
mode = 0
end
elseif mode == 2 then
if cc:match("[%d;]") then
cs = cs .. cc
else
mode = 0
local tA = {}
for s in cs:gmatch("%d+") do
tA[#tA+1] = tonumber(s)
end
if cc == "H" then
cx, cy = tA[1] or 1, tA[2] or 1
elseif cc == "A" then
cy = cy - (tA[1] or 1)
elseif cc == "B" then
cy = cy + (tA[1] or 1)
elseif cc == "C" then
cx = cx + (tA[1] or 1)
elseif cc == "D" then
cx = cx - (tA[1] or 1)
elseif cc == "s" then
sx, sy = cx, cy
elseif cc == "u" then
cx, cy = sx, sy
elseif cc == "n" and tA[1] == 6 then
rs = string.format("%s\27[%d;%dR",rs,cx,cy)
elseif cc == "K" and tA[1] == 1 then
gpu.fill(1,cy,cx,1," ")
elseif cc == "K" and tA[1] == 2 then
gpu.fill(cx,cy,mx,1," ")
elseif cc == "K" then
gpu.fill(1,cy,mx,1," ")
elseif cc == "J" and tA[1] == 1 then
gpu.fill(1,1,mx,cy," ")
elseif cc == "J" and tA[1] == 2 then
gpu.fill(1,1,mx,my," ")
cx, cy = 1, 1
elseif cc == "J" then
gpu.fill(1,cy,mx,my," ")
elseif cc == "m" then
for _,num in ipairs(tA) do
if num == 0 then
fg,bg,ec,lb = 0xFFFFFF,0,true,true
elseif num == 7 then
local nfg,nbg = bg, fg
fg, bg = nfg, nbg
elseif num > 29 and num < 38 then
fg = colours[num-29]
elseif num > 39 and num < 48 then
bg = colours[num-39]
elseif num == 100 then -- disable local echo
ec = false
elseif num == 101 then -- disable line mode
lb = false
end
end
gpu.setForeground(fg)
gpu.setBackground(bg)
end
cs = ""
checkCursor()
end
end
end
flushwb()
checkCursor()
pc = gpu.get(cx,cy)
gpu.setForeground(bg)
gpu.setBackground(fg)
gpu.set(cx,cy,pc)
gpu.setForeground(fg)
gpu.setBackground(bg)
return rs, lb, ec
end
return termwrite
end

View File

@ -1,4 +1,4 @@
function os.chdir(p) -- changes the current working directory of the calling process to the directory specified in *p*, returning true or false, error
function os.chdir(p) -- string -- boolean string -- changes the current working directory of the calling process to the directory specified in *p*, returning true or false, error
if not (p:sub(1,1) == "/") then
local np = {}
for k,v in pairs(fs.segments(os.getenv("PWD").."/"..p)) do

20
module/rtfsboot.lua Normal file
View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
--#include "module/vt-task.lua"
do
local r,w = vtemu(component.list("gpu")(),component.list("screen")())
devfs.register("tty0", function() return r,w,function() end end)
end

View File

@ -1,58 +0,0 @@
do
--#include "module/nvt100.lua"
function vtemu(gpua,scra) -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions.
local gpu = component.proxy(gpua)
gpu.bind(scra)
local write = vt100emu(gpu)
local kba = {}
for k,v in ipairs(component.invoke(scra,"getKeyboards")) do
kba[v]=true
end
local buf, lbuf, echo = "", true, true
os.spawn(function() dprint(pcall(function()
while true do
local ty,ka,ch = coroutine.yield()
if ty == "key_down" and kba[ka] then
if ch == 13 then ch = 10 end
if ch == 8 then
if buf:len() > 0 then
if echo then write("\8 \8") end
buf = buf:sub(1,-2)
end
elseif ch > 0 then
if echo then write(string.char(ch)) end
buf=buf..string.char(ch)
end
end
end
end)) end,string.format("ttyd[%s:%s]",gpua:sub(1,8),scra:sub(1,8)))
local function bread(n)
local r
if lbuf then
while not buf:find("\n") do
coroutine.yield()
end
local n = buf:find("\n")
r, buf = buf:sub(1,n), buf:sub(n+1)
else
r = buf
buf = ""
coroutine.yield()
end
return r
end
local function bwrite(d)
local ba, lb, ec = write(d)
buf = buf .. ba
if lb ~= nil then
dprint("local buffer mode: "..tostring(lb))
lbuf = lb
end
if ec ~= nil then
dprint("echo mode: "..tostring(ec))
echo = ec
end
end
return bread, bwrite, function() io.write("\27[2J\27[H") end
end
end

View File

@ -1,148 +0,0 @@
function vt100emu(gpu) -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal
local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF}
local mx, my = gpu.maxResolution()
local cx, cy = 1, 1
local pc = " "
local lc = ""
local mode = "n"
local lw = true
local sx, sy = 1,1
local cs = ""
local bg, fg = 0, 0xFFFFFF
-- setup
gpu.setResolution(mx,my)
gpu.fill(1,1,mx,my," ")
local function termwrite(s)
local rs = ""
s=s:gsub("\8","\27[D")
pc = gpu.get(cx,cy)
gpu.setForeground(fg)
gpu.setBackground(bg)
gpu.set(cx,cy,pc)
for i = 1, s:len() do
local cc = s:sub(i,i)
if mode == "n" then
if cc == "\n" then -- line feed
cx, cy = 1, cy+1
elseif cc == "\r" then -- cursor home
cx = 1
elseif cc == "\27" then -- escape
mode = "e"
elseif cc == "\t" then
cx = 8*((cx+9)//8)
elseif string.byte(cc) > 31 and string.byte(cc) < 127 then -- printable, I guess
gpu.set(cx, cy, cc)
cx = cx + 1
end
elseif mode == "e" then
if cc == "[" then
mode = "v"
cs = ""
elseif cc == "D" then -- scroll down
gpu.copy(1,2,mx,my-1,0,-1)
gpu.fill(1,my,mx,1," ")
cy=cy+1
mode = "n"
elseif cc == "M" then -- scroll up
gpu.copy(1,1,mx,my-1,0,1)
gpu.fill(1,1,mx,1," ")
mode = "n"
else
mode = "n"
end
elseif mode == "v" then
mode = "n"
if cc == "s" then -- save cursor
sx, sy = cx, cy
elseif cc == "u" then -- restore cursor
cx, cy = sx, sy
elseif cc == "H" then -- cursor home or to
local tx, ty = cs:match("(%d+);(%d+)")
tx, ty = tx or "1", ty or "1"
cx, cy = tonumber(tx), tonumber(ty)
elseif cc == "A" then -- cursor up
cy = cy - (tonumber(cs) or 1)
elseif cc == "B" then -- cursor down
cy = cy + (tonumber(cs) or 1)
elseif cc == "C" then -- cursor right
cx = cx + (tonumber(cs) or 1)
elseif cc == "D" then -- cursor left
cx = cx - (tonumber(cs) or 1)
elseif cc == "h" and lc == "7" then -- enable line wrap
lw = true
elseif cc == "l" and lc == "7" then -- disable line wrap
lw = false
elseif cc == "c" then
rs = string.format("%s\27[%d;%d0c",rs,mx,my)
elseif cc == "n" and lc == "6" then
rs = string.format("%s\27[%d;%dR",rs,cx,cy)
elseif cc == "K" then
if lc == "1" then
gpu.fill(1,cy,cx,1," ")
elseif lc == "2" then
gpu.fill(cx,cy,mx,1," ")
else
gpu.fill(1,cy,mx,1," ")
end
elseif cc == "J" then
if lc == "1" then
gpu.fill(1,1,mx,cy," ")
elseif lc == "2" then
gpu.full(1,1,mx,my," ")
cx,cy = 1, 1
else
gpu.fill(1,cy,mx,my," ")
end
elseif cc == "m" then
for num in cs:gmatch("%d+") do
num=tonumber(num)
if num == 0 then
fg,bg = 0xFFFFFF,0
elseif num == 7 then
local nfg,nbg = bg, fg
fg, bg = nfg, nbg
elseif num > 29 and num < 38 then
fg = colours[num-29]
elseif num > 39 and num < 48 then
bg = colours[num-39]
end
end
gpu.setForeground(fg)
gpu.setBackground(bg)
else
cs = cs .. cc
if cc:match("[%d;]") then
mode = "v"
end
end
end
if cx > mx and lw then
cx, cy = 1, cy+1
end
if cy > my then
gpu.copy(1,2,mx,my-1,0,-1)
gpu.fill(1,my,mx,1," ")
cy=my
end
if cy < 1 then cy = 1 end
if cx < 1 then cx = 1 end
lc = cc
end
pc = gpu.get(cx,cy)
gpu.setForeground(bg)
gpu.setBackground(fg)
gpu.set(cx,cy,pc)
gpu.setForeground(fg)
gpu.setBackground(bg)
return rs
end
return termwrite
end

31
obootstrap.lua Normal file
View File

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

121
preproc.lua Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

46
template.tex Normal file
View File

@ -0,0 +1,46 @@
\documentclass[11pt,twoside,a4paper]{article}
\usepackage{hyperref}
\usepackage{multicol}
\usepackage{standalone}
\usepackage{graphicx}
\usepackage{pdfpages}
\usepackage{listings}
\usepackage{color}
\usepackage{sectsty}
\usepackage[cm]{fullpage}
\lstset{
commentstyle=\color{cyan}, % comment style
keywordstyle=\color{cyan}, % keyword style
stringstyle=\color{red}, % string literal style
numbers=left, % where to put the line-numbers; possible values are (none, left, right)
numbersep=5pt, % how far the line-numbers are from the code
numberstyle=\tiny\color{gray}, % the style that is used for the line-numbers
}
\hypersetup{
colorlinks=true
}
\subsectionfont{\ttfamily}
% pandoc stuff
\providecommand{\tightlist}{%
\setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\let\stdsection\section
\renewcommand\section{\newpage\stdsection}
\lstset{basicstyle=\footnotesize\ttfamily,breaklines=true}
\newcommand{\ignore}[1]{}
\title{$title$$if(thanks)$\thanks{$thanks$}$endif$}
\author{$for(author)$$author$$sep$ \and $endfor$}
\date{}
\begin{document}
\pagenumbering{gobble}
\maketitle
\newpage
\pagenumbering{alph}
\tableofcontents
\newpage
\pagenumbering{arabic}
$body$
\end{document}