Compare commits

...

6 Commits

11 changed files with 186 additions and 39 deletions

View File

@ -4563,3 +4563,116 @@ plan:
introduce uevent-watcher command, update test to use it
make mount service use it
Tue Apr 16 18:59:25 BST 2024
Another idea for maybe-not-now: tftp local/peer addresses could be
provided as top-level params (e.g. to nix-build).
Wed Apr 17 18:57:49 BST 2024
I hatched a plan (and forgot to save this file) to build a service
that subscribes to uevents and retains state so that other services
can know about things that happened before they started. I'm wondering
if it's really needed though, because there could be one process to
read the socket and start/stop *all* the udev triggered services. Not
sure how we'd describe this in nix though: how do all the other
services
How we would do a uevent database service (sysfsq):
for each event e from socket
if e.action in (add, change)
path[e.path] = e.attribues
if e.action == 'remove'
path.remove e.path
(update-indices e)
(fn update-indices [event]
for each k in (keys event)
index.k.v += e)
we also want to not maintain indexes when there are so many values in
the index entry to make searching it worthless.
to retrieve, look at each criterion that has an index and choose the
index with fewest elements in the value. scan that index for the other
criteria
there are 813 uevent files in sysfs on arhcive, is this all overkill?
maybe we could simplify using a hardcoded stopword list - e.g. don't
have indices for MAJOR, MINOR
what are we going to use for querying? can't be netlink because that's
a shared medium (broadcast/multicast). unix dgram socket? alternative
would be to somehow use the filesystem as a database
Wed Apr 17 22:00:29 BST 2024
tests. assuming the sysfs setup from all-events.txt, we can write tests lik
- there is a path for $foo
- the attributes are x, y, z
- when I add a device with $attributes, I can recall it
- by path
- by attribute value
- when I remove it again, I cannot access it by path or attributes
- when I add a device with $attributes major minor foo bar baz
it is added to indices for foo bar baz but not major minor
- when I remove it, it can no longer be found by looking in any index
- when I query with multiple attributes, the search is performed
using the most specific attribute (= the attribute whose
value at this key has fewest elements)
I am still looking for ways to avoid doing this, but it is potentially
the first of several "database" services that triggers could want to
use so maybe it's an emerging pattern.
https://github.com/philanc/minisock useful? we could almost replace
nellie with it only not quite (it hardcodes 0 as the "protocol" param
to socket())
Fri Apr 19 20:55:22 BST 2024
We could have a service that's present only when a devdb entry is
present. For example mount_disk only runs when partlabel=foo
Or we could have a service that continues to run as the $somedatabase
service state changes and does different things depending on the
nature of those changes. For example, [I can't think of an example
now, but it was definitely an issue the other day, maybe I dreamt it]
I don't think this will be such an issue for devdb becuase there isn't
much in it that has continuously varying values. Maybe battery health
is the exception there
The step ahead we're thinking here is: how do clients do a request? A
single one-of request for state is fine but chances are that a client
will do that to get initial state and then need to open a netlink
socket to get updates: well, if we can feed them the initial state
filtered for their needs why can't we send them the relevant updates
as well? This makes the database server design a bit more complicated
as it needs to remember each client and their subscriptions, and then
send only relevant updates to each subscribed client
* should a client be allowed multiple subscriptions on the same
connection?
* do we guarantee that every message sent is matching the subscription
or can we send other stuff as well if it makes implementation easier?
it might defeat the purpose a bit because it means the client also
needs to filter, but the client will anyway have to do some message
parsing so they can distinguish add from remove
* where do we start?

View File

