forked from rosa/hakurei
internal/uevent: kobject_action lookup
This is encoded as part of kobject uevent message headers. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
74
internal/uevent/action.go
Normal file
74
internal/uevent/action.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package uevent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KobjectAction represents enum kobject_action found in include/linux/kobject.h
|
||||||
|
// and their corresponding string representations in lib/kobject_uevent.c.
|
||||||
|
type KobjectAction uint32
|
||||||
|
|
||||||
|
// include/linux/kobject.h
|
||||||
|
const (
|
||||||
|
KOBJ_ADD KobjectAction = iota
|
||||||
|
KOBJ_REMOVE
|
||||||
|
KOBJ_CHANGE
|
||||||
|
KOBJ_MOVE
|
||||||
|
KOBJ_ONLINE
|
||||||
|
KOBJ_OFFLINE
|
||||||
|
KOBJ_BIND
|
||||||
|
KOBJ_UNBIND
|
||||||
|
)
|
||||||
|
|
||||||
|
// lib/kobject_uevent.c
|
||||||
|
var kobject_actions = [...]string{
|
||||||
|
KOBJ_ADD: "add",
|
||||||
|
KOBJ_REMOVE: "remove",
|
||||||
|
KOBJ_CHANGE: "change",
|
||||||
|
KOBJ_MOVE: "move",
|
||||||
|
KOBJ_ONLINE: "online",
|
||||||
|
KOBJ_OFFLINE: "offline",
|
||||||
|
KOBJ_BIND: "bind",
|
||||||
|
KOBJ_UNBIND: "unbind",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid returns whether the value of act is defined.
|
||||||
|
func (act KobjectAction) Valid() bool { return int(act) < len(kobject_actions) }
|
||||||
|
|
||||||
|
// String returns the corresponding string sent over netlink.
|
||||||
|
func (act KobjectAction) String() string {
|
||||||
|
if !act.Valid() {
|
||||||
|
return "unsupported kobject_action " + strconv.Itoa(int(act))
|
||||||
|
}
|
||||||
|
return kobject_actions[act]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (act KobjectAction) AppendText(b []byte) ([]byte, error) {
|
||||||
|
if !act.Valid() {
|
||||||
|
return b, syscall.EINVAL
|
||||||
|
}
|
||||||
|
return append(b, act.String()...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (act KobjectAction) MarshalText() ([]byte, error) {
|
||||||
|
return act.AppendText(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An UnsupportedActionError describes a string representation of [KobjectAction]
|
||||||
|
// not yet supported by this package.
|
||||||
|
type UnsupportedActionError string
|
||||||
|
|
||||||
|
func (e UnsupportedActionError) Error() string {
|
||||||
|
return "unsupported kobject_action " + strconv.Quote(string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (act *KobjectAction) UnmarshalText(data []byte) error {
|
||||||
|
for v, s := range kobject_actions {
|
||||||
|
if string(data) == s {
|
||||||
|
*act = KobjectAction(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return UnsupportedActionError(data)
|
||||||
|
}
|
||||||
32
internal/uevent/action_test.go
Normal file
32
internal/uevent/action_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package uevent_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKobjectAction(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
adeT(t, "add", uevent.KOBJ_ADD, "add", nil, nil)
|
||||||
|
adeT(t, "remove", uevent.KOBJ_REMOVE, "remove", nil, nil)
|
||||||
|
adeT(t, "change", uevent.KOBJ_CHANGE, "change", nil, nil)
|
||||||
|
adeT(t, "move", uevent.KOBJ_MOVE, "move", nil, nil)
|
||||||
|
adeT(t, "online", uevent.KOBJ_ONLINE, "online", nil, nil)
|
||||||
|
adeT(t, "offline", uevent.KOBJ_OFFLINE, "offline", nil, nil)
|
||||||
|
adeT(t, "bind", uevent.KOBJ_BIND, "bind", nil, nil)
|
||||||
|
adeT(t, "unbind", uevent.KOBJ_UNBIND, "unbind", nil, nil)
|
||||||
|
|
||||||
|
adeT(t, "unsupported", uevent.KobjectAction(0xbad), "explode",
|
||||||
|
uevent.UnsupportedActionError("explode"), syscall.EINVAL)
|
||||||
|
t.Run("oob string", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const want = "unsupported kobject_action 2989"
|
||||||
|
if got := uevent.KobjectAction(0xbad).String(); got != want {
|
||||||
|
t.Errorf("String: %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
83
internal/uevent/uevent_test.go
Normal file
83
internal/uevent/uevent_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package uevent_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/internal/uevent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// adeT sets up a parallel subtest for a textual appender/decoder/encoder.
|
||||||
|
func adeT[V any, S interface {
|
||||||
|
encoding.TextAppender
|
||||||
|
encoding.TextMarshaler
|
||||||
|
encoding.TextUnmarshaler
|
||||||
|
fmt.Stringer
|
||||||
|
|
||||||
|
*V
|
||||||
|
}](t *testing.T, name string, v V, want string, wantErr, wantErrE error) {
|
||||||
|
t.Helper()
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
t.Run("decode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var got V
|
||||||
|
if err := S(&got).UnmarshalText([]byte(want)); !reflect.DeepEqual(err, wantErr) {
|
||||||
|
t.Fatalf("UnmarshalText: error = %v, want %v", err, wantErr)
|
||||||
|
}
|
||||||
|
if wantErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(&got, &v) {
|
||||||
|
t.Errorf("UnmarshalText: %#v, want %#v", got, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("encode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
if got, err := S(&v).MarshalText(); !reflect.DeepEqual(err, wantErrE) {
|
||||||
|
t.Fatalf("MarshalText: error = %v, want %v", err, wantErrE)
|
||||||
|
} else if err == nil && string(got) != want {
|
||||||
|
t.Errorf("MarshalText: %q, want %q", string(got), want)
|
||||||
|
}
|
||||||
|
if wantErrE != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := S(&v).String(); got != want {
|
||||||
|
t.Errorf("String: %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"UnsupportedActionError", uevent.UnsupportedActionError("explode"),
|
||||||
|
`unsupported kobject_action "explode"`},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if got := tc.err.Error(); got != tc.want {
|
||||||
|
t.Errorf("Error: %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user