github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/osutil/udev/netlink/uevent.go (about)

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