Compare commits

...

3 Commits

Author SHA1 Message Date
Emery Hemingway 0e25a32850 Build system refactor 2024-03-15 10:02:24 +00:00
Emery Hemingway f51f92c30d Rewrite syndev to uevent_dump 2024-03-15 09:56:40 +00:00
Emery Hemingway 4512b33fa9 Move SUBSYSTEM and DEVPATH to record fields
These attributes seem to always be present so don't label them.
2023-07-04 13:34:40 +01:00
12 changed files with 232 additions and 102 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/nim.cfg

View File

@ -28,9 +28,8 @@ Example Syndicate server configuration:
Example message stream:
```
<uevent 32663 add {DEVNAME: "input/event23", DEVPATH: "/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.4/0003:1D50:6122.0026/input/input47/event23", MAJOR: "13", MINOR: "87", SUBSYSTEM: "input"}>
<uevent 32664 add {DEVNAME: "input/mouse5", DEVPATH: "/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.4/0003:1D50:6122.0026/input/input47/mouse5", MAJOR: "13", MINOR: "37", SUBSYSTEM: "input"}>
<uevent 32665 add {DEVNAME: "hidraw10", DEVPATH: "/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.4/0003:1D50:6122.0026/hidraw/hidraw10", MAJOR: "247", MINOR: "10", SUBSYSTEM: "hidraw"}>
<uevent 32666 bind {DEVPATH: "/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.4/0003:1D50:6122.0026", DRIVER: "hid-generic", HID_ID: "0003:00001D50:00006122", HID_NAME: "Ultimate Gadget Laboratories Ultimate Hacking Keyboard", HID_PHYS: "usb-0000:00:14.0-3/input4", HID_UNIQ: "", MODALIAS: "hid:b0003g0001v00001D50p00006122", SUBSYSTEM: "hid"}>
<uevent 32667 bind {DEVPATH: "/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.4", DEVTYPE: "usb_interface", DRIVER: "usbhid", INTERFACE: "3/1/2", MODALIAS: "usb:v1D50p6122d0101dc00dsc00dp00ic03isc01ip02in04", PRODUCT: "1d50/6122/101", SUBSYSTEM: "usb", TYPE: "0/0/0"}>
<uevent "power_supply" "/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:18/PNP0C09:01/PNP0C0A:03/power_supply/BAT0" change {POWER_SUPPLY_CAPACITY: 98, POWER_SUPPLY_CAPACITY_LEVEL: "Normal", POWER_SUPPLY_CYCLE_COUNT: 236, POWER_SUPPLY_ENERGY_FULL: 44672000, POWER_SUPPLY_ENERGY_FULL_DESIGN: 50464000, POWER_SUPPLY_ENERGY_NOW: 43821000, POWER_SUPPLY_MANUFACTURER: "ASUSTeK", POWER_SUPPLY_MODEL_NAME: "ASUS Battery", POWER_SUPPLY_NAME: "BAT0", POWER_SUPPLY_POWER_NOW: 7691000, POWER_SUPPLY_PRESENT: 1, POWER_SUPPLY_SERIAL_NUMBER: " ", POWER_SUPPLY_STATUS: "Charging", POWER_SUPPLY_TECHNOLOGY: "Li-ion", POWER_SUPPLY_TYPE: "Battery", POWER_SUPPLY_VOLTAGE_MIN_DESIGN: 15200000, POWER_SUPPLY_VOLTAGE_NOW: 15200000} 3916>
,uevent "hidraw" "/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.2/0003:248A:5B2F.0011/hidraw/hidraw2" add {DEVNAME: "hidraw2", MAJOR: 247, MINOR: 2} 3980>
<uevent "hid" "/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.2/0003:248A:5B2F.0011" bind {DRIVER: "hid-generic", HID_ID: "0003:0000248A:00005B2F", HID_NAME: "XCTECH Ninjutso Katana Superlight", HID_PHYS: "usb-0000:00:14.0-2/input2", HID_UNIQ: 0, MODALIAS: "hid:b0003g0001v0000248Ap00005B2F"} 3981>
<uevent "usb" "/devices/pci0000:00/0000:00:14.0/usb1/1-2/1-2:1.2" bind {DEVTYPE: "usb_interface", DRIVER: "usbhid", INTERFACE: "3/0/0", MODALIAS: "usb:v248Ap5B2Fd0116dc00dsc00dp00ic03isc00ip00in02", PRODUCT: "248a/5b2f/116", TYPE: "0/0/0"} 3982>
```

3
Tupfile Normal file
View File

@ -0,0 +1,3 @@
include_rules
: |> !nim_lk |> {lockfile}
: {lockfile} |> !nim_cfg |> | ./<lock>

