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 break } } 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) 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.Equal(raw[:8], []byte("libudev\x00")) { 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), } 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 }