[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