- Go 98.3%
- Shell 1.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| cmd/dmancli | ||
| docs | ||
| internal/assert | ||
| scripts | ||
| .gitignore | ||
| conn.go | ||
| delta.go | ||
| device.go | ||
| doc.go | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| listen.go | ||
| listen_test.go | ||
| log.go | ||
| manager.go | ||
| query.go | ||
| README.md | ||
| scan.go | ||
| store.go | ||
| sysfs.go | ||
| uevent.go | ||
| uevent_test.go | ||
| watch.go | ||
dman
Subsystem-agnostic Linux device-monitoring core, in pure Go. No cgo.
Captures the Linux device model (/sys/devices) as a generic graph via netlink uevent transport and sysfs reads, and exposes it through a query/subscribe API. Device classification (gamepad/HID, USB block storage, etc.) is left entirely to consumers — dman has no built-in notion of what a "device" is beyond structural presence in sysfs.
Requirements
- Go 1.25.3+
- Linux (netlink uevent is a Linux kernel mechanism; no cross-platform support)
CGO_ENABLED=0compatible — no cgo anywhere in this module
Install
go get codeberg.org/neox5/dman
Quick start
m := dman.New()
// Subscribe before Start — calling Subscribe after Start panics.
events := m.Subscribe(dman.Match{Subsystem: "block", DevType: "disk"})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := m.Start(ctx); err != nil {
log.Fatal(err)
}
// Baseline: every already-attached matching device, as of Start's
// return — reliable, not subject to any live buffer.
for _, d := range m.Query(dman.NewQuery().MatchOn(dman.Match{Subsystem: "block", DevType: "disk"})) {
fmt.Println("already attached:", d.DevName())
}
go func() {
if err := m.Watch(ctx); err != nil {
log.Println("watch stopped:", err)
}
}()
for ev := range events {
switch ev.Action {
case dman.EventAdd:
fmt.Println("attached:", ev.Device.DevName())
case dman.EventRemove:
fmt.Println("removed:", ev.Device.DevName())
}
}
Start opens the connection and performs a full sysfs Scan, synchronously — once it returns, Query reflects a complete, reliable baseline. Watch (run separately, typically via go) then delivers only live changes from that point on to every Subscribed listener; pre-existing devices are read via Query, not delivered as events.
Correlating related devices
Device.Parent/Device.Children are bare DevPath strings, not nested Device values — resolve them via Query:
// walk up from a disk to its usb_device ancestor
usb := m.Query(dman.NewQuery().
Ancestors(disk.DevPath).
MatchOn(dman.Match{Subsystem: "usb", DevType: "usb_device"}))
// resolve one already-known DevPath directly
results := m.Query(dman.NewQuery().MatchOn(dman.Match{DevPath: disk.Parent}))
One-shot scan, no live watch
store := dman.NewStore()
for d, err := range dman.Scan(ctx) {
if err != nil {
log.Fatal(err)
}
if d.Type == dman.Upsert {
store.Add(ctx, d.Device)
}
}
API surface
| Type / Func | Purpose |
|---|---|
Manager |
Owns Store, the live connection, and subscribers. New, Subscribe, Start, Watch, Query. |
Device |
One captured sysfs node. Attrs, Refs, Children, Parent + Subsystem()/DevType()/DevName() accessors. |
Store |
The captured graph. Add/Remove (writes), Lookup/All/Query (reads). |
Match |
Filter vocabulary — DevPath, Subsystem, DevType, Attrs (AND-combined). Used by both Subscribe and Query. |
Query |
Match + Scope (Descendants/Ancestors, self-inclusive) against a Store. |
Scan |
Pure, one-shot, full sysfs capture, yielded as iter.Seq2[Delta, error]. |
Watch |
Pure, live netlink uevent stream, yielded as iter.Seq2[Delta, error]. |
ListenRaw |
Low-level: raw, unfiltered UEvent channel, bypassing Manager/Store entirely. Diagnostic use. |
Diagnostic tool
go build -o bin/dmancli ./cmd/dmancli
dmancli scan [devpath...]
dmancli watch [-subsystem=X] [-devtype=Y] [devpath...]
dmancli raw
dmancli is a diagnostic/manual-validation tool, not a public product surface — see dmancli -h. watch/raw require permission to open a netlink uevent socket (typically root or CAP_NET_ADMIN); scan reads only /sys and does not.
Development
go vet ./...
go build ./...
go test ./...
License
MIT — see LICENSE.