[lxc-devel] [lxd/master] Device and Devices type move to api package

tomponline on Github lxc-bot at linuxcontainers.org
Fri Aug 30 14:18:20 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 1515 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190830/97f720e7/attachment-0001.bin>
-------------- next part --------------
From ec8db9918941ab8d700c6c99db7ea909834209dc Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 12:04:57 +0100
Subject: [PATCH 01/36] test: Removes tmpfs references from gpu tests

Tests may break if not run on tmpfs otherwise.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 test/suites/container_devices_gpu.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/container_devices_gpu.sh b/test/suites/container_devices_gpu.sh
index c761bf1126..ce496f8b94 100644
--- a/test/suites/container_devices_gpu.sh
+++ b/test/suites/container_devices_gpu.sh
@@ -14,7 +14,7 @@ test_container_devices_gpu() {
   startMountCount=$(lxc exec "${ctName}" -- mount | wc -l)
   startDevCount=$(find "${LXD_DIR}"/devices/"${ctName}" -type c | wc -l)
   lxc config device add "${ctName}" gpu-basic gpu mode=0600 id=0
-  lxc exec "${ctName}" -- mount | grep "tmpfs on /dev/dri/card0 type tmpfs"
+  lxc exec "${ctName}" -- mount | grep "/dev/dri/card0"
   lxc exec "${ctName}" -- stat -c '%a' /dev/dri/card0 | grep 600
   stat -c '%a' "${LXD_DIR}"/devices/"${ctName}"/unix.gpu--basic.dev-dri-card0 | grep 600
   lxc config device remove "${ctName}" gpu-basic

From 270947007341b5f3d07956ad77cf56f7be03b247 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 16:02:09 +0100
Subject: [PATCH 02/36] device/device/utils/unix: Various small improvements

- Improves comments
- Removes duplicated path and source logic
- Removes duplicated default mode

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_unix.go | 63 +++++++++++++++++----------------
 1 file changed, 33 insertions(+), 30 deletions(-)

diff --git a/lxd/device/device_utils_unix.go b/lxd/device/device_utils_unix.go
index de2cf57644..17a97bd704 100644
--- a/lxd/device/device_utils_unix.go
+++ b/lxd/device/device_utils_unix.go
@@ -17,6 +17,9 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
+// unixDefaultMode default mode to create unix devices with if not specified in device config.
+const unixDefaultMode = 0660
+
 // unixDeviceInstanceAttributes returns the UNIX device attributes for an instance device.
 // Uses supplied device config for device properties, and if they haven't been set, falls back to
 // using UnixGetDeviceAttributes() to directly query an existing device file.
@@ -48,11 +51,7 @@ func unixDeviceInstanceAttributes(devicesPath string, prefix string, config conf
 		dType = "b"
 	}
 
-	// Figure out the paths
-	destPath := config["path"]
-	if destPath == "" {
-		destPath = config["source"]
-	}
+	destPath := unixDeviceDestPath(config)
 	relativeDestPath := strings.TrimPrefix(destPath, "/")
 	devName := fmt.Sprintf("%s.%s", strings.Replace(prefix, "/", "-", -1), strings.Replace(relativeDestPath, "/", "-", -1))
 	devPath := filepath.Join(devicesPath, devName)
@@ -93,13 +92,8 @@ func UnixDeviceAttributes(path string) (string, uint32, uint32, error) {
 	return dType, major, minor, nil
 }
 
+// unixDeviceModeOct converts a string unix octal mode to an int.
 func unixDeviceModeOct(strmode string) (int, error) {
-	// Default mode
-	if strmode == "" {
-		return 0600, nil
-	}
-
-	// Converted mode
 	i, err := strconv.ParseInt(strmode, 8, 32)
 	if err != nil {
 		return 0, fmt.Errorf("Bad device mode: %s", strmode)
@@ -120,8 +114,19 @@ type UnixDevice struct {
 	GID          int         // Owner GID.
 }
 
+// unixDeviceSourcePath returns the absolute path for a device on the host.
+// This is based on the "source" property of the device's config, or the "path" property if "source"
+// not define. This uses the shared.HostPath function so works when running in a snap environment.
+func unixDeviceSourcePath(m config.Device) string {
+	srcPath := m["source"]
+	if srcPath == "" {
+		srcPath = m["path"]
+	}
+	return shared.HostPath(srcPath)
+}
+
 // unixDeviceDestPath returns the absolute path for a device inside an instance.
-// This is based on the "path" property of the devices' config, or the "source" property if "path"
+// This is based on the "path" property of the device's config, or the "source" property if "path"
 // not defined.
 func unixDeviceDestPath(m config.Device) string {
 	destPath := m["path"]
@@ -153,51 +158,49 @@ func UnixDeviceCreate(s *state.State, idmapSet *idmap.IdmapSet, devicesPath stri
 		}
 	}
 
-	srcPath := m["source"]
-	if srcPath == "" {
-		srcPath = m["path"]
-	}
-	srcPath = shared.HostPath(srcPath)
+	srcPath := unixDeviceSourcePath(m)
 
 	// Get the major/minor of the device we want to create.
 	if m["major"] == "" && m["minor"] == "" {
 		// If no major and minor are set, use those from the device on the host.
 		_, d.Major, d.Minor, err = UnixDeviceAttributes(srcPath)
 		if err != nil {
-			return nil, fmt.Errorf("Failed to get device attributes for %s: %s", m["path"], err)
+			return nil, fmt.Errorf("Failed to get device attributes for %s: %s", srcPath, err)
 		}
 	} else if m["major"] == "" || m["minor"] == "" {
-		return nil, fmt.Errorf("Both major and minor must be supplied for device: %s", m["path"])
+		return nil, fmt.Errorf("Both major and minor must be supplied for device: %s", srcPath)
 	} else {
 		tmp, err := strconv.ParseUint(m["major"], 10, 32)
 		if err != nil {
-			return nil, fmt.Errorf("Bad major %s in device %s", m["major"], m["path"])
+			return nil, fmt.Errorf("Bad major %s in device %s", m["major"], srcPath)
 		}
 		d.Major = uint32(tmp)
 
 		tmp, err = strconv.ParseUint(m["minor"], 10, 32)
 		if err != nil {
-			return nil, fmt.Errorf("Bad minor %s in device %s", m["minor"], m["path"])
+			return nil, fmt.Errorf("Bad minor %s in device %s", m["minor"], srcPath)
 		}
 		d.Minor = uint32(tmp)
 	}
 
-	// Get the device mode (defaults to 0660 if not supplied).
-	d.Mode = os.FileMode(0660)
+	// Get the device mode (defaults to unixDefaultMode if not supplied).
+	d.Mode = os.FileMode(unixDefaultMode)
 	if m["mode"] != "" {
 		tmp, err := unixDeviceModeOct(m["mode"])
 		if err != nil {
-			return nil, fmt.Errorf("Bad mode %s in device %s", m["mode"], m["path"])
+			return nil, fmt.Errorf("Bad mode %s in device %s", m["mode"], srcPath)
 		}
 		d.Mode = os.FileMode(tmp)
 	} else if !defaultMode {
+		// If not specified mode in device config, and default mode is false, then try and
+		// read the source device's mode and use that inside the instance.
 		d.Mode, err = shared.GetPathMode(srcPath)
 		if err != nil {
 			errno, isErrno := shared.GetErrno(err)
 			if !isErrno || errno != unix.ENOENT {
-				return nil, fmt.Errorf("Failed to retrieve mode of device %s: %s", m["path"], err)
+				return nil, fmt.Errorf("Failed to retrieve mode of device %s: %s", srcPath, err)
 			}
-			d.Mode = os.FileMode(0660)
+			d.Mode = os.FileMode(unixDefaultMode)
 		}
 	}
 
@@ -213,14 +216,14 @@ func UnixDeviceCreate(s *state.State, idmapSet *idmap.IdmapSet, devicesPath stri
 	if m["uid"] != "" {
 		d.UID, err = strconv.Atoi(m["uid"])
 		if err != nil {
-			return nil, fmt.Errorf("Invalid uid %s in device %s", m["uid"], m["path"])
+			return nil, fmt.Errorf("Invalid uid %s in device %s", m["uid"], srcPath)
 		}
 	}
 
 	if m["gid"] != "" {
 		d.GID, err = strconv.Atoi(m["gid"])
 		if err != nil {
-			return nil, fmt.Errorf("Invalid gid %s in device %s", m["gid"], m["path"])
+			return nil, fmt.Errorf("Invalid gid %s in device %s", m["gid"], srcPath)
 		}
 	}
 
@@ -242,7 +245,7 @@ func UnixDeviceCreate(s *state.State, idmapSet *idmap.IdmapSet, devicesPath stri
 		devNum := int(unix.Mkdev(d.Major, d.Minor))
 		err := unix.Mknod(devPath, uint32(d.Mode), devNum)
 		if err != nil {
-			return nil, fmt.Errorf("Failed to create device %s for %s: %s", devPath, m["path"], err)
+			return nil, fmt.Errorf("Failed to create device %s for %s: %s", devPath, srcPath, err)
 		}
 
 		err = os.Chown(devPath, d.UID, d.GID)
@@ -260,7 +263,7 @@ func UnixDeviceCreate(s *state.State, idmapSet *idmap.IdmapSet, devicesPath stri
 			err := idmapSet.ShiftFile(devPath)
 			if err != nil {
 				// uidshift failing is weird, but not a big problem. Log and proceed.
-				logger.Debugf("Failed to uidshift device %s: %s\n", m["path"], err)
+				logger.Debugf("Failed to uidshift device %s: %s\n", srcPath, err)
 			}
 		}
 	} else {

From 7f7788421c13bf4c15e23d1ab5109bb3917a43b5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 16:03:24 +0100
Subject: [PATCH 03/36] device: Moves empty device type validation into device
 package

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/device/device.go b/lxd/device/device.go
index 83cc952537..a3bf232f61 100644
--- a/lxd/device/device.go
+++ b/lxd/device/device.go
@@ -129,6 +129,10 @@ func (d *deviceCommon) Remove() error {
 // is still returned with the validation error. If an unknown device is requested or the device is
 // not compatible with the instance type then an ErrUnsupportedDevType error is returned.
 func New(instance InstanceIdentifier, state *state.State, name string, conf config.Device, volatileGet VolatileGetter, volatileSet VolatileSetter) (Device, error) {
+	if conf["type"] == "" {
+		return nil, fmt.Errorf("Missing device type for device '%s'", name)
+	}
+
 	devFunc := devTypes[conf["type"]]
 
 	// Check if top-level type is recognised, if it is known type it will return a function.

From a39db1628c4a7ed18882ec2a2e9c27cd8a7ef7c9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 16:13:14 +0100
Subject: [PATCH 04/36] container: Removes unused unix-char and unix-block
 validation

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container.go | 48 ------------------------------------------------
 1 file changed, 48 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index e7894faa44..39d46f72b1 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -108,27 +108,6 @@ func containerValidDeviceConfigKey(t, k string) bool {
 	}
 
 	switch t {
-	case "unix-char", "unix-block":
-		switch k {
-		case "gid":
-			return true
-		case "major":
-			return true
-		case "minor":
-			return true
-		case "mode":
-			return true
-		case "source":
-			return true
-		case "path":
-			return true
-		case "required":
-			return true
-		case "uid":
-			return true
-		default:
-			return false
-		}
 	case "disk":
 		switch k {
 		case "limits.max":
@@ -341,33 +320,6 @@ func containerValidDevices(state *state.State, cluster *db.Cluster, devices conf
 					return fmt.Errorf("The \"shift\" property cannot be used with custom storage volumes")
 				}
 			}
-		} else if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-			if m["source"] == "" && m["path"] == "" {
-				return fmt.Errorf("Unix device entry is missing the required \"source\" or \"path\" property")
-			}
-
-			if (m["required"] == "" || shared.IsTrue(m["required"])) && (m["major"] == "" || m["minor"] == "") {
-				srcPath, exist := m["source"]
-				if !exist {
-					srcPath = m["path"]
-				}
-				if !shared.PathExists(srcPath) {
-					return fmt.Errorf("The device path doesn't exist on the host and major/minor wasn't specified")
-				}
-
-				dType, _, _, err := device.UnixDeviceAttributes(srcPath)
-				if err != nil {
-					return err
-				}
-
-				if m["type"] == "unix-char" && dType != "c" {
-					return fmt.Errorf("Path specified for unix-char device is a block device")
-				}
-
-				if m["type"] == "unix-block" && dType != "b" {
-					return fmt.Errorf("Path specified for unix-block device is a character device")
-				}
-			}
 		} else if m["type"] == "none" {
 			continue
 		} else {

From adb5d24ec38e6230f78798d7b46f70e0d82cb761 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 16:13:43 +0100
Subject: [PATCH 05/36] container: Moves missing device type validation into
 device package

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container.go | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 39d46f72b1..25e02f88a7 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -223,21 +223,18 @@ func containerValidDevices(state *state.State, cluster *db.Cluster, devices conf
 	var diskDevicePaths []string
 	// Check each device individually
 	for name, m := range devices {
-		if m["type"] == "" {
-			return fmt.Errorf("Missing device type for device '%s'", name)
-		}
-
-		if !shared.StringInSlice(m["type"], []string{"disk", "gpu", "infiniband", "nic", "none", "proxy", "unix-block", "unix-char", "usb"}) {
-			return fmt.Errorf("Invalid device type for device '%s'", name)
-		}
-
 		// Validate config using device interface.
 		_, err := device.New(&containerLXC{}, state, name, config.Device(m), nil, nil)
 		if err != device.ErrUnsupportedDevType {
 			if err != nil {
 				return err
 			}
-			continue
+			continue // Validated device OK.
+		}
+
+		// Fallback to legacy validation for non device package devices.
+		if !shared.StringInSlice(m["type"], []string{"disk", "none"}) {
+			return fmt.Errorf("Invalid device type for device '%s'", name)
 		}
 
 		for k := range m {

From 71125cf9204f68ce71813c487be7ea7119c28df8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 16:15:55 +0100
Subject: [PATCH 06/36] container/lxc: Adds safety net for deviceStop() in case
 no device returned

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container_lxc.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 81fa471d16..c571601076 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2072,6 +2072,12 @@ func (c *containerLXC) deviceStop(deviceName string, rawConfig map[string]string
 	// scenario that a new version of LXD has additional validation restrictions than older
 	// versions we still need to allow previously valid devices to be stopped.
 	if err != nil {
+		// If there is no device returned, then we cannot proceed, so return as error.
+		if d == nil {
+			return fmt.Errorf("Device stop validation failed for '%s': %v", deviceName, err)
+
+		}
+
 		logger.Errorf("Device stop validation failed for '%s': %v", deviceName, err)
 	}
 

From 924aed1361c6c0ba098baca94a1744b7c6dc750d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 16:16:35 +0100
Subject: [PATCH 07/36] container/lxc: Removes unused unix-char and unix-block
 code

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container_lxc.go | 309 +------------------------------------------
 1 file changed, 4 insertions(+), 305 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index c571601076..b25d526b52 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1639,36 +1639,7 @@ func (c *containerLXC) initLXC(config bool) error {
 	// Setup devices
 	for _, k := range c.expandedDevices.DeviceNames() {
 		m := c.expandedDevices[k]
-		if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-			// destination paths
-			destPath := m["path"]
-			if destPath == "" {
-				destPath = m["source"]
-			}
-
-			srcPath := m["source"]
-			if srcPath == "" {
-				srcPath = m["path"]
-			}
-
-			relativeDestPath := strings.TrimPrefix(destPath, "/")
-			sourceDevPath := filepath.Join(c.DevicesPath(), fmt.Sprintf("unix.%s.%s", strings.Replace(k, "/", "-", -1), strings.Replace(relativeDestPath, "/", "-", -1)))
-
-			// Don't add mount entry for devices that don't yet exist
-			if m["required"] != "" && !shared.IsTrue(m["required"]) && srcPath != "" && !shared.PathExists(srcPath) {
-				continue
-			}
-
-			// inform liblxc about the mount
-			err = lxcSetConfigItem(cc, "lxc.mount.entry",
-				fmt.Sprintf("%s %s %s",
-					shared.EscapePathFstab(sourceDevPath),
-					shared.EscapePathFstab(relativeDestPath),
-					"none bind,create=file 0 0"))
-			if err != nil {
-				return err
-			}
-		} else if m["type"] == "disk" {
+		if m["type"] == "disk" {
 			isRootfs := shared.IsRootDiskDevice(m)
 
 			// source paths
@@ -2520,21 +2491,6 @@ func (c *containerLXC) startCommon() (string, []func() error, error) {
 			if m["pool"] == "" && m["source"] != "" && !shared.IsTrue(m["optional"]) && !shared.PathExists(shared.HostPath(m["source"])) {
 				return "", postStartHooks, fmt.Errorf("Missing source '%s' for disk '%s'", m["source"], name)
 			}
-		case "unix-char", "unix-block":
-			srcPath, exist := m["source"]
-			if !exist {
-				srcPath = m["path"]
-			}
-
-			if srcPath != "" && m["required"] != "" && !shared.IsTrue(m["required"]) {
-				err = deviceInotifyAddClosestLivingAncestor(c.state, filepath.Dir(srcPath))
-				if err != nil {
-					logger.Errorf("Failed to add \"%s\" to inotify targets", srcPath)
-					return "", postStartHooks, fmt.Errorf("Failed to setup inotify watch for '%s': %v", srcPath, err)
-				}
-			} else if srcPath != "" && m["major"] == "" && m["minor"] == "" && !shared.PathExists(srcPath) {
-				return "", postStartHooks, fmt.Errorf("Missing source '%s' for device '%s'", srcPath, name)
-			}
 		}
 	}
 
@@ -2682,49 +2638,7 @@ func (c *containerLXC) startCommon() (string, []func() error, error) {
 	nicID := -1
 	for _, k := range c.expandedDevices.DeviceNames() {
 		m := c.expandedDevices[k]
-		if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-			idmapSet, err := c.CurrentIdmap()
-			if err != nil {
-				return "", postStartHooks, err
-			}
-
-			// Unix device
-			d, err := device.UnixDeviceCreate(c.state, idmapSet, c.DevicesPath(), fmt.Sprintf("unix.%s", k), m, true)
-			if err != nil {
-				// Deal with device hotplug
-				if m["required"] == "" || shared.IsTrue(m["required"]) {
-					return "", postStartHooks, err
-				}
-
-				srcPath := m["source"]
-				if srcPath == "" {
-					srcPath = m["path"]
-				}
-				srcPath = shared.HostPath(srcPath)
-
-				err = deviceInotifyAddClosestLivingAncestor(c.state, filepath.Dir(srcPath))
-				if err != nil {
-					logger.Errorf("Failed to add \"%s\" to inotify targets", srcPath)
-					return "", postStartHooks, err
-				}
-				continue
-			}
-			devPath := d.HostPath
-			if c.isCurrentlyPrivileged() && !c.state.OS.RunningInUserNS && c.state.OS.CGroupDevicesController {
-				// Add the new device cgroup rule
-				dType, dMajor, dMinor, err := device.UnixDeviceAttributes(devPath)
-				if err != nil {
-					if m["required"] == "" || shared.IsTrue(m["required"]) {
-						return "", postStartHooks, err
-					}
-				} else {
-					err = lxcSetConfigItem(c.c, "lxc.cgroup.devices.allow", fmt.Sprintf("%s %d:%d rwm", dType, dMajor, dMinor))
-					if err != nil {
-						return "", postStartHooks, fmt.Errorf("Failed to add cgroup rule for device")
-					}
-				}
-			}
-		} else if m["type"] == "disk" {
+		if m["type"] == "disk" {
 			if m["path"] != "/" {
 				diskDevices[k] = m
 			}
@@ -5068,22 +4982,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 
 		// Live update the devices
 		for k, m := range removeDevices {
-			if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-				prefix := fmt.Sprintf("unix.%s", k)
-				destPath := m["path"]
-				if destPath == "" {
-					destPath = m["source"]
-				}
-
-				if !device.UnixDeviceExists(c.DevicesPath(), prefix, destPath) && (m["required"] != "" && !shared.IsTrue(m["required"])) {
-					continue
-				}
-
-				err = c.removeUnixDevice(fmt.Sprintf("unix.%s", k), m, true)
-				if err != nil {
-					return err
-				}
-			} else if m["type"] == "disk" && m["path"] != "/" {
+			if m["type"] == "disk" && m["path"] != "/" {
 				err = c.removeDiskDevice(k, m)
 				if err != nil {
 					return err
@@ -5093,14 +4992,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 
 		diskDevices := map[string]config.Device{}
 		for k, m := range addDevices {
-			if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-				err = c.insertUnixDevice(fmt.Sprintf("unix.%s", k), m, true)
-				if err != nil {
-					if m["required"] == "" || shared.IsTrue(m["required"]) {
-						return err
-					}
-				}
-			} else if m["type"] == "disk" && m["path"] != "/" {
+			if m["type"] == "disk" && m["path"] != "/" {
 				diskDevices[k] = m
 			}
 		}
@@ -6826,73 +6718,6 @@ func (c *containerLXC) removeMount(mount string) error {
 	return nil
 }
 
-func (c *containerLXC) insertUnixDevice(prefix string, m config.Device, defaultMode bool) error {
-	// Check that the container is running
-	if !c.IsRunning() {
-		return fmt.Errorf("Can't insert device into stopped container")
-	}
-
-	idmapSet, err := c.CurrentIdmap()
-	if err != nil {
-		return err
-	}
-
-	// Create the device on the host
-	d, err := device.UnixDeviceCreate(c.state, idmapSet, c.DevicesPath(), prefix, m, defaultMode)
-	if err != nil {
-		return fmt.Errorf("Failed to setup device: %s", err)
-	}
-	devPath := d.HostPath
-	tgtPath := d.RelativePath
-
-	// Bind-mount it into the container
-	err = c.insertMount(devPath, tgtPath, "none", unix.MS_BIND, false)
-	if err != nil {
-		return fmt.Errorf("Failed to add mount for device: %s", err)
-	}
-
-	// Check if we've been passed major and minor numbers already.
-	var dMajor, dMinor uint32
-	if m["major"] != "" {
-		tmp, err := strconv.ParseUint(m["major"], 10, 32)
-		if err != nil {
-			return err
-		}
-		dMajor = uint32(tmp)
-	}
-
-	if m["minor"] != "" {
-		tmp, err := strconv.ParseUint(m["minor"], 10, 32)
-		if err != nil {
-			return err
-		}
-		dMinor = uint32(tmp)
-	}
-
-	dType := ""
-	if m["type"] == "unix-char" {
-		dType = "c"
-	} else if m["type"] == "unix-block" {
-		dType = "b"
-	}
-
-	if dType == "" || m["major"] == "" || m["minor"] == "" {
-		dType, dMajor, dMinor, err = device.UnixDeviceAttributes(devPath)
-		if err != nil {
-			return err
-		}
-	}
-
-	if c.isCurrentlyPrivileged() && !c.state.OS.RunningInUserNS && c.state.OS.CGroupDevicesController {
-		// Add the new device cgroup rule
-		if err := c.CGroupSet("devices.allow", fmt.Sprintf("%s %d:%d rwm", dType, dMajor, dMinor)); err != nil {
-			return fmt.Errorf("Failed to add cgroup rule for device")
-		}
-	}
-
-	return nil
-}
-
 func (c *containerLXC) InsertSeccompUnixDevice(prefix string, m config.Device, pid int) error {
 	if pid < 0 {
 		return fmt.Errorf("Invalid request PID specified")
@@ -6948,132 +6773,6 @@ func (c *containerLXC) InsertSeccompUnixDevice(prefix string, m config.Device, p
 	return c.insertMountLXD(devPath, tgtPath, "none", unix.MS_BIND, pid, false)
 }
 
-func (c *containerLXC) insertUnixDeviceNum(name string, m config.Device, major int, minor int, path string, defaultMode bool) error {
-	temp := config.Device{}
-	if err := shared.DeepCopy(&m, &temp); err != nil {
-		return err
-	}
-
-	temp["major"] = fmt.Sprintf("%d", major)
-	temp["minor"] = fmt.Sprintf("%d", minor)
-	temp["path"] = path
-
-	return c.insertUnixDevice(name, temp, defaultMode)
-}
-
-// removeUnixDevice does the following: retrieves the device type, major and minor numbers,
-// adds a cgroup deny rule for the device if container is privileged.
-// Then it removes the mount and file inside container.
-// Finally it removes the host-side mount if running in UserNS and removes host-side dev file.
-func (c *containerLXC) removeUnixDevice(prefix string, m config.Device, eject bool) error {
-	// Check that the container is running
-	pid := c.InitPID()
-	if pid == -1 {
-		return fmt.Errorf("Can't remove device from stopped container")
-	}
-
-	// Check if we've been passed major and minor numbers already.
-	var err error
-	var dMajor, dMinor uint32
-	if m["major"] != "" {
-		tmp, err := strconv.ParseUint(m["major"], 10, 32)
-		if err != nil {
-			return err
-		}
-
-		dMajor = uint32(tmp)
-	}
-
-	if m["minor"] != "" {
-		tmp, err := strconv.ParseUint(m["minor"], 10, 32)
-		if err != nil {
-			return err
-		}
-
-		dMinor = uint32(tmp)
-	}
-
-	dType := ""
-	if m["type"] == "unix-char" {
-		dType = "c"
-	} else if m["type"] == "unix-block" {
-		dType = "b"
-	}
-
-	// Figure out the paths
-	destPath := m["path"]
-	if destPath == "" {
-		destPath = m["source"]
-	}
-	relativeDestPath := strings.TrimPrefix(destPath, "/")
-	devName := fmt.Sprintf("%s.%s", strings.Replace(prefix, "/", "-", -1), strings.Replace(relativeDestPath, "/", "-", -1))
-	devPath := filepath.Join(c.DevicesPath(), devName)
-
-	if dType == "" || m["major"] == "" || m["minor"] == "" {
-		dType, dMajor, dMinor, err = device.UnixDeviceAttributes(devPath)
-		if err != nil {
-			return err
-		}
-	}
-
-	if c.isCurrentlyPrivileged() && !c.state.OS.RunningInUserNS && c.state.OS.CGroupDevicesController {
-		// Remove the device cgroup rule
-		err := c.CGroupSet("devices.deny", fmt.Sprintf("%s %d:%d rwm", dType, dMajor, dMinor))
-		if err != nil {
-			return err
-		}
-	}
-
-	if eject && c.FileExists(relativeDestPath) == nil {
-		err := c.removeMount(destPath)
-		if err != nil {
-			return fmt.Errorf("Error unmounting the device: %s", err)
-		}
-
-		err = c.FileRemove(relativeDestPath)
-		if err != nil {
-			return fmt.Errorf("Error removing the device: %s", err)
-		}
-	}
-
-	// Remove the host side
-	if c.state.OS.RunningInUserNS {
-		unix.Unmount(devPath, unix.MNT_DETACH)
-	}
-
-	err = os.Remove(devPath)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (c *containerLXC) removeUnixDeviceNum(prefix string, m config.Device, major int, minor int, path string) error {
-	pid := c.InitPID()
-	if pid == -1 {
-		return fmt.Errorf("Can't remove device from stopped container")
-	}
-
-	temp := config.Device{}
-	if err := shared.DeepCopy(&m, &temp); err != nil {
-		return err
-	}
-
-	temp["major"] = fmt.Sprintf("%d", major)
-	temp["minor"] = fmt.Sprintf("%d", minor)
-	temp["path"] = path
-
-	err := c.removeUnixDevice(prefix, temp, true)
-	if err != nil {
-		logger.Error("Failed to remove device", log.Ctx{"err": err, m["type"]: path, "container": c.Name()})
-		return err
-	}
-
-	c.FileRemove(filepath.Dir(path))
-	return nil
-}
-
 func (c *containerLXC) removeUnixDevices() error {
 	// Check that we indeed have devices to remove
 	if !shared.PathExists(c.DevicesPath()) {

From 953ed19ce29e76024e2a7964bdf026c9eedb3f1e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 23 Aug 2019 14:58:24 +0100
Subject: [PATCH 08/36] devices: Renames USBDevice to USBEvent

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/devices.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index 6331214a9b..73eae2be86 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -48,7 +48,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.USBDevice, error) {
+func deviceNetlinkListener() (chan []string, chan []string, chan device.USBEvent, error) {
 	NETLINK_KOBJECT_UEVENT := 15
 	UEVENT_BUFFER_SIZE := 2048
 
@@ -73,9 +73,9 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBDevic
 
 	chCPU := make(chan []string, 1)
 	chNetwork := make(chan []string, 0)
-	chUSB := make(chan device.USBDevice)
+	chUSB := make(chan device.USBEvent)
 
-	go func(chCPU chan []string, chNetwork chan []string, chUSB chan device.USBDevice) {
+	go func(chCPU chan []string, chNetwork chan []string, chUSB chan device.USBEvent) {
 		b := make([]byte, UEVENT_BUFFER_SIZE*2)
 		for {
 			r, err := unix.Read(fd, b)
@@ -170,7 +170,7 @@ func deviceNetlinkListener() (chan []string, chan []string, chan device.USBDevic
 					return strings.Repeat("0", l-len(s)) + s
 				}
 
-				usb, err := device.USBDeviceLoad(
+				usb, err := device.USBNewEvent(
 					props["ACTION"],
 					/* udev doesn't zero pad these, while
 					 * everything else does, so let's zero pad them

From a7011183c3c74bd7d93b34ceef86c8e58ad30be8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 23 Aug 2019 14:59:05 +0100
Subject: [PATCH 09/36] device/usb: Adds unexported usbIsOurDevice function and
 switches to USBEvent

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/usb.go | 41 ++++++++++++++++++++++++++++-------------
 1 file changed, 28 insertions(+), 13 deletions(-)

diff --git a/lxd/device/usb.go b/lxd/device/usb.go
index 30df3f3394..e2aa19b851 100644
--- a/lxd/device/usb.go
+++ b/lxd/device/usb.go
@@ -12,6 +12,21 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
+// usbDevPath is the path where USB devices can be enumerated.
+const usbDevPath = "/sys/bus/usb/devices"
+
+// usbIsOurDevice indicates whether the USB device event qualifies as part of our device.
+// This function is not defined against the usb struct type so that it can be used in event
+// callbacks without needing to keep a reference to the usb device struct.
+func usbIsOurDevice(config config.Device, usb *USBEvent) bool {
+	// Check if event matches criteria for this device, if not return.
+	if (config["vendorid"] != "" && config["vendorid"] != usb.Vendor) || (config["productid"] != "" && config["productid"] != usb.Product) {
+		return false
+	}
+
+	return true
+}
+
 type usb struct {
 	deviceCommon
 }
@@ -54,20 +69,20 @@ func (d *usb) Register() error {
 	state := d.state
 
 	// Handler for when a USB event occurs.
-	f := func(usb USBDevice) (*RunConfig, error) {
-		if !USBIsOurDevice(deviceConfig, &usb) {
+	f := func(e USBEvent) (*RunConfig, error) {
+		if !usbIsOurDevice(deviceConfig, &e) {
 			return nil, nil
 		}
 
 		runConf := RunConfig{}
 
-		if usb.Action == "add" {
-			err := unixDeviceSetupCharNum(state, devicesPath, "unix", deviceName, deviceConfig, usb.Major, usb.Minor, usb.Path, false, &runConf)
+		if e.Action == "add" {
+			err := unixDeviceSetupCharNum(state, devicesPath, "unix", deviceName, deviceConfig, e.Major, e.Minor, e.Path, false, &runConf)
 			if err != nil {
 				return nil, err
 			}
-		} else if usb.Action == "remove" {
-			relativeTargetPath := strings.TrimPrefix(usb.Path, "/")
+		} else if e.Action == "remove" {
+			relativeTargetPath := strings.TrimPrefix(e.Path, "/")
 			err := unixDeviceRemove(devicesPath, "unix", deviceName, relativeTargetPath, &runConf)
 			if err != nil {
 				return nil, err
@@ -84,7 +99,7 @@ func (d *usb) Register() error {
 			}}
 		}
 
-		runConf.Uevents = append(runConf.Uevents, usb.UeventParts)
+		runConf.Uevents = append(runConf.Uevents, e.UeventParts)
 
 		return &runConf, nil
 	}
@@ -109,7 +124,7 @@ func (d *usb) Start() (*RunConfig, error) {
 	runConf := RunConfig{}
 
 	for _, usb := range usbs {
-		if !USBIsOurDevice(d.config, &usb) {
+		if !usbIsOurDevice(d.config, &usb) {
 			continue
 		}
 
@@ -155,8 +170,8 @@ func (d *usb) postStop() error {
 }
 
 // loadUsb scans the host machine for USB devices.
-func (d *usb) loadUsb() ([]USBDevice, error) {
-	result := []USBDevice{}
+func (d *usb) loadUsb() ([]USBEvent, error) {
+	result := []USBEvent{}
 
 	ents, err := ioutil.ReadDir(usbDevPath)
 	if err != nil {
@@ -175,15 +190,15 @@ func (d *usb) loadUsb() ([]USBDevice, error) {
 				continue
 			}
 
-			return []USBDevice{}, err
+			return []USBEvent{}, err
 		}
 
 		parts := strings.Split(values["dev"], ":")
 		if len(parts) != 2 {
-			return []USBDevice{}, fmt.Errorf("invalid device value %s", values["dev"])
+			return []USBEvent{}, fmt.Errorf("invalid device value %s", values["dev"])
 		}
 
-		usb, err := USBDeviceLoad(
+		usb, err := USBNewEvent(
 			"add",
 			values["idVendor"],
 			values["idProduct"],

From 2443d6e579bfb309bad9ddd0c4daf67f4e938700 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 23 Aug 2019 14:59:44 +0100
Subject: [PATCH 10/36] device/device/utils/usb: Removed and moved into usb and
 device_utils_usb_events

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_usb.go | 149 ---------------------------------
 1 file changed, 149 deletions(-)
 delete mode 100644 lxd/device/device_utils_usb.go

diff --git a/lxd/device/device_utils_usb.go b/lxd/device/device_utils_usb.go
deleted file mode 100644
index 21a348c2ed..0000000000
--- a/lxd/device/device_utils_usb.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package device
-
-import (
-	"fmt"
-	"path/filepath"
-	"strconv"
-	"strings"
-	"sync"
-
-	"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"
-)
-
-// usbDevPath is the path where USB devices can be enumerated.
-const usbDevPath = "/sys/bus/usb/devices"
-
-// USBDevice represents the properties of a USB device and a USB uevent.
-type USBDevice struct {
-	Action string
-
-	Vendor  string
-	Product string
-
-	Path        string
-	Major       uint32
-	Minor       uint32
-	UeventParts []string
-	UeventLen   int
-}
-
-// usbHandlers stores the event handler callbacks for USB events.
-var usbHandlers = map[string]func(USBDevice) (*RunConfig, error){}
-
-// usbMutex controls access to the usbHandlers map.
-var usbMutex sync.Mutex
-
-// USBRegisterHandler registers a handler function to be called whenever a USB device event occurs.
-func USBRegisterHandler(instance InstanceIdentifier, deviceName string, handler func(USBDevice) (*RunConfig, error)) {
-	usbMutex.Lock()
-	defer usbMutex.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)
-	usbHandlers[key] = handler
-}
-
-// USBUnregisterHandler removes a registered USB handler function for a device.
-func USBUnregisterHandler(instance InstanceIdentifier, deviceName string) {
-	usbMutex.Lock()
-	defer usbMutex.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(usbHandlers, key)
-}
-
-// USBRunHandlers executes any handlers registered for USB events.
-func USBRunHandlers(state *state.State, event *USBDevice) {
-	usbMutex.Lock()
-	defer usbMutex.Unlock()
-
-	for key, hook := range usbHandlers {
-		keyParts := strings.SplitN(key, "\000", 3)
-		projectName := keyParts[0]
-		instanceName := keyParts[1]
-		deviceName := keyParts[2]
-
-		if hook == nil {
-			delete(usbHandlers, key)
-			continue
-		}
-
-		runConf, err := hook(*event)
-		if err != nil {
-			logger.Error("USB event hook failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
-			continue
-		}
-
-		// If runConf supplied, load instance and call its USB 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("USB event loading instance failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
-				continue
-			}
-
-			err = instance.DeviceEventHandler(runConf)
-			if err != nil {
-				logger.Error("USB event instance handler failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
-				continue
-			}
-		}
-	}
-}
-
-// USBDeviceLoad instantiates a new USBDevice struct.
-func USBDeviceLoad(action string, vendor string, product string, major string, minor string, busnum string, devnum string, devname string, ueventParts []string, ueventLen int) (USBDevice, error) {
-	majorInt, err := strconv.ParseUint(major, 10, 32)
-	if err != nil {
-		return USBDevice{}, err
-	}
-
-	minorInt, err := strconv.ParseUint(minor, 10, 32)
-	if err != nil {
-		return USBDevice{}, err
-	}
-
-	path := devname
-	if devname == "" {
-		busnumInt, err := strconv.Atoi(busnum)
-		if err != nil {
-			return USBDevice{}, err
-		}
-
-		devnumInt, err := strconv.Atoi(devnum)
-		if err != nil {
-			return USBDevice{}, err
-		}
-		path = fmt.Sprintf("/dev/bus/usb/%03d/%03d", busnumInt, devnumInt)
-	} else {
-		if !filepath.IsAbs(devname) {
-			path = fmt.Sprintf("/dev/%s", devname)
-		}
-	}
-
-	return USBDevice{
-		action,
-		vendor,
-		product,
-		path,
-		uint32(majorInt),
-		uint32(minorInt),
-		ueventParts,
-		ueventLen,
-	}, nil
-}
-
-// USBIsOurDevice indicates whether the USB device event qualifies as part of our device.
-func USBIsOurDevice(config config.Device, usb *USBDevice) bool {
-	// Check if event matches criteria for this device, if not return.
-	if (config["vendorid"] != "" && config["vendorid"] != usb.Vendor) || (config["productid"] != "" && config["productid"] != usb.Product) {
-		return false
-	}
-
-	return true
-}

From 0ce9a13e5da641b7c828ecd5643d81dec47e01de Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 23 Aug 2019 15:00:20 +0100
Subject: [PATCH 11/36] device/device/utils/usb/events: Adds USB event handler
 functions

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_usb_events.go | 135 ++++++++++++++++++++++++++
 1 file changed, 135 insertions(+)
 create mode 100644 lxd/device/device_utils_usb_events.go

diff --git a/lxd/device/device_utils_usb_events.go b/lxd/device/device_utils_usb_events.go
new file mode 100644
index 0000000000..08807d0c83
--- /dev/null
+++ b/lxd/device/device_utils_usb_events.go
@@ -0,0 +1,135 @@
+package device
+
+import (
+	"fmt"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"sync"
+
+	"github.com/lxc/lxd/lxd/state"
+	log "github.com/lxc/lxd/shared/log15"
+	"github.com/lxc/lxd/shared/logger"
+)
+
+// USBEvent represents the properties of a USB device uevent.
+type USBEvent struct {
+	Action string
+
+	Vendor  string
+	Product string
+
+	Path        string
+	Major       uint32
+	Minor       uint32
+	UeventParts []string
+	UeventLen   int
+}
+
+// usbHandlers stores the event handler callbacks for USB events.
+var usbHandlers = map[string]func(USBEvent) (*RunConfig, error){}
+
+// usbMutex controls access to the usbHandlers map.
+var usbMutex sync.Mutex
+
+// usbRegisterHandler registers a handler function to be called whenever a USB device event occurs.
+func usbRegisterHandler(instance InstanceIdentifier, deviceName string, handler func(USBEvent) (*RunConfig, error)) {
+	usbMutex.Lock()
+	defer usbMutex.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)
+	usbHandlers[key] = handler
+}
+
+// usbUnregisterHandler removes a registered USB handler function for a device.
+func usbUnregisterHandler(instance InstanceIdentifier, deviceName string) {
+	usbMutex.Lock()
+	defer usbMutex.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(usbHandlers, key)
+}
+
+// USBRunHandlers executes any handlers registered for USB events.
+func USBRunHandlers(state *state.State, event *USBEvent) {
+	usbMutex.Lock()
+	defer usbMutex.Unlock()
+
+	for key, hook := range usbHandlers {
+		keyParts := strings.SplitN(key, "\000", 3)
+		projectName := keyParts[0]
+		instanceName := keyParts[1]
+		deviceName := keyParts[2]
+
+		if hook == nil {
+			delete(usbHandlers, key)
+			continue
+		}
+
+		runConf, err := hook(*event)
+		if err != nil {
+			logger.Error("USB event hook failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
+			continue
+		}
+
+		// If runConf supplied, load instance and call its USB 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("USB event loading instance failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
+				continue
+			}
+
+			err = instance.DeviceEventHandler(runConf)
+			if err != nil {
+				logger.Error("USB event instance handler failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
+				continue
+			}
+		}
+	}
+}
+
+// USBNewEvent instantiates a new USBEvent struct.
+func USBNewEvent(action string, vendor string, product string, major string, minor string, busnum string, devnum string, devname string, ueventParts []string, ueventLen int) (USBEvent, error) {
+	majorInt, err := strconv.ParseUint(major, 10, 32)
+	if err != nil {
+		return USBEvent{}, err
+	}
+
+	minorInt, err := strconv.ParseUint(minor, 10, 32)
+	if err != nil {
+		return USBEvent{}, err
+	}
+
+	path := devname
+	if devname == "" {
+		busnumInt, err := strconv.Atoi(busnum)
+		if err != nil {
+			return USBEvent{}, err
+		}
+
+		devnumInt, err := strconv.Atoi(devnum)
+		if err != nil {
+			return USBEvent{}, err
+		}
+		path = fmt.Sprintf("/dev/bus/usb/%03d/%03d", busnumInt, devnumInt)
+	} else {
+		if !filepath.IsAbs(devname) {
+			path = fmt.Sprintf("/dev/%s", devname)
+		}
+	}
+
+	return USBEvent{
+		action,
+		vendor,
+		product,
+		path,
+		uint32(majorInt),
+		uint32(minorInt),
+		ueventParts,
+		ueventLen,
+	}, nil
+}

From 5784d2baf36d9cffa518f3033b40b5172c2b93da Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 23 Aug 2019 15:02:19 +0100
Subject: [PATCH 12/36] device/usb: Removes unused function

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/usb.go | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/lxd/device/usb.go b/lxd/device/usb.go
index e2aa19b851..137983fc50 100644
--- a/lxd/device/usb.go
+++ b/lxd/device/usb.go
@@ -54,11 +54,6 @@ func (d *usb) validateConfig() error {
 	return nil
 }
 
-// validateEnvironment checks the runtime environment for correctness.
-func (d *usb) validateEnvironment() error {
-	return nil
-}
-
 // Register is run after the device is started or when LXD starts.
 func (d *usb) Register() error {
 	// Extract variables needed to run the event hook so that the reference to this device
@@ -111,11 +106,6 @@ func (d *usb) Register() error {
 
 // Start is run when the device is added to the instance.
 func (d *usb) Start() (*RunConfig, error) {
-	err := d.validateEnvironment()
-	if err != nil {
-		return nil, err
-	}
-
 	usbs, err := d.loadUsb()
 	if err != nil {
 		return nil, err

From 3fa8bb9a9f06197dfd0e0a67538cdf10f4a2eb04 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 16:03:02 +0100
Subject: [PATCH 13/36] device: Links up unix-char and unix-block devices

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/device/device.go b/lxd/device/device.go
index a3bf232f61..a2eba93088 100644
--- a/lxd/device/device.go
+++ b/lxd/device/device.go
@@ -14,6 +14,8 @@ var devTypes = map[string]func(config.Device) device{
 	"proxy":      func(c config.Device) device { return &proxy{} },
 	"gpu":        func(c config.Device) device { return &gpu{} },
 	"usb":        func(c config.Device) device { return &usb{} },
+	"unix-char":  func(c config.Device) device { return &unixCommon{} },
+	"unix-block": func(c config.Device) device { return &unixCommon{} },
 }
 
 // VolatileSetter is a function that accepts one or more key/value strings to save into the LXD

From 65c544892af83eb64c1a1c5c0f5bc93d3c6ce3b1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 16:28:28 +0100
Subject: [PATCH 14/36] devices: Removes inotify code

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/devices.go | 473 -------------------------------------------------
 1 file changed, 473 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index 73eae2be86..02279b8e2b 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -13,13 +13,11 @@ import (
 	"sort"
 	"strconv"
 	"strings"
-	"unsafe"
 
 	"golang.org/x/sys/unix"
 
 	"github.com/lxc/lxd/lxd/device"
 	"github.com/lxc/lxd/lxd/state"
-	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
@@ -820,474 +818,3 @@ func deviceParseDiskLimit(readSpeed string, writeSpeed string) (int64, int64, in
 
 	return readBps, readIops, writeBps, writeIops, nil
 }
-
-func deviceInotifyInit(s *state.State) (int, error) {
-	s.OS.InotifyWatch.Lock()
-	defer s.OS.InotifyWatch.Unlock()
-
-	if s.OS.InotifyWatch.Fd >= 0 {
-		return s.OS.InotifyWatch.Fd, nil
-	}
-
-	inFd, err := unix.InotifyInit1(unix.IN_CLOEXEC)
-	if err != nil {
-		logger.Errorf("Failed to initialize inotify")
-		return -1, err
-	}
-	logger.Debugf("Initialized inotify with file descriptor %d", inFd)
-
-	s.OS.InotifyWatch.Fd = inFd
-	return inFd, nil
-}
-
-func findClosestLivingAncestor(cleanPath string) (bool, string) {
-	if shared.PathExists(cleanPath) {
-		return true, cleanPath
-	}
-
-	s := cleanPath
-	for {
-		s = filepath.Dir(s)
-		if s == cleanPath {
-			return false, s
-		}
-		if shared.PathExists(s) {
-			return true, s
-		}
-	}
-}
-
-func deviceInotifyAddClosestLivingAncestor(s *state.State, path string) error {
-	cleanPath := filepath.Clean(path)
-	// Find first existing ancestor directory and add it to the target.
-	exists, watchDir := findClosestLivingAncestor(cleanPath)
-	if !exists {
-		return fmt.Errorf("No existing ancestor directory found for \"%s\"", path)
-	}
-
-	err := deviceInotifyAddTarget(s, watchDir)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func deviceInotifyAddTarget(s *state.State, path string) error {
-	s.OS.InotifyWatch.Lock()
-	defer s.OS.InotifyWatch.Unlock()
-
-	inFd := s.OS.InotifyWatch.Fd
-	if inFd < 0 {
-		return fmt.Errorf("Inotify instance not intialized")
-	}
-
-	// Do not add the same target twice.
-	_, ok := s.OS.InotifyWatch.Targets[path]
-	if ok {
-		logger.Debugf("Inotify is already watching \"%s\"", path)
-		return nil
-	}
-
-	mask := uint32(0)
-	mask |= unix.IN_ONLYDIR
-	mask |= unix.IN_CREATE
-	mask |= unix.IN_DELETE
-	mask |= unix.IN_DELETE_SELF
-	wd, err := unix.InotifyAddWatch(inFd, path, mask)
-	if err != nil {
-		return err
-	}
-
-	s.OS.InotifyWatch.Targets[path] = &sys.InotifyTargetInfo{
-		Mask: mask,
-		Path: path,
-		Wd:   wd,
-	}
-
-	// Add a second key based on the watch file descriptor to the map that
-	// points to the same allocated memory. This is used to reverse engineer
-	// the absolute path when an event happens in the watched directory.
-	// We prefix the key with a \0 character as this is disallowed in
-	// directory and file names and thus guarantees uniqueness of the key.
-	wdString := fmt.Sprintf("\000:%d", wd)
-	s.OS.InotifyWatch.Targets[wdString] = s.OS.InotifyWatch.Targets[path]
-	return nil
-}
-
-func deviceInotifyDel(s *state.State) {
-	s.OS.InotifyWatch.Lock()
-	unix.Close(s.OS.InotifyWatch.Fd)
-	s.OS.InotifyWatch.Fd = -1
-	s.OS.InotifyWatch.Unlock()
-}
-
-const LXD_BATCH_IN_EVENTS uint = 100
-const LXD_SINGLE_IN_EVENT_SIZE uint = (unix.SizeofInotifyEvent + unix.PathMax)
-const LXD_BATCH_IN_BUFSIZE uint = LXD_BATCH_IN_EVENTS * LXD_SINGLE_IN_EVENT_SIZE
-
-func deviceInotifyWatcher(s *state.State) (chan sys.InotifyTargetInfo, error) {
-	targetChan := make(chan sys.InotifyTargetInfo)
-	go func(target chan sys.InotifyTargetInfo) {
-		for {
-			buf := make([]byte, LXD_BATCH_IN_BUFSIZE)
-			n, errno := unix.Read(s.OS.InotifyWatch.Fd, buf)
-			if errno != nil {
-				if errno == unix.EINTR {
-					continue
-				}
-
-				deviceInotifyDel(s)
-				return
-			}
-
-			if n < unix.SizeofInotifyEvent {
-				continue
-			}
-
-			var offset uint32
-			for offset <= uint32(n-unix.SizeofInotifyEvent) {
-				name := ""
-				event := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
-
-				nameLen := uint32(event.Len)
-				if nameLen > 0 {
-					bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
-					name = strings.TrimRight(string(bytes[0:nameLen]), "\000")
-				}
-
-				target <- sys.InotifyTargetInfo{
-					Mask: uint32(event.Mask),
-					Path: name,
-					Wd:   int(event.Wd),
-				}
-
-				offset += (unix.SizeofInotifyEvent + nameLen)
-			}
-		}
-	}(targetChan)
-
-	return targetChan, nil
-}
-
-func deviceInotifyDelWatcher(s *state.State, path string) error {
-	s.OS.InotifyWatch.Lock()
-	defer s.OS.InotifyWatch.Unlock()
-
-	if s.OS.InotifyWatch.Fd < 0 {
-		return nil
-	}
-
-	target, ok := s.OS.InotifyWatch.Targets[path]
-	if !ok {
-		logger.Debugf("Inotify target \"%s\" not present", path)
-		return nil
-	}
-
-	ret, err := unix.InotifyRmWatch(s.OS.InotifyWatch.Fd, uint32(target.Wd))
-	if ret != 0 {
-		// When a file gets deleted the wd for that file will
-		// automatically be deleted from the inotify instance. So
-		// ignore errors here.
-		logger.Debugf("Inotify syscall returned %s for \"%s\"", err, path)
-	}
-	delete(s.OS.InotifyWatch.Targets, path)
-	wdString := fmt.Sprintf("\000:%d", target.Wd)
-	delete(s.OS.InotifyWatch.Targets, wdString)
-	return nil
-}
-
-func createAncestorPaths(cleanPath string) []string {
-	components := strings.Split(cleanPath, "/")
-	ancestors := []string{}
-	newPath := "/"
-	ancestors = append(ancestors, newPath)
-	for _, v := range components[1:] {
-		newPath = filepath.Join(newPath, v)
-		ancestors = append(ancestors, newPath)
-	}
-
-	return ancestors
-}
-
-func deviceInotifyEvent(s *state.State, target *sys.InotifyTargetInfo) {
-	if (target.Mask & unix.IN_ISDIR) > 0 {
-		if (target.Mask & unix.IN_CREATE) > 0 {
-			deviceInotifyDirCreateEvent(s, target)
-		} else if (target.Mask & unix.IN_DELETE) > 0 {
-			deviceInotifyDirDeleteEvent(s, target)
-		}
-		deviceInotifyDirRescan(s)
-	} else if (target.Mask & unix.IN_DELETE_SELF) > 0 {
-		deviceInotifyDirDeleteEvent(s, target)
-		deviceInotifyDirRescan(s)
-	} else {
-		deviceInotifyFileEvent(s, target)
-	}
-}
-
-func deviceInotifyDirDeleteEvent(s *state.State, target *sys.InotifyTargetInfo) {
-	parentKey := fmt.Sprintf("\000:%d", target.Wd)
-	s.OS.InotifyWatch.RLock()
-	parent, ok := s.OS.InotifyWatch.Targets[parentKey]
-	s.OS.InotifyWatch.RUnlock()
-	if !ok {
-		return
-	}
-
-	// The absolute path of the file for which we received an event?
-	targetName := filepath.Join(parent.Path, target.Path)
-	targetName = filepath.Clean(targetName)
-	err := deviceInotifyDelWatcher(s, targetName)
-	if err != nil {
-		logger.Errorf("Failed to remove \"%s\" from inotify targets: %s", targetName, err)
-	} else {
-		logger.Errorf("Removed \"%s\" from inotify targets", targetName)
-	}
-}
-
-func deviceInotifyDirRescan(s *state.State) {
-	containers, err := containerLoadNodeAll(s)
-	if err != nil {
-		logger.Errorf("Failed to load containers: %s", err)
-		return
-	}
-
-	for _, containerIf := range containers {
-		c, ok := containerIf.(*containerLXC)
-		if !ok {
-			logger.Errorf("Received device event on non-LXC container")
-			return
-		}
-
-		if !c.IsRunning() {
-			continue
-		}
-
-		devices := c.ExpandedDevices()
-		for _, name := range devices.DeviceNames() {
-			m := devices[name]
-			if !shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-				continue
-			}
-
-			if m["required"] == "" || shared.IsTrue(m["required"]) {
-				continue
-			}
-
-			cmp := m["source"]
-			if cmp == "" {
-				cmp = m["path"]
-			}
-			cleanDevPath := filepath.Clean(cmp)
-			if shared.PathExists(cleanDevPath) {
-				c.insertUnixDevice(fmt.Sprintf("unix.%s", name), m, false)
-			} else {
-				c.removeUnixDevice(fmt.Sprintf("unix.%s", name), m, true)
-			}
-
-			// and add its nearest existing ancestor.
-			err = deviceInotifyAddClosestLivingAncestor(s, filepath.Dir(cleanDevPath))
-			if err != nil {
-				logger.Errorf("Failed to add \"%s\" to inotify targets: %s", filepath.Dir(cleanDevPath), err)
-			} else {
-				logger.Debugf("Added \"%s\" to inotify targets", filepath.Dir(cleanDevPath))
-			}
-		}
-	}
-}
-
-func deviceInotifyDirCreateEvent(s *state.State, target *sys.InotifyTargetInfo) {
-	parentKey := fmt.Sprintf("\000:%d", target.Wd)
-	s.OS.InotifyWatch.RLock()
-	parent, ok := s.OS.InotifyWatch.Targets[parentKey]
-	s.OS.InotifyWatch.RUnlock()
-	if !ok {
-		return
-	}
-
-	containers, err := containerLoadNodeAll(s)
-	if err != nil {
-		logger.Errorf("Failed to load containers: %s", err)
-		return
-	}
-
-	// The absolute path of the file for which we received an event?
-	targetName := filepath.Join(parent.Path, target.Path)
-	targetName = filepath.Clean(targetName)
-
-	// ancestors
-	del := createAncestorPaths(targetName)
-	keep := []string{}
-	for _, containerIf := range containers {
-		c, ok := containerIf.(*containerLXC)
-		if !ok {
-			logger.Errorf("Received device event on non-LXC container")
-			return
-		}
-
-		if !c.IsRunning() {
-			continue
-		}
-
-		devices := c.ExpandedDevices()
-		for _, name := range devices.DeviceNames() {
-			m := devices[name]
-			if !shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-				continue
-			}
-
-			if m["required"] == "" || shared.IsTrue(m["required"]) {
-				continue
-			}
-
-			cmp := m["source"]
-			if cmp == "" {
-				cmp = m["path"]
-			}
-			cleanDevPath := filepath.Clean(cmp)
-
-			for i := len(del) - 1; i >= 0; i-- {
-				// Only keep paths that can be deleted.
-				if strings.HasPrefix(cleanDevPath, del[i]) {
-					if shared.StringInSlice(del[i], keep) {
-						break
-					}
-
-					keep = append(keep, del[i])
-					break
-				}
-			}
-		}
-	}
-
-	for i, v := range del {
-		if shared.StringInSlice(v, keep) {
-			del[i] = ""
-		}
-	}
-
-	for _, v := range del {
-		if v == "" {
-			continue
-		}
-
-		err := deviceInotifyDelWatcher(s, v)
-		if err != nil {
-			logger.Errorf("Failed to remove \"%s\" from inotify targets: %s", v, err)
-		} else {
-			logger.Debugf("Removed \"%s\" from inotify targets", v)
-		}
-	}
-
-	for _, v := range keep {
-		if v == "" {
-			continue
-		}
-
-		err = deviceInotifyAddClosestLivingAncestor(s, v)
-		if err != nil {
-			logger.Errorf("Failed to add \"%s\" to inotify targets: %s", v, err)
-		} else {
-			logger.Debugf("Added \"%s\" to inotify targets", v)
-		}
-	}
-}
-
-func deviceInotifyFileEvent(s *state.State, target *sys.InotifyTargetInfo) {
-	parentKey := fmt.Sprintf("\000:%d", target.Wd)
-	s.OS.InotifyWatch.RLock()
-	parent, ok := s.OS.InotifyWatch.Targets[parentKey]
-	s.OS.InotifyWatch.RUnlock()
-	if !ok {
-		return
-	}
-
-	containers, err := containerLoadNodeAll(s)
-	if err != nil {
-		logger.Errorf("Failed to load containers: %s", err)
-		return
-	}
-
-	// Does the current file have watchers?
-	hasWatchers := false
-	// The absolute path of the file for which we received an event?
-	targetName := filepath.Join(parent.Path, target.Path)
-	for _, containerIf := range containers {
-		c, ok := containerIf.(*containerLXC)
-		if !ok {
-			logger.Errorf("Received device event on non-LXC container")
-			return
-		}
-
-		if !c.IsRunning() {
-			continue
-		}
-
-		devices := c.ExpandedDevices()
-		for _, name := range devices.DeviceNames() {
-			m := devices[name]
-			if !shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-				continue
-			}
-
-			cmp := m["source"]
-			if cmp == "" {
-				cmp = m["path"]
-			}
-
-			if m["required"] == "" || shared.IsTrue(m["required"]) {
-				continue
-			}
-
-			cleanDevPath := filepath.Clean(cmp)
-			cleanInotPath := filepath.Clean(targetName)
-			if !hasWatchers && strings.HasPrefix(cleanDevPath, cleanInotPath) {
-				hasWatchers = true
-			}
-
-			if cleanDevPath != cleanInotPath {
-				continue
-			}
-
-			if (target.Mask & unix.IN_CREATE) > 0 {
-				err := c.insertUnixDevice(fmt.Sprintf("unix.%s", name), m, false)
-				if err != nil {
-					logger.Error("Failed to create unix device", log.Ctx{"err": err, "dev": m, "container": c.Name()})
-					continue
-				}
-			} else if (target.Mask & unix.IN_DELETE) > 0 {
-				err := c.removeUnixDevice(fmt.Sprintf("unix.%s", name), m, true)
-				if err != nil {
-					logger.Error("Failed to remove unix device", log.Ctx{"err": err, "dev": m, "container": c.Name()})
-					continue
-				}
-			} else {
-				logger.Error("Uknown action for unix device", log.Ctx{"dev": m, "container": c.Name()})
-			}
-		}
-	}
-
-	if !hasWatchers {
-		err := deviceInotifyDelWatcher(s, targetName)
-		if err != nil {
-			logger.Errorf("Failed to remove \"%s\" from inotify targets: %s", targetName, err)
-		} else {
-			logger.Debugf("Removed \"%s\" from inotify targets", targetName)
-		}
-	}
-}
-
-func deviceInotifyHandler(s *state.State) {
-	watchChan, err := deviceInotifyWatcher(s)
-	if err != nil {
-		return
-	}
-
-	for {
-		select {
-		case v := <-watchChan:
-			deviceInotifyEvent(s, &v)
-		}
-	}
-}

From 6d4ec1890da93e4f29f309e60b99b006ec50e3f2 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 23 Aug 2019 15:38:32 +0100
Subject: [PATCH 15/36] device/device/utils/unix/events: Adds unix event
 handling functions

Moves inotify related functions into this file too as they were tightly coupled to the unix event handlers.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_unix_events.go | 567 +++++++++++++++++++++++++
 1 file changed, 567 insertions(+)
 create mode 100644 lxd/device/device_utils_unix_events.go

diff --git a/lxd/device/device_utils_unix_events.go b/lxd/device/device_utils_unix_events.go
new file mode 100644
index 0000000000..7c2e37d58c
--- /dev/null
+++ b/lxd/device/device_utils_unix_events.go
@@ -0,0 +1,567 @@
+package device
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+	"sync"
+	"unsafe"
+
+	"golang.org/x/sys/unix"
+
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/sys"
+	"github.com/lxc/lxd/shared"
+	log "github.com/lxc/lxd/shared/log15"
+	"github.com/lxc/lxd/shared/logger"
+)
+
+const inotifyBatchInEvents uint = 100
+const inotifySingleInEventSize uint = (unix.SizeofInotifyEvent + unix.PathMax)
+const inotifyBatchInBufSize uint = inotifyBatchInEvents * inotifySingleInEventSize
+
+// UnixEvent represents the properties of a Unix device inotify event.
+type UnixEvent struct {
+	Action string // The type of event, either add or remove.
+	Path   string // The absolute source path on the host.
+}
+
+// UnixSubscription used to subcribe to specific events.
+type UnixSubscription struct {
+	Path    string                              // The absolute source path on the host.
+	Handler func(UnixEvent) (*RunConfig, error) // The function to run when an event occurs.
+}
+
+// unixHandlers stores the event handler callbacks for Unix events.
+var unixHandlers = map[string]UnixSubscription{}
+
+// unixMutex controls access to the unixHandlers map.
+var unixMutex sync.Mutex
+
+// UnixInotifyInit initialises the inotify internal structures.
+func UnixInotifyInit(s *state.State) (int, error) {
+	s.OS.InotifyWatch.Lock()
+	defer s.OS.InotifyWatch.Unlock()
+
+	if s.OS.InotifyWatch.Fd >= 0 {
+		return s.OS.InotifyWatch.Fd, nil
+	}
+
+	inFd, err := unix.InotifyInit1(unix.IN_CLOEXEC)
+	if err != nil {
+		logger.Errorf("Failed to initialize inotify")
+		return -1, err
+	}
+	logger.Debugf("Initialized inotify with file descriptor %d", inFd)
+
+	s.OS.InotifyWatch.Fd = inFd
+
+	return inFd, nil
+}
+
+// UnixInotifyHandler starts watching for inotify events.
+func UnixInotifyHandler(s *state.State) {
+	watchChan, err := inotifyWatcher(s)
+	if err != nil {
+		return
+	}
+
+	for {
+		select {
+		case v := <-watchChan:
+			inotifyEvent(s, &v)
+		}
+	}
+}
+
+// unixRegisterHandler registers a handler function to be called whenever a Unix device event occurs.
+func unixRegisterHandler(s *state.State, instance InstanceIdentifier, deviceName, path string, handler func(UnixEvent) (*RunConfig, error)) error {
+	if path == "" || handler == nil {
+		return fmt.Errorf("Invalid subscription")
+	}
+
+	unixMutex.Lock()
+	defer unixMutex.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)
+	unixHandlers[key] = UnixSubscription{
+		Path:    path,
+		Handler: handler,
+	}
+
+	// Add inotify watcher to its nearest existing ancestor.
+	cleanDevDirPath := filepath.Dir(filepath.Clean(path))
+	err := inotifyAddClosestLivingAncestor(s, cleanDevDirPath)
+	if err != nil {
+		return fmt.Errorf("Failed to add \"%s\" to inotify targets: %s", cleanDevDirPath, err)
+	}
+
+	logger.Debugf("Added \"%s\" to inotify targets", cleanDevDirPath)
+	return nil
+}
+
+// unixUnregisterHandler removes a registered Unix handler function for a device.
+func unixUnregisterHandler(s *state.State, instance InstanceIdentifier, deviceName string) error {
+	unixMutex.Lock()
+	defer unixMutex.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)
+
+	sub, exists := unixHandlers[key]
+	if !exists {
+		return nil
+	}
+
+	// Remove active subscription for this device.
+	delete(unixHandlers, key)
+
+	// Create a map of all unique living ancestor paths for all active subscriptions and count
+	// how many subscriptions are using each living ancestor path.
+	subsLivingAncestors := make(map[string]uint)
+	for _, sub := range unixHandlers {
+
+		exists, path := inotifyFindClosestLivingAncestor(filepath.Dir(sub.Path))
+		if !exists {
+			continue
+		}
+
+		subsLivingAncestors[path]++ // Count how many subscriptions are sharing a watcher.
+	}
+
+	// Identify which living ancestor path the subscription we just deleted was using.
+	exists, ourSubPath := inotifyFindClosestLivingAncestor(filepath.Dir(sub.Path))
+
+	// If we were the only subscription using the living ancestor path, then remove the watcher.
+	if exists && subsLivingAncestors[ourSubPath] == 0 {
+		err := inotifyDelWatcher(s, ourSubPath)
+		if err != nil {
+			return fmt.Errorf("Failed to remove \"%s\" from inotify targets: %s", ourSubPath, err)
+		}
+		logger.Debugf("Removed \"%s\" from inotify targets", ourSubPath)
+	}
+
+	return nil
+}
+
+// unixRunHandlers executes any handlers registered for Unix events.
+func unixRunHandlers(state *state.State, event *UnixEvent) {
+	unixMutex.Lock()
+	defer unixMutex.Unlock()
+
+	for key, sub := range unixHandlers {
+		keyParts := strings.SplitN(key, "\000", 3)
+		projectName := keyParts[0]
+		instanceName := keyParts[1]
+		deviceName := keyParts[2]
+
+		// Delete subscription if no handler function defined.
+		if sub.Handler == nil {
+			delete(unixHandlers, key)
+			continue
+		}
+
+		// Don't execute handler if subscription path and event paths don't match.
+		if sub.Path != event.Path {
+			continue
+		}
+
+		// Run handler function.
+		runConf, err := sub.Handler(*event)
+		if err != nil {
+			logger.Error("Unix event hook failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName, "path": sub.Path})
+			continue
+		}
+
+		// If runConf supplied, load instance and call its Unix 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 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 event instance handler failed", log.Ctx{"err": err, "project": projectName, "instance": instanceName, "device": deviceName})
+				continue
+			}
+		}
+	}
+}
+
+// unixGetSubcribedPaths returns all the subcribed paths as a map keyed on path.
+func unixGetSubcribedPaths() map[string]struct{} {
+	unixMutex.Lock()
+	defer unixMutex.Unlock()
+
+	paths := make(map[string]struct{})
+
+	for _, sub := range unixHandlers {
+		paths[sub.Path] = struct{}{}
+	}
+
+	return paths
+}
+
+// unixNewEvent returns a newly created Unix device event struct.
+// If an empty action is supplied then the action of the event is derived from whether the path
+// exists (add) or not (removed). This allows the peculiarities of the inotify API to be somewhat
+// masked by the consuming event handler functions.
+func unixNewEvent(action string, path string) UnixEvent {
+	if action == "" {
+		if shared.PathExists(path) {
+			action = "add"
+		} else {
+			action = "remove"
+		}
+	}
+
+	return UnixEvent{
+		Action: action,
+		Path:   path,
+	}
+}
+
+// inotifyDirRescan tries to add inotify watchers for all subscribed paths. It generates pseudo
+// events to any registered handler with either add or remove action based on whether or not the
+// file exists.
+func inotifyDirRescan(s *state.State) {
+	// Because we don't know what sort of event actually occurred, lets generate a pseudo event
+	// for each of the unique paths that devices for all instances on this host are subcribed
+	// to. This will test for whether each file actually exists or not and allow the handler
+	// functions to add/remove as necessary. It also allows inotify watchers to be setup for
+	// all desired paths.
+	subs := unixGetSubcribedPaths()
+	for subPath := range subs {
+		cleanDevPath := filepath.Clean(subPath)
+		e := unixNewEvent("", subPath)
+		unixRunHandlers(s, &e)
+
+		// Add its nearest existing ancestor.
+		err := inotifyAddClosestLivingAncestor(s, filepath.Dir(cleanDevPath))
+		if err != nil {
+			logger.Errorf("Failed to add \"%s\" to inotify targets: %s", filepath.Dir(cleanDevPath), err)
+		} else {
+			logger.Debugf("Added \"%s\" to inotify targets", filepath.Dir(cleanDevPath))
+		}
+	}
+}
+
+func inotifyFindClosestLivingAncestor(cleanPath string) (bool, string) {
+	if shared.PathExists(cleanPath) {
+		return true, cleanPath
+	}
+
+	s := cleanPath
+	for {
+		s = filepath.Dir(s)
+		if s == cleanPath {
+			return false, s
+		}
+		if shared.PathExists(s) {
+			return true, s
+		}
+	}
+}
+
+func inotifyAddClosestLivingAncestor(s *state.State, path string) error {
+	cleanPath := filepath.Clean(path)
+	// Find first existing ancestor directory and add it to the target.
+	exists, watchDir := inotifyFindClosestLivingAncestor(cleanPath)
+	if !exists {
+		return fmt.Errorf("No existing ancestor directory found for \"%s\"", path)
+	}
+
+	err := inotifyAddTarget(s, watchDir)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func inotifyAddTarget(s *state.State, path string) error {
+	s.OS.InotifyWatch.Lock()
+	defer s.OS.InotifyWatch.Unlock()
+
+	inFd := s.OS.InotifyWatch.Fd
+	if inFd < 0 {
+		return fmt.Errorf("Inotify instance not intialized")
+	}
+
+	// Do not add the same target twice.
+	_, ok := s.OS.InotifyWatch.Targets[path]
+	if ok {
+		logger.Debugf("Inotify is already watching \"%s\"", path)
+		return nil
+	}
+
+	mask := uint32(0)
+	mask |= unix.IN_ONLYDIR
+	mask |= unix.IN_CREATE
+	mask |= unix.IN_DELETE
+	mask |= unix.IN_DELETE_SELF
+	wd, err := unix.InotifyAddWatch(inFd, path, mask)
+	if err != nil {
+		return err
+	}
+
+	s.OS.InotifyWatch.Targets[path] = &sys.InotifyTargetInfo{
+		Mask: mask,
+		Path: path,
+		Wd:   wd,
+	}
+
+	// Add a second key based on the watch file descriptor to the map that
+	// points to the same allocated memory. This is used to reverse engineer
+	// the absolute path when an event happens in the watched directory.
+	// We prefix the key with a \0 character as this is disallowed in
+	// directory and file names and thus guarantees uniqueness of the key.
+	wdString := fmt.Sprintf("\000:%d", wd)
+	s.OS.InotifyWatch.Targets[wdString] = s.OS.InotifyWatch.Targets[path]
+	return nil
+}
+
+func inotifyDel(s *state.State) {
+	s.OS.InotifyWatch.Lock()
+	unix.Close(s.OS.InotifyWatch.Fd)
+	s.OS.InotifyWatch.Fd = -1
+	s.OS.InotifyWatch.Unlock()
+}
+
+func inotifyWatcher(s *state.State) (chan sys.InotifyTargetInfo, error) {
+	targetChan := make(chan sys.InotifyTargetInfo)
+	go func(target chan sys.InotifyTargetInfo) {
+		for {
+			buf := make([]byte, inotifyBatchInBufSize)
+			n, errno := unix.Read(s.OS.InotifyWatch.Fd, buf)
+			if errno != nil {
+				if errno == unix.EINTR {
+					continue
+				}
+
+				inotifyDel(s)
+				return
+			}
+
+			if n < unix.SizeofInotifyEvent {
+				continue
+			}
+
+			var offset uint32
+			for offset <= uint32(n-unix.SizeofInotifyEvent) {
+				name := ""
+				event := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
+
+				nameLen := uint32(event.Len)
+				if nameLen > 0 {
+					bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
+					name = strings.TrimRight(string(bytes[0:nameLen]), "\000")
+				}
+
+				target <- sys.InotifyTargetInfo{
+					Mask: uint32(event.Mask),
+					Path: name,
+					Wd:   int(event.Wd),
+				}
+
+				offset += (unix.SizeofInotifyEvent + nameLen)
+			}
+		}
+	}(targetChan)
+
+	return targetChan, nil
+}
+
+func inotifyDelWatcher(s *state.State, path string) error {
+	s.OS.InotifyWatch.Lock()
+	defer s.OS.InotifyWatch.Unlock()
+
+	if s.OS.InotifyWatch.Fd < 0 {
+		return nil
+	}
+
+	target, ok := s.OS.InotifyWatch.Targets[path]
+	if !ok {
+		logger.Debugf("Inotify target \"%s\" not present", path)
+		return nil
+	}
+
+	ret, err := unix.InotifyRmWatch(s.OS.InotifyWatch.Fd, uint32(target.Wd))
+	if ret != 0 {
+		// When a file gets deleted the wd for that file will
+		// automatically be deleted from the inotify instance. So
+		// ignore errors here.
+		logger.Debugf("Inotify syscall returned %s for \"%s\"", err, path)
+	}
+	delete(s.OS.InotifyWatch.Targets, path)
+	wdString := fmt.Sprintf("\000:%d", target.Wd)
+	delete(s.OS.InotifyWatch.Targets, wdString)
+	return nil
+}
+
+func inotifyCreateAncestorPaths(cleanPath string) []string {
+	components := strings.Split(cleanPath, "/")
+	ancestors := []string{}
+	newPath := "/"
+	ancestors = append(ancestors, newPath)
+	for _, v := range components[1:] {
+		newPath = filepath.Join(newPath, v)
+		ancestors = append(ancestors, newPath)
+	}
+
+	return ancestors
+}
+
+func inotifyEvent(s *state.State, target *sys.InotifyTargetInfo) {
+	if (target.Mask & unix.IN_ISDIR) > 0 {
+		if (target.Mask & unix.IN_CREATE) > 0 {
+			inotifyDirCreateEvent(s, target)
+		} else if (target.Mask & unix.IN_DELETE) > 0 {
+			inotifyDirDeleteEvent(s, target)
+		}
+		inotifyDirRescan(s)
+	} else if (target.Mask & unix.IN_DELETE_SELF) > 0 {
+		inotifyDirDeleteEvent(s, target)
+		inotifyDirRescan(s)
+	} else {
+		inotifyFileEvent(s, target)
+	}
+}
+
+func inotifyDirDeleteEvent(s *state.State, target *sys.InotifyTargetInfo) {
+	parentKey := fmt.Sprintf("\000:%d", target.Wd)
+	s.OS.InotifyWatch.RLock()
+	parent, ok := s.OS.InotifyWatch.Targets[parentKey]
+	s.OS.InotifyWatch.RUnlock()
+	if !ok {
+		return
+	}
+
+	// The absolute path of the file for which we received an event?
+	targetName := filepath.Join(parent.Path, target.Path)
+	targetName = filepath.Clean(targetName)
+	err := inotifyDelWatcher(s, targetName)
+	if err != nil {
+		logger.Errorf("Failed to remove \"%s\" from inotify targets: %s", targetName, err)
+	} else {
+		logger.Debugf("Removed \"%s\" from inotify targets", targetName)
+	}
+}
+
+func inotifyDirCreateEvent(s *state.State, target *sys.InotifyTargetInfo) {
+	parentKey := fmt.Sprintf("\000:%d", target.Wd)
+	s.OS.InotifyWatch.RLock()
+	parent, ok := s.OS.InotifyWatch.Targets[parentKey]
+	s.OS.InotifyWatch.RUnlock()
+	if !ok {
+		return
+	}
+
+	// The absolute path of the file for which we received an event?
+	targetName := filepath.Join(parent.Path, target.Path)
+	targetName = filepath.Clean(targetName)
+
+	// ancestors
+	del := inotifyCreateAncestorPaths(targetName)
+	keep := []string{}
+
+	subs := unixGetSubcribedPaths()
+	for subPath := range subs {
+		cleanDevPath := filepath.Clean(subPath)
+
+		for i := len(del) - 1; i >= 0; i-- {
+			// Only keep paths that can be deleted.
+			if strings.HasPrefix(cleanDevPath, del[i]) {
+				if shared.StringInSlice(del[i], keep) {
+					break
+				}
+
+				keep = append(keep, del[i])
+				break
+			}
+		}
+	}
+
+	for i, v := range del {
+		if shared.StringInSlice(v, keep) {
+			del[i] = ""
+		}
+	}
+
+	for _, v := range del {
+		if v == "" {
+			continue
+		}
+
+		err := inotifyDelWatcher(s, v)
+		if err != nil {
+			logger.Errorf("Failed to remove \"%s\" from inotify targets: %s", v, err)
+		} else {
+			logger.Debugf("Removed \"%s\" from inotify targets", v)
+		}
+	}
+
+	for _, v := range keep {
+		if v == "" {
+			continue
+		}
+
+		err := inotifyAddClosestLivingAncestor(s, v)
+		if err != nil {
+			logger.Errorf("Failed to add \"%s\" to inotify targets: %s", v, err)
+		} else {
+			logger.Debugf("Added \"%s\" to inotify targets", v)
+		}
+	}
+}
+
+func inotifyFileEvent(s *state.State, target *sys.InotifyTargetInfo) {
+	parentKey := fmt.Sprintf("\000:%d", target.Wd)
+	s.OS.InotifyWatch.RLock()
+	parent, ok := s.OS.InotifyWatch.Targets[parentKey]
+	s.OS.InotifyWatch.RUnlock()
+	if !ok {
+		return
+	}
+
+	// Does the current file have watchers?
+	hasWatchers := false
+	// The absolute path of the file for which we received an event?
+	targetName := filepath.Join(parent.Path, target.Path)
+
+	subs := unixGetSubcribedPaths()
+	for subPath := range subs {
+		cleanDevPath := filepath.Clean(subPath)
+		cleanInotPath := filepath.Clean(targetName)
+		if !hasWatchers && strings.HasPrefix(cleanDevPath, cleanInotPath) {
+			hasWatchers = true
+		}
+
+		if cleanDevPath != cleanInotPath {
+			continue
+		}
+
+		if (target.Mask & unix.IN_CREATE) > 0 {
+			e := unixNewEvent("add", cleanInotPath)
+			unixRunHandlers(s, &e)
+		} else if (target.Mask & unix.IN_DELETE) > 0 {
+			e := unixNewEvent("remove", cleanInotPath)
+			unixRunHandlers(s, &e)
+		} else {
+			logger.Error("Uknown action for unix device", log.Ctx{"dev": subPath, "target": cleanInotPath})
+		}
+	}
+
+	if !hasWatchers {
+		err := inotifyDelWatcher(s, targetName)
+		if err != nil {
+			logger.Errorf("Failed to remove \"%s\" from inotify targets: %s", targetName, err)
+		} else {
+			logger.Debugf("Removed \"%s\" from inotify targets", targetName)
+		}
+	}
+}

From eaca1a04a6631f59cac8ce256fb0ec1f671c7e32 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 15:59:39 +0100
Subject: [PATCH 16/36] device/unixCommon: Adds implementation for unix-char
 and unix-block devices

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/unixCommon.go | 207 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 207 insertions(+)
 create mode 100644 lxd/device/unixCommon.go

diff --git a/lxd/device/unixCommon.go b/lxd/device/unixCommon.go
new file mode 100644
index 0000000000..398fa089e0
--- /dev/null
+++ b/lxd/device/unixCommon.go
@@ -0,0 +1,207 @@
+package device
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/shared"
+)
+
+// unixIsOurDeviceType checks that device file type matches what we are expecting in the config.
+func unixIsOurDeviceType(config config.Device, dType string) bool {
+	if config["type"] == "unix-char" && dType == "c" {
+		return true
+	}
+
+	if config["type"] == "unix-block" && dType == "b" {
+		return true
+	}
+
+	return false
+}
+
+type unixCommon struct {
+	deviceCommon
+}
+
+// validateConfig checks the supplied config for correctness.
+func (d *unixCommon) validateConfig() error {
+	if d.instance.Type() != instance.TypeContainer {
+		return ErrUnsupportedDevType
+	}
+
+	rules := map[string]func(string) error{
+		"source":   shared.IsAny,
+		"path":     shared.IsAny,
+		"major":    unixValidDeviceNum,
+		"minor":    unixValidDeviceNum,
+		"uid":      unixValidUserID,
+		"gid":      unixValidUserID,
+		"mode":     unixValidOctalFileMode,
+		"required": shared.IsBool,
+	}
+
+	err := config.ValidateDevice(rules, d.config)
+	if err != nil {
+		return err
+	}
+
+	if d.config["source"] == "" && d.config["path"] == "" {
+		return fmt.Errorf("Unix device entry is missing the required \"source\" or \"path\" property")
+	}
+
+	return nil
+}
+
+// Register is run after the device is started or when LXD starts.
+func (d *unixCommon) Register() error {
+	// Don't register for hot plug events if the device is required.
+	if d.config["required"] == "" || shared.IsTrue(d.config["required"]) {
+		return nil
+	}
+
+	// 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()
+	deviceConfig := d.config
+	deviceName := d.name
+	state := d.state
+
+	// Handler for when a Unix event occurs.
+	f := func(e UnixEvent) (*RunConfig, error) {
+		// Check if the event is for a device file that this device wants.
+		if unixDeviceSourcePath(deviceConfig) != e.Path {
+			return nil, nil
+		}
+
+		// Derive the host side path for the instance device file.
+		ourPrefix := unixDeviceJoinPath("unix", deviceName)
+		relativeDestPath := strings.TrimPrefix(unixDeviceDestPath(deviceConfig), "/")
+		devName := unixDeviceEncode(unixDeviceJoinPath(ourPrefix, relativeDestPath))
+		devPath := filepath.Join(devicesPath, devName)
+
+		runConf := RunConfig{}
+
+		if e.Action == "add" {
+			// Skip if host side instance device file already exists.
+			if shared.PathExists(devPath) {
+				return nil, nil
+			}
+
+			// Get the file type and sanity check it matches what the user was expecting.
+			dType, _, _, err := UnixDeviceAttributes(e.Path)
+			if err != nil {
+				return nil, err
+			}
+
+			if !unixIsOurDeviceType(d.config, dType) {
+				return nil, fmt.Errorf("Path specified is not a %s device", d.config["type"])
+			}
+
+			err = unixDeviceSetup(state, devicesPath, "unix", deviceName, deviceConfig, true, &runConf)
+			if err != nil {
+				return nil, err
+			}
+		} else if e.Action == "remove" {
+			// Skip if host side instance device file doesn't exist.
+			if !shared.PathExists(devPath) {
+				return nil, nil
+			}
+
+			err := unixDeviceRemove(devicesPath, "unix", deviceName, relativeDestPath, &runConf)
+			if err != nil {
+				return nil, err
+			}
+
+			// Add a post hook function to remove the specific USB device file after unmount.
+			runConf.PostHooks = []func() error{func() error {
+				err := unixDeviceDeleteFiles(state, devicesPath, "unix", deviceName, relativeDestPath)
+				if err != nil {
+					return fmt.Errorf("Failed to delete files for device '%s': %v", deviceName, err)
+				}
+
+				return nil
+			}}
+		}
+
+		return &runConf, nil
+	}
+
+	// Register the handler function against the device's source path.
+	subPath := unixDeviceSourcePath(deviceConfig)
+	err := unixRegisterHandler(d.state, d.instance, d.name, subPath, f)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Start is run when the device is added to the container.
+func (d *unixCommon) Start() (*RunConfig, error) {
+	runConf := RunConfig{}
+	runConf.PostHooks = []func() error{d.Register}
+	srcPath := unixDeviceSourcePath(d.config)
+
+	// If device file already exists on system, proceed to add it whether its required or not.
+	dType, _, _, err := UnixDeviceAttributes(srcPath)
+	if err == nil {
+		// Sanity check device type matches what the device config is expecting.
+		if !unixIsOurDeviceType(d.config, dType) {
+			return nil, fmt.Errorf("Path specified is not a %s device", d.config["type"])
+		}
+
+		err = unixDeviceSetup(d.state, d.instance.DevicesPath(), "unix", d.name, d.config, true, &runConf)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		// If the device file doesn't exist on the system, but major & minor numbers have
+		// been provided in the config then we can go ahead and create the device anyway.
+		if d.config["major"] != "" && d.config["minor"] != "" {
+			err := unixDeviceSetup(d.state, d.instance.DevicesPath(), "unix", d.name, d.config, true, &runConf)
+			if err != nil {
+				return nil, err
+			}
+		} else if d.config["required"] == "" || shared.IsTrue(d.config["required"]) {
+			// If the file is missing and the device is required then we cannot proceed.
+			return nil, fmt.Errorf("The required device path doesn't exist and the major and minor settings are not specified")
+		}
+	}
+
+	return &runConf, nil
+}
+
+// Stop is run when the device is removed from the instance.
+func (d *unixCommon) Stop() (*RunConfig, error) {
+	// Unregister any Unix event handlers for this device.
+	err := unixUnregisterHandler(d.state, d.instance, d.name)
+	if err != nil {
+		return nil, err
+	}
+
+	runConf := 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 *unixCommon) postStop() error {
+	// Remove host files for this device.
+	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
+}

From 97d6b69352910214f2d759d64512a6aad04ec27e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 27 Aug 2019 16:02:32 +0100
Subject: [PATCH 17/36] container/lxc: Removes device Register() after device
 start

Moves to using post start hooks so that Register is optional, controlled by the device, and always occurs after the instance has started or the device has started (when hot plugging).

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container_lxc.go | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b25d526b52..0dab11ff35 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1931,12 +1931,6 @@ func (c *containerLXC) deviceStart(deviceName string, rawConfig map[string]strin
 		}
 	}
 
-	// Check whether device wants to register for any events irrespective of instance run state.
-	err = d.Register()
-	if err != nil {
-		return nil, err
-	}
-
 	return runConf, nil
 }
 

From a8694914822ff35037ce24dc5472d5b9015f9024 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 27 Aug 2019 16:03:55 +0100
Subject: [PATCH 18/36] daemon: Reorganised the uevent and inotify event
 handler startup to occur before device registration.

This is to allow devices to trigger new inotify watches as devices are registered, and to ensure that instances are started before executing their event handlers.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/daemon.go | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 61170d95eb..49b704356b 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -30,6 +30,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/device"
 	"github.com/lxc/lxd/lxd/endpoints"
 	"github.com/lxc/lxd/lxd/maas"
 	"github.com/lxc/lxd/lxd/node"
@@ -843,20 +844,20 @@ func (d *Daemon) init() error {
 	}
 
 	if !d.os.MockMode {
-		// Register devices on running instances to receive events.
-		devicesRegister(d.State())
-
 		// Start the scheduler
 		go deviceEventListener(d.State())
 
-		// Setup inotify watches
-		_, err := deviceInotifyInit(d.State())
+		// Setup unix inotify watches
+		_, err := device.UnixInotifyInit(d.State())
 		if err != nil {
 			return err
 		}
 
-		deviceInotifyDirRescan(d.State())
-		go deviceInotifyHandler(d.State())
+		go device.UnixInotifyHandler(d.State())
+
+		// Register devices on running instances to receive events.
+		// This should come after the event handler go routines have been started.
+		devicesRegister(d.State())
 
 		// Setup seccomp handler
 		if d.os.SeccompListener {

From cae9a437366a4984aefa5db90cbfb274bbb58f39 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 27 Aug 2019 16:05:18 +0100
Subject: [PATCH 19/36] device/device/utils/unix: Moves some config validation
 functions into device package

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device_utils_unix.go | 42 +++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/lxd/device/device_utils_unix.go b/lxd/device/device_utils_unix.go
index 17a97bd704..a2c686255d 100644
--- a/lxd/device/device_utils_unix.go
+++ b/lxd/device/device_utils_unix.go
@@ -550,3 +550,45 @@ func unixDeviceDeleteFiles(s *state.State, devicesPath string, typePrefix string
 
 	return nil
 }
+
+// unixValidDeviceNum validates the major and minor numbers for a UNIX device.
+func unixValidDeviceNum(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	_, err := strconv.ParseUint(value, 10, 32)
+	if err != nil {
+		return fmt.Errorf("Invalid value for a UNIX device number")
+	}
+
+	return nil
+}
+
+// unixValidUserID validates the UNIX UID and GID values for ownership.
+func unixValidUserID(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	_, err := strconv.ParseUint(value, 10, 32)
+	if err != nil {
+		return fmt.Errorf("Invalid value for a UNIX ID")
+	}
+
+	return nil
+}
+
+// unixValidOctalFileMode validates the UNIX file mode.
+func unixValidOctalFileMode(value string) error {
+	if value == "" {
+		return nil
+	}
+
+	_, err := strconv.ParseUint(value, 8, 32)
+	if err != nil {
+		return fmt.Errorf("Invalid value for an octal file mode")
+	}
+
+	return nil
+}

From a197512925f04085c455adec04d0b70656f6ef52 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 27 Aug 2019 16:06:30 +0100
Subject: [PATCH 20/36] device/gpu: Used device package validation functions

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/gpu.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/device/gpu.go b/lxd/device/gpu.go
index 67a8a233cd..1b8f5d895a 100644
--- a/lxd/device/gpu.go
+++ b/lxd/device/gpu.go
@@ -41,9 +41,9 @@ func (d *gpu) validateConfig() error {
 		"productid": shared.IsDeviceID,
 		"id":        shared.IsAny,
 		"pci":       shared.IsAny,
-		"uid":       shared.IsUnixUserID,
-		"gid":       shared.IsUnixUserID,
-		"mode":      shared.IsOctalFileMode,
+		"uid":       unixValidUserID,
+		"gid":       unixValidUserID,
+		"mode":      unixValidOctalFileMode,
 	}
 
 	err := config.ValidateDevice(rules, d.config)

From d9533a1ee24566c5beb27d59bfb85d34dd2a84e3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 27 Aug 2019 16:07:02 +0100
Subject: [PATCH 21/36] device/proxy: Used device package validation functions

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/proxy.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/device/proxy.go b/lxd/device/proxy.go
index c9f00270f7..f27d544642 100644
--- a/lxd/device/proxy.go
+++ b/lxd/device/proxy.go
@@ -64,12 +64,12 @@ func (d *proxy) validateConfig() error {
 		"listen":         validateAddr,
 		"connect":        validateAddr,
 		"bind":           validateBind,
-		"mode":           shared.IsOctalFileMode,
+		"mode":           unixValidOctalFileMode,
 		"nat":            shared.IsBool,
-		"gid":            shared.IsUnixUserID,
-		"uid":            shared.IsUnixUserID,
-		"security.uid":   shared.IsUnixUserID,
-		"security.gid":   shared.IsUnixUserID,
+		"gid":            unixValidUserID,
+		"uid":            unixValidUserID,
+		"security.uid":   unixValidUserID,
+		"security.gid":   unixValidUserID,
 		"proxy_protocol": shared.IsBool,
 	}
 

From 226404176921812e047b47c4d532bef106269861 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 27 Aug 2019 16:07:30 +0100
Subject: [PATCH 22/36] device/usb: Updates Register() to be called by post
 start hook

Also updates util function names.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/usb.go | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/lxd/device/usb.go b/lxd/device/usb.go
index 137983fc50..ad5a6ba16d 100644
--- a/lxd/device/usb.go
+++ b/lxd/device/usb.go
@@ -40,9 +40,9 @@ func (d *usb) validateConfig() error {
 	rules := map[string]func(string) error{
 		"vendorid":  shared.IsDeviceID,
 		"productid": shared.IsDeviceID,
-		"uid":       shared.IsUnixUserID,
-		"gid":       shared.IsUnixUserID,
-		"mode":      shared.IsOctalFileMode,
+		"uid":       unixValidUserID,
+		"gid":       unixValidUserID,
+		"mode":      unixValidOctalFileMode,
 		"required":  shared.IsBool,
 	}
 
@@ -99,7 +99,7 @@ func (d *usb) Register() error {
 		return &runConf, nil
 	}
 
-	USBRegisterHandler(d.instance, d.name, f)
+	usbRegisterHandler(d.instance, d.name, f)
 
 	return nil
 }
@@ -112,6 +112,7 @@ func (d *usb) Start() (*RunConfig, error) {
 	}
 
 	runConf := RunConfig{}
+	runConf.PostHooks = []func() error{d.Register}
 
 	for _, usb := range usbs {
 		if !usbIsOurDevice(d.config, &usb) {
@@ -134,7 +135,7 @@ func (d *usb) Start() (*RunConfig, error) {
 // Stop is run when the device is removed from the instance.
 func (d *usb) Stop() (*RunConfig, error) {
 	// Unregister any USB event handlers for this device.
-	USBUnregisterHandler(d.instance, d.name)
+	usbUnregisterHandler(d.instance, d.name)
 
 	runConf := RunConfig{
 		PostHooks: []func() error{d.postStop},

From 15dc30c527a28d2e63814ca9cc9f1b67d96f48cd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 27 Aug 2019 16:08:51 +0100
Subject: [PATCH 23/36] shared/container: Removes device related validation
 functions

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 shared/container.go | 26 --------------------------
 1 file changed, 26 deletions(-)

diff --git a/shared/container.go b/shared/container.go
index 89ce027e55..bc5e63b837 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -115,32 +115,6 @@ func IsNotEmpty(value string) error {
 	return nil
 }
 
-func IsUnixUserID(value string) error {
-	if value == "" {
-		return nil
-	}
-
-	_, err := strconv.ParseUint(value, 10, 32)
-	if err != nil {
-		return fmt.Errorf("Invalid value for a UNIX ID")
-	}
-
-	return nil
-}
-
-func IsOctalFileMode(value string) error {
-	if value == "" {
-		return nil
-	}
-
-	_, err := strconv.ParseUint(value, 8, 32)
-	if err != nil {
-		return fmt.Errorf("Invalid value for an octal file mode")
-	}
-
-	return nil
-}
-
 // IsDeviceID validates string is four lowercase hex characters suitable as Vendor or Device ID.
 func IsDeviceID(value string) error {
 	if value == "" {

From 4506ba17b5a85150f1dc141a98ee59ae445555f8 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 22 Aug 2019 15:59:17 +0100
Subject: [PATCH 24/36] test: Adds tests for unix-char and unix-block devices

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 test/main.sh                          |   2 +
 test/suites/container_devices_unix.sh | 169 ++++++++++++++++++++++++++
 2 files changed, 171 insertions(+)
 create mode 100644 test/suites/container_devices_unix.sh

diff --git a/test/main.sh b/test/main.sh
index 33e60d42d2..b7c4cf9dfc 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -200,6 +200,8 @@ run_test test_container_devices_infiniband_physical "container devices - infinib
 run_test test_container_devices_infiniband_sriov "container devices - infiniband - sriov"
 run_test test_container_devices_proxy "container devices - proxy"
 run_test test_container_devices_gpu "container devices - gpu"
+run_test test_container_devices_unix_char "container devices - unix-char"
+run_test test_container_devices_unix_block "container devices - unix-block"
 run_test test_security "security features"
 run_test test_security_protection "container protection"
 run_test test_image_expiry "image expiry"
diff --git a/test/suites/container_devices_unix.sh b/test/suites/container_devices_unix.sh
new file mode 100644
index 0000000000..0a91a6c8c2
--- /dev/null
+++ b/test/suites/container_devices_unix.sh
@@ -0,0 +1,169 @@
+test_container_devices_unix_block() {
+  test_container_devices_unix "unix-block"
+}
+
+test_container_devices_unix_char() {
+  test_container_devices_unix "unix-char"
+}
+
+test_container_devices_unix() {
+  deviceType=$1
+  deviceTypeCode=""
+  deviceTypeDesc=""
+
+  if [ "$deviceType" = "unix-block" ]; then
+    deviceTypeCode="b"
+    deviceTypeDesc="block special file"
+  fi
+
+  if [ "$deviceType" = "unix-char" ]; then
+    deviceTypeCode="c"
+    deviceTypeDesc="character special file"
+  fi
+
+  if [ "$deviceTypeCode" = "" ]; then
+    echo "invalid device type specified in test"
+    false
+  fi
+
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+  ctName="ct$$"
+  lxc launch testimage "${ctName}"
+
+  # Create a test unix device.
+  testDev="${TEST_DIR}"/testdev-"${ctName}"
+  mknod "${testDev}" "${deviceTypeCode}" 0 0
+
+  # Check adding a device without source or path fails.
+  ! lxc config device add "${ctName}" test-dev-invalid "${deviceType}"
+  ! lxc config device add "${ctName}" test-dev-invalid "${deviceType}" required=false
+
+  # Check adding a device with missing source and no major/minor numbers fails.
+  ! lxc config device add "${ctName}" test-dev-invalid "${deviceType}" path=/tmp/testdevmissing
+
+  # Check adding a required (default) missing device fails.
+  ! lxc config device add "${ctName}" test-dev-invalid "${deviceType}" path=/tmp/testdevmissing
+  ! lxc config device add "${ctName}" test-dev-invalid "${deviceType}" path=/tmp/testdevmissing required=true
+
+  # Add device based on existing device, check its host-side name, default mode, major/minor inherited, and mounted in container.
+  lxc config device add "${ctName}" test-dev1 "${deviceType}" source="${testDev}" path=/tmp/testdev
+  lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  lxc exec "${ctName}" -- stat -c '%F %a %t %T' /tmp/testdev | grep "${deviceTypeDesc} 660 0 0"
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev1.tmp-testdev | grep "${deviceTypeDesc} 660 0 0"
+
+  # Add device with same dest path as existing device, but with different mode and major/minor and check original isn't replaced inside instance.
+  lxc config device add "${ctName}" test-dev2 "${deviceType}" source="${testDev}" path=/tmp/testdev major=1 minor=1 mode=600
+  lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  lxc exec "${ctName}" -- stat -c '%F %a %t %T' /tmp/testdev | grep "${deviceTypeDesc} 660 0 0"
+
+  # Check a new host side file was created with correct attributes.
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev2.tmp-testdev | grep "${deviceTypeDesc} 600 1 1"
+
+  # Remove dupe device and check the original is still mounted.
+  lxc config device remove "${ctName}" test-dev2
+  lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  lxc exec "${ctName}" -- stat -c '%F %a %t %T' /tmp/testdev | grep "${deviceTypeDesc} 660 0 0"
+
+  # Check dupe device host side file is removed though.
+  if ls "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev2.tmp-testdev; then
+    echo "test-dev2 host side file not removed"
+    false
+  fi
+
+  # Add new device with custom mode and check it creates correctly on boot.
+  lxc stop -f "${ctName}"
+  lxc config device add "${ctName}" test-dev3 "${deviceType}" source="${testDev}" path=/tmp/testdev3 major=1 minor=1 mode=600
+  lxc start "${ctName}"
+  lxc exec "${ctName}" -- mount | grep "/tmp/testdev3"
+  lxc exec "${ctName}" -- stat -c '%F %a %t %T' /tmp/testdev3 | grep "${deviceTypeDesc} 600 1 1"
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev3.tmp-testdev3 | grep "${deviceTypeDesc} 600 1 1"
+  lxc config device remove "${ctName}" test-dev3
+
+  # Add new device without a source, but with a path and major and minor numbers.
+  lxc config device add "${ctName}" test-dev4 "${deviceType}" path=/tmp/testdev4 major=0 minor=2 mode=777
+  lxc exec "${ctName}" -- mount | grep "/tmp/testdev4"
+  lxc exec "${ctName}" -- stat -c '%F %a %t %T' /tmp/testdev4 | grep "${deviceTypeDesc} 777 0 2"
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev4.tmp-testdev4 | grep "${deviceTypeDesc} 777 0 2"
+  lxc config device remove "${ctName}" test-dev4
+
+  lxc stop -f "${ctName}"
+  lxc config device remove "${ctName}" test-dev1
+  rm "${testDev}"
+
+  # Add a device that is missing, but not required, start instance and then add it.
+  lxc config device add "${ctName}" test-dev-dynamic "${deviceType}" required=false source="${testDev}" path=/tmp/testdev
+  lxc start "${ctName}"
+  ! ls "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev--dynamic.tmp-testdev
+  mknod "${testDev}" "${deviceTypeCode}" 0 0
+  sleep 0.5
+  lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  lxc exec "${ctName}" -- stat -c '%F %a %t %T' /tmp/testdev | grep "${deviceTypeDesc} 660 0 0"
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev--dynamic.tmp-testdev | grep "${deviceTypeDesc} 660 0 0"
+
+  # Remove host side device and check it is dynamically removed from instance.
+  rm "${testDev}"
+  sleep 0.5
+  ! lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  ! lxc exec "${ctName}" -- ls /tmp/testdev
+  ! ls "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev--dynamic.tmp-testdev
+
+  # Leave instance running, restart LXD, then add device back to check LXD start time inotify works.
+  shutdown_lxd "${LXD_DIR}"
+  respawn_lxd "${LXD_DIR}" true
+  mknod "${testDev}" "${deviceTypeCode}" 0 0
+  sleep 0.5
+  lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  lxc exec "${ctName}" -- stat -c '%F %a %t %T' /tmp/testdev | grep "${deviceTypeDesc} 660 0 0"
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev--dynamic.tmp-testdev | grep "${deviceTypeDesc} 660 0 0"
+
+  # Update device's source, check old instance device is removed and new watchers set up.
+  rm "${testDev}"
+  testDevSubDir="${testDev}"/subdev
+  ls -la "${TEST_DIR}"
+  lxc config device set "${ctName}" test-dev-dynamic source="${testDevSubDir}"
+  ! lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  ! lxc exec "${ctName}" -- ls /tmp/testdev
+  ! ls "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev--dynamic.tmp-testdev
+
+  mkdir "${testDev}"
+  mknod "${testDevSubDir}" "${deviceTypeCode}" 0 0
+  sleep 0.5
+  lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  lxc exec "${ctName}" -- stat -c '%F %a %t %T' /tmp/testdev | grep "${deviceTypeDesc} 660 0 0"
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev--dynamic.tmp-testdev | grep "${deviceTypeDesc} 660 0 0"
+
+  # Cleanup.
+  rm -rvf "${testDev}"
+  sleep 0.5
+  ! lxc exec "${ctName}" -- mount | grep "/tmp/testdev"
+  ! lxc exec "${ctName}" -- ls /tmp/testdev
+  ! ls "${LXD_DIR}"/devices/"${ctName}"/unix.test--dev--dynamic.tmp-testdev
+  lxc delete -f "${ctName}"
+
+  # Check multiple instances sharing same watcher.
+  lxc launch testimage "${ctName}1"
+  lxc config device add "${ctName}1" test-dev-dynamic "${deviceType}" required=false source="${testDev}" path=/tmp/testdev1
+  lxc launch testimage "${ctName}2"
+  lxc config device add "${ctName}2" test-dev-dynamic "${deviceType}" required=false source="${testDev}" path=/tmp/testdev2
+  mknod "${testDev}" "${deviceTypeCode}" 0 0
+  sleep 0.5
+  lxc exec "${ctName}1" -- mount | grep "/tmp/testdev1"
+  lxc exec "${ctName}1" -- stat -c '%F %a %t %T' /tmp/testdev1 | grep "${deviceTypeDesc} 660 0 0"
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"1/unix.test--dev--dynamic.tmp-testdev1 | grep "${deviceTypeDesc} 660 0 0"
+  lxc exec "${ctName}2" -- mount | grep "/tmp/testdev2"
+  lxc exec "${ctName}2" -- stat -c '%F %a %t %T' /tmp/testdev2 | grep "${deviceTypeDesc} 660 0 0"
+  stat -c '%F %a %t %T' "${LXD_DIR}"/devices/"${ctName}"2/unix.test--dev--dynamic.tmp-testdev2 | grep "${deviceTypeDesc} 660 0 0"
+
+  # Stop one instance, then remove the host device to check the watcher still works after first
+  # instance was stopped. This checks the removal logic when multiple containers share watch path.
+  lxc stop -f "${ctName}1"
+  rm "${testDev}"
+  sleep 0.5
+  ! lxc exec "${ctName}2" -- mount | grep "/tmp/testdev2"
+  ! lxc exec "${ctName}2" -- ls /tmp/testdev2
+  ! ls "${LXD_DIR}"/devices/"${ctName}"2/unix.test--dev--dynamic.tmp-testdev2
+  lxc delete -f "${ctName}1"
+  lxc delete -f "${ctName}2"
+}
+

From 61eff65f867ea52912587bf68140d89ce1f29de4 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 29 Aug 2019 16:39:29 +0100
Subject: [PATCH 25/36] container/lxc: Removes unused setupUnixDevice()

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container_lxc.go | 42 ------------------------------------------
 1 file changed, 42 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0dab11ff35..36be993264 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2378,48 +2378,6 @@ func (c *containerLXC) expandDevices(profiles []api.Profile) error {
 	return nil
 }
 
-// setupUnixDevice() creates the unix device and sets up the necessary low-level
-// liblxc configuration items.
-func (c *containerLXC) setupUnixDevice(prefix string, dev config.Device, major int, minor int, path string, createMustSucceed bool, defaultMode bool) error {
-	if c.isCurrentlyPrivileged() && !c.state.OS.RunningInUserNS && c.state.OS.CGroupDevicesController {
-		err := lxcSetConfigItem(c.c, "lxc.cgroup.devices.allow", fmt.Sprintf("c %d:%d rwm", major, minor))
-		if err != nil {
-			return err
-		}
-	}
-
-	temp := config.Device{}
-	err := shared.DeepCopy(&dev, &temp)
-	if err != nil {
-		return err
-	}
-
-	temp["major"] = fmt.Sprintf("%d", major)
-	temp["minor"] = fmt.Sprintf("%d", minor)
-	temp["path"] = path
-
-	idmapSet, err := c.CurrentIdmap()
-	if err != nil {
-		return err
-	}
-
-	d, err := device.UnixDeviceCreate(c.state, idmapSet, c.DevicesPath(), prefix, temp, defaultMode)
-	if err != nil {
-		logger.Debug("Failed to create device", log.Ctx{"err": err, "device": prefix})
-		if createMustSucceed {
-			return err
-		}
-
-		return nil
-	}
-
-	devPath := shared.EscapePathFstab(d.HostPath)
-	tgtPath := shared.EscapePathFstab(d.RelativePath)
-	val := fmt.Sprintf("%s %s none bind,create=file 0 0", devPath, tgtPath)
-
-	return lxcSetConfigItem(c.c, "lxc.mount.entry", val)
-}
-
 func shiftBtrfsRootfs(path string, diskIdmap *idmap.IdmapSet, shift bool) error {
 	var err error
 	roSubvols := []string{}

From 5381b0f20b7e7d955fa1eb97b1b2dc56218ab5bb Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:38:01 +0100
Subject: [PATCH 26/36] lx/device/config: Removes config package (moves into
 api package)

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/config/devices.go                  | 87 -------------------
 lxd/device/config/devices_sort.go             | 47 ----------
 lxd/device/config/devices_test.go             | 22 -----
 lxd/device/config/devices_utils.go            | 37 --------
 .../devices_validate.go => device_config.go}  |  7 +-
 5 files changed, 3 insertions(+), 197 deletions(-)
 delete mode 100644 lxd/device/config/devices.go
 delete mode 100644 lxd/device/config/devices_sort.go
 delete mode 100644 lxd/device/config/devices_test.go
 delete mode 100644 lxd/device/config/devices_utils.go
 rename lxd/device/{config/devices_validate.go => device_config.go} (85%)

diff --git a/lxd/device/config/devices.go b/lxd/device/config/devices.go
deleted file mode 100644
index 9bd7bad88c..0000000000
--- a/lxd/device/config/devices.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package config
-
-import (
-	"sort"
-
-	"github.com/lxc/lxd/shared"
-)
-
-// Device represents a LXD container device
-type Device map[string]string
-
-// Devices represents a set of LXD container devices
-type Devices map[string]map[string]string
-
-// Contains checks if a given device exists in the set and if it's
-// identical to that provided
-func (list Devices) Contains(k string, d Device) bool {
-	// If it didn't exist, it's different
-	if list[k] == nil {
-		return false
-	}
-
-	old := list[k]
-
-	return deviceEquals(old, d)
-}
-
-// Update returns the difference between two sets
-func (list Devices) Update(newlist Devices, updateFields func(Device, Device) []string) (map[string]Device, map[string]Device, map[string]Device, []string) {
-	rmlist := map[string]Device{}
-	addlist := map[string]Device{}
-	updatelist := map[string]Device{}
-
-	for key, d := range list {
-		if !newlist.Contains(key, d) {
-			rmlist[key] = d
-		}
-	}
-
-	for key, d := range newlist {
-		if !list.Contains(key, d) {
-			addlist[key] = d
-		}
-	}
-
-	updateDiff := []string{}
-	for key, d := range addlist {
-		srcOldDevice := rmlist[key]
-		var oldDevice Device
-		err := shared.DeepCopy(&srcOldDevice, &oldDevice)
-		if err != nil {
-			continue
-		}
-
-		srcNewDevice := newlist[key]
-		var newDevice Device
-		err = shared.DeepCopy(&srcNewDevice, &newDevice)
-		if err != nil {
-			continue
-		}
-
-		updateDiff = deviceEqualsDiffKeys(oldDevice, newDevice)
-		for _, k := range updateFields(oldDevice, newDevice) {
-			delete(oldDevice, k)
-			delete(newDevice, k)
-		}
-
-		if deviceEquals(oldDevice, newDevice) {
-			delete(rmlist, key)
-			delete(addlist, key)
-			updatelist[key] = d
-		}
-	}
-
-	return rmlist, addlist, updatelist, updateDiff
-}
-
-// DeviceNames returns the name of all devices in the set, sorted properly
-func (list Devices) DeviceNames() []string {
-	sortable := sortableDevices{}
-	for k, d := range list {
-		sortable = append(sortable, namedDevice{k, d})
-	}
-
-	sort.Sort(sortable)
-	return sortable.Names()
-}
diff --git a/lxd/device/config/devices_sort.go b/lxd/device/config/devices_sort.go
deleted file mode 100644
index d53314d257..0000000000
--- a/lxd/device/config/devices_sort.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package config
-
-type namedDevice struct {
-	name   string
-	device Device
-}
-
-type sortableDevices []namedDevice
-
-func (devices sortableDevices) Len() int {
-	return len(devices)
-}
-
-func (devices sortableDevices) Less(i, j int) bool {
-	a := devices[i]
-	b := devices[j]
-
-	// First sort by types
-	if a.device["type"] != b.device["type"] {
-		return a.device["type"] < b.device["type"]
-	}
-
-	// Special case disk paths
-	if a.device["type"] == "disk" && b.device["type"] == "disk" {
-		if a.device["path"] != b.device["path"] {
-			return a.device["path"] < b.device["path"]
-		}
-	}
-
-	// Fallback to sorting by names
-	return a.name < b.name
-}
-
-func (devices sortableDevices) Swap(i, j int) {
-	tmp := devices[i]
-	devices[i] = devices[j]
-	devices[j] = tmp
-}
-
-func (devices sortableDevices) Names() []string {
-	result := []string{}
-	for _, d := range devices {
-		result = append(result, d.name)
-	}
-
-	return result
-}
diff --git a/lxd/device/config/devices_test.go b/lxd/device/config/devices_test.go
deleted file mode 100644
index 7cee0dec90..0000000000
--- a/lxd/device/config/devices_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package config
-
-import (
-	"reflect"
-	"testing"
-)
-
-func TestSortableDevices(t *testing.T) {
-	devices := Devices{
-		"1": Device{"type": "nic"},
-		"3": Device{"type": "disk", "path": "/foo/bar"},
-		"4": Device{"type": "disk", "path": "/foo"},
-		"2": Device{"type": "nic"},
-	}
-
-	expected := []string{"4", "3", "1", "2"}
-
-	result := devices.DeviceNames()
-	if !reflect.DeepEqual(result, expected) {
-		t.Error("devices sorted incorrectly")
-	}
-}
diff --git a/lxd/device/config/devices_utils.go b/lxd/device/config/devices_utils.go
deleted file mode 100644
index 4b1c26b420..0000000000
--- a/lxd/device/config/devices_utils.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package config
-
-func deviceEquals(old Device, d Device) bool {
-	// Check for any difference and addition/removal of properties
-	for k := range d {
-		if d[k] != old[k] {
-			return false
-		}
-	}
-
-	for k := range old {
-		if d[k] != old[k] {
-			return false
-		}
-	}
-
-	return true
-}
-
-func deviceEqualsDiffKeys(old Device, d Device) []string {
-	keys := []string{}
-
-	// Check for any difference and addition/removal of properties
-	for k := range d {
-		if d[k] != old[k] {
-			keys = append(keys, k)
-		}
-	}
-
-	for k := range old {
-		if d[k] != old[k] {
-			keys = append(keys, k)
-		}
-	}
-
-	return keys
-}
diff --git a/lxd/device/config/devices_validate.go b/lxd/device/device_config.go
similarity index 85%
rename from lxd/device/config/devices_validate.go
rename to lxd/device/device_config.go
index 948fdb8766..eb04cfa379 100644
--- a/lxd/device/config/devices_validate.go
+++ b/lxd/device/device_config.go
@@ -1,13 +1,12 @@
-package config
+package device
 
 import (
 	"fmt"
-
 	"github.com/lxc/lxd/shared"
 )
 
-// ValidateDevice accepts a map of field/validation functions to run against supplied config.
-func ValidateDevice(rules map[string]func(value string) error, config map[string]string) error {
+// ConfigValidate accepts a map of field/validation functions to run against supplied config.
+func configValidate(rules map[string]func(value string) error, config map[string]string) error {
 	checkedFields := map[string]struct{}{}
 
 	for k, validator := range rules {

From a24b9d8cb40162f0e92ca00b1bb450bf6bf1c157 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:39:01 +0100
Subject: [PATCH 27/36] shared/api: Adds Device and Devices type along with
 config helper functions

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 shared/api/devices.go       | 85 +++++++++++++++++++++++++++++++++++++
 shared/api/devices_sort.go  | 47 ++++++++++++++++++++
 shared/api/devices_test.go  | 22 ++++++++++
 shared/api/devices_utils.go | 37 ++++++++++++++++
 4 files changed, 191 insertions(+)
 create mode 100644 shared/api/devices.go
 create mode 100644 shared/api/devices_sort.go
 create mode 100644 shared/api/devices_test.go
 create mode 100644 shared/api/devices_utils.go

diff --git a/shared/api/devices.go b/shared/api/devices.go
new file mode 100644
index 0000000000..2e059a5c45
--- /dev/null
+++ b/shared/api/devices.go
@@ -0,0 +1,85 @@
+package api
+
+import (
+	"sort"
+)
+
+// Device represents a LXD container device
+type Device map[string]string
+
+// Devices represents a set of LXD container devices
+type Devices map[string]Device
+
+// Contains checks if a given device exists in the set and if it's
+// identical to that provided
+func (list Devices) Contains(k string, d Device) bool {
+	// If it didn't exist, it's different
+	if list[k] == nil {
+		return false
+	}
+
+	old := list[k]
+
+	return deviceEquals(old, d)
+}
+
+// Update returns the difference between two sets
+func (list Devices) Update(newlist Devices, updateFields func(Device, Device) []string) (map[string]Device, map[string]Device, map[string]Device, []string) {
+	rmlist := map[string]Device{}
+	addlist := map[string]Device{}
+	updatelist := map[string]Device{}
+
+	for key, d := range list {
+		if !newlist.Contains(key, d) {
+			rmlist[key] = d
+		}
+	}
+
+	for key, d := range newlist {
+		if !list.Contains(key, d) {
+			addlist[key] = d
+		}
+	}
+
+	updateDiff := []string{}
+	for key, d := range addlist {
+		srcOldDevice := rmlist[key]
+		var oldDevice Device
+
+		for k, v := range srcOldDevice {
+			oldDevice[k] = v
+		}
+
+		srcNewDevice := newlist[key]
+		var newDevice Device
+
+		for k, v := range srcNewDevice {
+			newDevice[k] = v
+		}
+
+		updateDiff = deviceEqualsDiffKeys(oldDevice, newDevice)
+		for _, k := range updateFields(oldDevice, newDevice) {
+			delete(oldDevice, k)
+			delete(newDevice, k)
+		}
+
+		if deviceEquals(oldDevice, newDevice) {
+			delete(rmlist, key)
+			delete(addlist, key)
+			updatelist[key] = d
+		}
+	}
+
+	return rmlist, addlist, updatelist, updateDiff
+}
+
+// DeviceNames returns the name of all devices in the set, sorted properly
+func (list Devices) DeviceNames() []string {
+	sortable := sortableDevices{}
+	for k, d := range list {
+		sortable = append(sortable, namedDevice{k, d})
+	}
+
+	sort.Sort(sortable)
+	return sortable.Names()
+}
diff --git a/shared/api/devices_sort.go b/shared/api/devices_sort.go
new file mode 100644
index 0000000000..b9c682a5eb
--- /dev/null
+++ b/shared/api/devices_sort.go
@@ -0,0 +1,47 @@
+package api
+
+type namedDevice struct {
+	name   string
+	device Device
+}
+
+type sortableDevices []namedDevice
+
+func (devices sortableDevices) Len() int {
+	return len(devices)
+}
+
+func (devices sortableDevices) Less(i, j int) bool {
+	a := devices[i]
+	b := devices[j]
+
+	// First sort by types
+	if a.device["type"] != b.device["type"] {
+		return a.device["type"] < b.device["type"]
+	}
+
+	// Special case disk paths
+	if a.device["type"] == "disk" && b.device["type"] == "disk" {
+		if a.device["path"] != b.device["path"] {
+			return a.device["path"] < b.device["path"]
+		}
+	}
+
+	// Fallback to sorting by names
+	return a.name < b.name
+}
+
+func (devices sortableDevices) Swap(i, j int) {
+	tmp := devices[i]
+	devices[i] = devices[j]
+	devices[j] = tmp
+}
+
+func (devices sortableDevices) Names() []string {
+	result := []string{}
+	for _, d := range devices {
+		result = append(result, d.name)
+	}
+
+	return result
+}
diff --git a/shared/api/devices_test.go b/shared/api/devices_test.go
new file mode 100644
index 0000000000..f072169b00
--- /dev/null
+++ b/shared/api/devices_test.go
@@ -0,0 +1,22 @@
+package api
+
+import (
+	"reflect"
+	"testing"
+)
+
+func TestSortableDevices(t *testing.T) {
+	devices := Devices{
+		"1": Device{"type": "nic"},
+		"3": Device{"type": "disk", "path": "/foo/bar"},
+		"4": Device{"type": "disk", "path": "/foo"},
+		"2": Device{"type": "nic"},
+	}
+
+	expected := []string{"4", "3", "1", "2"}
+
+	result := devices.DeviceNames()
+	if !reflect.DeepEqual(result, expected) {
+		t.Error("devices sorted incorrectly")
+	}
+}
diff --git a/shared/api/devices_utils.go b/shared/api/devices_utils.go
new file mode 100644
index 0000000000..69e1f7d100
--- /dev/null
+++ b/shared/api/devices_utils.go
@@ -0,0 +1,37 @@
+package api
+
+func deviceEquals(old Device, d Device) bool {
+	// Check for any difference and addition/removal of properties
+	for k := range d {
+		if d[k] != old[k] {
+			return false
+		}
+	}
+
+	for k := range old {
+		if d[k] != old[k] {
+			return false
+		}
+	}
+
+	return true
+}
+
+func deviceEqualsDiffKeys(old Device, d Device) []string {
+	keys := []string{}
+
+	// Check for any difference and addition/removal of properties
+	for k := range d {
+		if d[k] != old[k] {
+			keys = append(keys, k)
+		}
+	}
+
+	for k := range old {
+		if d[k] != old[k] {
+			keys = append(keys, k)
+		}
+	}
+
+	return keys
+}

From 319d9ff3f5ffab401b8605b24a5ffa2e53a400c9 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:42:18 +0100
Subject: [PATCH 28/36] shared: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 shared/api/container.go          | 22 +++++++++++-----------
 shared/api/container_snapshot.go | 24 ++++++++++++------------
 shared/api/profile.go            |  6 +++---
 shared/container.go              |  3 ++-
 4 files changed, 28 insertions(+), 27 deletions(-)

diff --git a/shared/api/container.go b/shared/api/container.go
index ed41a6e61e..112f5b7f00 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -43,11 +43,11 @@ type ContainerPostTarget struct {
 
 // ContainerPut represents the modifiable fields of a LXD container
 type ContainerPut struct {
-	Architecture string                       `json:"architecture" yaml:"architecture"`
-	Config       map[string]string            `json:"config" yaml:"config"`
-	Devices      map[string]map[string]string `json:"devices" yaml:"devices"`
-	Ephemeral    bool                         `json:"ephemeral" yaml:"ephemeral"`
-	Profiles     []string                     `json:"profiles" yaml:"profiles"`
+	Architecture string            `json:"architecture" yaml:"architecture"`
+	Config       map[string]string `json:"config" yaml:"config"`
+	Devices      Devices           `json:"devices" yaml:"devices"`
+	Ephemeral    bool              `json:"ephemeral" yaml:"ephemeral"`
+	Profiles     []string          `json:"profiles" yaml:"profiles"`
 
 	// For snapshot restore
 	Restore  string `json:"restore,omitempty" yaml:"restore,omitempty"`
@@ -61,12 +61,12 @@ type ContainerPut struct {
 type Container struct {
 	ContainerPut `yaml:",inline"`
 
-	CreatedAt       time.Time                    `json:"created_at" yaml:"created_at"`
-	ExpandedConfig  map[string]string            `json:"expanded_config" yaml:"expanded_config"`
-	ExpandedDevices map[string]map[string]string `json:"expanded_devices" yaml:"expanded_devices"`
-	Name            string                       `json:"name" yaml:"name"`
-	Status          string                       `json:"status" yaml:"status"`
-	StatusCode      StatusCode                   `json:"status_code" yaml:"status_code"`
+	CreatedAt       time.Time         `json:"created_at" yaml:"created_at"`
+	ExpandedConfig  map[string]string `json:"expanded_config" yaml:"expanded_config"`
+	ExpandedDevices Devices           `json:"expanded_devices" yaml:"expanded_devices"`
+	Name            string            `json:"name" yaml:"name"`
+	Status          string            `json:"status" yaml:"status"`
+	StatusCode      StatusCode        `json:"status_code" yaml:"status_code"`
 
 	// API extension: container_last_used_at
 	LastUsedAt time.Time `json:"last_used_at" yaml:"last_used_at"`
diff --git a/shared/api/container_snapshot.go b/shared/api/container_snapshot.go
index e68a0fb8cc..ae92e0b365 100644
--- a/shared/api/container_snapshot.go
+++ b/shared/api/container_snapshot.go
@@ -26,24 +26,24 @@ type ContainerSnapshotPost struct {
 // ContainerSnapshotPut represents the modifiable fields of a LXD container snapshot
 // API extension: snapshot_expiry
 type ContainerSnapshotPut struct {
-	Architecture string                       `json:"architecture" yaml:"architecture"`
-	Config       map[string]string            `json:"config" yaml:"config"`
-	Devices      map[string]map[string]string `json:"devices" yaml:"devices"`
-	Ephemeral    bool                         `json:"ephemeral" yaml:"ephemeral"`
-	Profiles     []string                     `json:"profiles" yaml:"profiles"`
-	ExpiresAt    time.Time                    `json:"expires_at" yaml:"expires_at"`
+	Architecture string            `json:"architecture" yaml:"architecture"`
+	Config       map[string]string `json:"config" yaml:"config"`
+	Devices      Devices           `json:"devices" yaml:"devices"`
+	Ephemeral    bool              `json:"ephemeral" yaml:"ephemeral"`
+	Profiles     []string          `json:"profiles" yaml:"profiles"`
+	ExpiresAt    time.Time         `json:"expires_at" yaml:"expires_at"`
 }
 
 // ContainerSnapshot represents a LXD conainer snapshot
 type ContainerSnapshot struct {
 	ContainerSnapshotPut `yaml:",inline"`
 
-	CreatedAt       time.Time                    `json:"created_at" yaml:"created_at"`
-	ExpandedConfig  map[string]string            `json:"expanded_config" yaml:"expanded_config"`
-	ExpandedDevices map[string]map[string]string `json:"expanded_devices" yaml:"expanded_devices"`
-	LastUsedAt      time.Time                    `json:"last_used_at" yaml:"last_used_at"`
-	Name            string                       `json:"name" yaml:"name"`
-	Stateful        bool                         `json:"stateful" yaml:"stateful"`
+	CreatedAt       time.Time         `json:"created_at" yaml:"created_at"`
+	ExpandedConfig  map[string]string `json:"expanded_config" yaml:"expanded_config"`
+	ExpandedDevices Devices           `json:"expanded_devices" yaml:"expanded_devices"`
+	LastUsedAt      time.Time         `json:"last_used_at" yaml:"last_used_at"`
+	Name            string            `json:"name" yaml:"name"`
+	Stateful        bool              `json:"stateful" yaml:"stateful"`
 }
 
 // Writable converts a full ContainerSnapshot struct into a ContainerSnapshotPut struct
diff --git a/shared/api/profile.go b/shared/api/profile.go
index 3cc7a64280..564bcc1d3f 100644
--- a/shared/api/profile.go
+++ b/shared/api/profile.go
@@ -14,9 +14,9 @@ type ProfilePost struct {
 
 // ProfilePut represents the modifiable fields of a LXD profile
 type ProfilePut struct {
-	Config      map[string]string            `json:"config" yaml:"config"`
-	Description string                       `json:"description" yaml:"description"`
-	Devices     map[string]map[string]string `json:"devices" yaml:"devices"`
+	Config      map[string]string `json:"config" yaml:"config"`
+	Description string            `json:"description" yaml:"description"`
+	Devices     Devices           `json:"devices" yaml:"devices"`
 }
 
 // Profile represents a LXD profile
diff --git a/shared/container.go b/shared/container.go
index bc5e63b837..b53e0f4b3a 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -10,6 +10,7 @@ import (
 	"github.com/pkg/errors"
 	"gopkg.in/robfig/cron.v2"
 
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/units"
 )
 
@@ -145,7 +146,7 @@ func IsRootDiskDevice(device map[string]string) bool {
 }
 
 // GetRootDiskDevice returns the container device that is configured as root disk
-func GetRootDiskDevice(devices map[string]map[string]string) (string, map[string]string, error) {
+func GetRootDiskDevice(devices api.Devices) (string, map[string]string, error) {
 	var devName string
 	var dev map[string]string
 

From 8914cfc8171e1df6f9484839ecc2701655597415 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:43:05 +0100
Subject: [PATCH 29/36] lxd: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/api_internal.go          |  4 ++--
 lxd/api_project.go           |  3 +--
 lxd/container.go             | 13 ++++++-------
 lxd/container_test.go        | 11 +++++------
 lxd/containers_post.go       |  7 +++----
 lxd/main_init_auto.go        |  4 ++--
 lxd/main_init_interactive.go |  2 +-
 lxd/patches.go               |  3 ++-
 lxd/seccomp.go               |  6 +++---
 lxd/storage_migration.go     |  3 +--
 10 files changed, 26 insertions(+), 30 deletions(-)

diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index ddfffe5ae8..4ba26e91c4 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -879,7 +879,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	root, _, _ := shared.GetRootDiskDevice(backup.Container.Devices)
 	if root == "" {
 		if backup.Container.Devices == nil {
-			backup.Container.Devices = map[string]map[string]string{}
+			backup.Container.Devices = api.Devices{}
 		}
 
 		rootDevName := "root"
@@ -989,7 +989,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		root, _, _ := shared.GetRootDiskDevice(snap.Devices)
 		if root == "" {
 			if snap.Devices == nil {
-				snap.Devices = map[string]map[string]string{}
+				snap.Devices = api.Devices{}
 			}
 
 			rootDevName := "root"
diff --git a/lxd/api_project.go b/lxd/api_project.go
index bcac34b722..e19c44e0af 100644
--- a/lxd/api_project.go
+++ b/lxd/api_project.go
@@ -12,7 +12,6 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -168,7 +167,7 @@ func projectCreateDefaultProfile(tx *db.ClusterTx, project string) error {
 	profile.Name = "default"
 	profile.Description = fmt.Sprintf("Default LXD profile for project %s", project)
 	profile.Config = map[string]string{}
-	profile.Devices = config.Devices{}
+	profile.Devices = api.Devices{}
 
 	_, err := tx.ProfileCreate(profile)
 	if err != nil {
diff --git a/lxd/container.go b/lxd/container.go
index 25e02f88a7..2d9ff9f098 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -19,7 +19,6 @@ import (
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/device"
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/lxd/task"
@@ -214,7 +213,7 @@ func containerValidConfig(sysOS *sys.OS, config map[string]string, profile bool,
 	return nil
 }
 
-func containerValidDevices(state *state.State, cluster *db.Cluster, devices config.Devices, profile bool, expanded bool) error {
+func containerValidDevices(state *state.State, cluster *db.Cluster, devices api.Devices, profile bool, expanded bool) error {
 	// Empty device list
 	if devices == nil {
 		return nil
@@ -224,7 +223,7 @@ func containerValidDevices(state *state.State, cluster *db.Cluster, devices conf
 	// Check each device individually
 	for name, m := range devices {
 		// Validate config using device interface.
-		_, err := device.New(&containerLXC{}, state, name, config.Device(m), nil, nil)
+		_, err := device.New(&containerLXC{}, state, name, api.Device(m), nil, nil)
 		if err != device.ErrUnsupportedDevType {
 			if err != nil {
 				return err
@@ -422,9 +421,9 @@ type container interface {
 	CreationDate() time.Time
 	LastUsedDate() time.Time
 	ExpandedConfig() map[string]string
-	ExpandedDevices() config.Devices
+	ExpandedDevices() api.Devices
 	LocalConfig() map[string]string
-	LocalDevices() config.Devices
+	LocalDevices() api.Devices
 	Profiles() []string
 	InitPID() int
 	State() string
@@ -453,7 +452,7 @@ type container interface {
 	Storage() storage
 	TemplateApply(trigger string) error
 	DaemonState() *state.State
-	InsertSeccompUnixDevice(prefix string, m config.Device, pid int) error
+	InsertSeccompUnixDevice(prefix string, m api.Device, pid int) error
 
 	CurrentIdmap() (*idmap.IdmapSet, error)
 	DiskIdmap() (*idmap.IdmapSet, error)
@@ -915,7 +914,7 @@ func containerCreateInternal(s *state.State, args db.ContainerArgs) (container,
 	}
 
 	if args.Devices == nil {
-		args.Devices = config.Devices{}
+		args.Devices = api.Devices{}
 	}
 
 	if args.Architecture == 0 {
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 14ec2a715c..f97e2b0c29 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -7,7 +7,6 @@ import (
 	"github.com/stretchr/testify/suite"
 
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/device/config"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -48,7 +47,7 @@ func (suite *containerTestSuite) TestContainer_ProfilesMulti() {
 			Name:        "unprivileged",
 			Description: "unprivileged",
 			Config:      map[string]string{"security.privileged": "true"},
-			Devices:     config.Devices{},
+			Devices:     api.Devices{},
 			Project:     "default",
 		}
 		_, err := tx.ProfileCreate(profile)
@@ -89,8 +88,8 @@ func (suite *containerTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Config:    map[string]string{"security.privileged": "true"},
-		Devices: config.Devices{
-			"eth0": config.Device{
+		Devices: api.Devices{
+			"eth0": api.Device{
 				"type":    "nic",
 				"nictype": "bridged",
 				"parent":  "unknownbr0"}},
@@ -119,8 +118,8 @@ func (suite *containerTestSuite) TestContainer_LoadFromDB() {
 		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Config:    map[string]string{"security.privileged": "true"},
-		Devices: config.Devices{
-			"eth0": config.Device{
+		Devices: api.Devices{
+			"eth0": api.Device{
 				"type":    "nic",
 				"nictype": "bridged",
 				"parent":  "unknownbr0"}},
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index d6d6783959..91bc8e30b1 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -19,7 +19,6 @@ import (
 
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -248,7 +247,7 @@ func createFromMigration(d *Daemon, project string, req *api.ContainersPost) Res
 		rootDev["path"] = "/"
 		rootDev["pool"] = storagePool
 		if args.Devices == nil {
-			args.Devices = map[string]map[string]string{}
+			args.Devices = api.Devices{}
 		}
 
 		// Make sure that we do not overwrite a device the user
@@ -513,7 +512,7 @@ func createFromCopy(d *Daemon, project string, req *api.ContainersPost) Response
 	sourceDevices := source.LocalDevices()
 
 	if req.Devices == nil {
-		req.Devices = make(map[string]map[string]string)
+		req.Devices = make(api.Devices)
 	}
 
 	for key, value := range sourceDevices {
@@ -760,7 +759,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	}
 
 	if req.Devices == nil {
-		req.Devices = config.Devices{}
+		req.Devices = api.Devices{}
 	}
 
 	if req.Config == nil {
diff --git a/lxd/main_init_auto.go b/lxd/main_init_auto.go
index a819bca3e2..6cf06bd6b1 100644
--- a/lxd/main_init_auto.go
+++ b/lxd/main_init_auto.go
@@ -100,7 +100,7 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServ
 		config.Profiles = []api.ProfilesPost{{
 			Name: "default",
 			ProfilePut: api.ProfilePut{
-				Devices: map[string]map[string]string{
+				Devices: api.Devices{
 					"root": {
 						"type": "disk",
 						"path": "/",
@@ -160,7 +160,7 @@ func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServ
 			config.Profiles = []api.ProfilesPost{{
 				Name: "default",
 				ProfilePut: api.ProfilePut{
-					Devices: map[string]map[string]string{
+					Devices: api.Devices{
 						"eth0": {
 							"type":    "nic",
 							"nictype": "bridged",
diff --git a/lxd/main_init_interactive.go b/lxd/main_init_interactive.go
index af4e14ee6b..412515eea0 100644
--- a/lxd/main_init_interactive.go
+++ b/lxd/main_init_interactive.go
@@ -35,7 +35,7 @@ func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.Contai
 			Name: "default",
 			ProfilePut: api.ProfilePut{
 				Config:  map[string]string{},
-				Devices: map[string]map[string]string{},
+				Devices: api.Devices{},
 			},
 		},
 	}
diff --git a/lxd/patches.go b/lxd/patches.go
index 0ee421fa12..ae874cb73d 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -18,6 +18,7 @@ import (
 	"github.com/lxc/lxd/lxd/db/query"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	log "github.com/lxc/lxd/shared/log15"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -1821,7 +1822,7 @@ func updatePoolPropertyForAllObjects(d *Daemon, poolName string, allcontainers [
 				rootDev["path"] = "/"
 				rootDev["pool"] = poolName
 				if p.Devices == nil {
-					p.Devices = map[string]map[string]string{}
+					p.Devices = api.Devices{}
 				}
 
 				// Make sure that we do not overwrite a device the user
diff --git a/lxd/seccomp.go b/lxd/seccomp.go
index 05221cab9e..6c7a6f8fbf 100644
--- a/lxd/seccomp.go
+++ b/lxd/seccomp.go
@@ -18,9 +18,9 @@ import (
 
 	"golang.org/x/sys/unix"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	log "github.com/lxc/lxd/shared/log15"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/netutils"
@@ -801,7 +801,7 @@ func taskIds(pid int) (error, int64, int64, int64, int64) {
 	return nil, uid, gid, fsuid, fsgid
 }
 
-func CallForkmknod(c container, dev config.Device, requestPID int) int {
+func CallForkmknod(c container, dev api.Device, requestPID int) int {
 	rootLink := fmt.Sprintf("/proc/%d/root", requestPID)
 	rootPath, err := os.Readlink(rootLink)
 	if err != nil {
@@ -859,7 +859,7 @@ type MknodArgs struct {
 }
 
 func (s *SeccompServer) doDeviceSyscall(c container, args *MknodArgs, siov *SeccompIovec) int {
-	dev := config.Device{}
+	dev := api.Device{}
 	dev["type"] = "unix-char"
 	dev["mode"] = fmt.Sprintf("%#o", args.cMode)
 	dev["major"] = fmt.Sprintf("%d", unix.Major(uint64(args.cDev)))
diff --git a/lxd/storage_migration.go b/lxd/storage_migration.go
index 0802a44326..d1953649f7 100644
--- a/lxd/storage_migration.go
+++ b/lxd/storage_migration.go
@@ -7,7 +7,6 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/db"
-	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/migration"
 	"github.com/lxc/lxd/lxd/project"
 	driver "github.com/lxc/lxd/lxd/storage"
@@ -184,7 +183,7 @@ func snapshotProtobufToContainerArgs(project string, containerName string, snap
 		config[ent.GetKey()] = ent.GetValue()
 	}
 
-	devices := deviceConfig.Devices{}
+	devices := api.Devices{}
 	for _, ent := range snap.LocalDevices {
 		props := map[string]string{}
 		for _, prop := range ent.Config {

From 8d1afa5cdd584c47ae5ef4aade7937192bee06a2 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:43:30 +0100
Subject: [PATCH 30/36] lxc-to-lxd: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxc-to-lxd/main_migrate.go      |  7 +++----
 lxc-to-lxd/main_migrate_test.go | 23 +++++++++++------------
 2 files changed, 14 insertions(+), 16 deletions(-)

diff --git a/lxc-to-lxd/main_migrate.go b/lxc-to-lxd/main_migrate.go
index d5c478b891..a67d6d8560 100644
--- a/lxc-to-lxd/main_migrate.go
+++ b/lxc-to-lxd/main_migrate.go
@@ -14,7 +14,6 @@ import (
 	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/lxc/utils"
-	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
@@ -232,7 +231,7 @@ func convertContainer(d lxd.ContainerServer, container *lxc.Container, storage s
 		newConfig["security.privileged"] = "false"
 	}
 
-	newDevices := make(deviceConfig.Devices, 0)
+	newDevices := make(api.Devices, 0)
 
 	// Convert network configuration
 	err = convertNetworkConfig(container, newDevices)
@@ -449,7 +448,7 @@ func convertContainer(d lxd.ContainerServer, container *lxc.Container, storage s
 	return nil
 }
 
-func convertNetworkConfig(container *lxc.Container, devices deviceConfig.Devices) error {
+func convertNetworkConfig(container *lxc.Container, devices api.Devices) error {
 	networkDevice := func(network map[string]string) map[string]string {
 		if network == nil {
 			return nil
@@ -525,7 +524,7 @@ func convertNetworkConfig(container *lxc.Container, devices deviceConfig.Devices
 	return nil
 }
 
-func convertStorageConfig(conf []string, devices deviceConfig.Devices) error {
+func convertStorageConfig(conf []string, devices api.Devices) error {
 	fmt.Println("Processing storage configuration")
 
 	i := 0
diff --git a/lxc-to-lxd/main_migrate_test.go b/lxc-to-lxd/main_migrate_test.go
index 02ab931605..0e461bfe62 100644
--- a/lxc-to-lxd/main_migrate_test.go
+++ b/lxc-to-lxd/main_migrate_test.go
@@ -7,7 +7,6 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/stretchr/testify/require"
 	lxc "gopkg.in/lxc/go-lxc.v2"
 )
@@ -116,14 +115,14 @@ func TestConvertNetworkConfig(t *testing.T) {
 	tests := []struct {
 		name            string
 		config          []string
-		expectedDevices config.Devices
+		expectedDevices api.Devices
 		expectedError   string
 		shouldFail      bool
 	}{
 		{
 			"loopback only",
 			[]string{},
-			config.Devices{
+			api.Devices{
 				"eth0": map[string]string{
 					"type": "none",
 				},
@@ -144,7 +143,7 @@ func TestConvertNetworkConfig(t *testing.T) {
 				"lxc.net.1.hwaddr = 00:16:3e:a2:7d:54",
 				"lxc.net.1.name = eth2",
 			},
-			config.Devices{
+			api.Devices{
 				"net1": map[string]string{
 					"type":    "nic",
 					"nictype": "bridged",
@@ -179,7 +178,7 @@ func TestConvertNetworkConfig(t *testing.T) {
 				"lxc.net.1.link = lxcbr0",
 				"lxc.net.1.hwaddr = 00:16:3e:a2:7d:54",
 			},
-			config.Devices{
+			api.Devices{
 				"net1": map[string]string{
 					"type":    "nic",
 					"nictype": "bridged",
@@ -226,7 +225,7 @@ func TestConvertNetworkConfig(t *testing.T) {
 			require.NoError(t, err)
 		}
 
-		devices := make(config.Devices, 0)
+		devices := make(api.Devices, 0)
 		err = convertNetworkConfig(c, devices)
 		if tt.shouldFail {
 			require.EqualError(t, err, tt.expectedError)
@@ -244,7 +243,7 @@ func TestConvertStorageConfig(t *testing.T) {
 	tests := []struct {
 		name            string
 		config          []string
-		expectedDevices config.Devices
+		expectedDevices api.Devices
 		expectedError   string
 		shouldFail      bool
 	}{
@@ -253,7 +252,7 @@ func TestConvertStorageConfig(t *testing.T) {
 			[]string{
 				"lxc.mount.entry = /foo lib none ro,bind 0 0",
 			},
-			config.Devices{},
+			api.Devices{},
 			"Invalid path: /foo",
 			true,
 		},
@@ -262,7 +261,7 @@ func TestConvertStorageConfig(t *testing.T) {
 			[]string{
 				"lxc.mount.entry = proc /proc proc defaults 0 0",
 			},
-			config.Devices{},
+			api.Devices{},
 			"",
 			false,
 		},
@@ -271,7 +270,7 @@ func TestConvertStorageConfig(t *testing.T) {
 			[]string{
 				"lxc.mount.entry = shm /dev/shm tmpfs defaults 0 0",
 			},
-			config.Devices{},
+			api.Devices{},
 			"",
 			false,
 		},
@@ -285,7 +284,7 @@ func TestConvertStorageConfig(t *testing.T) {
 				"lxc.mount.entry = /sys/kernel/security /sys/kernel/security none ro,bind,optional 1 0",
 				"lxc.mount.entry = /mnt /tmp/mnt none ro,bind 0 0",
 			},
-			config.Devices{
+			api.Devices{
 				"mount0": map[string]string{
 					"type":     "disk",
 					"readonly": "true",
@@ -325,7 +324,7 @@ func TestConvertStorageConfig(t *testing.T) {
 
 	for i, tt := range tests {
 		log.Printf("Running test #%d: %s", i, tt.name)
-		devices := make(config.Devices, 0)
+		devices := make(api.Devices, 0)
 		err := convertStorageConfig(tt.config, devices)
 		if tt.shouldFail {
 			require.EqualError(t, err, tt.expectedError)

From b76ee3879e2c8aa4c4c77ee7d10dd7f4af36ffde Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:44:00 +0100
Subject: [PATCH 31/36] lxc/config/device: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxc/config_device.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxc/config_device.go b/lxc/config_device.go
index bbff5aa95f..2472e9b896 100644
--- a/lxc/config_device.go
+++ b/lxc/config_device.go
@@ -7,6 +7,7 @@ import (
 	"github.com/spf13/cobra"
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/shared/api"
 	cli "github.com/lxc/lxd/shared/cmd"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -624,7 +625,7 @@ func (c *cmdConfigDeviceShow) Run(cmd *cobra.Command, args []string) error {
 	}
 
 	// Show the devices
-	var devices map[string]map[string]string
+	var devices api.Devices
 	if c.profile != nil {
 		profile, _, err := resource.server.GetProfile(resource.name)
 		if err != nil {

From eec7e00823eb3ee443857c2af056f681b9731089 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:44:22 +0100
Subject: [PATCH 32/36] lxc/init: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxc/init.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/init.go b/lxc/init.go
index 9472e68e95..bfaf5cea2b 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -129,7 +129,7 @@ func (c *cmdInit) create(conf *config.Config, args []string) (lxd.ContainerServe
 		}
 	}
 
-	devicesMap := map[string]map[string]string{}
+	devicesMap := api.Devices{}
 	if c.flagNetwork != "" {
 		network, _, err := d.GetNetwork(c.flagNetwork)
 		if err != nil {

From 2f03da94f56c86dafb85763ae02fe4e27c7e0582 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:44:58 +0100
Subject: [PATCH 33/36] lxd-p2c: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd-p2c/main_migrate.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd-p2c/main_migrate.go b/lxd-p2c/main_migrate.go
index 8c8128b88d..dfdf000449 100644
--- a/lxd-p2c/main_migrate.go
+++ b/lxd-p2c/main_migrate.go
@@ -167,7 +167,7 @@ func (c *cmdMigrate) Run(cmd *cobra.Command, args []string) error {
 	}
 
 	// Devices
-	apiArgs.Devices = map[string]map[string]string{}
+	apiArgs.Devices = api.Devices{}
 
 	network := c.flagNetwork
 	if network != "" {

From 6824c534ba1968c3bd3ec12064550f89c4d78e67 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:46:18 +0100
Subject: [PATCH 34/36] lxd/device: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/device/device.go                  | 24 ++++++++++++------------
 lxd/device/device_instance_id.go      |  4 ++--
 lxd/device/device_utils_infiniband.go |  7 +++----
 lxd/device/device_utils_instance.go   |  4 ++--
 lxd/device/device_utils_network.go    | 12 ++++++------
 lxd/device/device_utils_unix.go       | 16 ++++++++--------
 lxd/device/gpu.go                     |  3 +--
 lxd/device/infiniband.go              |  4 ++--
 lxd/device/infiniband_physical.go     |  3 +--
 lxd/device/infiniband_sriov.go        |  3 +--
 lxd/device/nic.go                     |  4 ++--
 lxd/device/nic_bridged.go             | 14 +++++++-------
 lxd/device/nic_ipvlan.go              |  3 +--
 lxd/device/nic_macvlan.go             |  3 +--
 lxd/device/nic_p2p.go                 |  6 +++---
 lxd/device/nic_physical.go            |  3 +--
 lxd/device/nic_sriov.go               |  3 +--
 lxd/device/proxy.go                   |  3 +--
 lxd/device/unixCommon.go              |  6 +++---
 lxd/device/usb.go                     |  6 +++---
 20 files changed, 61 insertions(+), 70 deletions(-)

diff --git a/lxd/device/device.go b/lxd/device/device.go
index a2eba93088..d7bdf0a84e 100644
--- a/lxd/device/device.go
+++ b/lxd/device/device.go
@@ -3,19 +3,19 @@ package device
 import (
 	"fmt"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // devTypes defines supported top-level device type creation functions.
-var devTypes = map[string]func(config.Device) device{
+var devTypes = map[string]func(api.Device) device{
 	"nic":        nicLoadByType,
 	"infiniband": infinibandLoadByType,
-	"proxy":      func(c config.Device) device { return &proxy{} },
-	"gpu":        func(c config.Device) device { return &gpu{} },
-	"usb":        func(c config.Device) device { return &usb{} },
-	"unix-char":  func(c config.Device) device { return &unixCommon{} },
-	"unix-block": func(c config.Device) device { return &unixCommon{} },
+	"proxy":      func(c api.Device) device { return &proxy{} },
+	"gpu":        func(c api.Device) device { return &gpu{} },
+	"usb":        func(c api.Device) device { return &usb{} },
+	"unix-char":  func(c api.Device) device { return &unixCommon{} },
+	"unix-block": func(c api.Device) device { return &unixCommon{} },
 }
 
 // VolatileSetter is a function that accepts one or more key/value strings to save into the LXD
@@ -51,7 +51,7 @@ type Device interface {
 	// current config and previous config supplied as an argument. This called if the only
 	// config fields that have changed are supplied in the list returned from CanHotPlug().
 	// The function also accepts a boolean indicating whether the instance is running or not.
-	Update(oldConfig config.Device, running bool) error
+	Update(oldConfig api.Device, running bool) error
 
 	// Stop performs any host-side cleanup required when a device is removed from an instance,
 	// either due to unplugging it from a running instance or instance is being shutdown.
@@ -71,7 +71,7 @@ type device interface {
 	Device
 
 	// init stores the InstanceIdentifier, daemon State and Config into device and performs any setup.
-	init(InstanceIdentifier, *state.State, string, config.Device, VolatileGetter, VolatileSetter)
+	init(InstanceIdentifier, *state.State, string, api.Device, VolatileGetter, VolatileSetter)
 
 	// validateConfig checks Config stored by init() is valid for the instance type.
 	validateConfig() error
@@ -91,7 +91,7 @@ type deviceCommon struct {
 // It also needs to be provided with volatile get and set functions for the device to allow
 // persistent data to be accessed. This is implemented as part of deviceCommon so that the majority
 // of devices don't need to implement it and can just embed deviceCommon.
-func (d *deviceCommon) init(instance InstanceIdentifier, state *state.State, name string, conf config.Device, volatileGet VolatileGetter, volatileSet VolatileSetter) {
+func (d *deviceCommon) init(instance InstanceIdentifier, state *state.State, name string, conf api.Device, volatileGet VolatileGetter, volatileSet VolatileSetter) {
 	d.instance = instance
 	d.name = name
 	d.config = conf
@@ -117,7 +117,7 @@ func (d *deviceCommon) CanHotPlug() (bool, []string) {
 }
 
 // Update returns an error as most devices do not support live updates without being restarted.
-func (d *deviceCommon) Update(oldConfig config.Device, isRunning bool) error {
+func (d *deviceCommon) Update(oldConfig api.Device, isRunning bool) error {
 	return fmt.Errorf("Device does not support updates whilst started")
 }
 
@@ -130,7 +130,7 @@ func (d *deviceCommon) Remove() error {
 // If the device type is valid, but the other config validation fails then an instantiated device
 // is still returned with the validation error. If an unknown device is requested or the device is
 // not compatible with the instance type then an ErrUnsupportedDevType error is returned.
-func New(instance InstanceIdentifier, state *state.State, name string, conf config.Device, volatileGet VolatileGetter, volatileSet VolatileSetter) (Device, error) {
+func New(instance InstanceIdentifier, state *state.State, name string, conf api.Device, volatileGet VolatileGetter, volatileSet VolatileSetter) (Device, error) {
 	if conf["type"] == "" {
 		return nil, fmt.Errorf("Missing device type for device '%s'", name)
 	}
diff --git a/lxd/device/device_instance_id.go b/lxd/device/device_instance_id.go
index 3dc35cf5f7..31e015b2f9 100644
--- a/lxd/device/device_instance_id.go
+++ b/lxd/device/device_instance_id.go
@@ -1,7 +1,7 @@
 package device
 
 import (
-	"github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // InstanceIdentifier is an interface that allows us to identify an Instance and its properties.
@@ -14,6 +14,6 @@ type InstanceIdentifier interface {
 	DevicesPath() string
 	LogPath() string
 	ExpandedConfig() map[string]string
-	ExpandedDevices() config.Devices
+	ExpandedDevices() api.Devices
 	DeviceEventHandler(*RunConfig) error
 }
diff --git a/lxd/device/device_utils_infiniband.go b/lxd/device/device_utils_infiniband.go
index 718553a7bc..7f87effc26 100644
--- a/lxd/device/device_utils_infiniband.go
+++ b/lxd/device/device_utils_infiniband.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"regexp"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -77,7 +76,7 @@ func infinibandAddDevices(s *state.State, devicesPath string, deviceName string,
 
 	// Add IsSM device if defined.
 	if ibDev.Infiniband.IsSMName != "" {
-		dummyDevice := config.Device{
+		dummyDevice := api.Device{
 			"source": fmt.Sprintf("/dev/infiniband/%s", ibDev.Infiniband.IsSMName),
 		}
 
@@ -89,7 +88,7 @@ func infinibandAddDevices(s *state.State, devicesPath string, deviceName string,
 
 	// Add MAD device if defined.
 	if ibDev.Infiniband.MADName != "" {
-		dummyDevice := config.Device{
+		dummyDevice := api.Device{
 			"source": fmt.Sprintf("/dev/infiniband/%s", ibDev.Infiniband.MADName),
 		}
 
@@ -101,7 +100,7 @@ func infinibandAddDevices(s *state.State, devicesPath string, deviceName string,
 
 	// Add Verb device if defined.
 	if ibDev.Infiniband.VerbName != "" {
-		dummyDevice := config.Device{
+		dummyDevice := api.Device{
 			"source": fmt.Sprintf("/dev/infiniband/%s", ibDev.Infiniband.VerbName),
 		}
 
diff --git a/lxd/device/device_utils_instance.go b/lxd/device/device_utils_instance.go
index c7bb2d456f..2b7af4caf9 100644
--- a/lxd/device/device_utils_instance.go
+++ b/lxd/device/device_utils_instance.go
@@ -4,8 +4,8 @@ import (
 	"fmt"
 	"sync"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // InstanceLoadNodeAll returns all local instance configs.
@@ -19,7 +19,7 @@ var reservedDevicesMutex sync.Mutex
 
 // instanceGetReservedDevices returns a map of host device names that have been used by devices in
 // other instances on the local node. Used for selecting physical and SR-IOV VF devices.
-func instanceGetReservedDevices(s *state.State, m config.Device) (map[string]struct{}, error) {
+func instanceGetReservedDevices(s *state.State, m api.Device) (map[string]struct{}, error) {
 	reservedDevicesMutex.Lock()
 	defer reservedDevicesMutex.Unlock()
 
diff --git a/lxd/device/device_utils_network.go b/lxd/device/device_utils_network.go
index 073d1720e8..a9b2be484f 100644
--- a/lxd/device/device_utils_network.go
+++ b/lxd/device/device_utils_network.go
@@ -12,8 +12,8 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/units"
 )
@@ -272,7 +272,7 @@ func NetworkAttachInterface(netName string, devName string) error {
 // in the supplied config to the newly created peer interface. If mtu is not specified, but parent
 // is supplied in config, then the MTU of the new peer interface will inherit the parent MTU.
 // Accepts the name of the host side interface as a parameter and returns the peer interface name.
-func networkCreateVethPair(hostName string, m config.Device) (string, error) {
+func networkCreateVethPair(hostName string, m api.Device) (string, error) {
 	peerName := NetworkRandomDevName("veth")
 
 	_, err := shared.RunCommand("ip", "link", "add", "dev", hostName, "type", "veth", "peer", "name", peerName)
@@ -324,7 +324,7 @@ func networkCreateVethPair(hostName string, m config.Device) (string, error) {
 }
 
 // networkSetupHostVethDevice configures a nic device's host side veth settings.
-func networkSetupHostVethDevice(device config.Device, oldDevice config.Device, v map[string]string) error {
+func networkSetupHostVethDevice(device api.Device, oldDevice api.Device, v map[string]string) error {
 	// If not configured, check if volatile data contains the most recently added host_name.
 	if device["host_name"] == "" {
 		device["host_name"] = v["host_name"]
@@ -371,7 +371,7 @@ func networkSetupHostVethDevice(device config.Device, oldDevice config.Device, v
 }
 
 // networkSetVethRoutes applies any static routes configured from the host to the container nic.
-func networkSetVethRoutes(m config.Device) error {
+func networkSetVethRoutes(m api.Device) error {
 	// Decide whether the route should point to the veth parent or the bridge parent.
 	routeDev := m["host_name"]
 	if m["nictype"] == "bridged" {
@@ -409,7 +409,7 @@ func networkSetVethRoutes(m config.Device) error {
 
 // networkRemoveVethRoutes removes any routes created for this device on the host that were first added
 // with networkSetVethRoutes(). Expects to be passed the device config from the oldExpandedDevices.
-func networkRemoveVethRoutes(m config.Device) {
+func networkRemoveVethRoutes(m api.Device) {
 	// Decide whether the route should point to the veth parent or the bridge parent
 	routeDev := m["host_name"]
 	if m["nictype"] == "bridged" {
@@ -451,7 +451,7 @@ func networkRemoveVethRoutes(m config.Device) {
 }
 
 // networkSetVethLimits applies any network rate limits to the veth device specified in the config.
-func networkSetVethLimits(m config.Device) error {
+func networkSetVethLimits(m api.Device) error {
 	var err error
 
 	veth := m["host_name"]
diff --git a/lxd/device/device_utils_unix.go b/lxd/device/device_utils_unix.go
index a2c686255d..caf0fa52e3 100644
--- a/lxd/device/device_utils_unix.go
+++ b/lxd/device/device_utils_unix.go
@@ -10,9 +10,9 @@ import (
 
 	"golang.org/x/sys/unix"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/idmap"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -23,7 +23,7 @@ const unixDefaultMode = 0660
 // unixDeviceInstanceAttributes returns the UNIX device attributes for an instance device.
 // Uses supplied device config for device properties, and if they haven't been set, falls back to
 // using UnixGetDeviceAttributes() to directly query an existing device file.
-func unixDeviceInstanceAttributes(devicesPath string, prefix string, config config.Device) (string, uint32, uint32, error) {
+func unixDeviceInstanceAttributes(devicesPath string, prefix string, config api.Device) (string, uint32, uint32, error) {
 	// Check if we've been passed major and minor numbers already.
 	var err error
 	var dMajor, dMinor uint32
@@ -117,7 +117,7 @@ type UnixDevice struct {
 // unixDeviceSourcePath returns the absolute path for a device on the host.
 // This is based on the "source" property of the device's config, or the "path" property if "source"
 // not define. This uses the shared.HostPath function so works when running in a snap environment.
-func unixDeviceSourcePath(m config.Device) string {
+func unixDeviceSourcePath(m api.Device) string {
 	srcPath := m["source"]
 	if srcPath == "" {
 		srcPath = m["path"]
@@ -128,7 +128,7 @@ func unixDeviceSourcePath(m config.Device) string {
 // unixDeviceDestPath returns the absolute path for a device inside an instance.
 // This is based on the "path" property of the device's config, or the "source" property if "path"
 // not defined.
-func unixDeviceDestPath(m config.Device) string {
+func unixDeviceDestPath(m api.Device) string {
 	destPath := m["path"]
 	if destPath == "" {
 		destPath = m["source"]
@@ -145,7 +145,7 @@ func unixDeviceDestPath(m config.Device) string {
 // type field then it defaults to created a unix-char device. The ownership of the created device
 // defaults to root (0) but can be specified with the uid and gid fields in the device config map.
 // It returns a UnixDevice containing information about the device created.
-func UnixDeviceCreate(s *state.State, idmapSet *idmap.IdmapSet, devicesPath string, prefix string, m config.Device, defaultMode bool) (*UnixDevice, error) {
+func UnixDeviceCreate(s *state.State, idmapSet *idmap.IdmapSet, devicesPath string, prefix string, m api.Device, defaultMode bool) (*UnixDevice, error) {
 	var err error
 	d := UnixDevice{}
 
@@ -288,7 +288,7 @@ func UnixDeviceCreate(s *state.State, idmapSet *idmap.IdmapSet, devicesPath stri
 // mount and cgroup rule instructions to have it be attached to the instance. 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 unixDeviceSetup(s *state.State, devicesPath string, typePrefix string, deviceName string, m config.Device, defaultMode bool, runConf *RunConfig) error {
+func unixDeviceSetup(s *state.State, devicesPath string, typePrefix string, deviceName string, m api.Device, defaultMode bool, runConf *RunConfig) error {
 	// Before creating the device, check that another existing device isn't using the same mount
 	// path inside the instance as our device. If we find an existing device with the same mount
 	// path we will skip mounting our device inside the instance. This can happen when multiple
@@ -361,8 +361,8 @@ func unixDeviceSetup(s *state.State, devicesPath string, typePrefix string, devi
 // 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 unixDeviceSetupCharNum(s *state.State, devicesPath string, typePrefix string, deviceName string, m config.Device, major uint32, minor uint32, path string, defaultMode bool, runConf *RunConfig) error {
-	configCopy := config.Device{}
+func unixDeviceSetupCharNum(s *state.State, devicesPath string, typePrefix string, deviceName string, m api.Device, major uint32, minor uint32, path string, defaultMode bool, runConf *RunConfig) error {
+	configCopy := api.Device{}
 	for k, v := range m {
 		configCopy[k] = v
 	}
diff --git a/lxd/device/gpu.go b/lxd/device/gpu.go
index 1b8f5d895a..561ddb0d20 100644
--- a/lxd/device/gpu.go
+++ b/lxd/device/gpu.go
@@ -11,7 +11,6 @@ import (
 
 	"golang.org/x/sys/unix"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/resources"
 	"github.com/lxc/lxd/shared"
@@ -46,7 +45,7 @@ func (d *gpu) validateConfig() error {
 		"mode":      unixValidOctalFileMode,
 	}
 
-	err := config.ValidateDevice(rules, d.config)
+	err := configValidate(rules, d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/infiniband.go b/lxd/device/infiniband.go
index 4e836869bf..9221b57756 100644
--- a/lxd/device/infiniband.go
+++ b/lxd/device/infiniband.go
@@ -1,7 +1,7 @@
 package device
 
 import (
-	"github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // infinibandTypes defines the supported infiniband type devices and defines their creation functions.
@@ -11,7 +11,7 @@ var infinibandTypes = map[string]func() device{
 }
 
 // infinibandLoadByType returns an Infiniband device instantiated with supplied config.
-func infinibandLoadByType(c config.Device) device {
+func infinibandLoadByType(c api.Device) device {
 	f := infinibandTypes[c["nictype"]]
 	if f != nil {
 		return f()
diff --git a/lxd/device/infiniband_physical.go b/lxd/device/infiniband_physical.go
index ea61964cce..ffaa8735fd 100644
--- a/lxd/device/infiniband_physical.go
+++ b/lxd/device/infiniband_physical.go
@@ -3,7 +3,6 @@ package device
 import (
 	"fmt"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/resources"
 	"github.com/lxc/lxd/shared"
@@ -35,7 +34,7 @@ func (d *infinibandPhysical) validateConfig() error {
 		return infinibandValidMAC(value)
 	}
 
-	err := config.ValidateDevice(rules, d.config)
+	err := configValidate(rules, d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/infiniband_sriov.go b/lxd/device/infiniband_sriov.go
index 76a84b5e8f..37f5247c91 100644
--- a/lxd/device/infiniband_sriov.go
+++ b/lxd/device/infiniband_sriov.go
@@ -3,7 +3,6 @@ package device
 import (
 	"fmt"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/resources"
 	"github.com/lxc/lxd/shared"
@@ -36,7 +35,7 @@ func (d *infinibandSRIOV) validateConfig() error {
 		return infinibandValidMAC(value)
 	}
 
-	err := config.ValidateDevice(rules, d.config)
+	err := configValidate(rules, d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/nic.go b/lxd/device/nic.go
index 45a5e28cc8..173cf2c3b7 100644
--- a/lxd/device/nic.go
+++ b/lxd/device/nic.go
@@ -1,8 +1,8 @@
 package device
 
 import (
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // nicTypes defines the supported nic type devices and defines their creation functions.
@@ -16,7 +16,7 @@ var nicTypes = map[string]func() device{
 }
 
 // nicLoadByType returns a NIC device instantiated with supplied config.
-func nicLoadByType(c config.Device) device {
+func nicLoadByType(c api.Device) device {
 	f := nicTypes[c["nictype"]]
 	if f != nil {
 		return f()
diff --git a/lxd/device/nic_bridged.go b/lxd/device/nic_bridged.go
index c0c43911b0..ff4827086c 100644
--- a/lxd/device/nic_bridged.go
+++ b/lxd/device/nic_bridged.go
@@ -19,11 +19,11 @@ import (
 	"github.com/google/gopacket/layers"
 	"github.com/mdlayher/eui64"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/dnsmasq"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/iptables"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 )
 
@@ -70,7 +70,7 @@ func (d *nicBridged) validateConfig() error {
 		"maas.subnet.ipv4",
 		"maas.subnet.ipv6",
 	}
-	err := config.ValidateDevice(nicValidationRules(requiredFields, optionalFields), d.config)
+	err := configValidate(nicValidationRules(requiredFields, optionalFields), d.config)
 	if err != nil {
 		return err
 	}
@@ -172,7 +172,7 @@ func (d *nicBridged) Start() (*RunConfig, error) {
 }
 
 // Update applies configuration changes to a started device.
-func (d *nicBridged) Update(oldConfig config.Device, isRunning bool) error {
+func (d *nicBridged) Update(oldConfig api.Device, isRunning bool) error {
 	// If an IPv6 address has changed, flush all existing IPv6 leases for instance so instance
 	// isn't allocated old IP. This is important with IPv6 because DHCPv6 supports multiple IP
 	// address allocation and would result in instance having leases for both old and new IPs.
@@ -349,7 +349,7 @@ func (d *nicBridged) rebuildDnsmasqEntry() error {
 }
 
 // setupHostFilters applies any host side network filters.
-func (d *nicBridged) setupHostFilters(oldConfig config.Device) error {
+func (d *nicBridged) setupHostFilters(oldConfig api.Device) error {
 	// Remove any old network filters if non-empty oldConfig supplied as part of update.
 	if oldConfig != nil && (shared.IsTrue(oldConfig["security.mac_filtering"]) || shared.IsTrue(oldConfig["security.ipv4_filtering"]) || shared.IsTrue(oldConfig["security.ipv6_filtering"])) {
 		d.removeFilters(oldConfig)
@@ -367,7 +367,7 @@ func (d *nicBridged) setupHostFilters(oldConfig config.Device) error {
 }
 
 // removeFilters removes any network level filters defined for the instance.
-func (d *nicBridged) removeFilters(m config.Device) error {
+func (d *nicBridged) removeFilters(m api.Device) error {
 	if m["hwaddr"] == "" {
 		return fmt.Errorf("Failed to remove network filters for %s: hwaddr not defined", m["name"])
 	}
@@ -473,7 +473,7 @@ func (d *nicBridged) getDHCPStaticIPs(network string, instanceName string) (dhcp
 }
 
 // generateFilterEbtablesRules returns a customised set of ebtables filter rules based on the device.
-func (d *nicBridged) generateFilterEbtablesRules(m config.Device, IPv4 net.IP, IPv6 net.IP) [][]string {
+func (d *nicBridged) generateFilterEbtablesRules(m api.Device, IPv4 net.IP, IPv6 net.IP) [][]string {
 	// MAC source filtering rules. Blocks any packet coming from instance with an incorrect Ethernet source MAC.
 	// This is required for IP filtering too.
 	rules := [][]string{
@@ -731,7 +731,7 @@ func (d *nicBridged) allocateFilterIPs() (net.IP, net.IP, error) {
 }
 
 // generateFilterIptablesRules returns a customised set of iptables filter rules based on the device.
-func (d *nicBridged) generateFilterIptablesRules(m config.Device, IPv6 net.IP) (rules [][]string, err error) {
+func (d *nicBridged) generateFilterIptablesRules(m api.Device, IPv6 net.IP) (rules [][]string, err error) {
 	mac, err := net.ParseMAC(m["hwaddr"])
 	if err != nil {
 		return
diff --git a/lxd/device/nic_ipvlan.go b/lxd/device/nic_ipvlan.go
index da5d2990d5..fb985b71ea 100644
--- a/lxd/device/nic_ipvlan.go
+++ b/lxd/device/nic_ipvlan.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/shared"
 )
@@ -48,7 +47,7 @@ func (d *nicIPVLAN) validateConfig() error {
 		return NetworkValidAddressV6List(value)
 	}
 
-	err := config.ValidateDevice(rules, d.config)
+	err := configValidate(rules, d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/nic_macvlan.go b/lxd/device/nic_macvlan.go
index 24d0280bcd..f3e1ae7835 100644
--- a/lxd/device/nic_macvlan.go
+++ b/lxd/device/nic_macvlan.go
@@ -3,7 +3,6 @@ package device
 import (
 	"fmt"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/shared"
 )
@@ -20,7 +19,7 @@ func (d *nicMACVLAN) validateConfig() error {
 
 	requiredFields := []string{"parent"}
 	optionalFields := []string{"name", "mtu", "hwaddr", "vlan", "maas.subnet.ipv4", "maas.subnet.ipv6"}
-	err := config.ValidateDevice(nicValidationRules(requiredFields, optionalFields), d.config)
+	err := configValidate(nicValidationRules(requiredFields, optionalFields), d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/nic_p2p.go b/lxd/device/nic_p2p.go
index 4862981c0c..df1ce61650 100644
--- a/lxd/device/nic_p2p.go
+++ b/lxd/device/nic_p2p.go
@@ -3,9 +3,9 @@ package device
 import (
 	"fmt"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 type nicP2P struct {
@@ -29,7 +29,7 @@ func (d *nicP2P) validateConfig() error {
 		"ipv4.routes",
 		"ipv6.routes",
 	}
-	err := config.ValidateDevice(nicValidationRules([]string{}, optionalFields), d.config)
+	err := configValidate(nicValidationRules([]string{}, optionalFields), d.config)
 	if err != nil {
 		return err
 	}
@@ -95,7 +95,7 @@ func (d *nicP2P) Start() (*RunConfig, error) {
 }
 
 // Update applies configuration changes to a started device.
-func (d *nicP2P) Update(oldConfig config.Device, isRunning bool) error {
+func (d *nicP2P) Update(oldConfig api.Device, isRunning bool) error {
 	if !isRunning {
 		return nil
 	}
diff --git a/lxd/device/nic_physical.go b/lxd/device/nic_physical.go
index 6db4538a40..7f0a698081 100644
--- a/lxd/device/nic_physical.go
+++ b/lxd/device/nic_physical.go
@@ -3,7 +3,6 @@ package device
 import (
 	"fmt"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/shared"
 )
@@ -27,7 +26,7 @@ func (d *nicPhysical) validateConfig() error {
 		"maas.subnet.ipv4",
 		"maas.subnet.ipv6",
 	}
-	err := config.ValidateDevice(nicValidationRules(requiredFields, optionalFields), d.config)
+	err := configValidate(nicValidationRules(requiredFields, optionalFields), d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/nic_sriov.go b/lxd/device/nic_sriov.go
index d34536e943..46826a4013 100644
--- a/lxd/device/nic_sriov.go
+++ b/lxd/device/nic_sriov.go
@@ -13,7 +13,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/shared"
 )
@@ -38,7 +37,7 @@ func (d *nicSRIOV) validateConfig() error {
 		"maas.subnet.ipv4",
 		"maas.subnet.ipv6",
 	}
-	err := config.ValidateDevice(nicValidationRules(requiredFields, optionalFields), d.config)
+	err := configValidate(nicValidationRules(requiredFields, optionalFields), d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/proxy.go b/lxd/device/proxy.go
index f27d544642..43bc4cebbd 100644
--- a/lxd/device/proxy.go
+++ b/lxd/device/proxy.go
@@ -15,7 +15,6 @@ import (
 	"golang.org/x/sys/unix"
 	"gopkg.in/lxc/go-lxc.v2"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/iptables"
 	"github.com/lxc/lxd/lxd/project"
@@ -73,7 +72,7 @@ func (d *proxy) validateConfig() error {
 		"proxy_protocol": shared.IsBool,
 	}
 
-	err := config.ValidateDevice(rules, d.config)
+	err := configValidate(rules, d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/unixCommon.go b/lxd/device/unixCommon.go
index 398fa089e0..04b15e3ff1 100644
--- a/lxd/device/unixCommon.go
+++ b/lxd/device/unixCommon.go
@@ -5,13 +5,13 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // unixIsOurDeviceType checks that device file type matches what we are expecting in the config.
-func unixIsOurDeviceType(config config.Device, dType string) bool {
+func unixIsOurDeviceType(config api.Device, dType string) bool {
 	if config["type"] == "unix-char" && dType == "c" {
 		return true
 	}
@@ -44,7 +44,7 @@ func (d *unixCommon) validateConfig() error {
 		"required": shared.IsBool,
 	}
 
-	err := config.ValidateDevice(rules, d.config)
+	err := configValidate(rules, d.config)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/device/usb.go b/lxd/device/usb.go
index ad5a6ba16d..3639e7bba7 100644
--- a/lxd/device/usb.go
+++ b/lxd/device/usb.go
@@ -7,9 +7,9 @@ import (
 	"path"
 	"strings"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // usbDevPath is the path where USB devices can be enumerated.
@@ -18,7 +18,7 @@ const usbDevPath = "/sys/bus/usb/devices"
 // usbIsOurDevice indicates whether the USB device event qualifies as part of our device.
 // This function is not defined against the usb struct type so that it can be used in event
 // callbacks without needing to keep a reference to the usb device struct.
-func usbIsOurDevice(config config.Device, usb *USBEvent) bool {
+func usbIsOurDevice(config api.Device, usb *USBEvent) bool {
 	// Check if event matches criteria for this device, if not return.
 	if (config["vendorid"] != "" && config["vendorid"] != usb.Vendor) || (config["productid"] != "" && config["productid"] != usb.Product) {
 		return false
@@ -46,7 +46,7 @@ func (d *usb) validateConfig() error {
 		"required":  shared.IsBool,
 	}
 
-	err := config.ValidateDevice(rules, d.config)
+	err := configValidate(rules, d.config)
 	if err != nil {
 		return err
 	}

From 932de7c42dae88c424ca4d315dd1eb67640a9451 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:49:07 +0100
Subject: [PATCH 35/36] lxd/db: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/db/containers.go       |  7 +++----
 lxd/db/containers_test.go  |  5 ++---
 lxd/db/db_internal_test.go | 17 ++++++++---------
 lxd/db/devices.go          | 12 ++++++------
 lxd/db/instances.mapper.go | 12 ++++++------
 lxd/db/profiles.go         |  9 ++++-----
 lxd/db/profiles.mapper.go  | 12 ++++++------
 lxd/db/snapshots.go        |  3 ++-
 lxd/db/snapshots.mapper.go | 16 ++++++++--------
 9 files changed, 45 insertions(+), 48 deletions(-)

diff --git a/lxd/db/containers.go b/lxd/db/containers.go
index 77ab594ccd..e1ecd74f47 100644
--- a/lxd/db/containers.go
+++ b/lxd/db/containers.go
@@ -8,7 +8,6 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/lxd/db/query"
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -76,7 +75,7 @@ type Instance struct {
 	LastUseDate  time.Time
 	Description  string `db:"coalesce=''"`
 	Config       map[string]string
-	Devices      map[string]map[string]string
+	Devices      api.Devices
 	Profiles     []string
 	ExpiryDate   time.Time
 }
@@ -111,7 +110,7 @@ func ContainerToArgs(container *Instance) ContainerArgs {
 	}
 
 	if args.Devices == nil {
-		args.Devices = config.Devices{}
+		args.Devices = api.Devices{}
 	}
 
 	return args
@@ -133,7 +132,7 @@ type ContainerArgs struct {
 	Architecture int
 	Config       map[string]string
 	Description  string
-	Devices      config.Devices
+	Devices      api.Devices
 	Ephemeral    bool
 	LastUsedDate time.Time
 	Name         string
diff --git a/lxd/db/containers_test.go b/lxd/db/containers_test.go
index f9db53b518..f2476ba28f 100644
--- a/lxd/db/containers_test.go
+++ b/lxd/db/containers_test.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -360,8 +359,8 @@ func TestContainerPool(t *testing.T) {
 			Project: "default",
 			Name:    "c1",
 			Node:    "none",
-			Devices: config.Devices{
-				"root": config.Device{
+			Devices: api.Devices{
+				"root": api.Device{
 					"path": "/",
 					"pool": "default",
 					"type": "disk",
diff --git a/lxd/db/db_internal_test.go b/lxd/db/db_internal_test.go
index 7a628d6d07..59e5cb38ae 100644
--- a/lxd/db/db_internal_test.go
+++ b/lxd/db/db_internal_test.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/stretchr/testify/suite"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
@@ -331,14 +330,14 @@ func (s *dbTestSuite) Test_ContainerProfiles() {
 
 func (s *dbTestSuite) Test_dbDevices_profiles() {
 	var err error
-	var result config.Devices
-	var subresult config.Device
-	var expected config.Device
+	var result api.Devices
+	var subresult api.Device
+	var expected api.Device
 
 	result, err = s.db.Devices("default", "theprofile", true)
 	s.Nil(err)
 
-	expected = config.Device{"type": "nic", "devicekey": "devicevalue"}
+	expected = api.Device{"type": "nic", "devicekey": "devicevalue"}
 	subresult = result["devicename"]
 
 	for key, value := range expected {
@@ -349,14 +348,14 @@ func (s *dbTestSuite) Test_dbDevices_profiles() {
 
 func (s *dbTestSuite) Test_dbDevices_containers() {
 	var err error
-	var result config.Devices
-	var subresult config.Device
-	var expected config.Device
+	var result api.Devices
+	var subresult api.Device
+	var expected api.Device
 
 	result, err = s.db.Devices("default", "thename", false)
 	s.Nil(err)
 
-	expected = config.Device{"type": "nic", "configkey": "configvalue"}
+	expected = api.Device{"type": "nic", "configkey": "configvalue"}
 	subresult = result["somename"]
 
 	for key, value := range expected {
diff --git a/lxd/db/devices.go b/lxd/db/devices.go
index e78d7ec9d4..433fe23a0f 100644
--- a/lxd/db/devices.go
+++ b/lxd/db/devices.go
@@ -4,7 +4,7 @@ import (
 	"database/sql"
 	"fmt"
 
-	"github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func dbDeviceTypeToString(t int) (string, error) {
@@ -58,7 +58,7 @@ func dbDeviceTypeToInt(t string) (int, error) {
 }
 
 // DevicesAdd adds a new device.
-func DevicesAdd(tx *sql.Tx, w string, cID int64, devices config.Devices) error {
+func DevicesAdd(tx *sql.Tx, w string, cID int64, devices api.Devices) error {
 	// Prepare the devices entry SQL
 	str1 := fmt.Sprintf("INSERT INTO %ss_devices (%s_id, name, type) VALUES (?, ?, ?)", w, w)
 	stmt1, err := tx.Prepare(str1)
@@ -109,10 +109,10 @@ func DevicesAdd(tx *sql.Tx, w string, cID int64, devices config.Devices) error {
 	return nil
 }
 
-func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (config.Device, error) {
+func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (api.Device, error) {
 	var query string
 	var key, value string
-	newdev := config.Device{} // That's a map[string]string
+	newdev := api.Device{} // That's a map[string]string
 	inargs := []interface{}{id}
 	outfmt := []interface{}{key, value}
 
@@ -138,7 +138,7 @@ func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (config.Device, error) {
 }
 
 // Devices returns the devices matching the given filters.
-func (c *Cluster) Devices(project, qName string, isprofile bool) (config.Devices, error) {
+func (c *Cluster) Devices(project, qName string, isprofile bool) (api.Devices, error) {
 	err := c.Transaction(func(tx *ClusterTx) error {
 		enabled, err := tx.ProjectHasProfiles(project)
 		if err != nil {
@@ -176,7 +176,7 @@ func (c *Cluster) Devices(project, qName string, isprofile bool) (config.Devices
 		return nil, err
 	}
 
-	devices := config.Devices{}
+	devices := api.Devices{}
 	for _, r := range results {
 		id = r[0].(int)
 		name = r[1].(string)
diff --git a/lxd/db/instances.mapper.go b/lxd/db/instances.mapper.go
index f5b9e5c9cf..a278cbac63 100644
--- a/lxd/db/instances.mapper.go
+++ b/lxd/db/instances.mapper.go
@@ -267,13 +267,13 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
 	for i := range objects {
 		_, ok0 := devicesObjects[objects[i].Project]
 		if !ok0 {
-			subIndex := map[string]map[string]map[string]string{}
+			subIndex := map[string]api.Devices{}
 			devicesObjects[objects[i].Project] = subIndex
 		}
 
 		value := devicesObjects[objects[i].Project][objects[i].Name]
 		if value == nil {
-			value = map[string]map[string]string{}
+			value = api.Devices{}
 		}
 		objects[i].Devices = value
 	}
@@ -635,7 +635,7 @@ func (c *ClusterTx) InstanceConfigRef(filter InstanceFilter) (map[string]map[str
 }
 
 // InstanceDevicesRef returns entities used by instances.
-func (c *ClusterTx) InstanceDevicesRef(filter InstanceFilter) (map[string]map[string]map[string]map[string]string, error) {
+func (c *ClusterTx) InstanceDevicesRef(filter InstanceFilter) (map[string]map[string]api.Devices, error) {
 	// Result slice.
 	objects := make([]struct {
 		Project string
@@ -713,18 +713,18 @@ func (c *ClusterTx) InstanceDevicesRef(filter InstanceFilter) (map[string]map[st
 	}
 
 	// Build index by primary name.
-	index := map[string]map[string]map[string]map[string]string{}
+	index := map[string]map[string]api.Devices{}
 
 	for _, object := range objects {
 		_, ok0 := index[object.Project]
 		if !ok0 {
-			subIndex := map[string]map[string]map[string]string{}
+			subIndex := map[string]api.Devices{}
 			index[object.Project] = subIndex
 		}
 
 		item, ok := index[object.Project][object.Name]
 		if !ok {
-			item = map[string]map[string]string{}
+			item = api.Devices{}
 		}
 
 		index[object.Project][object.Name] = item
diff --git a/lxd/db/profiles.go b/lxd/db/profiles.go
index 2113e4d115..72fcafdc8b 100644
--- a/lxd/db/profiles.go
+++ b/lxd/db/profiles.go
@@ -4,7 +4,6 @@ import (
 	"database/sql"
 	"fmt"
 
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/pkg/errors"
 )
@@ -55,7 +54,7 @@ type Profile struct {
 	Name        string `db:"primary=yes"`
 	Description string `db:"coalesce=''"`
 	Config      map[string]string
-	Devices     map[string]map[string]string
+	Devices     api.Devices
 	UsedBy      []string
 }
 
@@ -378,11 +377,11 @@ func ProfilesExpandConfig(config map[string]string, profiles []api.Profile) map[
 
 // ProfilesExpandDevices expands the given container devices with the devices
 // defined in the given profiles.
-func ProfilesExpandDevices(devices config.Devices, profiles []api.Profile) config.Devices {
-	expandedDevices := config.Devices{}
+func ProfilesExpandDevices(devices api.Devices, profiles []api.Profile) api.Devices {
+	expandedDevices := api.Devices{}
 
 	// Apply all the profiles
-	profileDevices := make([]config.Devices, len(profiles))
+	profileDevices := make([]api.Devices, len(profiles))
 	for i, profile := range profiles {
 		profileDevices[i] = profile.Devices
 	}
diff --git a/lxd/db/profiles.mapper.go b/lxd/db/profiles.mapper.go
index d40e41c102..3c4e4f872c 100644
--- a/lxd/db/profiles.mapper.go
+++ b/lxd/db/profiles.mapper.go
@@ -234,13 +234,13 @@ func (c *ClusterTx) ProfileList(filter ProfileFilter) ([]Profile, error) {
 	for i := range objects {
 		_, ok0 := devicesObjects[objects[i].Project]
 		if !ok0 {
-			subIndex := map[string]map[string]map[string]string{}
+			subIndex := map[string]api.Devices{}
 			devicesObjects[objects[i].Project] = subIndex
 		}
 
 		value := devicesObjects[objects[i].Project][objects[i].Name]
 		if value == nil {
-			value = map[string]map[string]string{}
+			value = api.Devices{}
 		}
 		objects[i].Devices = value
 	}
@@ -415,7 +415,7 @@ func (c *ClusterTx) ProfileConfigRef(filter ProfileFilter) (map[string]map[strin
 }
 
 // ProfileDevicesRef returns entities used by profiles.
-func (c *ClusterTx) ProfileDevicesRef(filter ProfileFilter) (map[string]map[string]map[string]map[string]string, error) {
+func (c *ClusterTx) ProfileDevicesRef(filter ProfileFilter) (map[string]map[string]api.Devices, error) {
 	// Result slice.
 	objects := make([]struct {
 		Project string
@@ -482,18 +482,18 @@ func (c *ClusterTx) ProfileDevicesRef(filter ProfileFilter) (map[string]map[stri
 	}
 
 	// Build index by primary name.
-	index := map[string]map[string]map[string]map[string]string{}
+	index := map[string]map[string]api.Devices{}
 
 	for _, object := range objects {
 		_, ok0 := index[object.Project]
 		if !ok0 {
-			subIndex := map[string]map[string]map[string]string{}
+			subIndex := map[string]api.Devices{}
 			index[object.Project] = subIndex
 		}
 
 		item, ok := index[object.Project][object.Name]
 		if !ok {
-			item = map[string]map[string]string{}
+			item = api.Devices{}
 		}
 
 		index[object.Project][object.Name] = item
diff --git a/lxd/db/snapshots.go b/lxd/db/snapshots.go
index 5c48106218..39250e8e00 100644
--- a/lxd/db/snapshots.go
+++ b/lxd/db/snapshots.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // Code generation directives.
@@ -49,7 +50,7 @@ type InstanceSnapshot struct {
 	Stateful     bool
 	Description  string `db:"coalesce=''"`
 	Config       map[string]string
-	Devices      map[string]map[string]string
+	Devices      api.Devices
 	ExpiryDate   time.Time
 }
 
diff --git a/lxd/db/snapshots.mapper.go b/lxd/db/snapshots.mapper.go
index 66deb4257d..cf2dd40e43 100644
--- a/lxd/db/snapshots.mapper.go
+++ b/lxd/db/snapshots.mapper.go
@@ -182,19 +182,19 @@ func (c *ClusterTx) InstanceSnapshotList(filter InstanceSnapshotFilter) ([]Insta
 	for i := range objects {
 		_, ok0 := devicesObjects[objects[i].Project]
 		if !ok0 {
-			subIndex := map[string]map[string]map[string]map[string]string{}
+			subIndex := map[string]map[string]api.Devices{}
 			devicesObjects[objects[i].Project] = subIndex
 		}
 
 		_, ok1 := devicesObjects[objects[i].Project][objects[i].Instance]
 		if !ok1 {
-			subIndex := map[string]map[string]map[string]string{}
+			subIndex := map[string]api.Devices{}
 			devicesObjects[objects[i].Project][objects[i].Instance] = subIndex
 		}
 
 		value := devicesObjects[objects[i].Project][objects[i].Instance][objects[i].Name]
 		if value == nil {
-			value = map[string]map[string]string{}
+			value = api.Devices{}
 		}
 		objects[i].Devices = value
 	}
@@ -440,7 +440,7 @@ func (c *ClusterTx) InstanceSnapshotConfigRef(filter InstanceSnapshotFilter) (ma
 }
 
 // InstanceSnapshotDevicesRef returns entities used by instance_snapshots.
-func (c *ClusterTx) InstanceSnapshotDevicesRef(filter InstanceSnapshotFilter) (map[string]map[string]map[string]map[string]map[string]string, error) {
+func (c *ClusterTx) InstanceSnapshotDevicesRef(filter InstanceSnapshotFilter) (map[string]map[string]map[string]api.Devices, error) {
 	// Result slice.
 	objects := make([]struct {
 		Project  string
@@ -515,24 +515,24 @@ func (c *ClusterTx) InstanceSnapshotDevicesRef(filter InstanceSnapshotFilter) (m
 	}
 
 	// Build index by primary name.
-	index := map[string]map[string]map[string]map[string]map[string]string{}
+	index := map[string]map[string]map[string]api.Devices{}
 
 	for _, object := range objects {
 		_, ok0 := index[object.Project]
 		if !ok0 {
-			subIndex := map[string]map[string]map[string]map[string]string{}
+			subIndex := map[string]map[string]api.Devices{}
 			index[object.Project] = subIndex
 		}
 
 		_, ok1 := index[object.Project][object.Instance]
 		if !ok1 {
-			subIndex := map[string]map[string]map[string]string{}
+			subIndex := map[string]api.Devices{}
 			index[object.Project][object.Instance] = subIndex
 		}
 
 		item, ok := index[object.Project][object.Instance][object.Name]
 		if !ok {
-			item = map[string]map[string]string{}
+			item = api.Devices{}
 		}
 
 		index[object.Project][object.Instance][object.Name] = item

From 092c3ecafcd490aae7bdf28e41687d227eeb4b38 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 30 Aug 2019 14:52:41 +0100
Subject: [PATCH 36/36] container/lxc: Updates Device types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/container_lxc.go | 147 +++++++++++++++++++++----------------------
 1 file changed, 73 insertions(+), 74 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 36be993264..1a2cbf0dbf 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -30,7 +30,6 @@ import (
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/db/query"
 	"github.com/lxc/lxd/lxd/device"
-	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/maas"
 	"github.com/lxc/lxd/lxd/project"
@@ -606,10 +605,10 @@ type containerLXC struct {
 
 	// Config
 	expandedConfig  map[string]string
-	expandedDevices config.Devices
+	expandedDevices api.Devices
 	fromHook        bool
 	localConfig     map[string]string
-	localDevices    config.Devices
+	localDevices    api.Devices
 	profiles        []string
 
 	// Cache
@@ -1834,7 +1833,7 @@ func (c *containerLXC) runHooks(hooks []func() error) error {
 
 // deviceLoad instantiates and validates a new device and returns it along with enriched config.
 func (c *containerLXC) deviceLoad(deviceName string, rawConfig map[string]string) (device.Device, map[string]string, error) {
-	var configCopy config.Device
+	var configCopy api.Device
 	var err error
 
 	// Create copy of config and load some fields from volatile if device is nic or infiniband.
@@ -2240,7 +2239,7 @@ func (c *containerLXC) deviceVolatileSetFunc(devName string) func(save map[strin
 
 // deviceResetVolatile resets a device's volatile data when its removed or updated in such a way
 // that it is removed then added immediately afterwards.
-func (c *containerLXC) deviceResetVolatile(devName string, oldConfig, newConfig config.Device) error {
+func (c *containerLXC) deviceResetVolatile(devName string, oldConfig, newConfig api.Device) error {
 	volatileClear := make(map[string]string)
 	devicePrefix := fmt.Sprintf("volatile.%s.", devName)
 
@@ -2584,7 +2583,7 @@ func (c *containerLXC) startCommon() (string, []func() error, error) {
 	c.removeUnixDevices()
 	c.removeDiskDevices()
 
-	diskDevices := map[string]config.Device{}
+	diskDevices := map[string]api.Device{}
 
 	// Create the devices
 	nicID := -1
@@ -2651,7 +2650,7 @@ func (c *containerLXC) startCommon() (string, []func() error, error) {
 		}
 	}
 
-	err = c.addDiskDevices(diskDevices, func(name string, d config.Device) error {
+	err = c.addDiskDevices(diskDevices, func(name string, d api.Device) error {
 		_, err := c.createDiskDevice(name, d)
 		return err
 	})
@@ -4305,7 +4304,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 	}
 
 	if args.Devices == nil {
-		args.Devices = config.Devices{}
+		args.Devices = api.Devices{}
 	}
 
 	if args.Profiles == nil {
@@ -4388,7 +4387,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 		return err
 	}
 
-	oldExpandedDevices := config.Devices{}
+	oldExpandedDevices := api.Devices{}
 	err = shared.DeepCopy(&c.expandedDevices, &oldExpandedDevices)
 	if err != nil {
 		return err
@@ -4400,7 +4399,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 		return err
 	}
 
-	oldLocalDevices := config.Devices{}
+	oldLocalDevices := api.Devices{}
 	err = shared.DeepCopy(&c.localDevices, &oldLocalDevices)
 	if err != nil {
 		return err
@@ -4485,7 +4484,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 	}
 
 	// Diff the devices
-	removeDevices, addDevices, updateDevices, updateDiff := oldExpandedDevices.Update(c.expandedDevices, func(oldDevice config.Device, newDevice config.Device) []string {
+	removeDevices, addDevices, updateDevices, updateDiff := oldExpandedDevices.Update(c.expandedDevices, func(oldDevice api.Device, newDevice api.Device) []string {
 		// This function needs to return a list of fields that are excluded from differences
 		// between oldDevice and newDevice. The result of this is that as long as the
 		// devices are otherwise identical except for the fields returned here, then the
@@ -4494,7 +4493,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 			return []string{} // Device types aren't the same, so this cannot be an update.
 		}
 
-		d, err := device.New(c, c.state, "", config.Device(newDevice), nil, nil)
+		d, err := device.New(c, c.state, "", api.Device(newDevice), nil, nil)
 		if err != device.ErrUnsupportedDevType {
 			if err != nil {
 				return []string{} // Couldn't create Device, so this cannot be an update.
@@ -4942,7 +4941,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 			}
 		}
 
-		diskDevices := map[string]config.Device{}
+		diskDevices := map[string]api.Device{}
 		for k, m := range addDevices {
 			if m["type"] == "disk" && m["path"] != "/" {
 				diskDevices[k] = m
@@ -5137,12 +5136,12 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 	return nil
 }
 
-func (c *containerLXC) updateDevices(removeDevices map[string]config.Device, addDevices map[string]config.Device, updateDevices map[string]config.Device, oldExpandedDevices config.Devices) error {
+func (c *containerLXC) updateDevices(removeDevices api.Devices, addDevices api.Devices, updateDevices api.Devices, oldExpandedDevices api.Devices) error {
 	isRunning := c.IsRunning()
 
-	for k, m := range removeDevices {
+	for _, k := range removeDevices.DeviceNames() {
 		if isRunning {
-			err := c.deviceStop(k, m, "")
+			err := c.deviceStop(k, removeDevices[k], "")
 			if err == device.ErrUnsupportedDevType {
 				continue // No point in trying to remove device below.
 			} else if err != nil {
@@ -5150,7 +5149,7 @@ func (c *containerLXC) updateDevices(removeDevices map[string]config.Device, add
 			}
 		}
 
-		err := c.deviceRemove(k, m)
+		err := c.deviceRemove(k, removeDevices[k])
 		if err != nil && err != device.ErrUnsupportedDevType {
 			return errors.Wrapf(err, "Failed to remove device '%s'", k)
 		}
@@ -5158,14 +5157,14 @@ func (c *containerLXC) updateDevices(removeDevices map[string]config.Device, add
 		// Check whether we are about to add the same device back with updated config and
 		// if not, or if the device type has changed, then remove all volatile keys for
 		// this device (as its an actual removal or a device type change).
-		err = c.deviceResetVolatile(k, m, addDevices[k])
+		err = c.deviceResetVolatile(k, removeDevices[k], addDevices[k])
 		if err != nil {
 			return errors.Wrapf(err, "Failed to reset volatile data for device '%s'", k)
 		}
 	}
 
-	for k, m := range addDevices {
-		err := c.deviceAdd(k, m)
+	for _, k := range addDevices.DeviceNames() {
+		err := c.deviceAdd(k, addDevices[k])
 		if err == device.ErrUnsupportedDevType {
 			continue // No point in trying to start device below.
 		} else if err != nil {
@@ -5173,15 +5172,15 @@ func (c *containerLXC) updateDevices(removeDevices map[string]config.Device, add
 		}
 
 		if isRunning {
-			_, err := c.deviceStart(k, m, isRunning)
+			_, err := c.deviceStart(k, addDevices[k], isRunning)
 			if err != nil && err != device.ErrUnsupportedDevType {
 				return errors.Wrapf(err, "Failed to start device '%s'", k)
 			}
 		}
 	}
 
-	for k, m := range updateDevices {
-		err := c.deviceUpdate(k, m, oldExpandedDevices[k], isRunning)
+	for _, k := range updateDevices.DeviceNames() {
+		err := c.deviceUpdate(k, addDevices[k], oldExpandedDevices[k], isRunning)
 		if err != nil && err != device.ErrUnsupportedDevType {
 			return errors.Wrapf(err, "Failed to update device '%s'", k)
 		}
@@ -6670,7 +6669,7 @@ func (c *containerLXC) removeMount(mount string) error {
 	return nil
 }
 
-func (c *containerLXC) InsertSeccompUnixDevice(prefix string, m config.Device, pid int) error {
+func (c *containerLXC) InsertSeccompUnixDevice(prefix string, m api.Device, pid int) error {
 	if pid < 0 {
 		return fmt.Errorf("Invalid request PID specified")
 	}
@@ -6757,8 +6756,8 @@ func (c *containerLXC) removeUnixDevices() error {
 
 // fillNetworkDevice takes a nic or infiniband device type and enriches it with automatically
 // generated name and hwaddr properties if these are missing from the device.
-func (c *containerLXC) fillNetworkDevice(name string, m config.Device) (config.Device, error) {
-	newDevice := config.Device{}
+func (c *containerLXC) fillNetworkDevice(name string, m api.Device) (api.Device, error) {
+	newDevice := api.Device{}
 	err := shared.DeepCopy(&m, &newDevice)
 	if err != nil {
 		return nil, err
@@ -6921,7 +6920,7 @@ func (c *containerLXC) fillNetworkDevice(name string, m config.Device) (config.D
 }
 
 // Disk device handling
-func (c *containerLXC) createDiskDevice(name string, m config.Device) (string, error) {
+func (c *containerLXC) createDiskDevice(name string, m api.Device) (string, error) {
 	// source paths
 	relativeDestPath := strings.TrimPrefix(m["path"], "/")
 	devName := fmt.Sprintf("disk.%s.%s", strings.Replace(name, "/", "-", -1), strings.Replace(relativeDestPath, "/", "-", -1))
@@ -7048,7 +7047,7 @@ func (c *containerLXC) createDiskDevice(name string, m config.Device) (string, e
 	return devPath, nil
 }
 
-func (c *containerLXC) insertDiskDevice(name string, m config.Device) error {
+func (c *containerLXC) insertDiskDevice(name string, m api.Device) error {
 	// Check that the container is running
 	if !c.IsRunning() {
 		return fmt.Errorf("Can't insert device into stopped container")
@@ -7106,48 +7105,7 @@ func (c *containerLXC) insertDiskDevice(name string, m config.Device) error {
 	return nil
 }
 
-type byPath []config.Device
-
-func (a byPath) Len() int {
-	return len(a)
-}
-
-func (a byPath) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a byPath) Less(i, j int) bool {
-	return a[i]["path"] < a[j]["path"]
-}
-
-func (c *containerLXC) addDiskDevices(devices map[string]config.Device, handler func(string, config.Device) error) error {
-	ordered := byPath{}
-
-	for _, d := range devices {
-		ordered = append(ordered, d)
-	}
-
-	sort.Sort(ordered)
-	for _, d := range ordered {
-		key := ""
-		for k, dd := range devices {
-			key = ""
-			if reflect.DeepEqual(d, dd) {
-				key = k
-				break
-			}
-		}
-
-		err := handler(key, d)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func (c *containerLXC) removeDiskDevice(name string, m config.Device) error {
+func (c *containerLXC) removeDiskDevice(name string, m api.Device) error {
 	// Check that the container is running
 	pid := c.InitPID()
 	if pid == -1 {
@@ -7198,6 +7156,47 @@ func (c *containerLXC) removeDiskDevice(name string, m config.Device) error {
 	return nil
 }
 
+type byPath []api.Device
+
+func (a byPath) Len() int {
+	return len(a)
+}
+
+func (a byPath) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a byPath) Less(i, j int) bool {
+	return a[i]["path"] < a[j]["path"]
+}
+
+func (c *containerLXC) addDiskDevices(devices map[string]api.Device, handler func(string, api.Device) error) error {
+	ordered := byPath{}
+
+	for _, d := range devices {
+		ordered = append(ordered, d)
+	}
+
+	sort.Sort(ordered)
+	for _, d := range ordered {
+		key := ""
+		for k, dd := range devices {
+			key = ""
+			if reflect.DeepEqual(d, dd) {
+				key = k
+				break
+			}
+		}
+
+		err := handler(key, d)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 func (c *containerLXC) removeDiskDevices() error {
 	// Check that we indeed have devices to remove
 	if !shared.PathExists(c.DevicesPath()) {
@@ -7486,7 +7485,7 @@ func (c *containerLXC) ExpandedConfig() map[string]string {
 	return c.expandedConfig
 }
 
-func (c *containerLXC) ExpandedDevices() config.Devices {
+func (c *containerLXC) ExpandedDevices() api.Devices {
 	return c.expandedDevices
 }
 
@@ -7508,7 +7507,7 @@ func (c *containerLXC) LocalConfig() map[string]string {
 	return c.localConfig
 }
 
-func (c *containerLXC) LocalDevices() config.Devices {
+func (c *containerLXC) LocalDevices() api.Devices {
 	return c.localDevices
 }
 
@@ -7666,7 +7665,7 @@ func (c *containerLXC) updateProgress(progress string) {
 }
 
 // Internal MAAS handling
-func (c *containerLXC) maasInterfaces(devices map[string]map[string]string) ([]maas.ContainerInterface, error) {
+func (c *containerLXC) maasInterfaces(devices api.Devices) ([]maas.ContainerInterface, error) {
 	interfaces := []maas.ContainerInterface{}
 	for k, m := range devices {
 		if m["type"] != "nic" {
@@ -7716,7 +7715,7 @@ func (c *containerLXC) maasInterfaces(devices map[string]map[string]string) ([]m
 	return interfaces, nil
 }
 
-func (c *containerLXC) maasUpdate(oldDevices map[string]map[string]string) error {
+func (c *containerLXC) maasUpdate(oldDevices api.Devices) error {
 	// Check if MAAS is configured
 	maasURL, err := cluster.ConfigGetString(c.state.Cluster, "maas.api.url")
 	if err != nil {


More information about the lxc-devel mailing list