[lxc-devel] [lxd/master] Uevent-based matching for unix-char and unix-block

brauner on Github lxc-bot at linuxcontainers.org
Wed Jan 29 15:06:45 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 368 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200129/f95392ea/attachment-0001.bin>
-------------- next part --------------
From 5b4407eff32d86642cc22d7b6b57ac5215a462b4 Mon Sep 17 00:00:00 2001
From: David Mao <david.mao.92 at gmail.com>
Date: Sat, 16 Nov 2019 17:43:47 -0600
Subject: [PATCH 1/6] lxd/device: Add unix_hotplug device type

Signed-off-by: Lillian Jan Johnson <lillianjanjohnson at gmail.com>
Signed-off-by: David Mao <david.mao at utexas.edu>
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/device/device_utils_unix.go               |  21 ++
 .../device_utils_unix_hotplug_events.go       | 120 ++++++++++
 lxd/device/unix_hotplug.go                    | 215 ++++++++++++++++++
 3 files changed, 356 insertions(+)
 create mode 100644 lxd/device/device_utils_unix_hotplug_events.go
 create mode 100644 lxd/device/unix_hotplug.go

diff --git a/lxd/device/device_utils_unix.go b/lxd/device/device_utils_unix.go
index 2576d80152..0fe80873a7 100644
--- a/lxd/device/device_utils_unix.go
+++ b/lxd/device/device_utils_unix.go
@@ -378,6 +378,27 @@ func unixDeviceSetupCharNum(s *state.State, devicesPath string, typePrefix strin
 	return unixDeviceSetup(s, devicesPath, typePrefix, deviceName, configCopy, defaultMode, runConf)
 }
 
