[lxc-devel] [lxd/master] container_lxc: add unix device hotplug support

brauner on Github lxc-bot at linuxcontainers.org
Fri Jan 5 13:40:51 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 485 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180105/874ee8ff/attachment.bin>
-------------- next part --------------
From 20cc36c684b7b3a6e2a6cd41acd18672f531f3e6 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 5 Jan 2018 13:17:13 +0100
Subject: [PATCH 1/3] os: add inotify infrastructure

This adds a few generic helpers to support watching directories via inotify.
The inotify targets are recorded as a hashmap that has the following layout:

map[<dir>] -> info for inotify target n
map[<watch-file-descriptor-for-dir>] -> info for inotify target n

i.e. each target/directory is accessible via two distinct keys in the hashmap.
This is done so that we can cheaply reverse engineer the directory in which the
event occurred to construct the full path for the file. For example, let's say
we add a watch on the "/dev" directory and a watch on the "/mnt" directory. Now
a container waits on a create event for "/dev/kvm". Now an "kvm" event occurs.
The only way to know whether this event occurred in "/dev" or "/mnt" is the
watch file descriptor we receive from inotify_add_watch() when we add the target.
As LXD watches all inotify instances in the same thread we are guaranteed that
the file descriptors are distinct. So we can use the file descriptor to create
an additional entry in the hashmap pointing to the same inotify target info for
the watched directory it refers to. To ensure that there's uniqueness against
all targets/directories we prefix the key with a \0 character which is not
valid in directory and file names. This way we can cheaply reverse engineer the
full path against which we can check what containers requested this device.

