Compare commits

...

8 Commits

Author SHA1 Message Date
Daniel Barlow a5f16dfa81 convert inout test to use uevent-watch 2024-04-15 22:15:27 +01:00
Daniel Barlow 41a4b1f7ef clean cruft from inout test script 2024-04-15 22:00:44 +01:00
Daniel Barlow 42a5699326 remove unneeded config from inout test 2024-04-15 21:19:18 +01:00
Daniel Barlow ea2b25168e add uevent-watch, which toggles services based on uevent msgs 2024-04-15 21:15:07 +01:00
Daniel Barlow 5564cf0554 add nellie.close 2024-04-14 22:45:29 +01:00
Daniel Barlow f3a13630d3 add multicast groups param to nellie.open 2024-04-14 22:45:29 +01:00
Daniel Barlow f233acf9ff netlink uevent hello world 2024-04-14 22:45:29 +01:00
Daniel Barlow b6a054c588 add mdevd as module
following the upstream example, it republishes uevent messages
using multicast group 4 instead of group 2 as used by udev.
2024-04-14 21:59:23 +01:00
14 changed files with 466 additions and 27 deletions

View File

@ -19,6 +19,7 @@
./kernel
./outputs/kexecboot.nix
./mount
./mdevd.nix
./network
./ntp
./outputs.nix

17
modules/mdevd.nix Normal file
View File

@ -0,0 +1,17 @@
{ config, pkgs, lib, ...} :
let inherit (pkgs.liminix.services) oneshot longrun bundle target;
in {
config = {
services = rec {
mdevd = longrun {
name = "mdevd";
notification-fd = 3;
run = "${pkgs.mdevd}/bin/mdevd -D 3 -b 200000 -O4";
};
mdevd-coldplug = oneshot {
name ="mdev-coldplug";
up = "${pkgs.mdevd}/bin/mdevd-coldplug";
};
};
};
}

View File

