From 990665454303318098c7aaa56b21df7c083a9ebe Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Sun, 2 Jul 2023 15:22:15 +0100 Subject: [PATCH] Initial commit --- .envrc | 2 ++ README.md | 36 +++++++++++++++++++++ Tuprules.tup | 2 ++ shell.nix | 5 +++ src/Tupfile | 3 ++ src/syndev.nim | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++ syndev.nimble | 13 ++++++++ uevent.prs | 3 ++ 8 files changed, 150 insertions(+) create mode 100644 .envrc create mode 100644 README.md create mode 100644 Tuprules.tup create mode 100644 shell.nix create mode 100644 src/Tupfile create mode 100644 src/syndev.nim create mode 100644 syndev.nimble create mode 100644 uevent.prs diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..d324c24 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +source_env .. +use nix diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f4db68 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# Syndicated Device Event Broadcaster + +An actor for broadcasting Linux Kernel uevents. + +Intended as part of native a device manager for [Synit](https://synit.org). This is not a replacement for udev/eudev or mdev/mdevd. + +Message Schema is at [./uevent.prs](./uevent.prs). + +If you are looking for documentation on how to parse and handle Linux uevents, please let me know when you find it. + +Example Syndicate server configuration: +``` +? [ + + > + + + + ? ?cap> [ + $cap { machine: $machine } + ] +] + +``` + +Example message stream: +``` + + + + + +``` diff --git a/Tuprules.tup b/Tuprules.tup new file mode 100644 index 0000000..f902ead --- /dev/null +++ b/Tuprules.tup @@ -0,0 +1,2 @@ +include ../syndicate-nim/depends.tup +NIM_FLAGS += --path:$(TUP_CWD)/../syndicate-nim/src diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..8ff96e3 --- /dev/null +++ b/shell.nix @@ -0,0 +1,5 @@ +let + syndicate = builtins.getFlake "syndicate"; + pkgs = + import { overlays = builtins.attrValues syndicate.overlays; }; +in pkgs.acpi_actor diff --git a/src/Tupfile b/src/Tupfile new file mode 100644 index 0000000..0784bc1 --- /dev/null +++ b/src/Tupfile @@ -0,0 +1,3 @@ +include_rules +: syndev.nim | $(SYNDICATE_PROTOCOL) |> !nim_bin |> {bin} +: {bin} |> !assert_built |> diff --git a/src/syndev.nim b/src/syndev.nim new file mode 100644 index 0000000..4ed6faa --- /dev/null +++ b/src/syndev.nim @@ -0,0 +1,86 @@ +# 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) diff --git a/syndev.nimble b/syndev.nimble new file mode 100644 index 0000000..3447004 --- /dev/null +++ b/syndev.nimble @@ -0,0 +1,13 @@ +# Package + +version = "20230702" +author = "Emery Hemingway" +description = "Syndicated actor for publishing Linux device events" +license = "Unlicense" +srcDir = "src" +bin = @["syndev"] + + +# Dependencies + +requires "nim >= 1.6.12", "syndicate >= 20230701" diff --git a/uevent.prs b/uevent.prs new file mode 100644 index 0000000..6794646 --- /dev/null +++ b/uevent.prs @@ -0,0 +1,3 @@ +version 1. + +Uevent = .