The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6807
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) === This is a rebased version of https://github.com/lxc/lxd/pull/6530 .
From 5b4407eff32d86642cc22d7b6b57ac5215a462b4 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/6] lxd/device: Add unix_hotplug device type Signed-off-by: Lillian Jan Johnson <lillianjanjohn...@gmail.com> Signed-off-by: David Mao <david....@utexas.edu> Signed-off-by: Christian Brauner <christian.brau...@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...@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....@utexas.edu> Signed-off-by: Lillian Jan Johnson <lillianjanjohn...@gmail.com> Signed-off-by: Christian Brauner <christian.brau...@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.brau...@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.brau...@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...@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 <lillianjanjohn...@gmail.com> Signed-off-by: David Mao <david....@utexas.edu> Signed-off-by: Christian Brauner <christian.brau...@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....@utexas.edu> Signed-off-by: Lillian Jan Johnson <lillianjanjohn...@gmail.com> Signed-off-by: Christian Brauner <christian.brau...@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....@utexas.edu> Signed-off-by: Lillian Jan Johnson <lillianjanjohn...@gmail.com> Signed-off-by: Christian Brauner <christian.brau...@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.
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel