Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lxd/device: Add support for discovering multiple unix-hotplug devices #14375

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 50 additions & 40 deletions lxd/device/unix_hotplug.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,25 @@ import (
"github.com/canonical/lxd/shared/validate"
)

// 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) {
// unixHotplugDeviceMatch matches a unix-hotplug device based on vendorid and productid. USB bus and devices with a major number of 0 are ignored. This function is used to indicate whether a unix hotplug event qualifies as part of our registered devices, and to load matching devices.
func unixHotplugDeviceMatch(config deviceConfig.Device, vendorid string, productid string, subsystem string, major uint32) bool {
match := false
vendorIDMatch := vendorid == config["vendorid"]
productIDMatch := productid == config["productid"]

if strings.HasPrefix(subsystem, "usb") || major == 0 {
return false
}

return true
if config["vendorid"] != "" && config["productid"] != "" {
match = vendorIDMatch && productIDMatch
} else if config["vendorid"] != "" {
match = vendorIDMatch
} else if config["productid"] != "" {
match = productIDMatch
}

return match
}

type unixHotplug struct {
Expand Down Expand Up @@ -89,8 +98,7 @@ func (d *unixHotplug) validateConfig(instConf instance.ConfigReader) error {

// 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.
// Extract variables needed to run the event hook so that the reference to this device struct is not required to be stored in memory.
devicesPath := d.inst.DevicesPath()
devConfig := d.config
deviceName := d.name
Expand All @@ -101,7 +109,7 @@ func (d *unixHotplug) Register() error {
runConf := deviceConfig.RunConfig{}

if e.Action == "add" {
if !unixHotplugIsOurDevice(devConfig, &e) {
if !unixHotplugDeviceMatch(devConfig, e.Vendor, e.Product, e.Subsystem, e.Major) {
return nil, nil
}

Expand Down Expand Up @@ -149,29 +157,36 @@ func (d *unixHotplug) Start() (*deviceConfig.RunConfig, error) {
runConf := deviceConfig.RunConfig{}
runConf.PostHooks = []func() error{d.Register}

device := d.loadUnixDevice()
if d.isRequired() && device == nil {
devices := d.loadUnixDevices()
if d.isRequired() && devices == nil {
return nil, fmt.Errorf("Required Unix Hotplug device not found")
}

if device == nil {
if devices == nil {
return &runConf, nil
}

devnum := device.Devnum()
major := uint32(devnum.Major())
minor := uint32(devnum.Minor())
for _, device := range devices {
devnum := device.Devnum()
major := uint32(devnum.Major())
minor := uint32(devnum.Minor())

// Setup device.
var err error
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)
}

// setup device
var err error
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 {
err := unixDeviceRemove(d.inst.DevicesPath(), "unix", d.name, "", &runConf)
if err != nil {
return nil, err
}

if err != nil {
return nil, err
return nil, err
kadinsayani marked this conversation as resolved.
Show resolved Hide resolved
}
}

return &runConf, nil
Expand Down Expand Up @@ -203,9 +218,9 @@ func (d *unixHotplug) postStop() error {
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 {
// loadUnixDevices scans the host machine for unix devices with matching product/vendor ids
// and returns the matching devices with subsystem types of char or block.
func (d *unixHotplug) loadUnixDevices() []*udev.Device {
// Find device if exists
u := udev.Udev{}
e := u.NewEnumerate()
Expand All @@ -230,27 +245,22 @@ func (d *unixHotplug) loadUnixDevice() *udev.Device {
}

devices, _ := e.Devices()
var device *udev.Device
var matchingDevices []*udev.Device
for i := range devices {
device = devices[i]
device := devices[i]

if device == nil {
if device == nil || device.Devnode() == "" {
continue
}

devnum := device.Devnum()
if devnum.Major() == 0 || devnum.Minor() == 0 {
continue
}
match := unixHotplugDeviceMatch(d.config, device.PropertyValue("ID_VENDOR_ID"), device.PropertyValue("ID_MODEL_ID"), device.Subsystem(), uint32(device.Devnum().Major()))

if device.Devnode() == "" {
if !match {
continue
}

if !strings.HasPrefix(device.Subsystem(), "usb") {
return device
}
matchingDevices = append(matchingDevices, device)
}

return nil
return matchingDevices
}
Loading