1
0
forked from rosa/hakurei

internal/uevent: enumerate objects via sysfs

This is not a great way to implement cold boot, but I already have the implementation lying around.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-03-29 19:50:20 +09:00
parent 19c76e0831
commit 121fcfa406
5 changed files with 141 additions and 1 deletions

View File

@@ -19,6 +19,10 @@ const (
KOBJ_OFFLINE KOBJ_OFFLINE
KOBJ_BIND KOBJ_BIND
KOBJ_UNBIND KOBJ_UNBIND
// Synthetic denotes a [Message] that originates from outside the kernel. It
// is not valid in the wire format and is only meaningful within this package.
Synthetic KobjectAction = 0xfeed
) )
// lib/kobject_uevent.c // lib/kobject_uevent.c
@@ -38,6 +42,10 @@ func (act KobjectAction) Valid() bool { return int(act) < len(kobject_actions) }
// String returns the corresponding string sent over netlink. // String returns the corresponding string sent over netlink.
func (act KobjectAction) String() string { func (act KobjectAction) String() string {
if act == Synthetic {
return "synthetic"
}
if !act.Valid() { if !act.Valid() {
return "unsupported kobject_action " + strconv.Itoa(int(act)) return "unsupported kobject_action " + strconv.Itoa(int(act))
} }
@@ -45,7 +53,7 @@ func (act KobjectAction) String() string {
} }
func (act KobjectAction) AppendText(b []byte) ([]byte, error) { func (act KobjectAction) AppendText(b []byte) ([]byte, error) {
if !act.Valid() { if !act.Valid() && act != Synthetic {
return b, syscall.EINVAL return b, syscall.EINVAL
} }
return append(b, act.String()...), nil return append(b, act.String()...), nil

View File

@@ -29,4 +29,15 @@ func TestKobjectAction(t *testing.T) {
t.Errorf("String: %q, want %q", got, want) t.Errorf("String: %q, want %q", got, want)
} }
}) })
adeT(t, "synthetic", uevent.Synthetic, "synthetic",
uevent.UnsupportedActionError("synthetic"), nil)
t.Run("validate synthetic", func(t *testing.T) {
t.Parallel()
if uevent.Synthetic.Valid() {
t.Errorf("Valid unexpectedly succeeded")
}
})
} }

View File

@@ -110,6 +110,12 @@ SEQNUM=780`},
}, "move", uevent.MissingHeaderError( }, "move", uevent.MissingHeaderError(
"move", "move",
), syscall.EINVAL, "unsupported kobject_action 2989 event:"}, ), syscall.EINVAL, "unsupported kobject_action 2989 event:"},
{"synthetic", uevent.Message{
Action: uevent.Synthetic,
}, "synthetic@\x00", uevent.UnsupportedActionError(
"synthetic",
), nil, "synthetic event:"},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {

87
internal/uevent/sysfs.go Normal file
View File

@@ -0,0 +1,87 @@
package uevent
import (
"bytes"
"errors"
"io/fs"
"log"
"path/filepath"
"unsafe"
)
// Enumerate scans sysfs and emits [Synthetic] events. It returns the first
// error it encounters.
//
// The specified filesystem must present the sysfs root.
func Enumerate(
sysfs fs.FS,
handleWalkErr func(error) error,
events chan<- *Message,
) error {
if handleWalkErr == nil {
handleWalkErr = func(err error) error {
if errors.Is(err, fs.ErrNotExist) {
log.Println("enumerate", err)
return nil
}
return err
}
}
return fs.WalkDir(sysfs, "devices", func(
path string,
d fs.DirEntry,
err error,
) error {
if err != nil {
return handleWalkErr(err)
}
if d.IsDir() || d.Name() != "uevent" {
return nil
}
msg := Message{
Action: Synthetic,
// cleans path, appears to be compatible with kernel behaviour
DevPath: filepath.Dir(path),
}
var target string
if target, err = fs.ReadLink(
sysfs,
filepath.Join(msg.DevPath, "subsystem"),
); err != nil {
if err = handleWalkErr(err); err != nil {
return err
}
} else {
msg.Env = append(msg.Env, "SUBSYSTEM="+filepath.Base(target))
}
// read entire file: slicing does not copy
var env []byte
if env, err = fs.ReadFile(sysfs, path); err != nil {
return handleWalkErr(err)
}
for _, s := range bytes.Split(env, []byte{'\n'}) {
if len(s) == 0 {
continue
}
msg.Env = append(msg.Env, unsafe.String(unsafe.SliceData(s), len(s)))
}
if len(msg.Env) == 0 {
// this implies absent subsystem, its error is already handled
return nil
}
if msg.DevPath != "" && msg.DevPath[0] != '/' {
msg.DevPath = "/" + msg.DevPath
}
events <- &msg
return nil
})
}

View File

@@ -0,0 +1,28 @@
package uevent_test
import (
"os"
"sync"
"testing"
"hakurei.app/internal/uevent"
)
func TestEnumerate(t *testing.T) {
t.Parallel()
var wg sync.WaitGroup
defer wg.Wait()
events := make(chan *uevent.Message, 1<<10)
wg.Go(func() {
for msg := range events {
t.Log(msg)
}
})
if err := uevent.Enumerate(os.DirFS("/sys"), nil, events); err != nil {
t.Fatalf("Enumerate: error = %v", err)
}
close(events)
}