@ -1,7 +1,8 @@
(local subject (require :acquire-wan-address))
(local { : view } (require :fennel))
(import-macros { : expect= } :anoia.assert)
(local { : merge : dup } (require :anoia))
;; nix-shell --run "cd modules/dhcp6c && fennelrepl acquire-wan-address-test.fnl"
(local a1
{
@ -47,19 +48,6 @@
}
)
(macro expect [assertion]
(let [msg (.. "expectation failed: " (view assertion))]
`(when (not ,assertion)
(assert false ,msg))))
(macro expect= [actual expected]
`(let [ve# (view ,expected)
va# (view ,actual)]
(when (not (= ve# va#))
(assert false
(.. "\nexpected " ve# "\ngot " va#)
))))
(fn first-address []
(let [deleted
(subject.deletions

8
pkgs/anoia/Makefile Normal file
View File

@ -0,0 +1,8 @@
default: fs.lua init.lua nl.lua svc.lua
test:
fennel test.fnl
%.lua: %.fnl
fennel --compile $< > $@

21
pkgs/anoia/assert.fnl Normal file
View File

@ -0,0 +1,21 @@
;; these are macros; this module should be imported
;; using import-macros
;; e.g. (import-macros { : expect= } :anoia.assert)
(fn expect [assertion]
(let [msg (.. "expectation failed: " (view assertion))]
`(when (not ,assertion)
(assert false ,msg))))
(fn expect= [actual expected]
`(let [view# (. (require :fennel) :view)
ve# (view# ,expected)
va# (view# ,actual)]
(when (not (= ve# va#))
(assert false
(.. "\nexpected " ve# "\ngot " va#)
))))
{ : expect : expect= }

View File

@ -10,13 +10,15 @@ in stdenv.mkDerivation {
src = ./.;
nativeBuildInputs = [ fennel ];
buildInputs = with lua.pkgs; [ luafilesystem ];
buildPhase = ''
for f in *.fnl ; do
fennel --compile $f > `basename $f .fnl`.lua
done
'';
outputs = [ "out" "dev" ];
doCheck = true;
installPhase = ''
mkdir -p "$out/share/lua/${lua.luaversion}/${pname}"
cp *.lua "$out/share/lua/${lua.luaversion}/${pname}"
mkdir -p "$dev/share/lua/${lua.luaversion}/${pname}"
cp assert.fnl "$dev/share/lua/${lua.luaversion}/${pname}"
'';
}

View File

@ -1,9 +1,10 @@
(local { : hash : base64url } (require :anoia))
(local { : hash : base64url } (require :init))
(import-macros { : expect= } :assert)
(assert (= (hash "") 5381))
(expect= (hash "") 5381)
;; these examples from https://theartincode.stanis.me/008-djb2/
(assert (= (hash "Hello") 210676686969))
(assert (= (hash "Hello!") 6952330670010))
(expect= (hash "Hello") 210676686969)
(expect= (hash "Hello!") 6952330670010)
(assert (= (base64url "hello world") "aGVsbG8gd29ybGQ"))
(expect= (base64url "hello world") "aGVsbG8gd29ybGQ")

View File

@ -82,6 +82,7 @@ in {
zyxel-bootconfig = callPackage ./zyxel-bootconfig {};
min-collect-garbage = callPackage ./min-collect-garbage {};
min-copy-closure = callPackage ./min-copy-closure {};
minisock = callPackage ./minisock {};
nellie = callPackage ./nellie {};
netlink-lua = callPackage ./netlink-lua {};
odhcp-script = callPackage ./odhcp-script {};

View File

@ -31,6 +31,7 @@ in writeScriptBin "fennelrepl" ''
package.cpath = ${lib.strings.escapeShellArg luacpath} .. ";" .. (package.cpath or "")
local fennel = require "fennel"
table.insert(package.loaders or package.searchers,1, fennel.searcher)
fennel['macro-path'] = "${anoia.dev}/share/lua/${lua.luaversion}/?.fnl;" .. fennel['macro-path']
local more_fennel = os.getenv("FENNEL_PATH")
if more_fennel then

View File

@ -1,16 +1,12 @@
(local { : view &as fennel } (require :fennel))
(local anoia (require :anoia))
(import-macros { : expect= } :anoia.assert)
;; nix-shell --run "cd pkgs/ifwait && fennelrepl test-ifwait.fnl"
(var fake-system (fn [s] (print "executing " s)))
(tset anoia :system #(fake-system $1))
(macro expect= [actual expected]
`(let [ve# (view ,expected)
va# (view ,actual)]
(when (not (= ve# va#))
(assert false
(.. "\nexpected " ve# "\ngot " va#)
))))
(fn event-generator [events]
(coroutine.wrap
@ -117,3 +113,5 @@
(set upsies [])
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
(expect= upsies [:u :u :u :u]))
(print "OK")

21
pkgs/minisock/default.nix Normal file
View File

@ -0,0 +1,21 @@
{ lua, lib, fetchFromGitHub }:
let pname = "minisock";
in lua.pkgs.buildLuaPackage {
inherit pname;
version = "0.1"; # :shrug:
src = fetchFromGitHub {
repo = "minisock";
owner = "philanc";
rev = "a20db2aaa871653c61045019633279167cf1b458";
hash = "sha256-zB9KSt0WEGCSYTLA6W9QrsVRFEZYaoBBeXx9VEXmsGY=";
};
makeFlags = [ "LUADIR=." "minisock.so" ];
installPhase = ''
mkdir -p "$out/lib/lua/${lua.luaversion}"
cp ${pname}.so "$out/lib/lua/${lua.luaversion}/"
'';
}

View File

@ -1,16 +1,9 @@
(local { : view} (require :fennel))
(import-macros { : expect= } :anoia.assert)
(set _G.arg (doto [] (tset 0 "test")))
(local subject (require :watch))
(macro expect= [actual expected]
`(let [ve# (view ,expected)
va# (view ,actual)]
(when (not (= ve# va#))
(assert false
(.. "\nexpected " ve# "\ngot " va#)
))))
(let [params
{:matches {:devname "foo" :partname "my-usbstick"}}]
(expect= (subject.event-matches? params {}) false)