Closes #3995.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/devices.go | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/sys/os.go  |  19 ++++-
 shared/util.go |  14 ++++
 3 files changed, 286 insertions(+), 1 deletion(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index f29b2df24..137c9aa57 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -16,11 +16,14 @@ import (
 	"strconv"
 	"strings"
 	"syscall"
+	"unsafe"
 
 	_ "github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/sys"
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
@@ -1593,3 +1596,254 @@ func deviceLoadInfiniband() (map[string]IBF, error) {
 	// check whether the device is an infiniband device
 	return UseableDevices, nil
 }
+
+func deviceInotifyAddTarget(s *state.State, path string) error {
+	var inFd int
+	var err error
+	var mask uint32
+
+	s.OS.InotifyWatch.Lock()
+	defer s.OS.InotifyWatch.Unlock()
+
+	if s.OS.InotifyWatch.Fd < 0 {
+		inFd, err = syscall.InotifyInit1(syscall.IN_CLOEXEC)
+		if err != nil {
+			logger.Errorf("Failed to initialize inotify")
+			return err
+		}
+
+		s.OS.InotifyWatch.Fd = inFd
+	}
+
+	mask |= syscall.IN_CREATE
+	mask |= syscall.IN_DELETE
+	wd, err := syscall.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]
+	logger.Debugf("Added \"%s\" to inotify targets", path)
+
+	return nil
+}
+
+func deviceInotifyAddUnixTarget(s *state.State, m types.Device) error {
+	if m["required"] == "" || shared.IsTrue(m["required"]) {
+		return nil
+	}
+
+	cmp := m["source"]
+	if cmp == "" {
+		cmp = m["path"]
+	}
+	cleanPath := filepath.Clean(cmp)
+
+	if shared.PathExists(cleanPath) {
+		return nil
+	}
+
+	dirToWatch := path.Dir(cleanPath)
+	if !strings.HasPrefix(dirToWatch, "/dev") {
+		return fmt.Errorf("Only hotplug for devices in \"/dev\" is supported")
+	}
+
+	if dirToWatch == "/" && cleanPath == "/" {
+		return fmt.Errorf("Not a valid unix device")
+	}
+
+	// When the source is relative we don't know what to watch for.
+	if dirToWatch == "." {
+		return fmt.Errorf("Please specify absolute path (\"/dev/kvm\")")
+	}
+
+	return deviceInotifyAddTarget(s, dirToWatch)
+}
+
+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 {
+		return nil
+	}
+
+	ret, err := syscall.InotifyRmWatch(target.Wd, uint32(s.OS.InotifyWatch.Fd))
+	if err != nil || ret != 0 {
+		return fmt.Errorf("Failed to remove \"%s\" from inotify targets", path)
+	}
+	wdString := fmt.Sprintf("\000:%d", target.Wd)
+	syscall.Close(target.Wd)
+	delete(s.OS.InotifyWatch.Targets, wdString)
+	delete(s.OS.InotifyWatch.Targets, path)
+
+	return nil
+}
+
+func deviceInotifyDel(s *state.State) {
+	s.OS.InotifyWatch.Lock()
+	defer s.OS.InotifyWatch.Unlock()
+
+	// Cleanup the fd's manually as go's gc does not guarantee that they are
+	// closed immediately.
+	for _, v := range s.OS.InotifyWatch.Targets {
+		syscall.Close(v.Wd)
+	}
+
+	syscall.Close(s.OS.InotifyWatch.Fd)
+}
+
+const LXD_BATCH_IN_EVENTS uint = 20
+const LXD_SINGLE_IN_EVENT_SIZE uint = (syscall.SizeofInotifyEvent + syscall.PathMax)
+const LXD_BATCH_IN_BUFSIZE uint = LXD_BATCH_IN_EVENTS * LXD_SINGLE_IN_EVENT_SIZE
+
+func deviceInotifyWatcher(s *state.State) (chan map[string]*sys.InotifyTargetInfo, error) {
+	targetChan := make(chan map[string]*sys.InotifyTargetInfo)
+
+	go func(target chan map[string]*sys.InotifyTargetInfo) {
+		for {
+			buf := make([]byte, LXD_BATCH_IN_BUFSIZE)
+			n, errno := syscall.Read(s.OS.InotifyWatch.Fd, buf)
+			if errno != nil {
+				if errno == syscall.EINTR {
+					continue
+				}
+
+				deviceInotifyDel(s)
+				return
+			}
+
+			if n < syscall.SizeofInotifyEvent {
+				continue
+			}
+
+			var offset uint32
+			batch := make(map[string]*sys.InotifyTargetInfo)
+			for offset <= uint32(n-syscall.SizeofInotifyEvent) {
+				name := ""
+				event := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[0]))
+
+				nameLen := uint32(event.Len)
+				if nameLen > 0 {
+					bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
+					name = strings.TrimRight(string(bytes[0:nameLen]), "\000")
+				}
+
+				s.OS.InotifyWatch.Lock()
+				parent := fmt.Sprintf("\000:%d", event.Wd)
+				v, ok := s.OS.InotifyWatch.Targets[parent]
+				s.OS.InotifyWatch.Unlock()
+				if ok {
+					targetName := filepath.Join(v.Path, name)
+					// skip non-unix devices
+					if shared.IsUnixDev(targetName) {
+						batch[name] = &sys.InotifyTargetInfo{
+							Mask: uint32(event.Mask),
+							Path: targetName,
+							Wd:   int(event.Wd),
+						}
+					}
+				}
+
+				offset += (syscall.SizeofInotifyEvent + nameLen)
+			}
+			target <- batch
+		}
+	}(targetChan)
+
+	return targetChan, nil
+}
+
+func deviceUnixEvent(s *state.State, inot *sys.InotifyTargetInfo) {
+	containers, err := s.DB.ContainersList(db.CTypeRegular)
+	if err != nil {
+		logger.Error("problem loading containers list", log.Ctx{"err": err})
+		return
+	}
+
+	for _, name := range containers {
+		containerIf, err := containerLoadByName(s, name)
+		if err != nil {
+			continue
+		}
+
+		c, ok := containerIf.(*containerLXC)
+		if !ok {
+			logger.Errorf("got 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"]
+			}
+
+			cleanDevPath := filepath.Clean(cmp)
+			cleanInotPath := filepath.Clean(inot.Path)
+			if cleanDevPath != cleanInotPath {
+				continue
+			}
+
+			if (inot.Mask & syscall.IN_CREATE) > 0 {
+				err := c.insertUnixDevice(fmt.Sprintf("unix.%s", name), m)
+				if err != nil {
+					logger.Error("Failed to create unix device", log.Ctx{"err": err, "dev": m, "container": c.Name()})
+					return
+				}
+			} else if inot.Mask&syscall.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()})
+					return
+				}
+			} else {
+				logger.Error("Unknown action for unix device", log.Ctx{"dev": m})
+				continue
+			}
+		}
+	}
+}
+
+func deviceInotifyHandler(s *state.State) {
+	watchChan, err := deviceInotifyWatcher(s)
+	if err != nil {
+		return
+	}
+
+	for {
+		select {
+		case e := <-watchChan:
+			for _, v := range e {
+				deviceUnixEvent(s, v)
+			}
+		}
+	}
+}
diff --git a/lxd/sys/os.go b/lxd/sys/os.go
index b88482aa2..2ad71fbd0 100644
--- a/lxd/sys/os.go
+++ b/lxd/sys/os.go
@@ -2,6 +2,7 @@ package sys
 
 import (
 	"path/filepath"
+	"sync"
 
 	log "github.com/lxc/lxd/shared/log15"
 
@@ -11,6 +12,18 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
+type InotifyTargetInfo struct {
+	Mask uint32
+	Wd   int
+	Path string
+}
+
+type InotifyInfo struct {
+	Fd int
+	sync.Mutex
+	Targets map[string]*InotifyTargetInfo
+}
+
 // OS is a high-level facade for accessing all operating-system
 // level functionality that LXD uses.
 type OS struct {
@@ -39,17 +52,21 @@ type OS struct {
 	CGroupNetPrioController bool
 	CGroupPidsController    bool
 	CGroupSwapAccounting    bool
+	InotifyWatch            InotifyInfo
 
 	MockMode bool // If true some APIs will be mocked (for testing)
 }
 
 // DefaultOS returns a fresh uninitialized OS instance with default values.
 func DefaultOS() *OS {
-	return &OS{
+	newOS := &OS{
 		VarDir:   shared.VarPath(),
 		CacheDir: shared.CachePath(),
 		LogDir:   shared.LogPath(),
 	}
+	newOS.InotifyWatch.Fd = -1
+	newOS.InotifyWatch.Targets = make(map[string]*InotifyTargetInfo)
+	return newOS
 }
 
 // Init our internal data structures.
diff --git a/shared/util.go b/shared/util.go
index e039905f0..9732ce42c 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -502,6 +502,20 @@ func IsTrue(value string) bool {
 	return false
 }
 
+func IsUnixDev(path string) bool {
+	stat, err := os.Stat(path)
+	if err != nil {
+		return false
+
+	}
+
+	if (stat.Mode() & os.ModeDevice) == 0 {
+		return false
+	}
+
+	return true
+}
+
 func IsBlockdev(fm os.FileMode) bool {
 	return ((fm&os.ModeDevice != 0) && (fm&os.ModeCharDevice == 0))
 }

From ed5ae43e2ecafe51596f8529e30434c384a25418 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 5 Jan 2018 13:43:10 +0100
Subject: [PATCH 2/3] container_lxc: add unix device hotplug support

This adds support to hotplug unix devices into the container by supporting the
"required" property.

Closes #3995.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/api-extensions.md |  3 +++
 doc/containers.md     |  2 ++
 lxd/container.go      |  4 +++-
 lxd/container_lxc.go  | 40 ++++++++++++++++++++++++++++------------
 lxd/daemon.go         |  8 ++++++++
 shared/version/api.go |  1 +
 6 files changed, 45 insertions(+), 13 deletions(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index f260d67b2..7150fc82c 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -391,3 +391,6 @@ This adds a websocket API to the devlxd socket.
 
 When connecting to /1.0/events over the devlxd socket, you will now be
 getting a stream of events over websocket.
+
+## unix\_device\_hotplug
+This adds support for the "required" property for unix devices.
diff --git a/doc/containers.md b/doc/containers.md
index cfd23287b..b687c43a1 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -328,6 +328,7 @@ minor       | int       | device on host    |
 uid         | int       | 0                 |                                   | no        | UID of the device owner in the container
 gid         | int       | 0                 |                                   | no        | GID of the device owner in the container
 mode        | int       | 0660              |                                   | no        | Mode of the device in the container
+required    | boolean   | true              | unix\_device\_hotplug             | no        | Whether or not this device is required to start the container.
 
 ### Type: unix-block
 Unix block device entries simply make the requested block device
@@ -344,6 +345,7 @@ minor       | int       | device on host    |
 uid         | int       | 0                 |                                   | no        | UID of the device owner in the container
 gid         | int       | 0                 |                                   | no        | GID of the device owner in the container
 mode        | int       | 0660              |                                   | no        | Mode of the device in the container
+required    | boolean   | true              | unix\_device\_hotplug             | no        | Whether or not this device is required to start the container.
 
 ### Type: usb
 USB device entries simply make the requested USB device appear in the
diff --git a/lxd/container.go b/lxd/container.go
index 53b4db3c3..5f8cae411 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -102,6 +102,8 @@ func containerValidDeviceConfigKey(t, k string) bool {
 			return true
 		case "path":
 			return true
+		case "required":
+			return true
 		case "uid":
 			return true
 		default:
@@ -386,7 +388,7 @@ func containerValidDevices(db *db.Node, devices types.Devices, profile bool, exp
 				return fmt.Errorf("Unix device entry is missing the required \"source\" or \"path\" property.")
 			}
 
-			if m["major"] == "" || m["minor"] == "" {
+			if shared.IsTrue(m["required"]) && (m["major"] == "" || m["minor"] == "") {
 				srcPath, exist := m["source"]
 				if !exist {
 					srcPath = m["path"]
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index efe317fbc..209a4b80b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1898,19 +1898,23 @@ func (c *containerLXC) startCommon() (string, error) {
 			// Unix device
 			paths, err := c.createUnixDevice(fmt.Sprintf("unix.%s", k), m)
 			if err != nil {
-				return "", err
+				if m["required"] == "" || shared.IsTrue(m["required"]) {
+					return "", err
+				}
 			}
 			devPath := paths[0]
 			if c.IsPrivileged() && !c.state.OS.RunningInUserNS && c.state.OS.CGroupDevicesController {
 				// Add the new device cgroup rule
 				dType, dMajor, dMinor, err := deviceGetAttributes(devPath)
 				if err != nil {
-					return "", err
-				}
-
-				err = lxcSetConfigItem(c.c, "lxc.cgroup.devices.allow", fmt.Sprintf("%s %d:%d rwm", dType, dMajor, dMinor))
-				if err != nil {
-					return "", fmt.Errorf("Failed to add cgroup rule for device")
+					if shared.IsTrue(m["required"]) {
+						return "", err
+					}
+				} else {
+					err = lxcSetConfigItem(c.c, "lxc.cgroup.devices.allow", fmt.Sprintf("%s %d:%d rwm", dType, dMajor, dMinor))
+					if err != nil {
+						return "", fmt.Errorf("Failed to add cgroup rule for device")
+					}
 				}
 			}
 		} else if m["type"] == "usb" {
@@ -4038,6 +4042,16 @@ 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 !c.deviceExistsInDevicesFolder(prefix, destPath) && (m["required"] != "" && !shared.IsTrue(m["required"])) {
+					continue
+				}
+
 				err = c.removeUnixDevice(fmt.Sprintf("unix.%s", k), m, true)
 				if err != nil {
 					return err
@@ -4112,7 +4126,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 				nvidiaExists := false
 				for _, gpu := range gpus {
 					if gpu.nvidia.path != "" {
-						if c.deviceExists(k, gpu.path) {
+						if c.deviceExistsInDevicesFolder(fmt.Sprintf("unix.%s", k), gpu.path) {
 							nvidiaExists = true
 							break
 						}
@@ -4121,7 +4135,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 
 				if !nvidiaExists {
 					for _, gpu := range nvidiaDevices {
-						if !c.deviceExists(k, gpu.path) {
+						if !c.deviceExistsInDevicesFolder(fmt.Sprintf("unix.%s", k), gpu.path) {
 							continue
 						}
 						err = c.removeUnixDeviceNum(fmt.Sprintf("unix.%s", k), m, gpu.major, gpu.minor, gpu.path)
@@ -4139,7 +4153,9 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 			if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
 				err = c.insertUnixDevice(fmt.Sprintf("unix.%s", k), m)
 				if err != nil {
-					return err
+					if m["required"] == "" || shared.IsTrue(m["required"]) {
+						return err
+					}
 				}
 			} else if m["type"] == "disk" && m["path"] != "/" {
 				diskDevices[k] = m
@@ -4234,7 +4250,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 
 				if sawNvidia {
 					for _, gpu := range nvidiaDevices {
-						if c.deviceExists(k, gpu.path) {
+						if c.deviceExistsInDevicesFolder(k, gpu.path) {
 							continue
 						}
 						err = c.insertUnixDeviceNum(fmt.Sprintf("unix.%s", k), m, gpu.major, gpu.minor, gpu.path)
@@ -5907,7 +5923,7 @@ func (c *containerLXC) removeMount(mount string) error {
 }
 
 // Check if the unix device already exists.
-func (c *containerLXC) deviceExists(prefix string, path string) bool {
+func (c *containerLXC) deviceExistsInDevicesFolder(prefix string, path string) bool {
 	relativeDestPath := strings.TrimPrefix(path, "/")
 	devName := fmt.Sprintf("%s.%s", strings.Replace(prefix, "/", "-", -1), strings.Replace(relativeDestPath, "/", "-", -1))
 	devPath := filepath.Join(c.DevicesPath(), devName)
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 5262cdce3..ba5d3e7da 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -412,6 +412,14 @@ func (d *Daemon) init() error {
 	if !d.os.MockMode {
 		/* Start the scheduler */
 		go deviceEventListener(d.State())
+
+		err := deviceInotifyAddTarget(d.State(), "/dev")
+		if err != nil {
+			return err
+		}
+
+		go deviceInotifyHandler(d.State())
+
 		readSavedClientCAList(d)
 	}
 
diff --git a/shared/version/api.go b/shared/version/api.go
index 221bbce49..f29ac2f32 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -86,4 +86,5 @@ var APIExtensions = []string{
 	"infiniband",
 	"maas_network",
 	"devlxd_events",
+	"unix_device_hotplug",
 }

From 8f11240bc71f653f817b59a82a9c637e215bb296 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 5 Jan 2018 14:09:10 +0100
Subject: [PATCH 3/3] inotify: add more targets

- /dev/dri
- /dev/infiniband
- /dev/input
- /dev/mapper
- /dev/net
- /dev/pts
- /dev/raw
- /dev/scanners
- /dev/usb
- /dev/video

If necessary we can later add a daemon config key to modify the list of inotify
watches.

Closes #3995.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/containers.md | 10 ++++++++
 lxd/daemon.go     | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 78 insertions(+)

diff --git a/doc/containers.md b/doc/containers.md
index b687c43a1..ba872b41d 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -317,6 +317,11 @@ the average of the limits will be used.
 Unix character device entries simply make the requested character device
 appear in the container's `/dev` and allow read/write operations to it.
 
+If they exist at the time of the LXD daemon start the "required" property is
+available for devices in the following directories: `/dev`, `/dev/dri`,
+`/dev/infiniband`, `/dev/input`, `/dev/mapper`, `/dev/net`, `/dev/pts`,
+`/dev/raw`, `/dev/thinkpad`, `/dev/scanners`, `/dev/usb`, `/dev/video`.
+
 The following properties exist:
 
 Key         | Type      | Default           | API extension                     | Required  | Description
@@ -334,6 +339,11 @@ required    | boolean   | true              | unix\_device\_hotplug
 Unix block device entries simply make the requested block device
 appear in the container's `/dev` and allow read/write operations to it.
 
+If they exist at the time of the LXD daemon start the "required" property is
+available for devices in the following directories: `/dev`, `/dev/dri`,
+`/dev/infiniband`, `/dev/input`, `/dev/mapper`, `/dev/net`, `/dev/pts`,
+`/dev/raw`, `/dev/thinkpad`, `/dev/scanners`, `/dev/usb`, `/dev/video`.
+
 The following properties exist:
 
 Key         | Type      | Default           | API extension                     | Required  | Description
diff --git a/lxd/daemon.go b/lxd/daemon.go
index ba5d3e7da..fc149c84d 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -418,6 +418,74 @@ func (d *Daemon) init() error {
 			return err
 		}
 
+		// optional
+		// Direct Rendering Infrastructure (DRI)
+		err = deviceInotifyAddTarget(d.State(), "/dev/dri")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/dri\" does not exist")
+		}
+
+		// optional
+		// InfiniBand
+		err = deviceInotifyAddTarget(d.State(), "/dev/infiniband")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/infiniband\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/input")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/input\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/mapper")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/mapper\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/net")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/net\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/pts")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/pts\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/raw")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/raw\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/thinkpad")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/thinkpad\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/scanners")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/scanners\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/usb")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/usb\" does not exist")
+		}
+
+		// optional
+		err = deviceInotifyAddTarget(d.State(), "/dev/video")
+		if err != nil {
+			logger.Debugf("Inotify target \"/dev/video\" does not exist")
+		}
+
 		go deviceInotifyHandler(d.State())
 
 		readSavedClientCAList(d)


More information about the lxc-devel mailing list