+// unixDeviceSetupBlockNum calls unixDeviceSetup and overrides the supplied device config with the
+// type as "unix-block" and the supplied major and minor numbers. This function can be used when you
+// already know the device's major and minor numbers to avoid unixDeviceSetup() having to stat the
+// device to ascertain these attributes. If defaultMode is true or mode is supplied in the device
+// config then the origin device does not need to be accessed for its file mode.
+func unixDeviceSetupBlockNum(s *state.State, devicesPath string, typePrefix string, deviceName string, m deviceConfig.Device, major uint32, minor uint32, path string, defaultMode bool, runConf *deviceConfig.RunConfig) error {
+	configCopy := deviceConfig.Device{}
+	for k, v := range m {
+		configCopy[k] = v
+	}
+
+	// Overridng these in the config copy should avoid the need for unixDeviceSetup to stat
+	// the origin device to ascertain this information.
+	configCopy["type"] = "unix-block"
+	configCopy["major"] = fmt.Sprintf("%d", major)
+	configCopy["minor"] = fmt.Sprintf("%d", minor)
+	configCopy["path"] = path
+
+	return unixDeviceSetup(s, devicesPath, typePrefix, deviceName, configCopy, defaultMode, runConf)
+}
+
 // UnixDeviceExists checks if the unix device already exists in devices path.
 func UnixDeviceExists(devicesPath string, prefix string, path string) bool {
 	relativeDestPath := strings.TrimPrefix(path, "/")
diff --git a/lxd/device/device_utils_unix_hotplug_events.go b/lxd/device/device_utils_unix_hotplug_events.go
new file mode 100644
index 0000000000..15ea424ff3
--- /dev/null
+++ b/lxd/device/device_utils_unix_hotplug_events.go
@@ -0,0 +1,120 @@
+package device
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+	"sync"
+
+	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/state"
+	log "github.com/lxc/lxd/shared/log15"
+	"github.com/lxc/lxd/shared/logger"
+)
+
+// UnixHotplugEvent represents the properties of a Unix hotplug device uevent.
+type UnixHotplugEvent struct {
+	Action string
+
+	Vendor  string
+	Product string
+
+	Path        string
+	Major       uint32
+	Minor       uint32
+	Subsystem   string
+	UeventParts []string
+	UeventLen   int
+}
+
+// unixHotplugHandlers stores the event handler callbacks for Unix hotplug events.
+var unixHotplugHandlers = map[string]func(UnixHotplugEvent) (*deviceConfig.RunConfig, error){}
+
+// unixHotplugMutex controls access to the unixHotplugHandlers map.
+var unixHotplugMutex sync.Mutex
+
+// unixHotplugRegisterHandler registers a handler function to be called whenever a Unix hotplug device event occurs.
+func unixHotplugRegisterHandler(instance instance.Instance, deviceName string, handler func(UnixHotplugEvent) (*deviceConfig.RunConfig, error)) {
+	unixHotplugMutex.Lock()
+	defer unixHotplugMutex.Unlock()
+
+	// Null delimited string of project name, instance name and device name.
+	key := fmt.Sprintf("%s\000%s\000%s", instance.Project(), instance.Name(), deviceName)
+	unixHotplugHandlers[key] = handler
+}
+
+// unixHotplugUnregisterHandler removes a registered Unix hotplug handler function for a device.
+func unixHotplugUnregisterHandler(instance instance.Instance, deviceName string) {
+	unixHotplugMutex.Lock()
+	defer unixHotplugMutex.Unlock()
+
+	// Null delimited string of project name, instance name and device name.
+	key := fmt.Sprintf("%s\000%s\000%s", instance.Project(), instance.Name(), deviceName)
+	delete(unixHotplugHandlers, key)
+}
+
+// UnixHotplugRunHandlers executes any handlers registered for Unix hotplug events.
+func UnixHotplugRunHandlers(state *state.State, event *UnixHotplugEvent) {
+	unixHotplugMutex.Lock()
+	defer unixHotplugMutex.Unlock()
+
+	for key, hook := range unixHotplugHandlers {
+		keyParts := strings.SplitN(key, "\000", 3)
+		projectName := keyParts[0]
+		instanceName := keyParts[1]
+		deviceName := keyParts[2]
+
+		if hook == nil {
+			delete(unixHotplugHandlers, key)
+			continue
+		}
+
+		runConf, err := hook(*event)
+		if err != nil {
+			logger.Error("Unix hotplug event hook failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
+			continue
+		}
+
+		// If runConf supplied, load instance and call its Unix hotplug event handler function so
+		// any instance specific device actions can occur.
+		if runConf != nil {
+			instance, err := InstanceLoadByProjectAndName(state, projectName, instanceName)
+			if err != nil {
+				logger.Error("Unix hotplug event loading instance failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
+				continue
+			}
+
+			err = instance.DeviceEventHandler(runConf)
+			if err != nil {
+				logger.Error("Unix hotplug event instance handler failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
+				continue
+			}
+		}
+	}
+}
+
+// UnixHotplugNewEvent instantiates a new UnixHotplugEvent struct.
+func UnixHotplugNewEvent(action string, vendor string, product string, major string, minor string, subsystem string, devname string, ueventParts []string, ueventLen int) (UnixHotplugEvent, error) {
+	majorInt, err := strconv.ParseUint(major, 10, 32)
+	if err != nil {
+		return UnixHotplugEvent{}, err
+	}
+
+	minorInt, err := strconv.ParseUint(minor, 10, 32)
+	if err != nil {
+		return UnixHotplugEvent{}, err
+	}
+
+	return UnixHotplugEvent{
+		action,
+		vendor,
+		product,
+		devname,
+		uint32(majorInt),
+		uint32(minorInt),
+		subsystem,
+		ueventParts,
+		ueventLen,
+	}, nil
+}
diff --git a/lxd/device/unix_hotplug.go b/lxd/device/unix_hotplug.go
new file mode 100644
index 0000000000..5e02f96176
--- /dev/null
+++ b/lxd/device/unix_hotplug.go
@@ -0,0 +1,215 @@
+// +build linux,cgo
+
+package device
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	udev "github.com/farjump/go-libudev"
+
+	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/instance/instancetype"
+	"github.com/lxc/lxd/shared"
+)
+
+// unixHotplugIsOurDevice indicates whether the unixHotplug device event qualifies as part of our device.
+// This function is not defined against the unixHotplug struct type so that it can be used in event
+// callbacks without needing to keep a reference to the unixHotplug device struct.
+func unixHotplugIsOurDevice(config deviceConfig.Device, unixHotplug *UnixHotplugEvent) bool {
+	// Check if event matches criteria for this device, if not return.
+	if (config["vendorid"] != "" && config["vendorid"] != unixHotplug.Vendor) || (config["productid"] != "" && config["productid"] != unixHotplug.Product) {
+		return false
+	}
+
+	return true
+}
+
+type unixHotplug struct {
+	deviceCommon
+}
+
+// isRequired indicates whether the device config requires this device to start OK.
+func (d *unixHotplug) isRequired() bool {
+	// Defaults to not required.
+	if shared.IsTrue(d.config["required"]) {
+		return true
+	}
+
+	return false
+}
+
+// validateConfig checks the supplied config for correctness.
+func (d *unixHotplug) validateConfig() error {
+	if d.inst.Type() != instancetype.Container {
+		return ErrUnsupportedDevType
+	}
+
+	rules := map[string]func(string) error{
+		"vendorid":  shared.IsDeviceID,
+		"productid": shared.IsDeviceID,
+		"uid":       unixValidUserID,
+		"gid":       unixValidUserID,
+		"mode":      unixValidOctalFileMode,
+		"required":  shared.IsBool,
+	}
+
+	err := d.config.Validate(rules)
+	if err != nil {
+		return err
+	}
+
+	if d.config["vendorid"] == "" && d.config["productid"] == "" {
+		return fmt.Errorf("Unix hotplug devices require a vendorid or a productid")
+	}
+
+	return nil
+}
+
+// Register is run after the device is started or when LXD starts.
+func (d *unixHotplug) Register() error {
+	// Extract variables needed to run the event hook so that the reference to this device
+	// struct is not needed to be kept in memory.
+	devicesPath := d.inst.DevicesPath()
+	devConfig := d.config
+	deviceName := d.name
+	state := d.state
+
+	// Handler for when a UnixHotplug event occurs.
+	f := func(e UnixHotplugEvent) (*deviceConfig.RunConfig, error) {
+		if !unixHotplugIsOurDevice(devConfig, &e) {
+			return nil, nil
+		}
+
+		runConf := deviceConfig.RunConfig{}
+
+		if e.Action == "add" {
+			if e.Subsystem == "block" {
+				err := unixDeviceSetupBlockNum(state, devicesPath, "unix", deviceName, devConfig, e.Major, e.Minor, e.Path, false, &runConf)
+				if err != nil {
+					return nil, err
+				}
+			} else {
+				err := unixDeviceSetupCharNum(state, devicesPath, "unix", deviceName, devConfig, e.Major, e.Minor, e.Path, false, &runConf)
+				if err != nil {
+					return nil, err
+				}
+			}
+		} else if e.Action == "remove" {
+			relativeTargetPath := strings.TrimPrefix(e.Path, "/")
+			err := unixDeviceRemove(devicesPath, "unix", deviceName, relativeTargetPath, &runConf)
+			if err != nil {
+				return nil, err
+			}
+
+			// Add a post hook function to remove the specific unix hotplug device file after unmount.
+			runConf.PostHooks = []func() error{func() error {
+				err := unixDeviceDeleteFiles(state, devicesPath, "unix", deviceName, relativeTargetPath)
+				if err != nil {
+					return fmt.Errorf("Failed to delete files for device '%s': %v", deviceName, err)
+				}
+
+				return nil
+			}}
+		}
+
+		runConf.Uevents = append(runConf.Uevents, e.UeventParts)
+
+		return &runConf, nil
+	}
+
+	unixHotplugRegisterHandler(d.inst, d.name, f)
+
+	return nil
+}
+
+// Start is run when the device is added to the instance
+func (d *unixHotplug) Start() (*deviceConfig.RunConfig, error) {
+	runConf := deviceConfig.RunConfig{}
+	runConf.PostHooks = []func() error{d.Register}
+
+	device := d.loadUnixDevice()
+	if d.isRequired() && device == nil {
+		return nil, fmt.Errorf("Required Unix Hotplug device not found")
+	}
+	if device == nil {
+		return &runConf, nil
+	}
+
+	i, err := strconv.ParseUint(device.PropertyValue("MAJOR"), 10, 32)
+	if err != nil {
+		return nil, err
+	}
+	major := uint32(i)
+	j, err := strconv.ParseUint(device.PropertyValue("MINOR"), 10, 32)
+	if err != nil {
+		return nil, err
+	}
+	minor := uint32(j)
+
+	// setup device
+	if device.Subsystem() == "block" {
+		err = unixDeviceSetupBlockNum(d.state, d.inst.DevicesPath(), "unix", d.name, d.config, major, minor, device.Devnode(), false, &runConf)
+	} else {
+		err = unixDeviceSetupCharNum(d.state, d.inst.DevicesPath(), "unix", d.name, d.config, major, minor, device.Devnode(), false, &runConf)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &runConf, nil
+}
+
+// Stop is run when the device is removed from the instance
+func (d *unixHotplug) Stop() (*deviceConfig.RunConfig, error) {
+	unixHotplugUnregisterHandler(d.inst, d.name)
+
+	runConf := deviceConfig.RunConfig{
+		PostHooks: []func() error{d.postStop},
+	}
+
+	err := unixDeviceRemove(d.inst.DevicesPath(), "unix", d.name, "", &runConf)
+	if err != nil {
+		return nil, err
+	}
+
+	return &runConf, nil
+}
+
+// postStop is run after the device is removed from the instance
+func (d *unixHotplug) postStop() error {
+	err := unixDeviceDeleteFiles(d.state, d.inst.DevicesPath(), "unix", d.name, "")
+	if err != nil {
+		return fmt.Errorf("Failed to delete files for device '%s': %v", d.name, err)
+	}
+
+	return nil
+}
+
+// loadUnixDevice scans the host machine for unix devices with matching product/vendor ids
+// and returns the first matching device with the subsystem type char or block
+func (d *unixHotplug) loadUnixDevice() *udev.Device {
+	// Find device if exists
+	u := udev.Udev{}
+	e := u.NewEnumerate()
+
+	if d.config["vendorid"] != "" {
+		e.AddMatchProperty("ID_VENDOR_ID", d.config["vendorid"])
+	}
+	if d.config["productid"] != "" {
+		e.AddMatchProperty("ID_MODEL_ID", d.config["productid"])
+	}
+	e.AddMatchIsInitialized()
+	devices, _ := e.Devices()
+	var device *udev.Device
+	for i := range devices {
+		device = devices[i]
+		if !strings.HasPrefix(device.Subsystem(), "usb") {
+			return device
+		}
+	}
+
+	return nil
+}

From e05dc576cb34d499fc96451a234aef6a1585e858 Mon Sep 17 00:00:00 2001
From: David Mao <david.mao.92 at gmail.com>
Date: Tue, 19 Nov 2019 18:05:39 -0600
Subject: [PATCH 2/6] lxd/device: Add support for listening to unix char and
 block udev events

Signed-off-by: David Mao <david.mao at utexas.edu>
Signed-off-by: Lillian Jan Johnson <lillianjanjohnson at gmail.com>
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/device/device.go | 19 ++++-----
 lxd/devices.go       | 91 ++++++++++++++++++++++++++++++++++++++------
 2 files changed, 90 insertions(+), 20 deletions(-)

diff --git a/lxd/device/device.go b/lxd/device/device.go
index 45ba6a7bca..af9e4a07ed 100644
--- a/lxd/device/device.go
+++ b/lxd/device/device.go
@@ -10,15 +10,16 @@ import (
 
 // devTypes defines supported top-level device type creation functions.
 var devTypes = map[string]func(deviceConfig.Device) device{
-	"nic":        nicLoadByType,
-	"infiniband": infinibandLoadByType,
-	"proxy":      func(c deviceConfig.Device) device { return &proxy{} },
-	"gpu":        func(c deviceConfig.Device) device { return &gpu{} },
-	"usb":        func(c deviceConfig.Device) device { return &usb{} },
-	"unix-char":  func(c deviceConfig.Device) device { return &unixCommon{} },
-	"unix-block": func(c deviceConfig.Device) device { return &unixCommon{} },
-	"disk":       func(c deviceConfig.Device) device { return &disk{} },
-	"none":       func(c deviceConfig.Device) device { return &none{} },
+	"nic":          nicLoadByType,
+	"infiniband":   infinibandLoadByType,
+	"proxy":        func(c deviceConfig.Device) device { return &proxy{} },
+	"gpu":          func(c deviceConfig.Device) device { return &gpu{} },
+	"usb":          func(c deviceConfig.Device) device { return &usb{} },
+	"unix-char":    func(c deviceConfig.Device) device { return &unixCommon{} },
+	"unix-block":   func(c deviceConfig.Device) device { return &unixCommon{} },
+	"unix-hotplug": func(c deviceConfig.Device) device { return &unixHotplug{} },
+	"disk":         func(c deviceConfig.Device) device { return &disk{} },
+	"none":         func(c deviceConfig.Device) device { return &none{} },
 }
 
 // VolatileSetter is a function that accepts one or more key/value strings to save into the LXD
diff --git a/lxd/devices.go b/lxd/devices.go
index da5cb4b9ae..0210b9437d 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -32,7 +32,7 @@ func (c deviceTaskCPUs) Len() int           { return len(c) }
 func (c deviceTaskCPUs) Less(i, j int) bool { return *c[i].count < *c[j].count }
 func (c deviceTaskCPUs) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
 
-func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent, error) {
+func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent, chan device.UnixHotplugEvent, error) {
 	NETLINK_KOBJECT_UEVENT := 15
 	UEVENT_BUFFER_SIZE := 2048
 
@@ -41,25 +41,26 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 		NETLINK_KOBJECT_UEVENT,
 	)
 	if err != nil {
-		return nil, nil, nil, err
+		return nil, nil, nil, nil, err
 	}
 
 	nl := unix.SockaddrNetlink{
 		Family: unix.AF_NETLINK,
 		Pid:    uint32(os.Getpid()),
-		Groups: 1,
+		Groups: 3,
 	}
 
 	err = unix.Bind(fd, &nl)
 	if err != nil {
-		return nil, nil, nil, err
+		return nil, nil, nil, nil, err
 	}
 
 	chCPU := make(chan []string, 1)
 	chNetwork := make(chan []string, 0)
 	chUSB := make(chan device.USBEvent)
+	chUnix := make(chan device.UnixHotplugEvent)
 
-	go func(chCPU chan []string, chNetwork chan []string, chUSB chan device.USBEvent) {
+	go func(chCPU chan []string, chNetwork chan []string, chUSB chan device.USBEvent, chUnix chan device.UnixHotplugEvent) {
 		b := make([]byte, UEVENT_BUFFER_SIZE*2)
 		for {
 			r, err := unix.Read(fd, b)
@@ -70,6 +71,7 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 			ueventBuf := make([]byte, r)
 			copy(ueventBuf, b)
 			ueventLen := 0
+			udevEvent := false
 			ueventParts := strings.Split(string(ueventBuf), "\x00")
 			props := map[string]string{}
 			for _, part := range ueventParts {
@@ -77,6 +79,12 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 					continue
 				}
 
+				// libudev string prefix distinguishes udev events from kernel uevents
+				if strings.HasPrefix(part, "libudev") {
+					udevEvent = true
+					continue
+				}
+
 				ueventLen += len(part) + 1
 
 				fields := strings.SplitN(part, "=", 2)
@@ -89,7 +97,7 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 
 			ueventLen--
 
-			if props["SUBSYSTEM"] == "cpu" {
+			if props["SUBSYSTEM"] == "cpu" && !udevEvent {
 				if props["DRIVER"] != "processor" {
 					continue
 				}
@@ -106,7 +114,7 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 				}
 			}
 
-			if props["SUBSYSTEM"] == "net" {
+			if props["SUBSYSTEM"] == "net" && !udevEvent {
 				if props["ACTION"] != "add" && props["ACTION"] != "removed" {
 					continue
 				}
@@ -119,7 +127,7 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 				chNetwork <- []string{props["INTERFACE"], props["ACTION"]}
 			}
 
-			if props["SUBSYSTEM"] == "usb" {
+			if props["SUBSYSTEM"] == "usb" && !udevEvent {
 				parts := strings.Split(props["PRODUCT"], "/")
 				if len(parts) < 2 {
 					continue
@@ -178,10 +186,69 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 				chUSB <- usb
 			}
 
+			// unix hotplug device events rely on information added by udev
+			if udevEvent {
+				subsystem, ok := props["SUBSYSTEM"]
+				if !ok {
+					continue
+				}
+
+				vendor, ok := props["ID_VENDOR_ID"]
+				if !ok {
+					continue
+				}
+
+				product, ok := props["ID_MODEL_ID"]
+				if !ok {
+					continue
+				}
+
+				major, ok := props["MAJOR"]
+				if !ok {
+					continue
+				}
+
+				minor, ok := props["MINOR"]
+				if !ok {
+					continue
+				}
+
+				devname, ok := props["DEVNAME"]
+				if !ok {
+					continue
+				}
+
+				zeroPad := func(s string, l int) string {
+					return strings.Repeat("0", l-len(s)) + s
+				}
+
+				unix, err := device.UnixHotplugNewEvent(
+					props["ACTION"],
+					/* udev doesn't zero pad these, while
+					 * everything else does, so let's zero pad them
+					 * for consistency
+					 */
+					zeroPad(vendor, 4),
+					zeroPad(product, 4),
+					major,
+					minor,
+					subsystem,
+					devname,
+					ueventParts[:len(ueventParts)-1],
+					ueventLen,
+				)
+				if err != nil {
+					logger.Error("Error reading unix device", log.Ctx{"err": err, "path": props["PHYSDEVPATH"]})
+					continue
+				}
+
+				chUnix <- unix
+			}
+
 		}
-	}(chCPU, chNetwork, chUSB)
+	}(chCPU, chNetwork, chUSB, chUnix)
 
-	return chCPU, chNetwork, chUSB, nil
+	return chCPU, chNetwork, chUSB, chUnix, nil
 }
 
 func parseCpuset(cpu string) ([]int, error) {
@@ -438,7 +505,7 @@ func deviceNetworkPriority(s *state.State, netif string) {
 }
 
 func deviceEventListener(s *state.State) {
-	chNetlinkCPU, chNetlinkNetwork, chUSB, err := deviceNetlinkListener()
+	chNetlinkCPU, chNetlinkNetwork, chUSB, chUnix, err := deviceNetlinkListener()
 	if err != nil {
 		logger.Errorf("scheduler: Couldn't setup netlink listener: %v", err)
 		return
@@ -473,6 +540,8 @@ func deviceEventListener(s *state.State) {
 			networkAutoAttach(s.Cluster, e[0])
 		case e := <-chUSB:
 			device.USBRunHandlers(s, &e)
+		case e := <-chUnix:
+			device.UnixHotplugRunHandlers(s, &e)
 		case e := <-cgroup.DeviceSchedRebalance:
 			if len(e) != 3 {
 				logger.Errorf("Scheduler: received an invalid rebalance event")

From 0777d76ff9895150aa42874df0342c91f864904e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 28 Jan 2020 13:46:44 +0100
Subject: [PATCH 3/6] devices: retrieve vendor and product for hidraw devices

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/devices.go | 84 +++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 77 insertions(+), 7 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index 0210b9437d..9339514f13 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -5,6 +5,7 @@ import (
 	"io/ioutil"
 	"os"
 	"path"
+	"path/filepath"
 	"sort"
 	"strconv"
 	"strings"
@@ -21,6 +22,38 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
+/*
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#endif
+#include <stdio.h>
+#include <linux/hidraw.h>
+
+#include "include/memory_utils.h"
+
+#ifndef HIDIOCGRAWINFO
+#define HIDIOCGRAWINFO _IOR('H', 0x03, struct hidraw_devinfo)
+struct hidraw_devinfo {
+	__u32 bustype;
+	__s16 vendor;
+	__s16 product;
+};
+#endif
+
+static int get_hidraw_devinfo(int fd, struct hidraw_devinfo *info)
+{
+	int ret;
+
+	ret = ioctl(fd, HIDIOCGRAWINFO, info);
+	if (ret)
+		return -1;
+
+	return 0;
+}
+
+*/
+import "C"
+
 type deviceTaskCPU struct {
 	id    int
 	strId string
@@ -193,12 +226,12 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 					continue
 				}
 
-				vendor, ok := props["ID_VENDOR_ID"]
+				devname, ok := props["DEVNAME"]
 				if !ok {
 					continue
 				}
 
-				product, ok := props["ID_MODEL_ID"]
+				vendor, product, ok := ueventParseVendorProduct(props, subsystem, devname)
 				if !ok {
 					continue
 				}
@@ -213,11 +246,6 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent
 					continue
 				}
 
-				devname, ok := props["DEVNAME"]
-				if !ok {
-					continue
-				}
-
 				zeroPad := func(s string, l int) string {
 					return strings.Repeat("0", l-len(s)) + s
 				}
@@ -598,3 +626,45 @@ func devicesRegister(s *state.State) {
 		}
 	}
 }
+
+func getHidrawDevInfo(fd int) (string, string, error) {
+	info := C.struct_hidraw_devinfo{}
+	ret, err := C.get_hidraw_devinfo(C.int(fd), &info)
+	if ret != 0 {
+		return "", "", err
+	}
+
+	return fmt.Sprintf("%04x", info.vendor), fmt.Sprintf("%04x", info.product), nil
+}
+
+func ueventParseVendorProduct(props map[string]string, subsystem string, devname string) (string, string, bool) {
+	if subsystem != "hidraw" {
+		vendor, vendorOk := props["ID_VENDOR_ID"]
+		product, productOk := props["ID_MODEL_ID"]
+
+		if vendorOk && productOk {
+			return vendor, product, true
+		}
+
+		return "", "", false
+	}
+
+	if !filepath.IsAbs(devname) {
+		return "", "", false
+	}
+
+	file, err := os.OpenFile(devname, os.O_RDWR, 0000)
+	if err != nil {
+		return "", "", false
+	}
+
+	defer file.Close()
+
+	vendor, product, err := getHidrawDevInfo(int(file.Fd()))
+	if err != nil {
+		logger.Debugf("Failed to retrieve device info from hidraw device \"%s\"", devname)
+		return "", "", false
+	}
+
+	return vendor, product, true
+}

From 72926591394ea4bcbc7c4c394e4257cf88a93c9b Mon Sep 17 00:00:00 2001
From: David Mao <david.mao.92 at gmail.com>
Date: Thu, 12 Dec 2019 16:49:16 -0600
Subject: [PATCH 4/6] lxd/db: adds unix-hotplug device type to database

Signed-off-by: Lillian J. Johnson <lillianjanjohnson at gmail.com>
Signed-off-by: David Mao <david.mao at utexas.edu>
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/db/devices.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/db/devices.go b/lxd/db/devices.go
index 3691fd3478..e4fca429ed 100644
--- a/lxd/db/devices.go
+++ b/lxd/db/devices.go
@@ -29,6 +29,8 @@ func dbDeviceTypeToString(t int) (string, error) {
 		return "infiniband", nil
 	case 8:
 		return "proxy", nil
+	case 9:
+		return "unix-hotplug", nil
 	default:
 		return "", fmt.Errorf("Invalid device type %d", t)
 	}
@@ -54,6 +56,8 @@ func dbDeviceTypeToInt(t string) (int, error) {
 		return 7, nil
 	case "proxy":
 		return 8, nil
+	case "unix-hotplug":
+		return 9, nil
 	default:
 		return -1, fmt.Errorf("Invalid device type %s", t)
 	}

From ff6e343db8788cc1dc02809edc4405f7f94dc629 Mon Sep 17 00:00:00 2001
From: Lily <Lily>
Date: Fri, 29 Nov 2019 10:10:37 -0600
Subject: [PATCH 5/6] api: Add extension for new device type unix hotplug

Signed-off-by: David Mao <david.mao at utexas.edu>
Signed-off-by: Lillian Jan Johnson <lillianjanjohnson at gmail.com>
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/api-extensions.md | 4 ++++
 shared/version/api.go | 1 +
 2 files changed, 5 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index a5dad365ea..3ee25fc098 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -902,3 +902,7 @@ This adds the ability to use LVM stripes on normal volumes and thin pool volumes
 
 ## vm\_boot\_priority
 Adds a `boot.priority` property on nic and disk devices to control the boot order.
+Allows a list of profiles to be applied to an image when launching a new container. 
+
+## unix\_hotplug\_devices
+Adds support for unix char and block device hotplugging.
diff --git a/shared/version/api.go b/shared/version/api.go
index b06864911b..96d4396bfd 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -184,6 +184,7 @@ var APIExtensions = []string{
 	"resources_disk_id",
 	"storage_lvm_stripes",
 	"vm_boot_priority",
+	"unix_hotplug_devices",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 8646ecf0fb37be01f968b431c04e0440f7830b54 Mon Sep 17 00:00:00 2001
From: Lily <Lily>
Date: Fri, 29 Nov 2019 10:15:00 -0600
Subject: [PATCH 6/6] doc/instances: added new device type unix hotplug

Signed-off-by: David Mao <david.mao at utexas.edu>
Signed-off-by: Lillian Jan Johnson <lillianjanjohnson at gmail.com>
Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/instances.md | 39 ++++++++++++++++++++++++++++-----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/doc/instances.md b/doc/instances.md
index e0e935b860..37c177d72b 100644
--- a/doc/instances.md
+++ b/doc/instances.md
@@ -221,17 +221,18 @@ lxc profile device add <profile> <name> <type> [key=value]...
 ## Device types
 LXD supports the following device types:
 
-ID (database)   | Name                              | Condition     | Description
-:--             | :--                               | :--           | :--
-0               | [none](#type-none)                | -             | Inheritance blocker
-1               | [nic](#type-nic)                  | -             | Network interface
-2               | [disk](#type-disk)                | -             | Mountpoint inside the instance
-3               | [unix-char](#type-unix-char)      | container     | Unix character device
-4               | [unix-block](#type-unix-block)    | container     | Unix block device
-5               | [usb](#type-usb)                  | container     | USB device
-6               | [gpu](#type-gpu)                  | container     | GPU device
-7               | [infiniband](#type-infiniband)    | container     | Infiniband device
-8               | [proxy](#type-proxy)              | container     | Proxy device
+ID (database)   | Name                               | Condition     | Description
+:--             | :--                                | :--           | :--
+0               | [none](#type-none)                 | -             | Inheritance blocker
+1               | [nic](#type-nic)                   | -             | Network interface
+2               | [disk](#type-disk)                 | -             | Mountpoint inside the instance
+3               | [unix-char](#type-unix-char)       | container     | Unix character device
+4               | [unix-block](#type-unix-block)     | container     | Unix block device
+5               | [usb](#type-usb)                   | container     | USB device
+6               | [gpu](#type-gpu)                   | container     | GPU device
+7               | [infiniband](#type-infiniband)     | container     | Infiniband device
+8               | [proxy](#type-proxy)               | container     | Proxy device
+9               | [unix-hotplug](#type-unix-hotplug) | container     | Unix hotplug device
 
 ### Type: none
 
@@ -720,6 +721,22 @@ security.gid    | int       | 0             | no        | What GID to drop privi
 lxc config device add <instance> <device-name> proxy listen=<type>:<addr>:<port>[-<port>][,<port>] connect=<type>:<addr>:<port> bind=<host/instance>
 ```
 
+### Type: unix-hotplug
+Unix hotplug device entries make the requested unix device appear in the 
+instance's `/dev` and allow read/write operations to it if the device exists on 
+the host system. Implementation depends on systemd-udev to be run on the host.
+
+The following properties exist:
+
+Key         | Type      | Default           | Required  | Description
+:--         | :--       | :--               | :--       | :--
+vendorid    | string    | -                 | no        | The vendor id of the unix device
+productid   | string    | -                 | no        | The product id of the unix device
+uid         | int       | 0                 | no        | UID of the device owner in the instance
+gid         | int       | 0                 | no        | GID of the device owner in the instance
+mode        | int       | 0660              | no        | Mode of the device in the instance
+required    | boolean   | false             | no        | Whether or not this device is required to start the instance. (The default is false, and all devices are hot-pluggable)
+
 ## Units for storage and network limits
 Any value representing bytes or bits can make use of a number of useful
 suffixes to make it easier to understand what a particular limit is.


More information about the lxc-devel mailing list