View File

@ -1,2 +1,4 @@
include ../syndicate-nim/depends.tup
NIM = $(DIRENV) $(NIM)
NIM_FLAGS += --path:$(TUP_CWD)/../syndicate-nim/src
NIM_GROUPS += $(TUP_CWD)/<lock>

7
default.nix Normal file
View File

@ -0,0 +1,7 @@
{ pkgs ? import <nixpkgs> { } }:
pkgs.buildNimPackage {
name = "syndicate_utils_linux";
lockFile = ./lock.json;
src = pkgs.lib.sources.cleanSource ./.;
}

103
lock.json Normal file
View File

@ -0,0 +1,103 @@
{
"depends": [
{
"method": "fetchzip",
"packages": [
"bigints"
],
"path": "/nix/store/jvrm392g8adfsgf36prgwkbyd7vh5jsw-source",
"rev": "86ea14d31eea9275e1408ca34e6bfe9c99989a96",
"sha256": "15pcpmnk1bnw3k8769rjzcpg00nahyrypwbxs88jnwr4aczp99j4",
"srcDir": "src",
"url": "https://github.com/ehmry/nim-bigints/archive/86ea14d31eea9275e1408ca34e6bfe9c99989a96.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"cps"
],
"path": "/nix/store/452hfhasrn3gl6vijfmzs69djl099j0j-source",
"rev": "b7c179f172e3a256a482a9daee3c0815ea423206",
"sha256": "1sn9s7iv83sw1jl5jgi2h7b0xpgsn13f9icp5124jvbp0qkxskx2",
"srcDir": "",
"url": "https://github.com/nim-works/cps/archive/b7c179f172e3a256a482a9daee3c0815ea423206.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"hashlib"
],
"path": "/nix/store/fav82xdbicvlk34nmcbl89zx99lr3mbs-source",
"rev": "f9455d4be988e14e3dc7933eb7cc7d7c4820b7ac",
"sha256": "1sx6j952lj98629qfgr7ds5aipyw9d6lldcnnqs205wpj4pkcjb3",
"srcDir": "",
"url": "https://github.com/khchen/hashlib/archive/f9455d4be988e14e3dc7933eb7cc7d7c4820b7ac.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"nimcrypto"
],
"path": "/nix/store/jwz8pqbv6rsm8w4fjzdb37r0wzjn5hv0-source",
"rev": "d58da671799c69c0b3208b96c154e13c8b1a9e90",
"sha256": "12dm0gsy10ppga7zf7hpf4adaqjrd9b740n2w926xyazq1njf6k9",
"srcDir": "",
"url": "https://github.com/cheatfate/nimcrypto/archive/d58da671799c69c0b3208b96c154e13c8b1a9e90.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"npeg"
],
"path": "/nix/store/ffkxmjmigfs7zhhiiqm0iw2c34smyciy-source",
"rev": "26d62fdc40feb84c6533956dc11d5ee9ea9b6c09",
"sha256": "0xpzifjkfp49w76qmaylan8q181bs45anmp46l4bwr3lkrr7bpwh",
"srcDir": "src",
"url": "https://github.com/zevv/npeg/archive/26d62fdc40feb84c6533956dc11d5ee9ea9b6c09.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"preserves"
],
"path": "/nix/store/2hy124xgabz134dxj3wji7mp47fdwy3w-source",
"rev": "9ae435a83c6d5028405538af5d24a023af625b6e",
"sha256": "1k7ywcp1a53x2fpc6wc2b0qzb264dkifash0s1wcp66rw3lx15k2",
"srcDir": "src",
"url": "https://git.syndicate-lang.org/ehmry/preserves-nim/archive/9ae435a83c6d5028405538af5d24a023af625b6e.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"stew"
],
"path": "/nix/store/mqg8qzsbcc8xqabq2yzvlhvcyqypk72c-source",
"rev": "3c91b8694e15137a81ec7db37c6c58194ec94a6a",
"sha256": "17lfhfxp5nxvld78xa83p258y80ks5jb4n53152cdr57xk86y07w",
"srcDir": "",
"url": "https://github.com/status-im/nim-stew/archive/3c91b8694e15137a81ec7db37c6c58194ec94a6a.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"syndicate"
],
"path": "/nix/store/y9f3j4m7vmhf8gbpkvqa77jvzrc5ynlm-source",
"rev": "50a77995bcfe15e6062f54c6af0f55fba850c329",
"sha256": "1avrk86c34qg39w8vlixsksli2gwgbsf29jhlap27ffzdbj2zbal",
"srcDir": "src",
"url": "https://git.syndicate-lang.org/ehmry/syndicate-nim/archive/50a77995bcfe15e6062f54c6af0f55fba850c329.tar.gz"
},
{
"method": "fetchzip",
"packages": [
"sys"
],
"path": "/nix/store/anfijq8zsfvhcy86dx21n8ikh3z8927s-source",
"rev": "b974e1a4ca6ae7d89fc9e7b3714b1e7daf6f33e5",
"sha256": "13js2plyf34bdv9rwhbjkgygmyb89h43cf3z126yjsm2fvh2sl10",
"srcDir": "src",
"url": "https://github.com/ehmry/nim-sys/archive/b974e1a4ca6ae7d89fc9e7b3714b1e7daf6f33e5.tar.gz"
}
]
}