@ -33,4 +33,5 @@
: mktree
: rmtree
: directory?
:symlink (fn [from to] (lfs.link from to true))
}

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 {};
nellie = callPackage ./nellie {};
netlink-lua = callPackage ./netlink-lua {};
odhcp-script = callPackage ./odhcp-script {};
odhcp6c = callPackage ./odhcp6c {};
@ -105,6 +106,7 @@ in {
swconfig = callPackage ./swconfig {};
systemconfig = callPackage ./systemconfig {};
tufted = callPackage ./tufted {};
uevent-watch = callPackage ./uevent-watch {};
writeAshScript = callPackage ./write-ash-script {};
writeFennel = callPackage ./write-fennel {};
writeFennelScript = callPackage ./write-fennel-script {};

28
pkgs/nellie/default.nix Normal file
View File

@ -0,0 +1,28 @@
{ lua, lib, fetchpatch, fetchFromGitHub, stdenv }:
let pname = "nellie";
in lua.pkgs.buildLuaPackage {
inherit pname;
version = "0.1.1-1";
src = ./.;
buildPhase = "$CC -shared -l lua -o nellie.so nellie.c";
# for the checks to work you need to
# nix-build--option sandbox false
# otherwise the sandbox doesn't see any uevent messages
# doCheck = stdenv.hostPlatform == stdenv.buildPlatform;
checkPhase = ''
export LUA_CPATH=./?.so
lua test.lua
'';
installPhase = ''
mkdir -p "$out/lib/lua/${lua.luaversion}"
cp nellie.so "$out/lib/lua/${lua.luaversion}/"
'';
}

90
pkgs/nellie/nellie.c Normal file
View File

@ -0,0 +1,90 @@
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int l_close_socket(lua_State *L) {
lua_getfield(L, 1, "fileno");
int fd = (int) lua_tointeger(L, -1);
close(fd);
return 0;
}
static int l_read_from_socket(lua_State *L) {
int length = 32;
if(lua_isnumber(L, 2))
length = lua_tointeger(L, 2);
lua_getfield(L, 1, "fileno");
int fd = (int) lua_tointeger(L, -1);
char *buf = (char *) malloc(length);
int bytes = recv(fd, buf, length, 0);
if(bytes > 0) {
lua_pushlstring(L, buf, bytes);
free(buf);
return 1;
} else {
free(buf);
return 0;
}
}
static int l_open_socket(lua_State *L) {
int netlink_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
struct sockaddr_nl sa;
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_pid = getpid();
if(lua_isnumber(L, 1)) {
sa.nl_groups = lua_tointeger(L, 1);
lua_pop(L, 1);
}
else {
sa.nl_groups = 4; /* group 4 is rebroadcasts from mdevd */
}
if(bind(netlink_fd, (struct sockaddr *) &sa, sizeof(sa))==0) {
lua_newtable(L);
lua_pushliteral(L, "fileno");
lua_pushinteger(L, netlink_fd);
lua_settable(L, 1);
lua_pushliteral(L, "read");
lua_pushcfunction(L, l_read_from_socket);
lua_settable(L, 1);
lua_pushliteral(L, "close");
lua_pushcfunction(L, l_close_socket);
lua_settable(L, 1);
return 1;
} else {
return 0;
}
}
static const struct luaL_Reg funcs [] = {
{"open", l_open_socket},
{NULL, NULL} /* sentinel */
};
/* "luaopen_" prefix is magic and tells lua to run this function
* when it dlopens the library
*/
int luaopen_nellie (lua_State *L) {
luaL_newlib(L, funcs);
return 1;
}

6
pkgs/nellie/test.lua Normal file
View File

@ -0,0 +1,6 @@
local nellie = require('nellie')
print('dfg')
local f = nellie.open(2)
print(string.byte(f:read(1000), 0, 60))
print("CLOSED", f:close())

View File

@ -0,0 +1,23 @@
{
lua
, nellie
, writeFennelScript
, runCommand
, anoia
, fennel
, stdenv
, fennelrepl
}:
stdenv.mkDerivation {
name = "uevent-watch";
src = ./.;
nativeBuildInputs = [ fennelrepl ];
installPhase = ''
mkdir -p $out/bin
cp -p ${writeFennelScript "uevent-watch" [fennel anoia nellie lua.pkgs.luafilesystem] ./watch.fnl} $out/bin/uevent-watch
'';
checkPhase = ''
fennelrepl ./test.fnl
'';
doCheck = true;
}

View File

@ -0,0 +1,154 @@
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1
SUBSYSTEM=usb
MAJOR=189
MINOR=1
DEVNAME=bus/usb/001/002
DEVTYPE=usb_device
PRODUCT=46f4/1/0
TYPE=0/0/0
BUSNUM=001
DEVNUM=002
SEQNUM=1513
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0
SUBSYSTEM=usb
DEVTYPE=usb_interface
PRODUCT=46f4/1/0
TYPE=0/0/0
INTERFACE=8/6/80
MODALIAS=usb:v46F4p0001d0000dc00dsc00dp00ic08isc06ip50in00
SEQNUM=1514
add@/devices/virtual/workqueue/scsi_tmf_0
ACTION=add
DEVPATH=/devices/virtual/workqueue/scsi_tmf_0
SUBSYSTEM=workqueue
SEQNUM=1515
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0
SUBSYSTEM=scsi
DEVTYPE=scsi_host
SEQNUM=1516
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/scsi_host/host0
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/scsi_host/host0
SUBSYSTEM=scsi_host
SEQNUM=1517
bind@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0
ACTION=bind
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0
SUBSYSTEM=usb
DEVTYPE=usb_interface
DRIVER=usb-storage
PRODUCT=46f4/1/0
TYPE=0/0/0
INTERFACE=8/6/80
MODALIAS=usb:v46F4p0001d0000dc00dsc00dp00ic08isc06ip50in00
SEQNUM=1518
bind@/devices/pci0000:00/0000:00:13.0/usb1/1-1
ACTION=bind
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1
SUBSYSTEM=usb
MAJOR=189
MINOR=1
DEVNAME=bus/usb/001/002
DEVTYPE=usb_device
DRIVER=usb
PRODUCT=46f4/1/0
TYPE=0/0/0
BUSNUM=001
DEVNUM=002
SEQNUM=1519
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0
SUBSYSTEM=scsi
DEVTYPE=scsi_target
SEQNUM=1520
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0
SUBSYSTEM=scsi
DEVTYPE=scsi_device
MODALIAS=scsi:t-0x00
SEQNUM=1521
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/scsi_device/0:0:0:0
SUBSYSTEM=scsi_device
SEQNUM=1522
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/scsi_disk/0:0:0:0
SUBSYSTEM=scsi_disk
SEQNUM=1523
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/bsg/0:0:0:0
SUBSYSTEM=bsg
MAJOR=252
MINOR=0
DEVNAME=bsg/0:0:0:0
SEQNUM=1524
change@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0
ACTION=change
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0
SUBSYSTEM=scsi
SDEV_UA=POWER_ON_RESET_OCCURRED
DEVTYPE=scsi_device
DRIVER=sd
MODALIAS=scsi:t-0x00
SEQNUM=1525
add@/devices/virtual/bdi/8:0
ACTION=add
DEVPATH=/devices/virtual/bdi/8:0
SUBSYSTEM=bdi
SEQNUM=1526
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda
SUBSYSTEM=block
MAJOR=8
MINOR=0
DEVNAME=sda
DEVTYPE=disk
DISKSEQ=2
SEQNUM=1527
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1
SUBSYSTEM=block
MAJOR=8
MINOR=1
DEVNAME=sda1
DEVTYPE=partition
DISKSEQ=2
PARTN=1
SEQNUM=1528
bind@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0
ACTION=bind
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0
SUBSYSTEM=scsi
DEVTYPE=scsi_device
DRIVER=sd
MODALIAS=scsi:t-0x00
SEQNUM=1529

View File

@ -0,0 +1,48 @@
(local { : view} (require :fennel))
(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)
(expect= (subject.event-matches? params {:devname "bill"}) false)
(expect= (subject.event-matches? params {:devname "foo" :partname "my-usbstick"}) true)
(expect= (subject.event-matches? params {:devname "foo" :otherthing "bar" :partname "my-usbstick"}) true)
)
;; Events come from the netlink socket as an initial summary line
;; followed by a NUL character followed by newline-separated key=value
;; pairs. For ease of editing we don't have NULs in events.txt,
;; so we need to massage it into shape here
(local events
(with-open [f (io.open "./events.txt" :r)]
(let [text (string.gsub (f:read "*a") "\n\n" "\0") ]
(icollect [n (string.gmatch text "([^\0]+)")]
(string.gsub n "\n" "\0" 1)))))
(fn next-event []
(var i 0)
(fn []
(let [i_ (+ 1 i)
e (. events i_)]
(set i i_)
e)))
;; this tests event parsing but not whether anything
;; happens as a result of processing them
(subject.run
["-s" "foo" "-n" (os.getenv "TMPDIR") "partname=backup-disk" ]
{ :read (next-event) }
)

