[lxc-devel] [lxd/master] Uevent-based matching for `unix-char` and `unix-block`
splett2 on Github
lxc-bot at linuxcontainers.org
Fri Nov 29 17:31:37 UTC 2019
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 343 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191129/0f467cea/attachment-0001.bin>
-------------- next part --------------
From ba35c3c2169151d6c3d36cc5bc94d7edf7e5fa75 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/5] 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>
---
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.92 at 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.mao at utexas.edu>
Signed-off-by: Lillian Jan Johnson <lillianjanjohnson at 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.92 at 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 <lillianjanjohnson at gmail.com> Signed-off-by: David Mao
<david.mao at 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.mao at utexas.edu> Signed-off-by: Lillian Jan
Johnson <lillianjanjohnson at 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.mao at utexas.edu> Signed-off-by: Lillian Jan
Johnson <lillianjanjohnson at 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>
```
More information about the lxc-devel
mailing list