/
uevent.go
178 lines (150 loc) · 4.29 KB
/
uevent.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package netlink
import (
"bytes"
"encoding/binary"
"fmt"
"strings"
"unsafe"
)
// See: http://elixir.free-electrons.com/linux/v3.12/source/lib/kobject_uevent.c#L45
const (
ADD KObjAction = "add"
REMOVE KObjAction = "remove"
CHANGE KObjAction = "change"
MOVE KObjAction = "move"
ONLINE KObjAction = "online"
OFFLINE KObjAction = "offline"
BIND KObjAction = "bind"
UNBIND KObjAction = "unbind"
)
// The magic value used by udev, see https://github.com/systemd/systemd/blob/v239/src/libudev/libudev-monitor.c#L57
const libudevMagic = 0xfeedcafe
type KObjAction string
func (a KObjAction) String() string {
return string(a)
}
func ParseKObjAction(raw string) (a KObjAction, err error) {
a = KObjAction(raw)
switch a {
case ADD, REMOVE, CHANGE, MOVE, ONLINE, OFFLINE, BIND, UNBIND:
default:
err = fmt.Errorf("unknow kobject action (got: %s)", raw)
}
return
}
type UEvent struct {
Action KObjAction
KObj string
Env map[string]string
}
func (e UEvent) String() string {
rv := fmt.Sprintf("%s@%s\000", e.Action.String(), e.KObj)
for k, v := range e.Env {
rv += k + "=" + v + "\000"
}
return rv
}
func (e UEvent) Bytes() []byte {
return []byte(e.String())
}
func (e UEvent) Equal(e2 UEvent) (bool, error) {
if e.Action != e2.Action {
return false, fmt.Errorf("Wrong action (got: %s, wanted: %s)", e.Action, e2.Action)
}
if e.KObj != e2.KObj {
return false, fmt.Errorf("Wrong kobject (got: %s, wanted: %s)", e.KObj, e2.KObj)
}
if len(e.Env) != len(e2.Env) {
return false, fmt.Errorf("Wrong length of env (got: %d, wanted: %d)", len(e.Env), len(e2.Env))
}
var found bool
for k, v := range e.Env {
found = false
for i, e := range e2.Env {
if i == k && v == e {
found = true
}
}
if !found {
return false, fmt.Errorf("Unable to find %s=%s env var from uevent", k, v)
}
}
return true, nil
}
// Parse udev event created by udevd.
// The format of the data header is internal to udev and defined in libudev-monitor.c - see the udev_monitor_netlink_header struct.
// go-udev only looks at the "magic" number to filter out possibly invalid packets, and at the payload offset. Other fields of the header
// are ignored.
// Note, only some of the fields of the header use network byte order, for the rest udev uses native byte order of the platform.
func parseUdevEvent(raw []byte) (e *UEvent, err error) {
// the magic number is stored in network byte order.
magic := binary.BigEndian.Uint32(raw[8:])
if magic != libudevMagic {
return nil, fmt.Errorf("cannot parse libudev event: magic number mismatch")
}
// the payload offset int is stored in native byte order.
payloadoff := *(*uint32)(unsafe.Pointer(&raw[16]))
if payloadoff >= uint32(len(raw)) {
return nil, fmt.Errorf("cannot parse libudev event: invalid data offset")
}
fields := bytes.Split(raw[payloadoff:], []byte{0x00}) // 0x00 = end of string
if len(fields) == 0 {
err = fmt.Errorf("cannot parse libudev event: data missing")
return
}
envdata := make(map[string]string, 0)
for _, envs := range fields[0 : len(fields)-1] {
env := bytes.Split(envs, []byte("="))
if len(env) != 2 {
err = fmt.Errorf("cannot parse libudev event: invalid env data")
return
}
envdata[string(env[0])] = string(env[1])
}
var action KObjAction
action, err = ParseKObjAction(strings.ToLower(envdata["ACTION"]))
if err != nil {
return
}
// XXX: do we need kobj?
kobj := envdata["DEVPATH"]
e = &UEvent{
Action: action,
KObj: kobj,
Env: envdata,
}
return
}
func ParseUEvent(raw []byte) (e *UEvent, err error) {
if len(raw) > 40 && bytes.Compare(raw[:8], []byte("libudev\x00")) == 0 {
return parseUdevEvent(raw)
}
fields := bytes.Split(raw, []byte{0x00}) // 0x00 = end of string
if len(fields) == 0 {
err = fmt.Errorf("Wrong uevent format")
return
}
headers := bytes.Split(fields[0], []byte("@")) // 0x40 = @
if len(headers) != 2 {
err = fmt.Errorf("Wrong uevent header")
return
}
action, err := ParseKObjAction(string(headers[0]))
if err != nil {
return
}
e = &UEvent{
Action: action,
KObj: string(headers[1]),
Env: make(map[string]string, 0),
}
for _, envs := range fields[1 : len(fields)-1] {
env := bytes.Split(envs, []byte("="))
if len(env) != 2 {
err = fmt.Errorf("Wrong uevent env")
return
}
e.Env[string(env[0])] = string(env[1])
}
return
}