View File

@ -0,0 +1,70 @@
(local { : assoc : system : dirname } (require :anoia))
(local { : mktree : rmtree : symlink } (require :anoia.fs))
(fn parse-match [s] (string.match s "(.-)=(.+)"))
(fn parse-args [args]
(match args
["-s" service & rest] (assoc (parse-args rest) :service service)
["-n" path & rest] (assoc (parse-args rest) :linkname path)
matches { :matches (collect [_ m (ipairs matches)] (parse-match m)) }
_ nil))
(fn %% [fmt ...] (string.format fmt ...))
(fn event-matches? [params e]
(and
e
(accumulate [match? true
name value (pairs params.matches)]
(and match? (= value (. e name))))))
(var up :unknown)
(fn start-service [devname linkname service]
(match (symlink (.. "/dev/" devname ) linkname)
ok (pcall system (%% "s6-rc -b -u change %q" service))
(nil err) false))
(fn stop-service [linkname service]
(match (pcall system (%% "s6-rc -b -d change %q" linkname service))
ok (os.remove linkname)
(nil err) false))
(fn toggle-service [devname linkname service wanted?]
(when (not (= up wanted?))
(set up
(if wanted?
(start-service devname linkname service)
(not (stop-service linkname service))))))
(fn parse-uevent [s]
(when s
(let [(nl nxt) (string.find s "\0" 1 true)]
(collect [k v (string.gmatch
(string.sub s (+ 1 nxt))
"(%g-)=(%g+)")]
(k:lower) v))))
(fn run [args fh]
(set up :unknown)
(let [parameters
(assert (parse-args args) (.. "can't parse args: " (table.concat args " ")))]
(mktree (dirname parameters.linkname))
(var finished? false)
(while (not finished?)
(let [e (parse-uevent (fh:read 5000))]
(when (event-matches? parameters e)
(let [wanted? (. {:add true :change true} e.action)]
(toggle-service e.devname parameters.linkname parameters.service wanted?)))
(set finished? (= e nil))
))))
(when (not (= (. arg 0) "test"))
(let [nellie (require :nellie)
netlink (nellie.open 4)]
(run arg netlink)))
{ : run : event-matches? }

