The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6530

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
For https://github.com/lxc/lxd/issues/1750
From ba35c3c2169151d6c3d36cc5bc94d7edf7e5fa75 Mon Sep 17 00:00:00 2001
From: David Mao <david.mao...@gmail.com>
Date: Sat, 16 Nov 2019 17:43:47 -0600
Subject: [PATCH 1/5] lxd/device: Add unix_hotplug device type

Signed-off-by: Lillian Jan Johnson <lillianjanjohn...@gmail.com>
Signed-off-by: David Mao <david....@utexas.edu>
---
 lxd/device/device_utils_unix.go               |  21 ++
 .../device_utils_unix_hotplug_events.go       | 119 ++++++++++
 lxd/device/unix_hotplug.go                    | 212 ++++++++++++++++++
 3 files changed, 352 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..20683723eb
--- /dev/null
+++ b/lxd/device/device_utils_unix_hotplug_events.go
@@ -0,0 +1,119 @@
+package device
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+       "sync"
+
+       deviceConfig "github.com/lxc/lxd/lxd/device/config"
+       "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, 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, 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..94bc378318
--- /dev/null
+++ b/lxd/device/unix_hotplug.go
@@ -0,0 +1,212 @@
+package device
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+   
+    deviceConfig "github.com/lxc/lxd/lxd/device/config"
+       "github.com/farjump/go-libudev"
+       "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.instance.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.instance.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 == "char" {
+                               err := unixDeviceSetupCharNum(state, 
devicesPath, "unix", deviceName, devConfig, e.Major, e.Minor, e.Path, false, 
&runConf)
+                               if err != nil {
+                                       return nil, err
+                               }
+                       } else {
+                               err := unixDeviceSetupBlockNum(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.instance, 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() == "char" {
+               err = unixDeviceSetupCharNum(d.state, d.instance.DevicesPath(), 
"unix", d.name, d.config, major, minor, device.Devnode(), false, &runConf)
+       } else if device.Subsystem() == "block" {
+               err = unixDeviceSetupBlockNum(d.state, 
d.instance.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.instance, d.name)
+
+       runConf := deviceConfig.RunConfig{
+               PostHooks: []func() error{d.postStop},
+       }
+
+       err := unixDeviceRemove(d.instance.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.instance.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 device.Subsystem() == "block" || device.Subsystem() == 
"char" {
+                       return device
+               }
+       }
+
+       return nil
+}

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

Signed-off-by: David Mao <david....@utexas.edu>
Signed-off-by: Lillian Jan Johnson <lillianjanjohn...@gmail.com>
---
 lxd/device/device.go | 19 +++++-----
 lxd/devices.go       | 88 ++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 90 insertions(+), 17 deletions(-)

diff --git a/lxd/device/device.go b/lxd/device/device.go
index c608f80bc9..9745867d7c 100644
--- a/lxd/device/device.go
+++ b/lxd/device/device.go
@@ -9,15 +9,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 5939a0c494..7a9cd3738b 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -34,7 +34,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
 
@@ -43,25 +43,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)
@@ -72,6 +73,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 {
@@ -79,6 +81,10 @@ func deviceNetlinkListener() (chan []string, chan []string, 
chan device.USBEvent
                                        continue
                                }
 
+                               if strings.HasPrefix(part, "libudev") {
+                                       udevEvent = true
+                               }
+
                                ueventLen += len(part) + 1
 
                                fields := strings.SplitN(part, "=", 2)
@@ -180,10 +186,74 @@ func deviceNetlinkListener() (chan []string, chan 
[]string, chan device.USBEvent
                                chUSB <- usb
                        }
 
+                       if (props["SUBSYSTEM"] == "char" || props["SUBSYSTEM"] 
== "block") && udevEvent {
+                               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
+                               }
+
+                               busnum, ok := props["BUSNUM"]
+                               if !ok {
+                                       continue
+                               }
+
+                               devnum, ok := props["DEVNUM"]
+                               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,
+                                       busnum,
+                                       devnum,
+                                       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) {
@@ -440,7 +510,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
@@ -475,6 +545,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 b817720285fad8e128f1e90407d978876409cf91 Mon Sep 17 00:00:00 2001
From: David Mao <david.mao...@gmail.com>
Date: Wed, 27 Nov 2019 10:12:26 -0600
Subject: [PATCH 3/5] lxd/db: added device type Signed-off-by: Lillian J.
 Johnson <lillianjanjohn...@gmail.com> Signed-off-by: David Mao
 <david....@utexas.edu>

---
 lxd/db/devices.go |  4 ++++
 lxd/devices.go    | 25 ++++++++++---------------
 2 files changed, 14 insertions(+), 15 deletions(-)

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)
        }
