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:
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
87
internal/uevent/sysfs.go
Normal 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
|
||||||
|
})
|
||||||
|
}
|
||||||
28
internal/uevent/sysfs_test.go
Normal file
28
internal/uevent/sysfs_test.go
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user