View File

@ -1,5 +0,0 @@
let
syndicate = builtins.getFlake "syndicate";
pkgs =
import <nixpkgs> { overlays = builtins.attrValues syndicate.overlays; };
in pkgs.acpi_actor

View File

@ -1,3 +1,3 @@
include_rules
: syndev.nim | $(SYNDICATE_PROTOCOL) |> !nim_bin |> {bin}
: foreach *.nim | $(SYNDICATE_PROTOCOL) |> !nim_bin |> {bin}
: {bin} |> !assert_built |>

View File

@ -1,86 +0,0 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
# *Do not misinterpret any of this as documentation for handling Linux uevents.*
import std/[asyncdispatch, nativesockets, asyncnet, os, parseutils, posix]
import preserves, syndicate
var
AF_NETLINK {.importc, header: "sys/socket.h", nodecl.}: uint16
NETLINK_KOBJECT_UEVENT {.importc, header: "linux/netlink.h", nodecl.}: cint
type
Sockaddr_nl {.importc: "struct sockaddr_nl", header: "linux/netlink.h".} = object
nl_family: uint16
nl_pad: uint16
nl_pid: uint32
nl_groups: uint32
proc initSockaddr(family = AF_NETLINK; pid, groups = 0'u32): Sockaddr_nl =
Sockaddr_nl(nl_family: family, nl_pid: pid, nl_groups: groups)
proc saddr(sa_nl: var Sockaddr_nl): ptr Sockaddr =
cast[ptr Sockaddr](addr sa_nl)
proc openUeventSocket: AsyncSocket =
result = newAsyncSocket(
cint AF_NETLINK,
posix.SOCK_DGRAM,
NETLINK_KOBJECT_UEVENT,
buffered = false
)
var sa = initSockaddr(pid = uint32 getPid(), groups = 1)
if bindAddr(result.getFd, saddr sa, SockLen sizeof(sa)) != 0:
close(result)
raiseOSError(osLastError(), "failed to bind Netlink socket")
if sa.nl_family != AF_NETLINK:
close(result)
raise newException(IOError, "Netlink not supported")
proc main(facet: Facet; ds: Ref; sock: AsyncSocket) =
var
buf = newString(1 shl 14)
msg = initRecord("uevent", 0.toPreserve, "".toSymbol, initDictionary())
key, val: string
proc recvUevent {.gcsafe.} =
let fut = recvInto(sock, buf[0].addr, buf.len)
addCallback(fut, facet) do (turn: var Turn):
let n = read fut
if n < 1:
close(sock)
else:
var i = 0
while i < n:
inc i, skipWhile(buf, {'\0'}, i)
inc i, parseUntil(buf, key, {'=', '@'}, i)
if i < n:
if buf[i] == '@':
if msg.record[2].dict.len > 0:
cannonicalize(msg.record[1])
message(turn, ds, msg)
msg.record[2].dict.setLen(0)
inc i, parseUntil(buf, val, '\0', i+1)+1
elif key == "SEQNUM":
var seqnum: int
inc i, parseInt(buf, seqnum, i+1)+1
msg.record[0].int = seqnum.BiggestInt
else:
inc i, parseUntil(buf, val, '\0', i+1)+1
if key == "ACTION":
msg.record[1].symbol = Symbol val
else:
add(msg.record[2].dict, (key.toSymbol, val.toPreserve,))
recvUevent()
recvUevent()
type Args {.preservesDictionary.} = object
machine: Ref
runActor("syndev") do (root: Ref; turn: var Turn):
connectStdio(root, turn)
during(turn, root, ?Args) do (machine: Ref):
let sock = openUeventSocket()
main(turn.facet, machine, sock)
do:
close(sock)

106
src/uevent_dump.nim Normal file
View File

@ -0,0 +1,106 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
# *Do not misinterpret any of this as documentation for handling Linux uevents.*
import std/[nativesockets, os, parseutils, posix]
import pkg/sys/[handles, ioqueue, sockets]
import preserves, syndicate, syndicate/relays
var
AF_NETLINK {.importc, header: "sys/socket.h", nodecl.}: uint16
NETLINK_KOBJECT_UEVENT {.importc, header: "linux/netlink.h", nodecl.}: cint
type
Sockaddr_nl {.importc: "struct sockaddr_nl", header: "linux/netlink.h".} = object
nl_family: uint16
nl_pad: uint16
nl_pid: uint32
nl_groups: uint32
proc initSockaddr(family = AF_NETLINK; pid, groups: uint32): Sockaddr_nl =
Sockaddr_nl(nl_family: family, nl_pid: pid, nl_groups: groups)
proc saddr(sa_nl: var Sockaddr_nl): ptr Sockaddr =
cast[ptr Sockaddr](addr sa_nl)
proc openUeventSocket: SocketHandle =
result = createNativeSocket(
cint AF_NETLINK,
cint posix.SOCK_DGRAM,
NETLINK_KOBJECT_UEVENT,
)
var sa = initSockaddr(pid = uint32 getPid(), groups = 1)
if bindAddr(result, saddr sa, SockLen sizeof(sa)) != 0:
close(result)
raiseOSError(osLastError(), "failed to bind Netlink socket")
if sa.nl_family != AF_NETLINK:
close(result)
raise newException(IOError, "Netlink not supported")
proc openUeventSocketAsync: AsyncConn[sockets.Protocol.Unix] =
let fd = openUeventSocket()
var flags = fcntl(fd.cint, F_GETFL)
if flags < 0:
raiseOSError(osLastError())
flags = flags or O_NONBLOCK
if fcntl(fd.cint, F_SETFL, flags) < 0:
raiseOSError(osLastError())
AsyncConn[sockets.Protocol.Unix] fd.SocketFD.initHandle.newAsyncSocket
proc sendMessage(turn: var Turn; ds: Cap; buf: string; n: int) =
var
msg = initRecord("uevent", toPreserves"", toPreserves"", toSymbol"", initDictionary(), 0.toPreserves)
key, val: string
i = 0
while i < n:
inc i, skipWhile(buf, {'\0'}, i)
inc i, parseUntil(buf, key, {'=', '@'}, i)
if i < n:
let sep = buf[i]
inc i, parseUntil(buf, val, '\0', i+1)+1
case sep
of '@': discard
of '=':
if key == "SUBSYSTEM":
msg.record[0].string = val
elif key == "DEVPATH":
msg.record[1].string = val
elif key == "ACTION":
msg.record[2].symbol = Symbol val
elif key == "SEQNUM":
msg.record[4] = parsePreserves(val)
else:
# TODO: check if val can be an integer
var num: BiggestInt
if parseBiggestInt(val, num) == val.len:
add(msg.record[3].dict, (key.toSymbol, num.toPreserve,))
else:
add(msg.record[3].dict, (key.toSymbol, val.toPreserve,))
val.setLen(0)
else:
stderr.writeLine "uevent parser failure"
return
if msg.record[3].dict.len > 0:
cannonicalize(msg.record[3])
message(turn, ds, msg)
msg.record[3].dict.setLen(0)
proc loop(facet: Facet; ds: Cap; sock: AsyncConn[sockets.Protocol.Unix]) {.asyncio.} =
let buf = new string
buf[].setLen(16 shl 10)
while true:
let n = read(sock, buf)
if n < 1: stopActor(facet)
else:
proc act(turn: var Turn) =
sendMessage(turn, ds, buf[], n)
run(facet, act)
runActor("uevent_dump") do (turn: var Turn):
let sock = openUeventSocketAsync()
onStop(turn) do (turn: var Turn):
close(sock)
resolveEnvironment(turn) do (turn: var Turn; ds: Cap):
discard trampoline:
whelp loop(turn.facet, ds, sock)

View File

@ -1,13 +1,13 @@
# Package
version = "20230702"
version = "20240315"
author = "Emery Hemingway"
description = "Syndicated actor for publishing Linux device events"
description = "Syndicate utilties for Linux."
license = "Unlicense"
srcDir = "src"
bin = @["syndev"]
bin = @["uevent_dump"]
# Dependencies
requires "nim >= 1.6.12", "syndicate >= 20230701"
requires "nim >= 1.6.12", "syndicate >= 20240208", "https://github.com/ehmry/nim-sys.git#b974e1a4ca6ae7d89fc9e7b3714b1e7daf6f33e5", "https://github.com/nim-works/cps"

View File

@ -1,3 +1,3 @@
version 1.
Uevent = <uvent @seqnum int @action string @attrs {symbol: string ...:...}> .
Uevent = <uvent @subsystem string @devpath string @action string @attrs {symbol: string ...:...} @seqnum int> .