diff --git a/lxd/devices.go b/lxd/devices.go
index 7a9cd3738b..1fbe2e123d 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -83,6 +83,7 @@ func deviceNetlinkListener() (chan []string, chan []string, 
chan device.USBEvent
 
                                if strings.HasPrefix(part, "libudev") {
                                        udevEvent = true
+                                       continue
                                }
 
                                ueventLen += len(part) + 1
@@ -97,7 +98,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
                                }
@@ -114,7 +115,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
                                }
@@ -127,7 +128,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
@@ -187,6 +188,11 @@ func deviceNetlinkListener() (chan []string, chan 
[]string, chan device.USBEvent
                        }
 
                        if (props["SUBSYSTEM"] == "char" || props["SUBSYSTEM"] 
== "block") && udevEvent {
+                               subsystem, ok := props["SUBSYSTEM"]
+                               if !ok {
+                                       continue
+                               }
+
                                vendor, ok := props["ID_VENDOR_ID"]
                                if !ok {
                                        continue
@@ -212,16 +218,6 @@ func deviceNetlinkListener() (chan []string, chan 
[]string, chan device.USBEvent
                                        continue
                                }
 
-                               busnum, ok := props["BUSNUM"]
-                               if !ok {
-                                       continue
-                               }
-
-                               devnum, ok := props["DEVNUM"]
-                               if !ok {
-                                       continue
-                               }
-
                                zeroPad := func(s string, l int) string {
                                        return strings.Repeat("0", l-len(s)) + s
                                }
@@ -236,8 +232,7 @@ func deviceNetlinkListener() (chan []string, chan []string, 
chan device.USBEvent
                                        zeroPad(product, 4),
                                        major,
                                        minor,
-                                       busnum,
-                                       devnum,
+                                       subsystem,
                                        devname,
                                        ueventParts[:len(ueventParts)-1],
                                        ueventLen,

From 486e500177dfc88dafadce5a1e835c27e20996cc Mon Sep 17 00:00:00 2001
From: Lily <Lily>
Date: Fri, 29 Nov 2019 10:10:37 -0600
Subject: [PATCH 4/5] api: Add extention for new device type unix hotplug
 Signed-off-by: David Mao <david....@utexas.edu> Signed-off-by: Lillian Jan
 Johnson <lillianjanjohn...@gmail.com>

---
 doc/api-extensions.md | 3 +++
 shared/version/api.go | 1 +
 2 files changed, 4 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 1b836a0623..980dddb2ac 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -886,3 +886,6 @@ This allows for existing a CEPH RDB or FS to be directly 
connected to a LXD cont
 
 ## virtual\_machines
 Add virtual machine support.
+
+## 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 1afdc1b2d0..1f36ee5c4f 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -179,6 +179,7 @@ var APIExtensions = []string{
        "container_syscall_intercept_mount_fuse",
        "container_disk_ceph",
        "virtual-machines",
+       "unix_hotplug_devices",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From a382a1c8c33a0abb7d325aa17aed508d99d540d6 Mon Sep 17 00:00:00 2001
From: Lily <Lily>
Date: Fri, 29 Nov 2019 10:15:00 -0600
Subject: [PATCH 5/5] doc/instances: added new device type unix hotplug
 Signed-off-by: David Mao <david....@utexas.edu> Signed-off-by: Lillian Jan
 Johnson <lillianjanjohn...@gmail.com>

---
 doc/instances.md | 39 ++++++++++++++++++++++++++++-----------
 1 file changed, 28 insertions(+), 11 deletions(-)

diff --git a/doc/instances.md b/doc/instances.md
index 9ef017d60a..da4ae31c4b 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
 A none type device doesn't have any property and doesn't create anything 
inside the instance.
@@ -666,6 +667,22 @@ proxy\_protocol | bool      | false         | no        | 
Whether to use the HAP
 security.uid    | int       | 0             | no        | What UID to drop 
privilege to
 security.gid    | int       | 0             | no        | What GID to drop 
privilege to
 
+### Type: unix-hotplug
+Unix hotplug device entries make the requested unix device
+appear in the container's `/dev` and allow read/write operations to it
+if the device exists on the host system.
+
+The following properties exist:
+
+Key         | Type      | Default           | Required  | Description
+:--         | :--       | :--               | :--       | :--
+vendorid    | string    | -                 | no        | The vendor id of the 
USB device.
+productid   | string    | -                 | no        | The product id of 
the USB device.
+uid         | int       | 0                 | no        | UID of the device 
owner in the container
+gid         | int       | 0                 | no        | GID of the device 
owner in the container
+mode        | int       | 0660              | no        | Mode of the device 
in the container
+required    | boolean   | false             | no        | Whether or not this 
device is required to start the container. (The default is false, and all 
devices are hot-pluggable)
+
 ```
 lxc config device add <instance> <device-name> proxy 
listen=<type>:<addr>:<port>[-<port>][,<port>] connect=<type>:<addr>:<port> 
bind=<host/instance>
 ```
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to