View File

@ -15,6 +15,7 @@ in rec {
"${modulesPath}/network"
"${modulesPath}/ssh"
"${modulesPath}/mount"
"${modulesPath}/mdevd.nix"
];
filesystem = dir { srv = dir {}; };
@ -46,16 +47,20 @@ in rec {
rootfsType = "jffs2";
hostname = "inout";
services.mount_external_disk = svc.mount.build {
device = "LABEL=backup-disk";
mountpoint = "/srv";
fstype = "ext4";
};
services.sshd = svc.ssh.build { };
defaultProfile.packages = with pkgs; [
min-collect-garbage
tcpdump
];
services.watch_mount_srv =
let
node = "/dev/disk/by-partlabel/backup-disk";
mount = oneshot {
name = "mount-srv";
up = "mount -t ext2 ${node} /srv";
down = "umount /srv";
};
in longrun {
name = "mount_srv";
run = ''
${pkgs.uevent-watch}/bin/uevent-watch -s ${mount.name} -n ${node} partname=backup-disk devtype=partition
'';
dependencies = [ config.services.mdevd ];
buildInputs = [ mount ];
};
}

View File

@ -9,21 +9,14 @@ proc chat {instr outstr} {
spawn socat -,echo=0,icanon=1 unix-connect:vm/monitor
set monitor_id $spawn_id
# expect "(qemu)"
# send "set_link virtio-net-pci.1 off\n"
# expect "(qemu)"
# send "set_link virtio-net-pci.0 off\n"
# expect "(qemu)"
# send "c\r\n"
spawn socat unix-connect:vm/console -
set console_id $spawn_id
expect "BusyBox"
chat "#" "PS1=RE\\ADY_\\ ; stty -echo \r"
chat "READY_" "s6-rc -b -a list\r"
chat "READY_" "tail -f /run/uncaught-logs/current & \rs6-rc -b -a list\r"
chat "watch-mount" "\r"
chat "mount" "\r"
set spawn_id $monitor_id
chat "QEMU" "device_add usb-storage,bus=xhci.0,drive=usbstick\n"
@ -32,16 +25,15 @@ chat "(qemu)" "version\r"
set spawn_id $console_id
expect {
"mounted filesystem" { }
"sda: sda1" { }
timeout { exit 1 }
}
send "\r"
chat "READY_" "s6-rc -b -a list\r"
chat "READY_" "cat /proc/mounts\r"
chat "READY_" "sleep 3; grep /srv /proc/mounts && hostname\r"
expect {
"/srv" { }
"inout" { }
timeout { exit 1 }
}

View File

@ -13,13 +13,15 @@ in pkgs.runCommand "check" {
socat
e2fsprogs
util-linux # for sfdisk, fallocate
parted
] ;
} ''
mkdir vm
dd if=/dev/zero of=./vm/stick.e2fs bs=1M count=32
mkfs.ext2 -L backup-disk ./vm/stick.e2fs
cat <(dd if=/dev/zero bs=512 count=4) ./vm/stick.e2fs > ./vm/stick.img
echo '4,-,L,*' | sfdisk ./vm/stick.img
dd if=/dev/zero of=./vm/stick.img bs=1M count=38
dd if=./vm/stick.e2fs of=./vm/stick.img bs=512 seek=34 conv=notrunc
parted -s ./vm/stick.img -- mklabel gpt mkpart backup-disk ext2 34s -0M
${img}/run.sh --background ./vm --flag -device --flag usb-ehci,id=xhci --flag -drive --flag if=none,id=usbstick,format=raw,file=$(pwd)/vm/stick.img
expect ${./script.expect} | tee $out