[lxc-devel] [lxd/master] WIP: Backport daemon state and db

freeekanayaka on Github lxc-bot at linuxcontainers.org
Wed Oct 4 12:49:51 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 3848 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20171004/54d66d80/attachment.bin>
-------------- next part --------------
From 1c175230caaf5056177320650ec3c59ff5f49a7c Mon Sep 17 00:00:00 2001
From: Serge Hallyn <serge.hallyn at ubuntu.com>
Date: Wed, 13 Apr 2016 18:09:48 +0000
Subject: [PATCH 0001/1193] lxd: do run if we fail to mount shmounts

If a container is setup without security.nesting = true, and someone
runs lxd in that container, we want lxd to run but fail (with clear
error) when starting a container.  Otherwise 'lxc list' and
dpkg-reconfigure lxd will hang forever, with errors going only to
syslog.

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 lxd/container_lxc.go | 4 ++++
 lxd/daemon.go        | 7 ++++++-
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 68b25e186..5e64a0aa8 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1115,6 +1115,10 @@ func (c *containerLXC) Start(stateful bool) error {
 		wgStopping.Wait()
 	}
 
+	if !c.daemon.SharedMounts {
+		return fmt.Errorf("Daemon failed to setup shared mounts")
+	}
+
 	// Run the shared start code
 	configPath, err := c.startCommon()
 	if err != nil {
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 6fc384d51..bda605312 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -81,6 +81,8 @@ type Daemon struct {
 	shutdownChan        chan bool
 	resetAutoUpdateChan chan bool
 	execPath            string
+	// did we manage to setup shared mounts?
+	SharedMounts bool
 
 	Storage storage
 
@@ -761,7 +763,10 @@ func (d *Daemon) Init() error {
 	}
 
 	if err := setupSharedMounts(); err != nil {
-		return err
+		d.SharedMounts = false
+		shared.Log.Error("Error setting up shared mounts base", log.Ctx{"err": err})
+	} else {
+		d.SharedMounts = true
 	}
 
 	if !d.MockMode {

From 16109cb679b9fbbf9485d6e5ad4db751f9253427 Mon Sep 17 00:00:00 2001
From: Serge Hallyn <serge.hallyn at ubuntu.com>
Date: Wed, 13 Apr 2016 14:59:31 -0500
Subject: [PATCH 0002/1193] lxd shared mounts setup; do it when containers need
 it

For one thing it allows the container to be fixed easily without
restarting the nested lxd, after the first failed container start.

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 lxd/container_lxc.go |  4 ++--
 lxd/daemon.go        | 27 ++++++++++++++++++---------
 2 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5e64a0aa8..8a6e2de22 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1115,8 +1115,8 @@ func (c *containerLXC) Start(stateful bool) error {
 		wgStopping.Wait()
 	}
 
-	if !c.daemon.SharedMounts {
-		return fmt.Errorf("Daemon failed to setup shared mounts")
+	if err := setupSharedMounts(); err != nil {
+		return fmt.Errorf("Daemon failed to setup shared mounts base: %s", err)
 	}
 
 	// Run the shared start code
diff --git a/lxd/daemon.go b/lxd/daemon.go
index bda605312..d8b754c51 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -81,8 +81,6 @@ type Daemon struct {
 	shutdownChan        chan bool
 	resetAutoUpdateChan chan bool
 	execPath            string
-	// did we manage to setup shared mounts?
-	SharedMounts bool
 
 	Storage storage
 
@@ -401,7 +399,23 @@ func (d *Daemon) SetupStorageDriver() error {
 	return err
 }
 
+// have we setup shared mounts?
+var sharedMounted bool
+var sharedMountsLock sync.Mutex
+
 func setupSharedMounts() error {
+
+	if sharedMounted {
+		return nil
+	}
+
+	sharedMountsLock.Lock()
+	defer sharedMountsLock.Unlock()
+
+	if sharedMounted {
+		return nil
+	}
+
 	path := shared.VarPath("shmounts")
 
 	isShared, err := shared.IsOnSharedMount(path)
@@ -412,6 +426,7 @@ func setupSharedMounts() error {
 	if isShared {
 		// / may already be ms-shared, or shmounts may have
 		// been mounted by a previous lxd run
+		sharedMounted = true
 		return nil
 	}
 
@@ -424,6 +439,7 @@ func setupSharedMounts() error {
 		return err
 	}
 
+	sharedMounted = true
 	return nil
 }
 
@@ -762,13 +778,6 @@ func (d *Daemon) Init() error {
 		return err
 	}
 
-	if err := setupSharedMounts(); err != nil {
-		d.SharedMounts = false
-		shared.Log.Error("Error setting up shared mounts base", log.Ctx{"err": err})
-	} else {
-		d.SharedMounts = true
-	}
-
 	if !d.MockMode {
 		/* Start the scheduler */
 		go deviceEventListener(d)

From 6d02fb1442b561ccc52ece3fa898e4281afcc176 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 13 Apr 2016 18:53:08 -0400
Subject: [PATCH 0003/1193] Invalidate the simplestreams cache on proxy change
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is needed to make a proxy config change immediately effective.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/api_1.0.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index ba653db5a..db3a8fb90 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -240,7 +240,16 @@ func api10Put(d *Daemon, r *http.Request) Response {
 				return InternalError(err)
 			}
 
+			// Update the cached proxy function
 			d.updateProxy()
+
+			// Clear the simplestreams cache as it's tied to the old proxy config
+			imageStreamCacheLock.Lock()
+			for k, _ := range imageStreamCache {
+				delete(imageStreamCache, k)
+			}
+			imageStreamCacheLock.Unlock()
+
 		} else {
 			err := d.ConfigValueSet(key, value.(string))
 			if err != nil {

From 437c82fe2b31246661cf67c6933af6613702bf37 Mon Sep 17 00:00:00 2001
From: Serge Hallyn <serge.hallyn at ubuntu.com>
Date: Wed, 13 Apr 2016 18:10:44 -0500
Subject: [PATCH 0004/1193] suggest nesting on shmounts failure

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8a6e2de22..e5adcfcc3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1116,7 +1116,7 @@ func (c *containerLXC) Start(stateful bool) error {
 	}
 
 	if err := setupSharedMounts(); err != nil {
-		return fmt.Errorf("Daemon failed to setup shared mounts base: %s", err)
+		return fmt.Errorf("Daemon failed to setup shared mounts base: %s.\nDoes security.nesting need to be turned on?", err)
 	}
 
 	// Run the shared start code

From 88b499016e4e5d5f00032691edb1c56287af3f74 Mon Sep 17 00:00:00 2001
From: Serge Hallyn <serge.hallyn at ubuntu.com>
Date: Wed, 13 Apr 2016 18:11:37 -0500
Subject: [PATCH 0005/1193] drop blank line

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 lxd/daemon.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index d8b754c51..0a715802b 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -404,7 +404,6 @@ var sharedMounted bool
 var sharedMountsLock sync.Mutex
 
 func setupSharedMounts() error {
-
 	if sharedMounted {
 		return nil
 	}

From 5495fcc2990db76aff2d43ecdf0dc83b3885318d Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 14 Apr 2016 15:10:54 -0600
Subject: [PATCH 0006/1193] write the container's config file on start to the
 log path directly

Now that we're dending on this file to exist in all cases when execing,
let's always write it.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 14 +-------------
 lxd/containers.go    |  3 ---
 2 files changed, 1 insertion(+), 16 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index e5adcfcc3..4cd152451 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1084,19 +1084,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	}
 
 	// Generate the LXC config
-	f, err := ioutil.TempFile("", "lxd_lxc_startconfig_")
-	if err != nil {
-		return "", err
-	}
-
-	configPath := f.Name()
-	if err = f.Chmod(0600); err != nil {
-		f.Close()
-		os.Remove(configPath)
-		return "", err
-	}
-	f.Close()
-
+	configPath := filepath.Join(c.LogPath(), "lxc.conf")
 	err = c.c.SaveConfigFile(configPath)
 	if err != nil {
 		os.Remove(configPath)
diff --git a/lxd/containers.go b/lxd/containers.go
index 4231399b4..b125768f7 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -240,9 +240,6 @@ func startContainer(args []string) error {
 		syscall.Dup3(int(logFile.Fd()), 2, 0)
 	}
 
-	// Move the config so we can inspect it on failure
-	shared.FileMove(configPath, shared.LogPath(name, "lxc.conf"))
-
 	return c.Start()
 }
 

From 21b5228d6f7f34e4534b20f2bb94c688ca4acbfb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 18 Apr 2016 13:57:42 +0100
Subject: [PATCH 0007/1193] list: Handle empty responses
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1903

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index e0cfd6cbe..2c1f48cfe 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -399,7 +399,7 @@ func (c *listCmd) statusColumnData(cInfo shared.ContainerInfo, cState *shared.Co
 }
 
 func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
-	if cInfo.IsActive() && cState.Network != nil {
+	if cInfo.IsActive() && cState != nil && cState.Network != nil {
 		ipv4s := []string{}
 		for netName, net := range cState.Network {
 			if net.Type == "loopback" {
@@ -423,7 +423,7 @@ func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 }
 
 func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
-	if cInfo.IsActive() && cState.Network != nil {
+	if cInfo.IsActive() && cState != nil && cState.Network != nil {
 		ipv6s := []string{}
 		for netName, net := range cState.Network {
 			if net.Type == "loopback" {
@@ -455,11 +455,15 @@ func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.Cont
 }
 
 func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
-	return fmt.Sprintf("%d", len(cSnaps))
+	if cSnaps != nil {
+		return fmt.Sprintf("%d", len(cSnaps))
+	}
+
+	return ""
 }
 
 func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
-	if cInfo.IsActive() {
+	if cInfo.IsActive() && cState != nil {
 		return fmt.Sprintf("%d", cState.Pid)
 	}
 

From 54c544007eaf175ce0bf55775b811ddf6d3d3dc4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 18 Apr 2016 15:38:39 +0100
Subject: [PATCH 0008/1193] Fail when removing non-existent profiles
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes: #1886

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/profiles.go | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/lxd/profiles.go b/lxd/profiles.go
index 16d563eab..6cbbf0f6a 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -259,10 +259,15 @@ func profilePost(d *Daemon, r *http.Request) Response {
 // The handler for the delete operation.
 func profileDelete(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
-	err := dbProfileDelete(d.db, name)
 
+	_, err := doProfileGet(d, name)
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
+	}
+
+	err = dbProfileDelete(d.db, name)
+	if err != nil {
+		return SmartError(err)
 	}
 
 	return EmptySyncResponse

From 3cb2a2284fe74f7928b965bad20a8fd50231c6d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 18 Apr 2016 15:48:40 +0100
Subject: [PATCH 0009/1193] Document --alias to image import
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1900

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 2 +-
 po/lxd.pot   | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index cf333a67f..752b9c68f 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -108,7 +108,7 @@ Images can be referenced by their full hash, shortest unique partial
 hash or alias name (if one is set).
 
 
-lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [prop=value]
+lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS].. [prop=value]
     Import an image tarball (or tarballs) into the LXD image store.
 
 lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update]
diff --git a/po/lxd.pot b/po/lxd.pot
index e828098b7..98cf83f5b 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-04-11 15:01-0400\n"
+        "POT-Creation-Date: 2016-04-18 15:47+0100\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -622,7 +622,7 @@ msgid   "Manipulate container images.\n"
         "hash or alias name (if one is set).\n"
         "\n"
         "\n"
-        "lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [prop=value]\n"
+        "lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS].. [prop=value]\n"
         "    Import an image tarball (or tarballs) into the LXD image store.\n"
         "\n"
         "lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update]\n"

From 0a86ef9832c317f20a0c135a5f533b956cf20535 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 19 Apr 2016 18:44:49 +0100
Subject: [PATCH 0010/1193] Fix "lxc start" and "lxc stop" options.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/action.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/action.go b/lxc/action.go
index c214ce4c4..355529af8 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -35,9 +35,9 @@ func (c *actionCmd) flags() {
 	if c.hasTimeout {
 		gnuflag.IntVar(&c.timeout, "timeout", -1, i18n.G("Time to wait for the container before killing it."))
 		gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the container to shutdown."))
-		gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Store the container state (only for stop)."))
-		gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only forstart)."))
 	}
+	gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Store the container state (only for stop)."))
+	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only forstart)."))
 }
 
 func (c *actionCmd) run(config *lxd.Config, args []string) error {

From 5d2aab53d8d180c4fde87498c4e6b9266dea33f3 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 21 Apr 2016 17:02:22 -0600
Subject: [PATCH 0011/1193] simplestreams: give better error on invalid source
 stream

Before, we always tried to hash the body regardless of the status code,
which meant that we would hash the contents of e.g. a 404 message if the
thing wasn't found, and issue a "Hash mismatch" error, masking the real
error.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/simplestreams.go | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index ab2d540ba..7842e75fc 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -482,6 +482,10 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 		}
 		defer resp.Body.Close()
 
+		if resp.StatusCode != http.StatusOK {
+			return fmt.Errorf("invalid simplestreams source: got %d looking for %s", resp.StatusCode, path)
+		}
+
 		body := &TransferProgress{Reader: resp.Body, Length: resp.ContentLength, Handler: progress}
 
 		sha256 := sha256.New()
@@ -490,9 +494,10 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 			return err
 		}
 
-		if fmt.Sprintf("%x", sha256.Sum(nil)) != hash {
+		result := fmt.Sprintf("%x", sha256.Sum(nil))
+		if result != hash {
 			os.Remove(target)
-			return fmt.Errorf("Hash mismatch")
+			return fmt.Errorf("Hash mismatch for %s: %s != %s", path, result, hash)
 		}
 
 		return nil

From bc092286babf2e6c434a302071dcad5df0e02112 Mon Sep 17 00:00:00 2001
From: Daniel Middleton <monokal at users.noreply.github.com>
Date: Fri, 22 Apr 2016 21:52:35 +0100
Subject: [PATCH 0012/1193] Add basic REST API usage example to README.md

Signed-off-by: Daniel Middleton <d at monokal.io>
---
 README.md | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/README.md b/README.md
index e9c8983e0..6e4bfda11 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,39 @@ shell you're going to interact with lxd from.
 After you've got LXD installed and a session with the right permissions, you
 can take your [first steps](#first-steps).
 
+## Using the REST API
+Here's a simple example of REST API usage via cURL:
+```bash
+curl -k -L -I \
+    --cert ${LXD_API_CRT} \
+    --key ${LXD_API_KEY} \
+    -H "Content-Type: application/json" \
+    -X POST \
+    -d @hello-ubuntu.json \
+    "${LXD_API_URL}/containers"
+```
+where `hello-ubuntu.json` could contain:
+```json
+{
+    "name":"some-ubuntu",
+    "architecture":"x86_64",
+    "profiles":[
+        "default"
+    ],
+    "ephemeral":true,
+    "config":{
+        "limits.cpu":"2"
+    },
+    "source": {
+        "type":"image",
+        "mode":"pull",
+        "protocol":"simplestreams",
+        "server":"https://cloud-images.ubuntu.com/releases",
+        "alias":"14.04"
+    }
+}
+```
+
 ## Building from source
 
 We recommend having the latest versions of liblxc (>= 1.1 required) and CRIU

From 346fe17e69624a6d647b03c7a4b6f409daacb09d Mon Sep 17 00:00:00 2001
From: Daniel Middleton <monokal at users.noreply.github.com>
Date: Fri, 22 Apr 2016 22:14:37 +0100
Subject: [PATCH 0013/1193] Amended REST API usage example as per feedback.

Signed-off-by: Daniel Middleton <d at monokal.io>
---
 README.md | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/README.md b/README.md
index 6e4bfda11..50037e250 100644
--- a/README.md
+++ b/README.md
@@ -32,21 +32,17 @@ can take your [first steps](#first-steps).
 Here's a simple example of REST API usage via cURL:
 ```bash
 curl -k -L -I \
-    --cert ${LXD_API_CRT} \
-    --key ${LXD_API_KEY} \
+    --cert ~/.config/lxc/client.crt \
+    --key ~/.config/lxc/client.key \
     -H "Content-Type: application/json" \
     -X POST \
     -d @hello-ubuntu.json \
-    "${LXD_API_URL}/containers"
+    "https://127.0.0.1:8443/1.0/containers"
 ```
 where `hello-ubuntu.json` could contain:
 ```json
 {
     "name":"some-ubuntu",
-    "architecture":"x86_64",
-    "profiles":[
-        "default"
-    ],
     "ephemeral":true,
     "config":{
         "limits.cpu":"2"

From d48d18c1f17d28b50684c6bee3b8b22a07058d33 Mon Sep 17 00:00:00 2001
From: Andrew N Golovkov <andrew.golovkov at gmail.com>
Date: Sun, 24 Apr 2016 12:59:22 +0300
Subject: [PATCH 0014/1193] mistype in `lxc stop --help'

Signed-off-by: Andrew N Golovkov <andrew.golovkov at gmail.com>
---
 lxc/action.go | 2 +-
 po/de.po      | 2 +-
 po/fr.po      | 2 +-
 po/ja.po      | 2 +-
 po/lxd.pot    | 2 +-
 5 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxc/action.go b/lxc/action.go
index 355529af8..b91ad1ce4 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -37,7 +37,7 @@ func (c *actionCmd) flags() {
 		gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the container to shutdown."))
 	}
 	gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Store the container state (only for stop)."))
-	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only forstart)."))
+	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only for start)."))
 }
 
 func (c *actionCmd) run(config *lxd.Config, args []string) error {
diff --git a/po/de.po b/po/de.po
index d393ecb65..c5cb2f9ff 100644
--- a/po/de.po
+++ b/po/de.po
@@ -474,7 +474,7 @@ msgstr ""
 
 #: lxc/action.go:39
 #, fuzzy
-msgid "Ignore the container state (only forstart)."
+msgid "Ignore the container state (only for start)."
 msgstr "Herunterfahren des Containers erzwingen."
 
 #: lxc/image.go:273
diff --git a/po/fr.po b/po/fr.po
index 675552d80..c14dfcadb 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -410,7 +410,7 @@ msgstr ""
 
 #: lxc/action.go:39
 #, fuzzy
-msgid "Ignore the container state (only forstart)."
+msgid "Ignore the container state (only for start)."
 msgstr "Force l'arrêt du conteneur."
 
 #: lxc/image.go:273
diff --git a/po/ja.po b/po/ja.po
index 5a6e60a6d..eafb93ebd 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -441,7 +441,7 @@ msgid "Ignore aliases when determining what command to run."
 msgstr "コマンドを実行する際にエイリアスを無視します。"
 
 #: lxc/action.go:39
-msgid "Ignore the container state (only forstart)."
+msgid "Ignore the container state (only for start)."
 msgstr "コンテナの状態を無視します (startのみ)。"
 
 #: lxc/image.go:273
diff --git a/po/lxd.pot b/po/lxd.pot
index 98cf83f5b..ba75b6bf1 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -386,7 +386,7 @@ msgid   "Ignore aliases when determining what command to run."
 msgstr  ""
 
 #: lxc/action.go:39
-msgid   "Ignore the container state (only forstart)."
+msgid   "Ignore the container state (only for start)."
 msgstr  ""
 
 #: lxc/image.go:273

From 62938a33955b1f95b3a699928f0e2fc033b2d41a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 23 Apr 2016 07:53:42 +0200
Subject: [PATCH 0015/1193] lxc-to-lxd: Convert to stable pylxd API
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1901

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 30 ++++++++++++++++--------------
 1 file changed, 16 insertions(+), 14 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 88df3481d..8574e459a 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -5,8 +5,7 @@ import json
 import lxc
 import os
 import subprocess
-import time
-from pylxd import api as api
+from pylxd.client import Client
 
 
 # Fetch a config key as a list
@@ -76,7 +75,7 @@ def convert_container(container_name, args):
     # Connect to LXD
     if args.lxdpath:
         os.environ['LXD_DIR'] = args.lxdpath
-    lxd = api.API()
+    lxd = Client()
 
     print("==> Processing container: %s" % container_name)
 
@@ -107,9 +106,12 @@ def convert_container(container_name, args):
 
     # Make sure we don't have a conflict
     print("Checking for existing containers")
-    if lxd.container_defined(container_name):
+    try:
+        lxd.containers.get(container_name)
         print("Container already exists, skipping...")
         return False
+    except NameError:
+        pass
 
     # Validate lxc.utsname
     print("Validating container name")
@@ -314,14 +316,14 @@ def convert_container(container_name, args):
 
     # Set the container architecture if set in LXC
     print("Converting container architecture configuration")
-    arches = {'i686': 1,
-              'x86_64': 2,
-              'armhf': 3,
-              'arm64': 4,
-              'powerpc': 5,
-              'powerpc64': 6,
-              'ppc64el': 7,
-              's390x': 8}
+    arches = {'i686': "i686",
+              'x86_64': "x86_64",
+              'armhf': "armv7l",
+              'arm64': "aarch64",
+              'powerpc': "ppc",
+              'powerpc64': "ppc64",
+              'ppc64el': "ppc64le",
+              's390x': "s390x"}
 
     arch = None
     try:
@@ -344,11 +346,11 @@ def convert_container(container_name, args):
 
     try:
         print("Creating the container")
-        lxd.container_init(new)
+        lxd.containers.create(new, wait=True)
     except Exception as e:
+        raise
         print("Failed to create the container: %s" % e)
         return False
-    time.sleep(1)
 
     # Transfer the filesystem
     lxd_rootfs = os.path.join("/var/lib/lxd/", "containers",

From dc30c1e596b233dd2b3eaaea1c314c4d77541613 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 23 Apr 2016 10:06:44 -0400
Subject: [PATCH 0016/1193] Properly log image update failures
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/images.go b/lxd/images.go
index da3163ccd..1e28e40cc 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -841,6 +841,9 @@ func autoUpdateImages(d *Daemon) {
 		if hash == fp {
 			shared.Log.Debug("Already up to date", log.Ctx{"fp": fp})
 			continue
+		} else if err != nil {
+			shared.Log.Error("Failed to update the image", log.Ctx{"err": err, "fp": fp})
+			continue
 		}
 
 		newId, _, err := dbImageGet(d.db, hash, false, true)

From 64b99eafd7e132678f5d6b61fc9d34b188321078 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 23 Apr 2016 10:50:10 -0400
Subject: [PATCH 0017/1193] Better validate and rollback bad images
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1913

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go        | 22 +++++++++++++++++++---
 lxd/storage_btrfs.go |  1 +
 lxd/storage_lvm.go   |  7 ++++++-
 lxd/storage_zfs.go   |  3 +++
 4 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 1e28e40cc..28c8a90b1 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -107,8 +107,8 @@ func untarImage(imagefname string, destpath string) error {
 		return err
 	}
 
+	rootfsPath := fmt.Sprintf("%s/rootfs", destpath)
 	if shared.PathExists(imagefname + ".rootfs") {
-		rootfsPath := fmt.Sprintf("%s/rootfs", destpath)
 		err = os.MkdirAll(rootfsPath, 0755)
 		if err != nil {
 			return fmt.Errorf("Error creating rootfs directory")
@@ -120,6 +120,10 @@ func untarImage(imagefname string, destpath string) error {
 		}
 	}
 
+	if !shared.PathExists(rootfsPath) {
+		return fmt.Errorf("Image is missing a rootfs: %s", imagefname)
+	}
+
 	return nil
 }
 
@@ -598,6 +602,9 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 func imageBuildFromInfo(d *Daemon, info shared.ImageInfo) (metadata map[string]string, err error) {
 	err = d.Storage.ImageCreate(info.Fingerprint)
 	if err != nil {
+		os.Remove(shared.VarPath("images", info.Fingerprint))
+		os.Remove(shared.VarPath("images", info.Fingerprint) + ".rootfs")
+
 		return metadata, err
 	}
 
@@ -754,7 +761,7 @@ func getImageMetadata(fname string) (*imageMetadata, error) {
 
 	if err != nil {
 		outputLines := strings.Split(string(output), "\n")
-		return nil, fmt.Errorf("Could not extract image metadata %s from tar: %v (%s)", metadataName, err, outputLines[0])
+		return nil, fmt.Errorf("Could not extract image %s from tar: %v (%s)", metadataName, err, outputLines[0])
 	}
 
 	metadata := imageMetadata{}
@@ -764,6 +771,15 @@ func getImageMetadata(fname string) (*imageMetadata, error) {
 		return nil, fmt.Errorf("Could not parse %s: %v", metadataName, err)
 	}
 
+	_, err = shared.ArchitectureId(metadata.Architecture)
+	if err != nil {
+		return nil, err
+	}
+
+	if metadata.CreationDate == 0 {
+		return nil, fmt.Errorf("Missing creation date.")
+	}
+
 	return &metadata, nil
 }
 
@@ -930,7 +946,7 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 		}
 	}
 
-	// Remote the rootfs file
+	// Remove the rootfs file
 	fname = shared.VarPath("images", imgInfo.Fingerprint) + ".rootfs"
 	if shared.PathExists(fname) {
 		err = os.Remove(fname)
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 2c2779def..5093d4ce8 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -412,6 +412,7 @@ func (s *storageBtrfs) ImageCreate(fingerprint string) error {
 	}
 
 	if err := untarImage(imagePath, subvol); err != nil {
+		s.subvolDelete(subvol)
 		return err
 	}
 
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 2cf3e77f4..9f571aa46 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -807,7 +807,12 @@ func (s *storageLvm) ImageCreate(fingerprint string) error {
 			tempLVMountPoint, untarErr)
 	}
 
-	return untarErr
+	if untarErr != nil {
+		s.removeLV(fingerprint)
+		return untarErr
+	}
+
+	return nil
 }
 
 func (s *storageLvm) ImageDelete(fingerprint string) error {
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index d0bb903c6..b5989e82a 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -599,16 +599,19 @@ func (s *storageZfs) ImageCreate(fingerprint string) error {
 
 	err = untarImage(imagePath, subvol)
 	if err != nil {
+		s.zfsDestroy(fs)
 		return err
 	}
 
 	err = s.zfsSet(fs, "readonly", "on")
 	if err != nil {
+		s.zfsDestroy(fs)
 		return err
 	}
 
 	err = s.zfsSnapshotCreate(fs, "readonly")
 	if err != nil {
+		s.zfsDestroy(fs)
 		return err
 	}
 

From 616a7a8e8f3dd9e5d9c9de76035c84ec3ae4d4dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 23 Apr 2016 11:11:01 -0400
Subject: [PATCH 0018/1193] Send operation return value through SmartError
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/events.go     |  4 ++++
 lxd/operations.go | 11 ++++++++++-
 lxd/response.go   | 26 ++++++++++++++++++++++++++
 3 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/lxd/events.go b/lxd/events.go
index 08dad7913..6b10374d3 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -61,6 +61,10 @@ func (r *eventsServe) Render(w http.ResponseWriter) error {
 	return eventsSocket(r.req, w)
 }
 
+func (r *eventsServe) String() string {
+	return "event handler"
+}
+
 func eventsSocket(r *http.Request, w http.ResponseWriter) error {
 	listener := eventListener{}
 
diff --git a/lxd/operations.go b/lxd/operations.go
index 639bee33a..63e9ea90a 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -111,7 +111,7 @@ func (op *operation) Run() (chan error, error) {
 			if err != nil {
 				op.lock.Lock()
 				op.status = shared.Failure
-				op.err = err.Error()
+				op.err = SmartError(err).String()
 				op.lock.Unlock()
 				op.done()
 				chanRun <- err
@@ -541,6 +541,15 @@ func (r *operationWebSocket) Render(w http.ResponseWriter) error {
 	return err
 }
 
+func (r *operationWebSocket) String() string {
+	_, md, err := r.op.Render()
+	if err != nil {
+		return fmt.Sprintf("error: %s", err)
+	}
+
+	return md.Id
+}
+
 func operationAPIWebsocketGet(d *Daemon, r *http.Request) Response {
 	id := mux.Vars(r)["id"]
 	op, err := operationGet(id)
diff --git a/lxd/response.go b/lxd/response.go
index f4f3f48c8..59a7a667f 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -33,6 +33,7 @@ type asyncResp struct {
 
 type Response interface {
 	Render(w http.ResponseWriter) error
+	String() string
 }
 
 // Sync response
@@ -55,6 +56,14 @@ func SyncResponse(success bool, metadata interface{}) Response {
 	return &syncResponse{success, metadata}
 }
 
+func (r *syncResponse) String() string {
+	if r.success {
+		return "success"
+	}
+
+	return "failure"
+}
+
 var EmptySyncResponse = &syncResponse{true, make(map[string]interface{})}
 
 // File transfer response
@@ -141,6 +150,10 @@ func (r *fileResponse) Render(w http.ResponseWriter) error {
 	return err
 }
 
+func (r *fileResponse) String() string {
+	return fmt.Sprintf("%d files", len(r.files))
+}
+
 func FileResponse(r *http.Request, files []fileResponseEntry, headers map[string]string, removeAfterServe bool) Response {
 	return &fileResponse{r, files, headers, removeAfterServe}
 }
@@ -174,6 +187,15 @@ func (r *operationResponse) Render(w http.ResponseWriter) error {
 	return WriteJSON(w, body)
 }
 
+func (r *operationResponse) String() string {
+	_, md, err := r.op.Render()
+	if err != nil {
+		return fmt.Sprintf("error: %s", err)
+	}
+
+	return md.Id
+}
+
 func OperationResponse(op *operation) Response {
 	return &operationResponse{op}
 }
@@ -184,6 +206,10 @@ type errorResponse struct {
 	msg  string
 }
 
+func (r *errorResponse) String() string {
+	return r.msg
+}
+
 func (r *errorResponse) Render(w http.ResponseWriter) error {
 	var output io.Writer
 

From fc60aeb75bf563e8ad7a75b511ee6b354028be8f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 23 Apr 2016 12:21:41 -0400
Subject: [PATCH 0019/1193] list: Fix basic filtering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This solves part of #1917 by doing the following:
 - Update help to reflect the fact that we've only ever been matching
   the beginning of the container name.
 - Allow regular expressions to match container name too.
 - Document that regular expressions are allowed.
 - Fix remote name parsing when it contains a key=value where the key or
   value contain a colon.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 43 +++++++++++++++++++++++++------------------
 po/lxd.pot  | 48 +++++++++++++++++++++++++-----------------------
 2 files changed, 50 insertions(+), 41 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index 2c1f48cfe..0d725b8d1 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -70,12 +70,14 @@ func (c *listCmd) usage() string {
 lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]
 
 The filters are:
-* A single keyword like "web" which will list any container with "web" in its name.
+* A single keyword like "web" which will list any container with a name starting by "web".
+* A regular expression on the container name. (e.g. .*web.*01$)
 * A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:
-* "user.blah=abc" will list all containers with the "blah" user property set to "abc"
-* "u.blah=abc" will do the same
-* "security.privileged=1" will list all privileged containers
-* "s.privileged=1" will do the same
+ * "user.blah=abc" will list all containers with the "blah" user property set to "abc".
+ * "u.blah=abc" will do the same
+ * "security.privileged=1" will list all privileged containers
+ * "s.privileged=1" will do the same
+* A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*)
 
 Columns for table format are:
 * 4 - IPv4 address
@@ -160,7 +162,17 @@ func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool
 				return false
 			}
 		} else {
-			if !strings.Contains(state.Name, filter) {
+			regexpValue := filter
+			if !(strings.Contains(filter, "^") || strings.Contains(filter, "$")) {
+				regexpValue = "^" + regexpValue + "$"
+			}
+
+			r, err := regexp.Compile(regexpValue)
+			if err == nil && r.MatchString(state.Name) == true {
+				return true
+			}
+
+			if !strings.HasPrefix(state.Name, filter) {
 				return false
 			}
 		}
@@ -233,10 +245,6 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 	}
 
 	for _, cInfo := range cinfos {
-		if !c.shouldShow(filters, &cInfo) {
-			continue
-		}
-
 		for _, column := range columns {
 			if column.NeedsState && cInfo.IsActive() {
 				_, ok := cStates[cInfo.Name]
@@ -327,7 +335,7 @@ func (c *listCmd) run(config *lxd.Config, args []string) error {
 
 	if len(args) != 0 {
 		filters = args
-		if strings.Contains(args[0], ":") {
+		if strings.Contains(args[0], ":") && !strings.Contains(args[0], "=") {
 			remote, name = config.ParseRemoteAndContainer(args[0])
 			filters = args[1:]
 		} else if !strings.Contains(args[0], "=") {
@@ -335,6 +343,7 @@ func (c *listCmd) run(config *lxd.Config, args []string) error {
 			name = args[0]
 		}
 	}
+	filters = append(filters, name)
 
 	if remote == "" {
 		remote = config.DefaultRemote
@@ -351,14 +360,12 @@ func (c *listCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	if name == "" {
-		cts = ctslist
-	} else {
-		for _, cinfo := range ctslist {
-			if len(cinfo.Name) >= len(name) && cinfo.Name[0:len(name)] == name {
-				cts = append(cts, cinfo)
-			}
+	for _, cinfo := range ctslist {
+		if !c.shouldShow(filters, &cinfo) {
+			continue
 		}
+
+		cts = append(cts, cinfo)
 	}
 
 	columns_map := map[rune]column{
diff --git a/po/lxd.pot b/po/lxd.pot
index ba75b6bf1..9e3ab179c 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-04-18 15:47+0100\n"
+        "POT-Creation-Date: 2016-05-11 18:48-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -98,7 +98,7 @@ msgstr  ""
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:367
+#: lxc/list.go:374
 msgid   "ARCHITECTURE"
 msgstr  ""
 
@@ -145,7 +145,7 @@ msgstr  ""
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:368
+#: lxc/list.go:375
 msgid   "CREATED AT"
 msgstr  ""
 
@@ -179,7 +179,7 @@ msgstr  ""
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
-#: lxc/list.go:97 lxc/list.go:98
+#: lxc/list.go:99 lxc/list.go:100
 msgid   "Columns"
 msgstr  ""
 
@@ -281,7 +281,7 @@ msgstr  ""
 msgid   "Device %s removed from %s"
 msgstr  ""
 
-#: lxc/list.go:451
+#: lxc/list.go:458
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -330,7 +330,7 @@ msgstr  ""
 msgid   "FINGERPRINT"
 msgstr  ""
 
-#: lxc/list.go:100
+#: lxc/list.go:102
 msgid   "Fast mode (same as --columns=nsacPt"
 msgstr  ""
 
@@ -357,7 +357,7 @@ msgstr  ""
 msgid   "Force using the local unix socket."
 msgstr  ""
 
-#: lxc/list.go:99
+#: lxc/list.go:101
 msgid   "Format"
 msgstr  ""
 
@@ -365,11 +365,11 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:365
+#: lxc/list.go:372
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:366
+#: lxc/list.go:373
 msgid   "IPV6"
 msgstr  ""
 
@@ -385,7 +385,7 @@ msgstr  ""
 msgid   "Ignore aliases when determining what command to run."
 msgstr  ""
 
-#: lxc/action.go:39
+#: lxc/action.go:40
 msgid   "Ignore the container state (only for start)."
 msgstr  ""
 
@@ -473,12 +473,14 @@ msgid   "Lists the available resources.\n"
         "lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
         "\n"
         "The filters are:\n"
-        "* A single keyword like \"web\" which will list any container with \"web\" in its name.\n"
+        "* A single keyword like \"web\" which will list any container with a name starting by \"web\".\n"
+        "* A regular expression on the container name. (e.g. .*web.*01$)\n"
         "* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:\n"
-        "* \"user.blah=abc\" will list all containers with the \"blah\" user property set to \"abc\"\n"
-        "* \"u.blah=abc\" will do the same\n"
-        "* \"security.privileged=1\" will list all privileged containers\n"
-        "* \"s.privileged=1\" will do the same\n"
+        " * \"user.blah=abc\" will list all containers with the \"blah\" user property set to \"abc\".\n"
+        " * \"u.blah=abc\" will do the same\n"
+        " * \"security.privileged=1\" will list all privileged containers\n"
+        " * \"s.privileged=1\" will do the same\n"
+        "* A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*)\n"
         "\n"
         "Columns for table format are:\n"
         "* 4 - IPv4 address\n"
@@ -707,7 +709,7 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:369 lxc/remote.go:363
+#: lxc/list.go:376 lxc/remote.go:363
 msgid   "NAME"
 msgstr  ""
 
@@ -753,15 +755,15 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:453
+#: lxc/list.go:460
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:370
+#: lxc/list.go:377
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:371
+#: lxc/list.go:378
 msgid   "PROFILES"
 msgstr  ""
 
@@ -904,11 +906,11 @@ msgstr  ""
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:372
+#: lxc/list.go:379
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:373
+#: lxc/list.go:380
 msgid   "STATE"
 msgstr  ""
 
@@ -992,7 +994,7 @@ msgstr  ""
 msgid   "Stopping container failed!"
 msgstr  ""
 
-#: lxc/action.go:38
+#: lxc/action.go:39
 msgid   "Store the container state (only for stop)."
 msgstr  ""
 
@@ -1004,7 +1006,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:374
+#: lxc/list.go:381
 msgid   "TYPE"
 msgstr  ""
 

From 1a4d5adf223d31d2b7ff48045470f36957d4c597 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 23 Apr 2016 16:25:39 -0400
Subject: [PATCH 0020/1193] Tell the user how to launch a container on first
 start
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Related to #1931

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go |  3 ++-
 po/lxd.pot  | 10 +++++++---
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index 8bbcbc875..fa49434f0 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -143,7 +143,8 @@ func run() error {
 		}
 
 		if shared.PathExists("/var/lib/lxd/") {
-			fmt.Fprintf(os.Stderr, i18n.G("If this is your first time using LXD, you should also run: sudo lxd init")+"\n\n")
+			fmt.Fprintf(os.Stderr, i18n.G("If this is your first time using LXD, you should also run: sudo lxd init")+"\n")
+			fmt.Fprintf(os.Stderr, i18n.G("To start your first container, try: lxc launch ubuntu:16.04")+"\n\n")
 		}
 	}
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 9e3ab179c..c609fcd94 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -1034,6 +1034,10 @@ msgstr  ""
 msgid   "Timestamps:"
 msgstr  ""
 
+#: lxc/main.go:147
+msgid   "To start your first container, try: lxc launch ubuntu:16.04"
+msgstr  ""
+
 #: lxc/image.go:402
 #, c-format
 msgid   "Transferring image: %d%%"
@@ -1134,7 +1138,7 @@ msgstr  ""
 msgid   "enabled"
 msgstr  ""
 
-#: lxc/main.go:25 lxc/main.go:158
+#: lxc/main.go:25 lxc/main.go:159
 #, c-format
 msgid   "error: %v"
 msgstr  ""
@@ -1160,7 +1164,7 @@ msgstr  ""
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:265 lxc/main.go:269
+#: lxc/main.go:266 lxc/main.go:270
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
@@ -1202,7 +1206,7 @@ msgstr  ""
 msgid   "unreachable return reached"
 msgstr  ""
 
-#: lxc/main.go:198
+#: lxc/main.go:199
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 

From 6e97adeb4b6a5c99e870587274b42e9201d7062d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 23 Apr 2016 17:06:08 -0400
Subject: [PATCH 0021/1193] Redirect "remote" to "remote:" when not conflicting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1931

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/init.go   | 27 ++++++++++++++++++++++++++-
 lxc/launch.go |  2 ++
 po/lxd.pot    | 21 +++++++++++++--------
 3 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index 27446f581..9cad5f8ad 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -177,12 +177,13 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 		fmt.Printf(i18n.G("Creating %s")+"\n", name)
 	}
 
+	iremote, image = c.guessImage(config, d, remote, iremote, image)
+
 	if !initRequestedEmptyProfiles && len(profiles) == 0 {
 		resp, err = d.Init(name, iremote, image, nil, configMap, c.ephem)
 	} else {
 		resp, err = d.Init(name, iremote, image, &profiles, configMap, c.ephem)
 	}
-
 	if err != nil {
 		return err
 	}
@@ -252,3 +253,27 @@ func (c *initCmd) initProgressTracker(d *lxd.Client, operation string) {
 	}
 	go d.Monitor([]string{"operation"}, handler)
 }
+
+func (c *initCmd) guessImage(config *lxd.Config, d *lxd.Client, remote string, iremote string, image string) (string, string) {
+	if remote != iremote {
+		return iremote, image
+	}
+
+	_, ok := config.Remotes[image]
+	if !ok {
+		return iremote, image
+	}
+
+	target := d.GetAlias(image)
+	if target != "" {
+		return iremote, image
+	}
+
+	_, err := d.GetImageInfo(image)
+	if err == nil {
+		return iremote, image
+	}
+
+	fmt.Fprintf(os.Stderr, i18n.G("The local image '%s' couldn't be found, trying '%s:' instead.")+"\n", image, image)
+	return image, "default"
+}
diff --git a/lxc/launch.go b/lxc/launch.go
index c0658724e..5def52d41 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -75,6 +75,8 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 		profiles = append(profiles, p)
 	}
 
+	iremote, image = c.init.guessImage(config, d, remote, iremote, image)
+
 	if !initRequestedEmptyProfiles && len(profiles) == 0 {
 		resp, err = d.Init(name, iremote, image, nil, configMap, c.init.ephem)
 	} else {
diff --git a/po/lxd.pot b/po/lxd.pot
index c609fcd94..f1775caf8 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -200,7 +200,7 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/init.go:209
+#: lxc/init.go:210
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
@@ -250,7 +250,7 @@ msgstr  ""
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:177 lxc/launch.go:116
+#: lxc/init.go:177 lxc/launch.go:118
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
@@ -897,7 +897,7 @@ msgstr  ""
 msgid   "Resources:"
 msgstr  ""
 
-#: lxc/init.go:246
+#: lxc/init.go:247
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
@@ -976,7 +976,7 @@ msgstr  ""
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/launch.go:122
+#: lxc/launch.go:124
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -1022,6 +1022,11 @@ msgstr  ""
 msgid   "The device doesn't exist"
 msgstr  ""
 
+#: lxc/init.go:277
+#, c-format
+msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
+msgstr  ""
+
 #: lxc/publish.go:62
 msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
@@ -1043,7 +1048,7 @@ msgstr  ""
 msgid   "Transferring image: %d%%"
 msgstr  ""
 
-#: lxc/action.go:93 lxc/launch.go:130
+#: lxc/action.go:93 lxc/launch.go:132
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1106,7 +1111,7 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:109
+#: lxc/launch.go:111
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
@@ -1126,7 +1131,7 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:199 lxc/init.go:204 lxc/launch.go:93 lxc/launch.go:98
+#: lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
@@ -1148,7 +1153,7 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:113
+#: lxc/launch.go:115
 msgid   "got bad version"
 msgstr  ""
 

From 42baffd43079df94f29ffac3bd0efb8973da68ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 24 Apr 2016 14:06:17 -0400
Subject: [PATCH 0022/1193] Don't load the LXC config for snapshots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1935

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 4cd152451..70f483f5c 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -267,6 +267,11 @@ func (c *containerLXC) init() error {
 }
 
 func (c *containerLXC) initLXC() error {
+	// No need to go through all that for snapshots
+	if c.IsSnapshot() {
+		return nil
+	}
+
 	// Check if being called from a hook
 	if c.fromHook {
 		return fmt.Errorf("You can't use go-lxc from inside a LXC hook.")
@@ -1538,6 +1543,10 @@ var LxcMonitorStateError = fmt.Errorf("Monitor is hung")
 // Get lxc container state, with 1 second timeout
 // If we don't get a reply, assume the lxc monitor is hung
 func (c *containerLXC) getLxcState() (lxc.State, error) {
+	if c.IsSnapshot() {
+		return lxc.StateMap["STOPPED"], nil
+	}
+
 	monitor := make(chan lxc.State, 1)
 
 	go func(c *lxc.Container) {
@@ -1818,9 +1827,11 @@ func (c *containerLXC) Rename(newName string) error {
 
 	// Rename the logging path
 	os.RemoveAll(shared.LogPath(newName))
-	err := os.Rename(c.LogPath(), shared.LogPath(newName))
-	if err != nil {
-		return err
+	if shared.PathExists(c.LogPath()) {
+		err := os.Rename(c.LogPath(), shared.LogPath(newName))
+		if err != nil {
+			return err
+		}
 	}
 
 	// Rename the storage entry

From 28ddb7195ea575bd35bbe9684cae7acd45051467 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 25 Apr 2016 09:29:20 -0400
Subject: [PATCH 0023/1193] list: Allow filtering by unset key
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1917

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxc/list.go b/lxc/list.go
index 0d725b8d1..e0e809ff1 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -158,6 +158,10 @@ func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool
 				}
 			}
 
+			if state.ExpandedConfig[key] == value {
+				return true
+			}
+
 			if !found {
 				return false
 			}

From 6cd0be472c21304fa499760c7d76c5fe65c9a95f Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 25 Apr 2016 11:14:17 -0500
Subject: [PATCH 0024/1193] command line: fix example in `lxc launch`

Let's give a CLI example that actually launches a container successfully.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/launch.go |  2 +-
 po/lxd.pot    | 28 ++++++++++++++--------------
 2 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/lxc/launch.go b/lxc/launch.go
index 5def52d41..eea49297e 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -30,7 +30,7 @@ Not specifying -p will result in the default profile.
 Specifying "-p" with no argument will result in no profile.
 
 Example:
-lxc launch ubuntu u1`)
+lxc launch ubuntu:16.04 u1`)
 }
 
 func (c *launchCmd) flags() {
diff --git a/po/lxd.pot b/po/lxd.pot
index f1775caf8..54ff04255 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-05-11 18:48-0400\n"
+        "POT-Creation-Date: 2016-05-11 18:51-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -98,7 +98,7 @@ msgstr  ""
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:374
+#: lxc/list.go:378
 msgid   "ARCHITECTURE"
 msgstr  ""
 
@@ -145,7 +145,7 @@ msgstr  ""
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:375
+#: lxc/list.go:379
 msgid   "CREATED AT"
 msgstr  ""
 
@@ -281,7 +281,7 @@ msgstr  ""
 msgid   "Device %s removed from %s"
 msgstr  ""
 
-#: lxc/list.go:458
+#: lxc/list.go:462
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -365,11 +365,11 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:372
+#: lxc/list.go:376
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:373
+#: lxc/list.go:377
 msgid   "IPV6"
 msgstr  ""
 
@@ -454,7 +454,7 @@ msgid   "Launch a container from a particular image.\n"
         "Specifying \"-p\" with no argument will result in no profile.\n"
         "\n"
         "Example:\n"
-        "lxc launch ubuntu u1"
+        "lxc launch ubuntu:16.04 u1"
 msgstr  ""
 
 #: lxc/info.go:25
@@ -709,7 +709,7 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:376 lxc/remote.go:363
+#: lxc/list.go:380 lxc/remote.go:363
 msgid   "NAME"
 msgstr  ""
 
@@ -755,15 +755,15 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:460
+#: lxc/list.go:464
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:377
+#: lxc/list.go:381
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:378
+#: lxc/list.go:382
 msgid   "PROFILES"
 msgstr  ""
 
@@ -906,11 +906,11 @@ msgstr  ""
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:379
+#: lxc/list.go:383
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:380
+#: lxc/list.go:384
 msgid   "STATE"
 msgstr  ""
 
@@ -1006,7 +1006,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:381
+#: lxc/list.go:385
 msgid   "TYPE"
 msgstr  ""
 

From 6d9be60b634fbd16031b49b8459cd503a7a7aee7 Mon Sep 17 00:00:00 2001
From: KATOH Yasufumi <karma at jazz.email.ne.jp>
Date: Tue, 26 Apr 2016 14:26:45 +0900
Subject: [PATCH 0025/1193] i18n: Update Japanese translation and other po
 files

Signed-off-by: KATOH Yasufumi <karma at jazz.email.ne.jp>
---
 po/de.po | 119 +++++++++++++++++++++++++++------------------
 po/fr.po | 123 +++++++++++++++++++++++++++-------------------
 po/ja.po | 167 ++++++++++++++++++++++++++++++++++++---------------------------
 3 files changed, 242 insertions(+), 167 deletions(-)

diff --git a/po/de.po b/po/de.po
index c5cb2f9ff..38d1d3ed4 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-04-11 15:01-0400\n"
+"POT-Creation-Date: 2016-04-25 14:47-0500\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -133,7 +133,7 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 
-#: lxc/profile.go:226
+#: lxc/profile.go:251
 msgid "(none)"
 msgstr ""
 
@@ -145,7 +145,7 @@ msgstr ""
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:367
+#: lxc/list.go:378
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -193,7 +193,7 @@ msgstr ""
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:368
+#: lxc/list.go:379
 msgid "CREATED AT"
 msgstr ""
 
@@ -207,7 +207,7 @@ msgstr ""
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:343
+#: lxc/profile.go:417
 msgid "Cannot provide container name to list"
 msgstr ""
 
@@ -231,7 +231,7 @@ msgstr ""
 msgid "Client certificate stored at server: "
 msgstr "Gespeichertes Nutzerzertifikat auf dem Server: "
 
-#: lxc/list.go:97 lxc/list.go:98
+#: lxc/list.go:99 lxc/list.go:100
 msgid "Columns"
 msgstr ""
 
@@ -240,7 +240,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:190
+#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:215
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
@@ -253,7 +253,7 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:209
+#: lxc/init.go:210
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
@@ -313,7 +313,7 @@ msgstr ""
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:177 lxc/launch.go:116
+#: lxc/init.go:177 lxc/launch.go:118
 #, c-format
 msgid "Creating %s"
 msgstr ""
@@ -353,7 +353,7 @@ msgstr "Gerät %s wurde zu %s hinzugefügt\n"
 msgid "Device %s removed from %s"
 msgstr "Gerät %s wurde von %s entfernt\n"
 
-#: lxc/list.go:451
+#: lxc/list.go:462
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -410,7 +410,7 @@ msgstr ""
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/list.go:100
+#: lxc/list.go:102
 msgid "Fast mode (same as --columns=nsacPt"
 msgstr ""
 
@@ -442,7 +442,7 @@ msgstr ""
 msgid "Force using the local unix socket."
 msgstr ""
 
-#: lxc/list.go:99
+#: lxc/list.go:101
 msgid "Format"
 msgstr ""
 
@@ -451,11 +451,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Generiere Nutzerzertifikat. Dies kann wenige Minuten dauern...\n"
 
-#: lxc/list.go:365
+#: lxc/list.go:376
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:366
+#: lxc/list.go:377
 msgid "IPV6"
 msgstr ""
 
@@ -472,7 +472,7 @@ msgstr ""
 msgid "Ignore aliases when determining what command to run."
 msgstr ""
 
-#: lxc/action.go:39
+#: lxc/action.go:40
 #, fuzzy
 msgid "Ignore the container state (only for start)."
 msgstr "Herunterfahren des Containers erzwingen."
@@ -559,7 +559,7 @@ msgid ""
 "Specifying \"-p\" with no argument will result in no profile.\n"
 "\n"
 "Example:\n"
-"lxc launch ubuntu u1"
+"lxc launch ubuntu:16.04 u1"
 msgstr ""
 "Starte Container von gegebenem Abbild.\n"
 "\n"
@@ -599,15 +599,18 @@ msgid ""
 "lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
 "\n"
 "The filters are:\n"
-"* A single keyword like \"web\" which will list any container with \"web\" "
-"in its name.\n"
+"* A single keyword like \"web\" which will list any container with a name "
+"starting by \"web\".\n"
+"* A regular expression on the container name. (e.g. .*web.*01$)\n"
 "* A key/value pair referring to a configuration item. For those, the "
 "namespace can be abreviated to the smallest unambiguous identifier:\n"
-"* \"user.blah=abc\" will list all containers with the \"blah\" user property "
-"set to \"abc\"\n"
-"* \"u.blah=abc\" will do the same\n"
-"* \"security.privileged=1\" will list all privileged containers\n"
-"* \"s.privileged=1\" will do the same\n"
+" * \"user.blah=abc\" will list all containers with the \"blah\" user "
+"property set to \"abc\".\n"
+" * \"u.blah=abc\" will do the same\n"
+" * \"security.privileged=1\" will list all privileged containers\n"
+" * \"s.privileged=1\" will do the same\n"
+"* A regular expression matching a configuration item or its value. (e.g. "
+"volatile.eth0.hwaddr=00:16:3e:.*)\n"
 "\n"
 "Columns for table format are:\n"
 "* 4 - IPv4 address\n"
@@ -670,6 +673,7 @@ msgid ""
 "    Example: lxc profile edit <profile> # launch editor\n"
 "             cat profile.yml | lxc profile edit <profile> # read from "
 "profile.yml\n"
+"\n"
 "lxc profile apply <container> <profiles>\n"
 "    Apply a comma-separated list of profiles to a container, in order.\n"
 "    All profiles passed in this call (and only those) will be applied\n"
@@ -678,6 +682,8 @@ msgid ""
 "             lxc profile apply foo default # Only default is active\n"
 "             lxc profile apply '' # no profiles are applied anymore\n"
 "             lxc profile apply bar,default # Apply default second now\n"
+"lxc profile apply-add <container> <profile>\n"
+"lxc profile apply-remove <container> <profile>\n"
 "\n"
 "Devices:\n"
 "lxc profile device list <profile>                                   List "
@@ -903,8 +909,8 @@ msgid ""
 "\n"
 "\n"
 "lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] "
-"[prop=value]\n"
+"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
+"alias=ALIAS].. [prop=value]\n"
 "    Import an image tarball (or tarballs) into the LXD image store.\n"
 "\n"
 "lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
@@ -1000,7 +1006,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
-#: lxc/list.go:369 lxc/remote.go:363
+#: lxc/list.go:380 lxc/remote.go:363
 msgid "NAME"
 msgstr ""
 
@@ -1047,15 +1053,15 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:453
+#: lxc/list.go:464
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:370
+#: lxc/list.go:381
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:371
+#: lxc/list.go:382
 msgid "PROFILES"
 msgstr ""
 
@@ -1105,7 +1111,7 @@ msgstr ""
 "\n"
 "lxd help [—all]\n"
 
-#: lxc/profile.go:191
+#: lxc/profile.go:216
 msgid "Press enter to open the editor again"
 msgstr ""
 
@@ -1141,26 +1147,36 @@ msgstr ""
 msgid "Processes: %d"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:228
+#: lxc/profile.go:272
 #, fuzzy, c-format
-msgid "Profile %s applied to %s"
+msgid "Profile %s added to %s"
 msgstr "Profil %s wurde auf %s angewandt\n"
 
-#: lxc/profile.go:142
+#: lxc/profile.go:167
 #, fuzzy, c-format
 msgid "Profile %s created"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:212
+#: lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Profile %s deleted"
 msgstr "Profil %s gelöscht\n"
 
+#: lxc/profile.go:303
+#, fuzzy, c-format
+msgid "Profile %s removed from %s"
+msgstr "Gerät %s wurde von %s entfernt\n"
+
 #: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
 #, fuzzy
 msgid "Profile to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
+#: lxc/profile.go:253
+#, fuzzy, c-format
+msgid "Profiles %s applied to %s"
+msgstr "Profil %s wurde auf %s angewandt\n"
+
 #: lxc/info.go:101
 #, fuzzy, c-format
 msgid "Profiles: %s"
@@ -1205,7 +1221,7 @@ msgstr ""
 msgid "Resources:"
 msgstr ""
 
-#: lxc/init.go:246
+#: lxc/init.go:247
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
@@ -1214,11 +1230,11 @@ msgstr ""
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:372
+#: lxc/list.go:383
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:373
+#: lxc/list.go:384
 msgid "STATE"
 msgstr ""
 
@@ -1286,7 +1302,7 @@ msgstr ""
 msgid "Source:"
 msgstr ""
 
-#: lxc/launch.go:122
+#: lxc/launch.go:124
 #, c-format
 msgid "Starting %s"
 msgstr ""
@@ -1304,7 +1320,7 @@ msgstr ""
 msgid "Stopping container failed!"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/action.go:38
+#: lxc/action.go:39
 #, fuzzy
 msgid "Store the container state (only for stop)."
 msgstr "Herunterfahren des Containers erzwingen."
@@ -1317,7 +1333,7 @@ msgstr ""
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:374
+#: lxc/list.go:385
 msgid "TYPE"
 msgstr ""
 
@@ -1337,6 +1353,11 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
+#: lxc/init.go:277
+#, c-format
+msgid "The local image '%s' couldn't be found, trying '%s:' instead."
+msgstr ""
+
 #: lxc/publish.go:62
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
@@ -1350,12 +1371,16 @@ msgstr "Wartezeit bevor der Container gestoppt wird."
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
 
+#: lxc/main.go:147
+msgid "To start your first container, try: lxc launch ubuntu:16.04"
+msgstr ""
+
 #: lxc/image.go:402
 #, c-format
 msgid "Transferring image: %d%%"
 msgstr ""
 
-#: lxc/action.go:93 lxc/launch.go:130
+#: lxc/action.go:93 lxc/launch.go:132
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
@@ -1429,7 +1454,7 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:109
+#: lxc/launch.go:111
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "Falsche Anzahl an Objekten im Abbild, Container oder Sicherungspunkt gelesen."
@@ -1450,7 +1475,7 @@ msgstr ""
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:199 lxc/init.go:204 lxc/launch.go:93 lxc/launch.go:98
+#: lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
@@ -1462,7 +1487,7 @@ msgstr ""
 msgid "enabled"
 msgstr ""
 
-#: lxc/main.go:25 lxc/main.go:158
+#: lxc/main.go:25 lxc/main.go:159
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "Fehler: %v\n"
@@ -1472,7 +1497,7 @@ msgstr "Fehler: %v\n"
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/launch.go:113
+#: lxc/launch.go:115
 msgid "got bad version"
 msgstr "Versionskonflikt"
 
@@ -1489,7 +1514,7 @@ msgstr "nicht alle Profile der Quelle sind am Ziel vorhanden."
 msgid "ok (y/n)?"
 msgstr "OK (y/n)? "
 
-#: lxc/main.go:265 lxc/main.go:269
+#: lxc/main.go:266 lxc/main.go:270
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
@@ -1531,7 +1556,7 @@ msgstr ""
 msgid "unreachable return reached"
 msgstr ""
 
-#: lxc/main.go:198
+#: lxc/main.go:199
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
diff --git a/po/fr.po b/po/fr.po
index c14dfcadb..9f74b9d5c 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-04-11 15:01-0400\n"
+"POT-Creation-Date: 2016-04-25 14:47-0500\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -90,7 +90,7 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 
-#: lxc/profile.go:226
+#: lxc/profile.go:251
 msgid "(none)"
 msgstr ""
 
@@ -102,7 +102,7 @@ msgstr ""
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:367
+#: lxc/list.go:378
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -149,7 +149,7 @@ msgstr ""
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:368
+#: lxc/list.go:379
 msgid "CREATED AT"
 msgstr ""
 
@@ -163,7 +163,7 @@ msgstr ""
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:343
+#: lxc/profile.go:417
 msgid "Cannot provide container name to list"
 msgstr ""
 
@@ -184,7 +184,7 @@ msgstr "Change l'état du conteneur à %s.\n"
 msgid "Client certificate stored at server: "
 msgstr "Certificat client enregistré avec le serveur: "
 
-#: lxc/list.go:97 lxc/list.go:98
+#: lxc/list.go:99 lxc/list.go:100
 msgid "Columns"
 msgstr ""
 
@@ -192,7 +192,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:190
+#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:215
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
@@ -205,7 +205,7 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:209
+#: lxc/init.go:210
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
@@ -261,7 +261,7 @@ msgstr ""
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:177 lxc/launch.go:116
+#: lxc/init.go:177 lxc/launch.go:118
 #, c-format
 msgid "Creating %s"
 msgstr ""
@@ -295,7 +295,7 @@ msgstr ""
 msgid "Device %s removed from %s"
 msgstr ""
 
-#: lxc/list.go:451
+#: lxc/list.go:462
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -349,7 +349,7 @@ msgstr ""
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/list.go:100
+#: lxc/list.go:102
 msgid "Fast mode (same as --columns=nsacPt"
 msgstr ""
 
@@ -378,7 +378,7 @@ msgstr ""
 msgid "Force using the local unix socket."
 msgstr ""
 
-#: lxc/list.go:99
+#: lxc/list.go:101
 msgid "Format"
 msgstr ""
 
@@ -387,11 +387,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Géneration d'un certificat client. Ceci peut prendre une minute...\n"
 
-#: lxc/list.go:365
+#: lxc/list.go:376
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:366
+#: lxc/list.go:377
 msgid "IPV6"
 msgstr ""
 
@@ -408,7 +408,7 @@ msgstr ""
 msgid "Ignore aliases when determining what command to run."
 msgstr ""
 
-#: lxc/action.go:39
+#: lxc/action.go:40
 #, fuzzy
 msgid "Ignore the container state (only for start)."
 msgstr "Force l'arrêt du conteneur."
@@ -483,7 +483,7 @@ msgid ""
 "Specifying \"-p\" with no argument will result in no profile.\n"
 "\n"
 "Example:\n"
-"lxc launch ubuntu u1"
+"lxc launch ubuntu:16.04 u1"
 msgstr ""
 
 #: lxc/info.go:25
@@ -504,15 +504,18 @@ msgid ""
 "lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
 "\n"
 "The filters are:\n"
-"* A single keyword like \"web\" which will list any container with \"web\" "
-"in its name.\n"
+"* A single keyword like \"web\" which will list any container with a name "
+"starting by \"web\".\n"
+"* A regular expression on the container name. (e.g. .*web.*01$)\n"
 "* A key/value pair referring to a configuration item. For those, the "
 "namespace can be abreviated to the smallest unambiguous identifier:\n"
-"* \"user.blah=abc\" will list all containers with the \"blah\" user property "
-"set to \"abc\"\n"
-"* \"u.blah=abc\" will do the same\n"
-"* \"security.privileged=1\" will list all privileged containers\n"
-"* \"s.privileged=1\" will do the same\n"
+" * \"user.blah=abc\" will list all containers with the \"blah\" user "
+"property set to \"abc\".\n"
+" * \"u.blah=abc\" will do the same\n"
+" * \"security.privileged=1\" will list all privileged containers\n"
+" * \"s.privileged=1\" will do the same\n"
+"* A regular expression matching a configuration item or its value. (e.g. "
+"volatile.eth0.hwaddr=00:16:3e:.*)\n"
 "\n"
 "Columns for table format are:\n"
 "* 4 - IPv4 address\n"
@@ -559,6 +562,7 @@ msgid ""
 "    Example: lxc profile edit <profile> # launch editor\n"
 "             cat profile.yml | lxc profile edit <profile> # read from "
 "profile.yml\n"
+"\n"
 "lxc profile apply <container> <profiles>\n"
 "    Apply a comma-separated list of profiles to a container, in order.\n"
 "    All profiles passed in this call (and only those) will be applied\n"
@@ -567,6 +571,8 @@ msgid ""
 "             lxc profile apply foo default # Only default is active\n"
 "             lxc profile apply '' # no profiles are applied anymore\n"
 "             lxc profile apply bar,default # Apply default second now\n"
+"lxc profile apply-add <container> <profile>\n"
+"lxc profile apply-remove <container> <profile>\n"
 "\n"
 "Devices:\n"
 "lxc profile device list <profile>                                   List "
@@ -700,8 +706,8 @@ msgid ""
 "\n"
 "\n"
 "lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] "
-"[prop=value]\n"
+"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
+"alias=ALIAS].. [prop=value]\n"
 "    Import an image tarball (or tarballs) into the LXD image store.\n"
 "\n"
 "lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
@@ -792,7 +798,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr ""
 
-#: lxc/list.go:369 lxc/remote.go:363
+#: lxc/list.go:380 lxc/remote.go:363
 msgid "NAME"
 msgstr ""
 
@@ -840,15 +846,15 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:453
+#: lxc/list.go:464
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:370
+#: lxc/list.go:381
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:371
+#: lxc/list.go:382
 msgid "PROFILES"
 msgstr ""
 
@@ -895,7 +901,7 @@ msgid ""
 "lxd help [--all]"
 msgstr "Explique comment utiliser LXD.\n"
 
-#: lxc/profile.go:191
+#: lxc/profile.go:216
 msgid "Press enter to open the editor again"
 msgstr ""
 
@@ -928,25 +934,35 @@ msgstr "Montre le numéro de version de LXD.\n"
 msgid "Processes: %d"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:228
-#, c-format
-msgid "Profile %s applied to %s"
-msgstr ""
+#: lxc/profile.go:272
+#, fuzzy, c-format
+msgid "Profile %s added to %s"
+msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:142
+#: lxc/profile.go:167
 #, c-format
 msgid "Profile %s created"
 msgstr ""
 
-#: lxc/profile.go:212
+#: lxc/profile.go:237
 #, c-format
 msgid "Profile %s deleted"
 msgstr ""
 
+#: lxc/profile.go:303
+#, c-format
+msgid "Profile %s removed from %s"
+msgstr ""
+
 #: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
 msgid "Profile to apply to the new container"
 msgstr ""
 
+#: lxc/profile.go:253
+#, c-format
+msgid "Profiles %s applied to %s"
+msgstr ""
+
 #: lxc/info.go:101
 #, fuzzy, c-format
 msgid "Profiles: %s"
@@ -990,7 +1006,7 @@ msgstr ""
 msgid "Resources:"
 msgstr ""
 
-#: lxc/init.go:246
+#: lxc/init.go:247
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
@@ -999,11 +1015,11 @@ msgstr ""
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:372
+#: lxc/list.go:383
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:373
+#: lxc/list.go:384
 msgid "STATE"
 msgstr ""
 
@@ -1070,7 +1086,7 @@ msgstr ""
 msgid "Source:"
 msgstr ""
 
-#: lxc/launch.go:122
+#: lxc/launch.go:124
 #, c-format
 msgid "Starting %s"
 msgstr ""
@@ -1088,7 +1104,7 @@ msgstr ""
 msgid "Stopping container failed!"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/action.go:38
+#: lxc/action.go:39
 #, fuzzy
 msgid "Store the container state (only for stop)."
 msgstr "Force l'arrêt du conteneur."
@@ -1101,7 +1117,7 @@ msgstr ""
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:374
+#: lxc/list.go:385
 msgid "TYPE"
 msgstr ""
 
@@ -1121,6 +1137,11 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
+#: lxc/init.go:277
+#, c-format
+msgid "The local image '%s' couldn't be found, trying '%s:' instead."
+msgstr ""
+
 #: lxc/publish.go:62
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
@@ -1133,12 +1154,16 @@ msgstr "Temps d'attente avant de tuer le conteneur."
 msgid "Timestamps:"
 msgstr ""
 
+#: lxc/main.go:147
+msgid "To start your first container, try: lxc launch ubuntu:16.04"
+msgstr ""
+
 #: lxc/image.go:402
 #, c-format
 msgid "Transferring image: %d%%"
 msgstr ""
 
-#: lxc/action.go:93 lxc/launch.go:130
+#: lxc/action.go:93 lxc/launch.go:132
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
@@ -1215,7 +1240,7 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:109
+#: lxc/launch.go:111
 #, fuzzy
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr "nombre de propriété invalide pour la ressource"
@@ -1236,7 +1261,7 @@ msgstr ""
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:199 lxc/init.go:204 lxc/launch.go:93 lxc/launch.go:98
+#: lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
 #, fuzzy
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
@@ -1249,7 +1274,7 @@ msgstr ""
 msgid "enabled"
 msgstr ""
 
-#: lxc/main.go:25 lxc/main.go:158
+#: lxc/main.go:25 lxc/main.go:159
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "erreur: %v\n"
@@ -1259,7 +1284,7 @@ msgstr "erreur: %v\n"
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/launch.go:113
+#: lxc/launch.go:115
 msgid "got bad version"
 msgstr "reçu une version invalide"
 
@@ -1276,7 +1301,7 @@ msgstr ""
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:265 lxc/main.go:269
+#: lxc/main.go:266 lxc/main.go:270
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
@@ -1318,7 +1343,7 @@ msgstr ""
 msgid "unreachable return reached"
 msgstr "Un retour inacessible à été atteint"
 
-#: lxc/main.go:198
+#: lxc/main.go:199
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
diff --git a/po/ja.po b/po/ja.po
index eafb93ebd..fe4589f43 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-04-11 15:01-0400\n"
-"PO-Revision-Date: 2016-04-10 23:40+0900\n"
+"POT-Creation-Date: 2016-04-25 14:47-0500\n"
+"PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
 "Language: \n"
@@ -89,7 +89,7 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' はスナップショットの名前には使用できません。"
 
-#: lxc/profile.go:226
+#: lxc/profile.go:251
 msgid "(none)"
 msgstr ""
 
@@ -101,7 +101,7 @@ msgstr ""
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:367
+#: lxc/list.go:378
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -148,7 +148,7 @@ msgstr "送信バイト数"
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:368
+#: lxc/list.go:379
 msgid "CREATED AT"
 msgstr ""
 
@@ -162,7 +162,7 @@ msgstr "標準入力から読み込めません: %s"
 msgid "Can't unset key '%s', it's not currently set."
 msgstr "キー '%s' が指定されていないので削除できません。"
 
-#: lxc/profile.go:343
+#: lxc/profile.go:417
 msgid "Cannot provide container name to list"
 msgstr "コンテナ名を取得できません"
 
@@ -186,7 +186,7 @@ msgstr ""
 msgid "Client certificate stored at server: "
 msgstr "クライアント証明書がサーバに格納されました: "
 
-#: lxc/list.go:97 lxc/list.go:98
+#: lxc/list.go:99 lxc/list.go:100
 msgid "Columns"
 msgstr "カラムレイアウト"
 
@@ -194,7 +194,7 @@ msgstr "カラムレイアウト"
 msgid "Config key/value to apply to the new container"
 msgstr "設定キー/値の組を新しいコンテナに適用しました"
 
-#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:190
+#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:215
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
@@ -207,7 +207,7 @@ msgstr "接続が拒否されました。LXDが実行されていますか?"
 msgid "Container name is mandatory"
 msgstr "コンテナ名を指定する必要があります"
 
-#: lxc/init.go:209
+#: lxc/init.go:210
 #, c-format
 msgid "Container name is: %s"
 msgstr "コンテナ名: %s"
@@ -279,7 +279,7 @@ msgstr ""
 msgid "Created: %s"
 msgstr "作成日時: %s"
 
-#: lxc/init.go:177 lxc/launch.go:116
+#: lxc/init.go:177 lxc/launch.go:118
 #, c-format
 msgid "Creating %s"
 msgstr "%s を作成中"
@@ -320,7 +320,7 @@ msgstr "デバイス %s が %s に追加されました"
 msgid "Device %s removed from %s"
 msgstr "デバイス %s が %s から削除されました"
 
-#: lxc/list.go:451
+#: lxc/list.go:462
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -380,7 +380,7 @@ msgstr "失効日時: 失効しない"
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/list.go:100
+#: lxc/list.go:102
 msgid "Fast mode (same as --columns=nsacPt"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
@@ -411,7 +411,7 @@ msgstr "停止したコンテナを強制的に削除します。"
 msgid "Force using the local unix socket."
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
-#: lxc/list.go:99
+#: lxc/list.go:101
 msgid "Format"
 msgstr ""
 
@@ -419,11 +419,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "クライアント証明書を生成します。1分ぐらいかかります..."
 
-#: lxc/list.go:365
+#: lxc/list.go:376
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:366
+#: lxc/list.go:377
 msgid "IPV6"
 msgstr ""
 
@@ -440,7 +440,7 @@ msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要が
 msgid "Ignore aliases when determining what command to run."
 msgstr "コマンドを実行する際にエイリアスを無視します。"
 
-#: lxc/action.go:39
+#: lxc/action.go:40
 msgid "Ignore the container state (only for start)."
 msgstr "コンテナの状態を無視します (startのみ)。"
 
@@ -525,17 +525,19 @@ msgid ""
 "Specifying \"-p\" with no argument will result in no profile.\n"
 "\n"
 "Example:\n"
-"lxc launch ubuntu u1"
+"lxc launch ubuntu:16.04 u1"
 msgstr ""
 "指定したイメージからコンテナを起動します。\n"
 "\n"
-"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
+"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
 "\n"
 "指定したイメージと名前を使ってコンテナを起動します。\n"
 "\n"
 "-p を指定しない場合はデフォルトのプロファイルを使います。\n"
-"\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。"
+"\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n"
+"\n"
+"例:\n"
+"lxc launch ubuntu:16.04 u1"
 
 #: lxc/info.go:25
 msgid ""
@@ -562,15 +564,18 @@ msgid ""
 "lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
 "\n"
 "The filters are:\n"
-"* A single keyword like \"web\" which will list any container with \"web\" "
-"in its name.\n"
+"* A single keyword like \"web\" which will list any container with a name "
+"starting by \"web\".\n"
+"* A regular expression on the container name. (e.g. .*web.*01$)\n"
 "* A key/value pair referring to a configuration item. For those, the "
 "namespace can be abreviated to the smallest unambiguous identifier:\n"
-"* \"user.blah=abc\" will list all containers with the \"blah\" user property "
-"set to \"abc\"\n"
-"* \"u.blah=abc\" will do the same\n"
-"* \"security.privileged=1\" will list all privileged containers\n"
-"* \"s.privileged=1\" will do the same\n"
+" * \"user.blah=abc\" will list all containers with the \"blah\" user "
+"property set to \"abc\".\n"
+" * \"u.blah=abc\" will do the same\n"
+" * \"security.privileged=1\" will list all privileged containers\n"
+" * \"s.privileged=1\" will do the same\n"
+"* A regular expression matching a configuration item or its value. (e.g. "
+"volatile.eth0.hwaddr=00:16:3e:.*)\n"
 "\n"
 "Columns for table format are:\n"
 "* 4 - IPv4 address\n"
@@ -592,18 +597,17 @@ msgstr ""
 "lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
 "\n"
 "フィルタの指定:\n"
-"* 単一の \"web\" のようなキーワードを指定すると、名前に \"web\" が含まれるコ"
-"ンテ\n"
+"* 単一の \"web\" のようなキーワードを指定すると、名前が \"web\" ではじまるコンテ\n"
 "  ナが一覧表示されます。\n"
-"* 設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することが"
-"で\n"
+"* コンテナ名の正規表現 (例: .*web.*01$)\n"
+"* 設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することがで\n"
 "  きます:\n"
-"* \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定されて"
-"いる\n"
+" * \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定されている\n"
 "  コンテナをすべて一覧表示します。\n"
-"* \"u.blah\" は上記と同じ意味になります。\n"
-"* \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
-"* \"s.privilaged=1\" は上記と同じ意味になります。\n"
+" * \"u.blah=abc\" は上記と同じ意味になります。\n"
+" * \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
+" * \"s.privilaged=1\" は上記と同じ意味になります。\n"
+" * 設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:.*)\n"
 "\n"
 "表のカラムの指定:\n"
 "* 4 - IPv4 アドレス\n"
@@ -649,6 +653,7 @@ msgid ""
 "    Example: lxc profile edit <profile> # launch editor\n"
 "             cat profile.yml | lxc profile edit <profile> # read from "
 "profile.yml\n"
+"\n"
 "lxc profile apply <container> <profiles>\n"
 "    Apply a comma-separated list of profiles to a container, in order.\n"
 "    All profiles passed in this call (and only those) will be applied\n"
@@ -657,6 +662,8 @@ msgid ""
 "             lxc profile apply foo default # Only default is active\n"
 "             lxc profile apply '' # no profiles are applied anymore\n"
 "             lxc profile apply bar,default # Apply default second now\n"
+"lxc profile apply-add <container> <profile>\n"
+"lxc profile apply-remove <container> <profile>\n"
 "\n"
 "Devices:\n"
 "lxc profile device list <profile>                                   List "
@@ -695,8 +702,8 @@ msgstr ""
 "lxc profile edit <profile>\n"
 "    プロファイルを編集します。外部エディタもしくはSTDINから読み込みます。\n"
 "    例: lxc profile edit <profile> # エディタの起動\n"
-"        cat profile.yml | lxc profile edit <profile> # profile.yml から読み込"
-"み\n"
+"        cat profile.yml | lxc profile edit <profile> # profile.yml から読み込み\n"
+"\n"
 "lxc profile apply <container> <profiles>\n"
 "    プロファイルのコンマ区切りのリストをコンテナに順番に適用します。\n"
 "    このコマンドで指定したプロファイルだけが対象のコンテナに適用されます。\n"
@@ -704,6 +711,8 @@ msgstr ""
 "        lxc profile apply foo default # defaultだけを有効化\n"
 "        lxc profile apply '' # 一切のプロファイルを適用しない\n"
 "        lxc profile apply bar,default # defaultを2番目に適用\n"
+"lxc profile apply-add <container> <profile>\n"
+"lxc profile apply-remove <container> <profile>\n"
 "\n"
 "デバイス:\n"
 "lxc profile device list <profile>\n"
@@ -718,8 +727,7 @@ msgstr ""
 "    デバイスプロパティを設定します\n"
 "lxc profile device unset <[remote:]profile> <name> <key>\n"
 "    デバイスプロパティを削除します\n"
-"lxc profile device add <profile name> <device name> <device type> "
-"[key=value]...\n"
+"lxc profile device add <profile name> <device name> <device type> [key=value]...\n"
 "    ディスクやNICのようなプロファイルデバイスを指定したプロファイルを使って\n"
 "    コンテナに追加します。"
 
@@ -917,8 +925,8 @@ msgid ""
 "\n"
 "\n"
 "lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] "
-"[prop=value]\n"
+"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
+"alias=ALIAS].. [prop=value]\n"
 "    Import an image tarball (or tarballs) into the LXD image store.\n"
 "\n"
 "lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
@@ -975,14 +983,11 @@ msgstr ""
 "る場合は) エイリアスで参照できます。\n"
 "\n"
 "\n"
-"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] "
-"[prop=value]\n"
+"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS].. [prop=value]\n"
 "    イメージの tarball (複数も可能) を LXD のイメージストアにインポートしま\n"
 "    す。\n"
 "\n"
-"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
-"[--public] [--auto-update]\n"
+"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update]\n"
 "    ネットワーク経由である LXD デーモンから他の LXD デーモンへイメージを\n"
 "    コピーします。\n"
 "\n"
@@ -1019,8 +1024,7 @@ msgstr ""
 "    エイリアスを削除します。\n"
 "\n"
 "lxc image alias list [remote:] [filter]\n"
-"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリア"
-"ス\n"
+"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリアス\n"
 "    名の一部をフィルタとして指定できます。\n"
 
 #: lxc/info.go:147
@@ -1091,7 +1095,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "コンテナ名を指定する必要があります: "
 
-#: lxc/list.go:369 lxc/remote.go:363
+#: lxc/list.go:380 lxc/remote.go:363
 msgid "NAME"
 msgstr ""
 
@@ -1137,15 +1141,15 @@ msgstr "%s に出力されます"
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)"
 
-#: lxc/list.go:453
+#: lxc/list.go:464
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:370
+#: lxc/list.go:381
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:371
+#: lxc/list.go:382
 msgid "PROFILES"
 msgstr ""
 
@@ -1192,7 +1196,7 @@ msgstr ""
 "\n"
 "lxd help [--all]"
 
-#: lxc/profile.go:191
+#: lxc/profile.go:216
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
@@ -1227,25 +1231,35 @@ msgstr ""
 msgid "Processes: %d"
 msgstr "プロセス数: %d"
 
-#: lxc/profile.go:228
+#: lxc/profile.go:272
 #, c-format
-msgid "Profile %s applied to %s"
+msgid "Profile %s added to %s"
 msgstr "プロファイル %s が %s に追加されました"
 
-#: lxc/profile.go:142
+#: lxc/profile.go:167
 #, c-format
 msgid "Profile %s created"
 msgstr "プロファイル %s を作成しました"
 
-#: lxc/profile.go:212
+#: lxc/profile.go:237
 #, c-format
 msgid "Profile %s deleted"
 msgstr "プロファイル %s を削除しました"
 
+#: lxc/profile.go:303
+#, c-format
+msgid "Profile %s removed from %s"
+msgstr "プロファイル %s が %s から削除されました"
+
 #: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
 msgid "Profile to apply to the new container"
 msgstr "プロファイルが新しいコンテナに適用されました"
 
+#: lxc/profile.go:253
+#, c-format
+msgid "Profiles %s applied to %s"
+msgstr "プロファイル %s が %s に追加されました"
+
 #: lxc/info.go:101
 #, c-format
 msgid "Profiles: %s"
@@ -1293,7 +1307,7 @@ msgstr "ユーザの確認を要求する。"
 msgid "Resources:"
 msgstr "リソース:"
 
-#: lxc/init.go:246
+#: lxc/init.go:247
 #, c-format
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
@@ -1302,11 +1316,11 @@ msgstr "イメージの取得中: %s"
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:372
+#: lxc/list.go:383
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:373
+#: lxc/list.go:384
 msgid "STATE"
 msgstr ""
 
@@ -1383,7 +1397,7 @@ msgstr "スナップショット:"
 msgid "Source:"
 msgstr "取得元:"
 
-#: lxc/launch.go:122
+#: lxc/launch.go:124
 #, c-format
 msgid "Starting %s"
 msgstr "%s を起動中"
@@ -1401,7 +1415,7 @@ msgstr "実行中の場合、コンテナを停止します"
 msgid "Stopping container failed!"
 msgstr "コンテナの停止に失敗しました!"
 
-#: lxc/action.go:38
+#: lxc/action.go:39
 msgid "Store the container state (only for stop)."
 msgstr "コンテナの状態を保存します (stopのみ)。"
 
@@ -1413,7 +1427,7 @@ msgstr "Swap (現在値)"
 msgid "Swap (peak)"
 msgstr "Swap (ピーク)"
 
-#: lxc/list.go:374
+#: lxc/list.go:385
 msgid "TYPE"
 msgstr ""
 
@@ -1434,6 +1448,11 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "デバイスが存在しません"
 
+#: lxc/init.go:277
+#, c-format
+msgid "The local image '%s' couldn't be found, trying '%s:' instead."
+msgstr "ローカルイメージ '%s' が見つかりません。代わりに '%s:' を試してみてください。"
+
 #: lxc/publish.go:62
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
@@ -1449,12 +1468,18 @@ msgstr "コンテナを強制停止するまでの時間"
 msgid "Timestamps:"
 msgstr "タイムスタンプ:"
 
+#: lxc/main.go:147
+msgid "To start your first container, try: lxc launch ubuntu:16.04"
+msgstr ""
+"初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてくだ\n"
+"さい。"
+
 #: lxc/image.go:402
 #, c-format
 msgid "Transferring image: %d%%"
 msgstr "イメージを転送中: %d%%"
 
-#: lxc/action.go:93 lxc/launch.go:130
+#: lxc/action.go:93 lxc/launch.go:132
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr "更に情報を得るために `lxc info --show-log` を実行してみてください"
@@ -1520,7 +1545,7 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
-#: lxc/launch.go:109
+#: lxc/launch.go:111
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "イメージ、コンテナ、スナップショットのいずれかからスキャンされた数が不正"
@@ -1541,7 +1566,7 @@ msgstr "デフォルトのリモートは削除できません"
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:199 lxc/init.go:204 lxc/launch.go:93 lxc/launch.go:98
+#: lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
@@ -1555,7 +1580,7 @@ msgstr "無効"
 msgid "enabled"
 msgstr "有効"
 
-#: lxc/main.go:25 lxc/main.go:158
+#: lxc/main.go:25 lxc/main.go:159
 #, c-format
 msgid "error: %v"
 msgstr "エラー: %v"
@@ -1565,7 +1590,7 @@ msgstr "エラー: %v"
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/launch.go:113
+#: lxc/launch.go:115
 msgid "got bad version"
 msgstr "不正なバージョンを得ました"
 
@@ -1581,7 +1606,7 @@ msgstr "コピー元の全てのプロファイルがターゲットに存在し
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:265 lxc/main.go:269
+#: lxc/main.go:266 lxc/main.go:270
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr "エイリアスの処理が失敗しました %s\n"
@@ -1623,7 +1648,7 @@ msgstr "%s に取得しました"
 msgid "unreachable return reached"
 msgstr "到達しないはずのreturnに到達しました"
 
-#: lxc/main.go:198
+#: lxc/main.go:199
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 

From 9038c16763f5792f40e50a2bbd1553f99068b771 Mon Sep 17 00:00:00 2001
From: Serge Hallyn <serge.hallyn at ubuntu.com>
Date: Tue, 26 Apr 2016 15:59:05 -0500
Subject: [PATCH 0026/1193] cpuset: fall back to cpuset.cpus on older kernels

cpuset.effective_cpus is better because it does not include cpus which
we cannot use.  But it is not available on older kernels.  In that
case fall back to using cpuset.cpus and hoping for the best.

Closes #1929

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 lxd/devices.go | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index f25a02c33..0c95dc8ae 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -184,8 +184,12 @@ func deviceTaskBalance(d *Daemon) {
 	// Get effective cpus list - those are all guaranteed to be online
 	effectiveCpus, err := cGroupGet("cpuset", "/", "cpuset.effective_cpus")
 	if err != nil {
-		shared.Log.Error("Error reading host's cpuset.effective_cpus")
-		return
+		// Older kernel - use cpuset.cpus
+		effectiveCpus, err = cGroupGet("cpuset", "/", "cpuset.cpus")
+		if err != nil {
+			shared.Log.Error("Error reading host's cpuset.cpus")
+			return
+		}
 	}
 	err = cGroupSet("cpuset", "/lxc", "cpuset.cpus", effectiveCpus)
 	if err != nil && shared.PathExists("/sys/fs/cgroup/cpuset/lxc") {
@@ -193,7 +197,7 @@ func deviceTaskBalance(d *Daemon) {
 	}
 	cpus, err := parseCpuset(effectiveCpus)
 	if err != nil {
-		shared.Log.Error("Error parsing host's cpuset.effective_cpus", log.Ctx{"cpuset": effectiveCpus, "err": err})
+		shared.Log.Error("Error parsing host's cpu set", log.Ctx{"cpuset": effectiveCpus, "err": err})
 		return
 	}
 

From 4b4d08a3e4da253e839d494cb045681e93989d83 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Apr 2016 14:43:52 -0400
Subject: [PATCH 0027/1193] Properly validate the server configuration keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1939

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/api_1.0.go         | 113 ++++-------------
 lxd/certificates.go    |   2 +-
 lxd/containers_post.go |   5 +-
 lxd/daemon.go          | 245 +++++++------------------------------
 lxd/daemon_config.go   | 324 +++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/daemon_test.go     |  23 ++--
 lxd/db_images.go       |   2 +-
 lxd/db_test.go         |   3 +-
 lxd/db_update.go       |   6 +-
 lxd/images.go          |  32 +----
 lxd/main.go            |   6 +-
 lxd/storage_lvm.go     | 143 ++++++----------------
 lxd/storage_zfs.go     |  34 ++----
 13 files changed, 469 insertions(+), 469 deletions(-)
 create mode 100644 lxd/daemon_config.go

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index db3a8fb90..fd8b4f1c4 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net/http"
 	"os"
+	"reflect"
 	"syscall"
 
 	"gopkg.in/lxc/go-lxc.v2"
@@ -125,23 +126,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 
 		body["environment"] = env
 		body["public"] = false
-
-		serverConfig, err := d.ConfigValuesGet()
-		if err != nil {
-			return InternalError(err)
-		}
-
-		config := shared.Jmap{}
-
-		for key, value := range serverConfig {
-			if key == "core.trust_password" {
-				config[key] = true
-			} else {
-				config[key] = value
-			}
-		}
-
-		body["config"] = config
+		body["config"] = daemonConfigRender()
 	} else {
 		body["auth"] = "untrusted"
 		body["public"] = false
@@ -166,6 +151,14 @@ func api10Put(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
+	// Deal with special keys
+	for k, v := range req.Config {
+		config := daemonConfig[k]
+		if config != nil && config.hiddenValue && v == true {
+			req.Config[k] = oldConfig[k]
+		}
+	}
+
 	// Diff the configs
 	changedConfig := map[string]interface{}{}
 	for key, value := range oldConfig {
@@ -180,84 +173,26 @@ func api10Put(d *Daemon, r *http.Request) Response {
 		}
 	}
 
-	for key, value := range changedConfig {
-		if value == nil {
-			value = ""
+	for key, valueRaw := range changedConfig {
+		if valueRaw == nil {
+			valueRaw = ""
 		}
 
-		if !d.ConfigKeyIsValid(key) {
-			return BadRequest(fmt.Errorf("Bad server config key: '%s'", key))
+		s := reflect.ValueOf(valueRaw)
+		if !s.IsValid() || s.Kind() != reflect.String {
+			return BadRequest(fmt.Errorf("Invalid value type for '%s'", key))
 		}
 
-		if key == "core.trust_password" {
-			if value == true {
-				continue
-			}
-
-			err := d.PasswordSet(value.(string))
-			if err != nil {
-				return InternalError(err)
-			}
-		} else if key == "storage.lvm_vg_name" {
-			err := storageLVMSetVolumeGroupNameConfig(d, value.(string))
-			if err != nil {
-				return InternalError(err)
-			}
-			if err = d.SetupStorageDriver(); err != nil {
-				return InternalError(err)
-			}
-		} else if key == "storage.lvm_thinpool_name" {
-			err := storageLVMSetThinPoolNameConfig(d, value.(string))
-			if err != nil {
-				return InternalError(err)
-			}
-		} else if key == "storage.zfs_pool_name" {
-			err := storageZFSSetPoolNameConfig(d, value.(string))
-			if err != nil {
-				return InternalError(err)
-			}
-			if err = d.SetupStorageDriver(); err != nil {
-				return InternalError(err)
-			}
-		} else if key == "core.https_address" {
-			old_address, err := d.ConfigValueGet("core.https_address")
-			if err != nil {
-				return InternalError(err)
-			}
-
-			err = d.UpdateHTTPsPort(old_address, value.(string))
-			if err != nil {
-				return InternalError(err)
-			}
+		value := valueRaw.(string)
 
-			err = d.ConfigValueSet(key, value.(string))
-			if err != nil {
-				return InternalError(err)
-			}
-		} else if key == "core.proxy_https" || key == "core.proxy_http" || key == "core.proxy_ignore_hosts" {
-			err = d.ConfigValueSet(key, value.(string))
-			if err != nil {
-				return InternalError(err)
-			}
-
-			// Update the cached proxy function
-			d.updateProxy()
-
-			// Clear the simplestreams cache as it's tied to the old proxy config
-			imageStreamCacheLock.Lock()
-			for k, _ := range imageStreamCache {
-				delete(imageStreamCache, k)
-			}
-			imageStreamCacheLock.Unlock()
+		confKey, ok := daemonConfig[key]
+		if !ok {
+			return BadRequest(fmt.Errorf("Bad server config key: '%s'", key))
+		}
 
-		} else {
-			err := d.ConfigValueSet(key, value.(string))
-			if err != nil {
-				return InternalError(err)
-			}
-			if key == "images.remote_cache_expiry" {
-				d.pruneChan <- true
-			}
+		err := confKey.Set(d, value)
+		if err != nil {
+			return BadRequest(err)
 		}
 	}
 
diff --git a/lxd/certificates.go b/lxd/certificates.go
index e432f4a56..df3b70b55 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -140,7 +140,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 		}
 	}
 
-	if !d.isTrustedClient(r) && !d.PasswordCheck(req.Password) {
+	if !d.isTrustedClient(r) && d.PasswordCheck(req.Password) != nil {
 		return Forbidden
 	}
 
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 8668dd1c0..25e37ca82 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -122,8 +122,9 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 
 	run := func(op *operation) error {
 		if req.Source.Server != "" {
-			updateCached, _ := d.ConfigValueGet("images.auto_update_cached")
-			hash, err = d.ImageDownload(op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret, hash, true, updateCached != "false")
+			hash, err = d.ImageDownload(
+				op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret,
+				hash, true, daemonConfig["images.auto_update_cached"].GetBool())
 			if err != nil {
 				return err
 			}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 0a715802b..4e3f63549 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -2,7 +2,6 @@ package main
 
 import (
 	"bytes"
-	"crypto/rand"
 	"crypto/tls"
 	"crypto/x509"
 	"database/sql"
@@ -55,11 +54,6 @@ var cgSwapAccounting = false
 // UserNS
 var runningInUserns = false
 
-const (
-	pwSaltBytes = 32
-	pwHashBytes = 64
-)
-
 type Socket struct {
 	Socket      net.Listener
 	CloseOnExit bool
@@ -89,8 +83,6 @@ type Daemon struct {
 
 	devlxd *net.UnixListener
 
-	configValues map[string]string
-
 	MockMode  bool
 	SetupMode bool
 
@@ -113,14 +105,6 @@ type Command struct {
 	delete        func(d *Daemon, r *http.Request) Response
 }
 
-func (d *Daemon) updateProxy() {
-	d.proxy = shared.ProxyFromConfig(
-		d.configValues["core.proxy_https"],
-		d.configValues["core.proxy_http"],
-		d.configValues["core.proxy_ignore_hosts"],
-	)
-}
-
 func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) {
 	var err error
 
@@ -360,32 +344,27 @@ func (d *Daemon) createCmd(version string, c Command) {
 	})
 }
 
-func (d *Daemon) SetupStorageDriver() error {
-	lvmVgName, err := d.ConfigValueGet("storage.lvm_vg_name")
-	if err != nil {
-		return fmt.Errorf("Couldn't read config: %s", err)
-	}
+func (d *Daemon) SetupStorageDriver(driver string) error {
+	var err error
 
-	zfsPoolName, err := d.ConfigValueGet("storage.zfs_pool_name")
-	if err != nil {
-		return fmt.Errorf("Couldn't read config: %s", err)
-	}
+	lvmVgName := daemonConfig["storage.lvm_vg_name"].Get()
+	zfsPoolName := daemonConfig["storage.zfs_pool_name"].Get()
 
-	if lvmVgName != "" {
+	if driver == "lvm" || lvmVgName != "" {
 		d.Storage, err = newStorage(d, storageTypeLvm)
 		if err != nil {
 			shared.Logf("Could not initialize storage type LVM: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
-	} else if zfsPoolName != "" {
+	} else if driver == "zfs" || zfsPoolName != "" {
 		d.Storage, err = newStorage(d, storageTypeZfs)
 		if err != nil {
 			shared.Logf("Could not initialize storage type ZFS: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
-	} else if d.BackingFs == "btrfs" {
+	} else if driver == "btrfs" || d.BackingFs == "btrfs" {
 		d.Storage, err = newStorage(d, storageTypeBtrfs)
 		if err != nil {
 			shared.Logf("Could not initialize storage type btrfs: %s - falling back to dir", err)
@@ -445,9 +424,9 @@ func setupSharedMounts() error {
 func (d *Daemon) ListenAddresses() ([]string, error) {
 	addresses := make([]string, 0)
 
-	value, err := d.ConfigValueGet("core.https_address")
-	if err != nil || value == "" {
-		return addresses, err
+	value := daemonConfig["core.https_address"].Get()
+	if value == "" {
+		return addresses, nil
 	}
 
 	localHost, localPort, err := net.SplitHostPort(value)
@@ -502,7 +481,9 @@ func (d *Daemon) ListenAddresses() ([]string, error) {
 	return addresses, nil
 }
 
-func (d *Daemon) UpdateHTTPsPort(oldAddress string, newAddress string) error {
+func (d *Daemon) UpdateHTTPsPort(newAddress string) error {
+	oldAddress := daemonConfig["core.https_address"].Get()
+
 	if oldAddress == newAddress {
 		return nil
 	}
@@ -754,22 +735,26 @@ func (d *Daemon) Init() error {
 		return err
 	}
 
+	/* Load all config values from the database */
+	err = daemonConfigInit(d.db)
+	if err != nil {
+		return err
+	}
+
 	/* Setup the storage driver */
 	if !d.MockMode {
-		err = d.SetupStorageDriver()
+		err = d.SetupStorageDriver("")
 		if err != nil {
 			return fmt.Errorf("Failed to setup storage: %s", err)
 		}
 	}
 
-	/* Load all config values from the database */
-	_, err = d.ConfigValuesGet()
-	if err != nil {
-		return err
-	}
-
 	/* set the initial proxy function based on config values in the DB */
-	d.updateProxy()
+	d.proxy = shared.ProxyFromConfig(
+		daemonConfig["core.proxy_https"].Get(),
+		daemonConfig["core.proxy_http"].Get(),
+		daemonConfig["core.proxy_ignore_hosts"].Get(),
+	)
 
 	/* Setup /dev/lxd */
 	d.devlxd, err = createAndBindDevLxd()
@@ -902,11 +887,7 @@ func (d *Daemon) Init() error {
 		d.UnixSocket = &Socket{Socket: unixl, CloseOnExit: true}
 	}
 
-	listenAddr, err := d.ConfigValueGet("core.https_address")
-	if err != nil {
-		return err
-	}
-
+	listenAddr := daemonConfig["core.https_address"].Get()
 	if listenAddr != "" {
 		_, _, err := net.SplitHostPort(listenAddr)
 		if err != nil {
@@ -980,18 +961,9 @@ func (d *Daemon) Ready() error {
 		autoUpdateImages(d)
 
 		for {
-			interval, _ := d.ConfigValueGet("images.auto_update_interval")
-			if interval == "" {
-				interval = "6"
-			}
-
-			intervalInt, err := strconv.Atoi(interval)
-			if err != nil {
-				intervalInt = 0
-			}
-
-			if intervalInt > 0 {
-				timer := time.NewTimer(time.Duration(intervalInt) * time.Hour)
+			interval := daemonConfig["images.auto_update_interval"].GetInt64()
+			if interval > 0 {
+				timer := time.NewTimer(time.Duration(interval) * time.Hour)
 				timeChan := timer.C
 
 				select {
@@ -1101,160 +1073,31 @@ func (d *Daemon) Stop() error {
 	return err
 }
 
-// ConfigKeyIsValid returns if the given key is a known config value.
-func (d *Daemon) ConfigKeyIsValid(key string) bool {
-	switch key {
-	case "core.https_address":
-		return true
-	case "core.https_allowed_origin":
-		return true
-	case "core.https_allowed_methods":
-		return true
-	case "core.https_allowed_headers":
-		return true
-	case "core.proxy_https":
-		return true
-	case "core.proxy_http":
-		return true
-	case "core.proxy_ignore_hosts":
-		return true
-	case "core.trust_password":
-		return true
-	case "storage.lvm_vg_name":
-		return true
-	case "storage.lvm_thinpool_name":
-		return true
-	case "storage.lvm_fstype":
-		return true
-	case "storage.lvm_volume_size":
-		return true
-	case "storage.zfs_pool_name":
-		return true
-	case "images.remote_cache_expiry":
-		return true
-	case "images.compression_algorithm":
-		return true
-	case "images.auto_update_interval":
-		return true
-	case "images.auto_update_cached":
-		return true
-	}
-
-	return false
-}
-
-// ConfigValueGet returns a config value from the memory,
-// calls ConfigValuesGet if required.
-// It returns a empty result if the config key isn't given.
-func (d *Daemon) ConfigValueGet(key string) (string, error) {
-	if d.configValues == nil {
-		if _, err := d.ConfigValuesGet(); err != nil {
-			return "", err
-		}
-	}
-
-	if val, ok := d.configValues[key]; ok {
-		return val, nil
-	}
-
-	return "", nil
-}
-
-// ConfigValuesGet fetches all config values and stores them in memory.
-func (d *Daemon) ConfigValuesGet() (map[string]string, error) {
-	if d.configValues == nil {
-		var err error
-		d.configValues, err = dbConfigValuesGet(d.db)
-		if err != nil {
-			return d.configValues, err
-		}
-	}
-
-	return d.configValues, nil
-}
-
-// ConfigValueSet sets a new or updates a config value,
-// it updates the value in the DB and in memory.
-func (d *Daemon) ConfigValueSet(key string, value string) error {
-	if err := dbConfigValueSet(d.db, key, value); err != nil {
-		return err
-	}
-
-	if d.configValues == nil {
-		if _, err := d.ConfigValuesGet(); err != nil {
-			return err
-		}
-	}
+func (d *Daemon) PasswordCheck(password string) error {
+	value := daemonConfig["core.trust_password"].Get()
 
+	// No password set
 	if value == "" {
-		delete(d.configValues, key)
-	} else {
-		d.configValues[key] = value
+		return fmt.Errorf("No password is set")
 	}
 
-	return nil
-}
-
-// PasswordSet sets the password to the new value.
-func (d *Daemon) PasswordSet(password string) error {
-	shared.Log.Info("Setting new https password")
-	var value = password
-	if password != "" {
-		buf := make([]byte, pwSaltBytes)
-		_, err := io.ReadFull(rand.Reader, buf)
-		if err != nil {
-			return err
-		}
-
-		hash, err := scrypt.Key([]byte(password), buf, 1<<14, 8, 1, pwHashBytes)
-		if err != nil {
-			return err
-		}
-
-		buf = append(buf, hash...)
-		value = hex.EncodeToString(buf)
-	}
-
-	err := d.ConfigValueSet("core.trust_password", value)
+	// Compare the password
+	buff, err := hex.DecodeString(value)
 	if err != nil {
 		return err
 	}
 
-	return nil
-}
-
-// PasswordCheck checks if the given password is the same
-// as we have in the DB.
-func (d *Daemon) PasswordCheck(password string) bool {
-	value, err := d.ConfigValueGet("core.trust_password")
+	salt := buff[0:32]
+	hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, 64)
 	if err != nil {
-		shared.Log.Error("verifyAdminPwd", log.Ctx{"err": err})
-		return false
-	}
-
-	// No password set
-	if value == "" {
-		return false
+		return err
 	}
 
-	buff, err := hex.DecodeString(value)
-	if err != nil {
-		shared.Log.Error("hex decode failed", log.Ctx{"err": err})
-		return false
+	if !bytes.Equal(hash, buff[32:]) {
+		return fmt.Errorf("Bad password provided")
 	}
 
-	salt := buff[0:pwSaltBytes]
-	hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, pwHashBytes)
-	if err != nil {
-		shared.Log.Error("Failed to create hash to check", log.Ctx{"err": err})
-		return false
-	}
-	if !bytes.Equal(hash, buff[pwSaltBytes:]) {
-		shared.Log.Error("Bad password received", log.Ctx{"err": err})
-		return false
-	}
-	shared.Log.Debug("Verified the admin password")
-	return true
+	return nil
 }
 
 type lxdHttpServer struct {
@@ -1263,18 +1106,18 @@ type lxdHttpServer struct {
 }
 
 func (s *lxdHttpServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
-	allowedOrigin, _ := s.d.ConfigValueGet("core.https_allowed_origin")
+	allowedOrigin := daemonConfig["core.https_allowed_origin"].Get()
 	origin := req.Header.Get("Origin")
 	if allowedOrigin != "" && origin != "" {
 		rw.Header().Set("Access-Control-Allow-Origin", allowedOrigin)
 	}
 
-	allowedMethods, _ := s.d.ConfigValueGet("core.https_allowed_methods")
+	allowedMethods := daemonConfig["core.https_allowed_methods"].Get()
 	if allowedMethods != "" && origin != "" {
 		rw.Header().Set("Access-Control-Allow-Methods", allowedMethods)
 	}
 
-	allowedHeaders, _ := s.d.ConfigValueGet("core.https_allowed_headers")
+	allowedHeaders := daemonConfig["core.https_allowed_headers"].Get()
 	if allowedHeaders != "" && origin != "" {
 		rw.Header().Set("Access-Control-Allow-Headers", allowedHeaders)
 	}
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
new file mode 100644
index 000000000..a1e59bfa6
--- /dev/null
+++ b/lxd/daemon_config.go
@@ -0,0 +1,324 @@
+package main
+
+import (
+	"crypto/rand"
+	"database/sql"
+	"encoding/hex"
+	"fmt"
+	"io"
+	"os/exec"
+	"strconv"
+	"strings"
+	"sync"
+
+	"golang.org/x/crypto/scrypt"
+	log "gopkg.in/inconshreveable/log15.v2"
+
+	"github.com/lxc/lxd/shared"
+)
+
+var daemonConfigLock sync.Mutex
+var daemonConfig map[string]*daemonConfigKey
+
+type daemonConfigKey struct {
+	valueType    string
+	defaultValue string
+	validValues  []string
+	currentValue string
+	hiddenValue  bool
+
+	validator func(d *Daemon, key string, value string) error
+	setter    func(d *Daemon, key string, value string) (string, error)
+	trigger   func(d *Daemon, key string, value string)
+}
+
+func (k *daemonConfigKey) name() string {
+	name := ""
+
+	// Look for a matching entry in daemonConfig
+	daemonConfigLock.Lock()
+	for key, value := range daemonConfig {
+		if value == k {
+			name = key
+			break
+		}
+	}
+	daemonConfigLock.Unlock()
+
+	return name
+}
+
+func (k *daemonConfigKey) Validate(d *Daemon, value string) error {
+	// No need to validate when unsetting
+	if value == "" {
+		return nil
+	}
+
+	// Validate booleans
+	if k.valueType == "bool" && !shared.StringInSlice(strings.ToLower(value), []string{"true", "false", "1", "0", "yes", "no"}) {
+		return fmt.Errorf("Invalid value for a boolean: %s", value)
+	}
+
+	// Validate integers
+	if k.valueType == "int" {
+		_, err := strconv.ParseInt(value, 10, 64)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Check against valid values
+	if k.validValues != nil && !shared.StringInSlice(value, k.validValues) {
+		return fmt.Errorf("Invalid value, only the following values are allowed: %s", k.validValues)
+	}
+
+	// Run external validation function
+	if k.validator != nil {
+		err := k.validator(d, k.name(), value)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (k *daemonConfigKey) Set(d *Daemon, value string) error {
+	var name string
+
+	// Check if we are actually changing things
+	oldValue := k.currentValue
+	if oldValue == value {
+		return nil
+	}
+
+	// Validate the new value
+	err := k.Validate(d, value)
+	if err != nil {
+		return err
+	}
+
+	// Run external setting function
+	if k.setter != nil {
+		value, err = k.setter(d, k.name(), value)
+		if err != nil {
+			return err
+		}
+	}
+
+	// Get the configuration key and make sure daemonConfig is sane
+	name = k.name()
+	if name == "" {
+		return fmt.Errorf("Corrupted configuration cache")
+	}
+
+	// Actually apply the change
+	daemonConfigLock.Lock()
+	k.currentValue = value
+	daemonConfigLock.Unlock()
+
+	err = dbConfigValueSet(d.db, name, value)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (k *daemonConfigKey) Get() string {
+	value := k.currentValue
+
+	// Get the default value if not set
+	if value == "" {
+		value = k.defaultValue
+	}
+
+	return value
+}
+
+func (k *daemonConfigKey) GetBool() bool {
+	value := k.currentValue
+
+	// Get the default value if not set
+	if value == "" {
+		value = k.defaultValue
+	}
+
+	// Convert to boolean
+	if shared.StringInSlice(strings.ToLower(value), []string{"true", "1", "yes"}) {
+		return true
+	}
+
+	return false
+}
+
+func (k *daemonConfigKey) GetInt64() int64 {
+	value := k.currentValue
+
+	// Get the default value if not set
+	if value == "" {
+		value = k.defaultValue
+	}
+
+	// Convert to int64
+	ret, _ := strconv.ParseInt(value, 10, 64)
+	return ret
+}
+
+func daemonConfigInit(db *sql.DB) error {
+	// Set all the keys
+	daemonConfig = map[string]*daemonConfigKey{
+		"core.https_address":         &daemonConfigKey{valueType: "string", setter: daemonConfigSetAddress},
+		"core.https_allowed_headers": &daemonConfigKey{valueType: "string"},
+		"core.https_allowed_methods": &daemonConfigKey{valueType: "string"},
+		"core.https_allowed_origin":  &daemonConfigKey{valueType: "string"},
+		"core.proxy_http":            &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy},
+		"core.proxy_https":           &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy},
+		"core.proxy_ignore_hosts":    &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy},
+		"core.trust_password":        &daemonConfigKey{valueType: "string", hiddenValue: true, setter: daemonConfigSetPassword},
+
+		"images.auto_update_cached":    &daemonConfigKey{valueType: "bool", defaultValue: "true"},
+		"images.auto_update_interval":  &daemonConfigKey{valueType: "int", defaultValue: "6"},
+		"images.compression_algorithm": &daemonConfigKey{valueType: "string", validator: daemonConfigValidateCommand, defaultValue: "gzip"},
+		"images.remote_cache_expiry":   &daemonConfigKey{valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry},
+
+		"storage.lvm_fstype":        &daemonConfigKey{valueType: "string", defaultValue: "ext4", validValues: []string{"ext4", "xfs"}},
+		"storage.lvm_thinpool_name": &daemonConfigKey{valueType: "string", defaultValue: "LXDPool", validator: storageLVMValidateThinPoolName},
+		"storage.lvm_vg_name":       &daemonConfigKey{valueType: "string", validator: storageLVMValidateVolumeGroupName, setter: daemonConfigSetStorage},
+		"storage.lvm_volume_size":   &daemonConfigKey{valueType: "string", defaultValue: "10GiB"},
+		"storage.zfs_pool_name":     &daemonConfigKey{valueType: "string", validator: storageZFSValidatePoolName, setter: daemonConfigSetStorage},
+	}
+
+	// Load the values from the DB
+	dbValues, err := dbConfigValuesGet(db)
+	if err != nil {
+		return err
+	}
+
+	daemonConfigLock.Lock()
+	for k, v := range dbValues {
+		_, ok := daemonConfig[k]
+		if !ok {
+			shared.Log.Error("Found invalid configuration key in database", log.Ctx{"key": k})
+		}
+
+		daemonConfig[k].currentValue = v
+	}
+	daemonConfigLock.Unlock()
+
+	return nil
+}
+
+func daemonConfigRender() map[string]interface{} {
+	config := map[string]interface{}{}
+
+	// Turn the config into a JSON-compatible map
+	for k, v := range daemonConfig {
+		value := v.Get()
+		if value != v.defaultValue {
+			if v.hiddenValue {
+				config[k] = true
+			} else {
+				config[k] = value
+			}
+		}
+	}
+
+	return config
+}
+
+func daemonConfigSetPassword(d *Daemon, key string, value string) (string, error) {
+	// Nothing to do on unset
+	if value == "" {
+		return value, nil
+	}
+
+	// Hash the password
+	buf := make([]byte, 32)
+	_, err := io.ReadFull(rand.Reader, buf)
+	if err != nil {
+		return "", err
+	}
+
+	hash, err := scrypt.Key([]byte(value), buf, 1<<14, 8, 1, 64)
+	if err != nil {
+		return "", err
+	}
+
+	buf = append(buf, hash...)
+	value = hex.EncodeToString(buf)
+
+	return value, nil
+}
+
+func daemonConfigSetStorage(d *Daemon, key string, value string) (string, error) {
+	driver := ""
+
+	// Guess the driver name from the key
+	switch key {
+	case "storage.lvm_vg_name":
+		driver = "lvm"
+	case "storage.zfs_pool_name":
+		driver = "zfs"
+	}
+
+	// Should never actually hit this
+	if driver == "" {
+		return "", fmt.Errorf("Invalid storage key: %s", key)
+	}
+
+	// Update the current storage driver
+	err := d.SetupStorageDriver(driver)
+	if err != nil {
+		return "", err
+	}
+
+	return value, nil
+}
+
+func daemonConfigSetAddress(d *Daemon, key string, value string) (string, error) {
+	// Update the current https address
+	err := d.UpdateHTTPsPort(value)
+	if err != nil {
+		return "", err
+	}
+
+	return value, nil
+}
+
+func daemonConfigSetProxy(d *Daemon, key string, value string) (string, error) {
+	// Get the current config
+	config := map[string]string{}
+	config["core.proxy_https"] = daemonConfig["core.proxy_https"].Get()
+	config["core.proxy_http"] = daemonConfig["core.proxy_http"].Get()
+	config["core.proxy_ignore_hosts"] = daemonConfig["core.proxy_ignore_hosts"].Get()
+
+	// Apply the change
+	config[key] = value
+
+	// Update the cached proxy function
+	d.proxy = shared.ProxyFromConfig(
+		config["core.proxy_https"],
+		config["core.proxy_http"],
+		config["core.proxy_ignore_hosts"],
+	)
+
+	// Clear the simplestreams cache as it's tied to the old proxy config
+	imageStreamCacheLock.Lock()
+	for k, _ := range imageStreamCache {
+		delete(imageStreamCache, k)
+	}
+	imageStreamCacheLock.Unlock()
+
+	return value, nil
+}
+
+func daemonConfigTriggerExpiry(d *Daemon, key string, value string) {
+	// Trigger an image pruning run
+	d.pruneChan <- true
+}
+
+func daemonConfigValidateCommand(d *Daemon, key string, value string) error {
+	_, err := exec.LookPath(value)
+	return err
+}
diff --git a/lxd/daemon_test.go b/lxd/daemon_test.go
index f6c622cc2..948f58a94 100644
--- a/lxd/daemon_test.go
+++ b/lxd/daemon_test.go
@@ -4,23 +4,24 @@ func (suite *lxdTestSuite) Test_config_value_set_empty_removes_val() {
 	var err error
 	d := suite.d
 
-	err = d.ConfigValueSet("storage.lvm_vg_name", "foo")
+	err = daemonConfig["core.trust_password"].Set(d, "foo")
 	suite.Req.Nil(err)
 
-	val, err := d.ConfigValueGet("storage.lvm_vg_name")
-	suite.Req.Nil(err)
-	suite.Req.Equal(val, "foo")
+	val := daemonConfig["core.trust_password"].Get()
+	suite.Req.Equal(len(val), 192)
 
-	err = d.ConfigValueSet("storage.lvm_vg_name", "")
-	suite.Req.Nil(err)
+	valMap := daemonConfigRender()
+	value, present := valMap["core.trust_password"]
+	suite.Req.True(present)
+	suite.Req.Equal(value, true)
 
-	val, err = d.ConfigValueGet("storage.lvm_vg_name")
+	err = daemonConfig["core.trust_password"].Set(d, "")
 	suite.Req.Nil(err)
-	suite.Req.Equal(val, "")
 
-	valMap, err := d.ConfigValuesGet()
-	suite.Req.Nil(err)
+	val = daemonConfig["core.trust_password"].Get()
+	suite.Req.Equal(val, "")
 
-	_, present := valMap["storage.lvm_vg_name"]
+	valMap = daemonConfigRender()
+	_, present = valMap["core.trust_password"]
 	suite.Req.False(present)
 }
diff --git a/lxd/db_images.go b/lxd/db_images.go
index efaefd4ab..76ce7bb81 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -38,7 +38,7 @@ func dbImagesGet(db *sql.DB, public bool) ([]string, error) {
 	return results, nil
 }
 
-func dbImagesGetExpired(db *sql.DB, expiry int) ([]string, error) {
+func dbImagesGetExpired(db *sql.DB, expiry int64) ([]string, error) {
 	q := `SELECT fingerprint FROM images WHERE cached=1 AND creation_date<=strftime('%s', date('now', '-` + fmt.Sprintf("%d", expiry) + ` day'))`
 
 	var fp string
diff --git a/lxd/db_test.go b/lxd/db_test.go
index 64417172d..5a4d2a51c 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -374,8 +374,9 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	// Let's run the schema upgrades.
 	d := &Daemon{MockMode: true}
 	d.db = db
-	err = dbUpdate(d, 1)
+	daemonConfigInit(db)
 
+	err = dbUpdate(d, 1)
 	if err != nil {
 		t.Error("Error upgrading database schema!")
 		t.Fatal(err)
diff --git a/lxd/db_update.go b/lxd/db_update.go
index d5c695607..4b0df51f9 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -236,11 +236,13 @@ func dbUpdateFromV15(d *Daemon) error {
 		return err
 	}
 
-	vgName, err := d.ConfigValueGet("storage.lvm_vg_name")
+	err = daemonConfigInit(d.db)
 	if err != nil {
-		return fmt.Errorf("Error checking server config: %v", err)
+		return err
 	}
 
+	vgName := daemonConfig["storage.lvm_vg_name"].Get()
+
 	for _, cName := range cNames {
 		var lvLinkPath string
 		if strings.Contains(cName, shared.SnapshotDelimiter) {
diff --git a/lxd/images.go b/lxd/images.go
index 28c8a90b1..1a7879613 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -230,17 +230,8 @@ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq,
 	}
 	tarfile.Close()
 
-	compress, err := d.ConfigValueGet("images.compression_algorithm")
-	if err != nil {
-		return info, err
-	}
-
-	// Default to gzip for this
-	if compress == "" {
-		compress = "gzip"
-	}
-
 	var compressedPath string
+	compress := daemonConfig["images.compression_algorithm"].Get()
 	if compress != "none" {
 		compressedPath, err = compressFile(tarfile.Name(), compress)
 		if err != nil {
@@ -889,33 +880,22 @@ func autoUpdateImages(d *Daemon) {
 
 func pruneExpiredImages(d *Daemon) {
 	shared.Debugf("Pruning expired images")
-	expiry, err := d.ConfigValueGet("images.remote_cache_expiry")
-	if err != nil {
-		shared.Log.Error("Unable to read the images.remote_cache_expiry key")
-		return
-	}
-
-	if expiry == "" {
-		expiry = "10"
-	}
-
-	expiryInt, err := strconv.Atoi(expiry)
-	if err != nil {
-		shared.Log.Error("Invalid value for images.remote_cache_expiry", log.Ctx{"err": err})
-		return
-	}
 
-	images, err := dbImagesGetExpired(d.db, expiryInt)
+	// Get the list of expires images
+	expiry := daemonConfig["images.remote_cache_expiry"].GetInt64()
+	images, err := dbImagesGetExpired(d.db, expiry)
 	if err != nil {
 		shared.Log.Error("Unable to retrieve the list of expired images", log.Ctx{"err": err})
 		return
 	}
 
+	// Delete them
 	for _, fp := range images {
 		if err := doDeleteImage(d, fp); err != nil {
 			shared.Log.Error("Error deleting image", log.Ctx{"err": err, "fp": fp})
 		}
 	}
+
 	shared.Debugf("Done pruning expired images")
 }
 
diff --git a/lxd/main.go b/lxd/main.go
index 2065163e1..09f8a8b5a 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -473,12 +473,14 @@ func cmdActivateIfNeeded() error {
 		return err
 	}
 
-	// Look for network socket
-	value, err := d.ConfigValueGet("core.https_address")
+	/* Load all config values from the database */
+	err = daemonConfigInit(d.db)
 	if err != nil {
 		return err
 	}
 
+	// Look for network socket
+	value := daemonConfig["core.https_address"].Get()
 	if value != "" {
 		shared.Debugf("Daemon has core.https_address set, activating...")
 		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 9f571aa46..5fb501d26 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -17,9 +17,6 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-var storageLvmDefaultThinLVSize = "10GiB"
-var storageLvmDefaultThinPoolName = "LXDPool"
-
 func storageLVMCheckVolumeGroup(vgName string) error {
 	output, err := exec.Command("vgdisplay", "-s", vgName).CombinedOutput()
 	if err != nil {
@@ -53,18 +50,8 @@ func storageLVMThinpoolExists(vgName string, poolName string) (bool, error) {
 
 func storageLVMGetThinPoolUsers(d *Daemon) ([]string, error) {
 	results := []string{}
-	vgname, err := d.ConfigValueGet("storage.lvm_vg_name")
-	if err != nil {
-		return results, fmt.Errorf("Error getting lvm_vg_name config")
-	}
-	if vgname == "" {
-		return results, nil
-	}
-	poolname, err := d.ConfigValueGet("storage.lvm_thinpool_name")
-	if err != nil {
-		return results, fmt.Errorf("Error getting lvm_thinpool_name config")
-	}
-	if poolname == "" {
+
+	if daemonConfig["storage.lvm_vg_name"].Get() == "" {
 		return results, nil
 	}
 
@@ -72,6 +59,7 @@ func storageLVMGetThinPoolUsers(d *Daemon) ([]string, error) {
 	if err != nil {
 		return results, err
 	}
+
 	for _, cName := range cNames {
 		var lvLinkPath string
 		if strings.Contains(cName, shared.SnapshotDelimiter) {
@@ -100,63 +88,52 @@ func storageLVMGetThinPoolUsers(d *Daemon) ([]string, error) {
 	return results, nil
 }
 
-func storageLVMSetThinPoolNameConfig(d *Daemon, poolname string) error {
+func storageLVMValidateThinPoolName(d *Daemon, key string, value string) error {
 	users, err := storageLVMGetThinPoolUsers(d)
 	if err != nil {
 		return fmt.Errorf("Error checking if a pool is already in use: %v", err)
 	}
+
 	if len(users) > 0 {
 		return fmt.Errorf("Can not change LVM config. Images or containers are still using LVs: %v", users)
 	}
 
-	vgname, err := d.ConfigValueGet("storage.lvm_vg_name")
-	if err != nil {
-		return fmt.Errorf("Error getting lvm_vg_name config: %v", err)
-	}
-
-	if poolname != "" {
+	vgname := daemonConfig["storage.lvm_vg_name"].Get()
+	if value != "" {
 		if vgname == "" {
 			return fmt.Errorf("Can not set lvm_thinpool_name without lvm_vg_name set.")
 		}
 
-		poolExists, err := storageLVMThinpoolExists(vgname, poolname)
+		poolExists, err := storageLVMThinpoolExists(vgname, value)
 		if err != nil {
-			return fmt.Errorf("Error checking for thin pool '%s' in '%s': %v", poolname, vgname, err)
+			return fmt.Errorf("Error checking for thin pool '%s' in '%s': %v", value, vgname, err)
 		}
+
 		if !poolExists {
-			return fmt.Errorf("Pool '%s' does not exist in Volume Group '%s'", poolname, vgname)
+			return fmt.Errorf("Pool '%s' does not exist in Volume Group '%s'", value, vgname)
 		}
 	}
 
-	err = d.ConfigValueSet("storage.lvm_thinpool_name", poolname)
-	if err != nil {
-		return err
-	}
-
 	return nil
 }
 
-func storageLVMSetVolumeGroupNameConfig(d *Daemon, vgname string) error {
+func storageLVMValidateVolumeGroupName(d *Daemon, key string, value string) error {
 	users, err := storageLVMGetThinPoolUsers(d)
 	if err != nil {
 		return fmt.Errorf("Error checking if a pool is already in use: %v", err)
 	}
+
 	if len(users) > 0 {
 		return fmt.Errorf("Can not change LVM config. Images or containers are still using LVs: %v", users)
 	}
 
-	if vgname != "" {
-		err = storageLVMCheckVolumeGroup(vgname)
+	if value != "" {
+		err = storageLVMCheckVolumeGroup(value)
 		if err != nil {
 			return err
 		}
 	}
 
-	err = d.ConfigValueSet("storage.lvm_vg_name", vgname)
-	if err != nil {
-		return err
-	}
-
 	return nil
 }
 
@@ -210,10 +187,7 @@ func (s *storageLvm) Init(config map[string]interface{}) (storage, error) {
 	}
 
 	if config["vgName"] == nil {
-		vgName, err := s.d.ConfigValueGet("storage.lvm_vg_name")
-		if err != nil {
-			return s, fmt.Errorf("Error checking server config: %v", err)
-		}
+		vgName := daemonConfig["storage.lvm_vg_name"].Get()
 		if vgName == "" {
 			return s, fmt.Errorf("LVM isn't enabled")
 		}
@@ -345,17 +319,8 @@ func (s *storageLvm) ContainerCreateFromImage(
 		return err
 	}
 
-	var fstype string
-	fstype, err = s.d.ConfigValueGet("storage.lvm_fstype")
-	if err != nil {
-		return fmt.Errorf("Error checking server config, err=%v", err)
-	}
-
-	if fstype == "" {
-		fstype = "ext4"
-	}
-
 	// Generate a new xfs's UUID
+	fstype := daemonConfig["storage.lvm_fstype"].Get()
 	if fstype == "xfs" {
 		err := xfsGenerateNewUUID(lvpath)
 		if err != nil {
@@ -458,16 +423,9 @@ func (s *storageLvm) ContainerCopy(container container, sourceContainer containe
 func (s *storageLvm) ContainerStart(container container) error {
 	lvName := containerNameToLVName(container.Name())
 	lvpath := fmt.Sprintf("/dev/%s/%s", s.vgName, lvName)
-	fstype, err := s.d.ConfigValueGet("storage.lvm_fstype")
-	if err != nil {
-		return fmt.Errorf("Error checking server config, err=%v", err)
-	}
+	fstype := daemonConfig["storage.lvm_fstype"].Get()
 
-	if fstype == "" {
-		fstype = "ext4"
-	}
-
-	err = tryMount(lvpath, container.Path(), fstype, 0, "discard")
+	err := tryMount(lvpath, container.Path(), fstype, 0, "discard")
 	if err != nil {
 		return fmt.Errorf(
 			"Error mounting snapshot LV path='%s': %v",
@@ -702,17 +660,8 @@ func (s *storageLvm) ContainerSnapshotStart(container container) error {
 		}
 	}
 
-	var fstype string
-	fstype, err = s.d.ConfigValueGet("storage.lvm_fstype")
-	if err != nil {
-		return fmt.Errorf("Error checking server config, err=%v", err)
-	}
-
-	if fstype == "" {
-		fstype = "ext4"
-	}
-
 	// Generate a new xfs's UUID
+	fstype := daemonConfig["storage.lvm_fstype"].Get()
 	if fstype == "xfs" {
 		err := xfsGenerateNewUUID(lvpath)
 		if err != nil {
@@ -775,21 +724,11 @@ func (s *storageLvm) ImageCreate(fingerprint string) error {
 		}
 	}()
 
-	var fstype string
-	fstype, err = s.d.ConfigValueGet("storage.lvm_fstype")
-	if err != nil {
-		return fmt.Errorf("Error checking server config, err=%v", err)
-	}
-
-	if fstype == "" {
-		fstype = "ext4"
-	}
-
+	fstype := daemonConfig["storage.lvm_fstype"].Get()
 	err = tryMount(lvpath, tempLVMountPoint, fstype, 0, "discard")
 	if err != nil {
 		shared.Logf("Error mounting image LV for untarring: %v", err)
 		return fmt.Errorf("Error mounting image LV: %v", err)
-
 	}
 
 	untarErr := untarImage(finalName, tempLVMountPoint)
@@ -833,24 +772,26 @@ func (s *storageLvm) ImageDelete(fingerprint string) error {
 }
 
 func (s *storageLvm) createDefaultThinPool() (string, error) {
+	thinPoolName := daemonConfig["storage.lvm_thinpool_name"].Get()
+
 	// Create a tiny 1G thinpool
 	output, err := tryExec(
 		"lvcreate",
 		"--poolmetadatasize", "1G",
 		"-L", "1G",
 		"--thinpool",
-		fmt.Sprintf("%s/%s", s.vgName, storageLvmDefaultThinPoolName))
+		fmt.Sprintf("%s/%s", s.vgName, thinPoolName))
 
 	if err != nil {
 		s.log.Error(
 			"Could not create thin pool",
 			log.Ctx{
-				"name":   storageLvmDefaultThinPoolName,
+				"name":   thinPoolName,
 				"err":    err,
 				"output": string(output)})
 
 		return "", fmt.Errorf(
-			"Could not create LVM thin pool named %s", storageLvmDefaultThinPoolName)
+			"Could not create LVM thin pool named %s", thinPoolName)
 	}
 
 	// Grow it to the maximum VG size (two step process required by old LVM)
@@ -858,49 +799,41 @@ func (s *storageLvm) createDefaultThinPool() (string, error) {
 		"lvextend",
 		"--alloc", "anywhere",
 		"-l", "100%FREE",
-		fmt.Sprintf("%s/%s", s.vgName, storageLvmDefaultThinPoolName))
+		fmt.Sprintf("%s/%s", s.vgName, thinPoolName))
 
 	if err != nil {
 		s.log.Error(
 			"Could not grow thin pool",
 			log.Ctx{
-				"name":   storageLvmDefaultThinPoolName,
+				"name":   thinPoolName,
 				"err":    err,
 				"output": string(output)})
 
 		return "", fmt.Errorf(
-			"Could not grow LVM thin pool named %s", storageLvmDefaultThinPoolName)
+			"Could not grow LVM thin pool named %s", thinPoolName)
 	}
 
-	return storageLvmDefaultThinPoolName, nil
+	return thinPoolName, nil
 }
 
 func (s *storageLvm) createThinLV(lvname string) (string, error) {
-	poolname, err := s.d.ConfigValueGet("storage.lvm_thinpool_name")
-	if err != nil {
-		return "", fmt.Errorf("Error checking server config, err=%v", err)
-	}
+	var err error
 
+	poolname := daemonConfig["storage.lvm_thinpool_name"].Get()
 	if poolname == "" {
 		poolname, err = s.createDefaultThinPool()
 		if err != nil {
 			return "", fmt.Errorf("Error creating LVM thin pool: %v", err)
 		}
-		err = storageLVMSetThinPoolNameConfig(s.d, poolname)
+
+		err = storageLVMValidateThinPoolName(s.d, "", poolname)
 		if err != nil {
 			s.log.Error("Setting thin pool name", log.Ctx{"err": err})
 			return "", fmt.Errorf("Error setting LVM thin pool config: %v", err)
 		}
 	}
 
-	lvSize, err := s.d.ConfigValueGet("storage.lvm_volume_size")
-	if err != nil {
-		return "", fmt.Errorf("Error checking server config, err=%v", err)
-	}
-
-	if lvSize == "" {
-		lvSize = storageLvmDefaultThinLVSize
-	}
+	lvSize := daemonConfig["storage.lvm_volume_size"].Get()
 
 	output, err := tryExec(
 		"lvcreate",
@@ -908,7 +841,6 @@ func (s *storageLvm) createThinLV(lvname string) (string, error) {
 		"-n", lvname,
 		"--virtualsize", lvSize,
 		fmt.Sprintf("%s/%s", s.vgName, poolname))
-
 	if err != nil {
 		s.log.Error("Could not create LV", log.Ctx{"lvname": lvname, "output": string(output)})
 		return "", fmt.Errorf("Could not create thin LV named %s", lvname)
@@ -916,8 +848,7 @@ func (s *storageLvm) createThinLV(lvname string) (string, error) {
 
 	lvpath := fmt.Sprintf("/dev/%s/%s", s.vgName, lvname)
 
-	fstype, err := s.d.ConfigValueGet("storage.lvm_fstype")
-
+	fstype := daemonConfig["storage.lvm_fstype"].Get()
 	switch fstype {
 	case "xfs":
 		output, err = tryExec(
@@ -932,7 +863,7 @@ func (s *storageLvm) createThinLV(lvname string) (string, error) {
 	}
 
 	if err != nil {
-		s.log.Error("mkfs.ext4", log.Ctx{"output": string(output)})
+		s.log.Error("Filesystem creation failed", log.Ctx{"output": string(output)})
 		return "", fmt.Errorf("Error making filesystem on image LV: %v", err)
 	}
 
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index b5989e82a..9e7ae3ac0 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -34,11 +34,7 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 	}
 
 	if config["zfsPool"] == nil {
-		zfsPool, err := s.d.ConfigValueGet("storage.zfs_pool_name")
-		if err != nil {
-			return s, fmt.Errorf("Error checking server config: %v", err)
-		}
-
+		zfsPool := daemonConfig["storage.zfs_pool_name"].Get()
 		if zfsPool == "" {
 			return s, fmt.Errorf("ZFS isn't enabled")
 		}
@@ -167,16 +163,12 @@ func (s *storageZfs) ContainerCreateFromImage(container container, fingerprint s
 }
 
 func (s *storageZfs) ContainerCanRestore(container container, sourceContainer container) error {
-	fields := strings.SplitN(sourceContainer.Name(), shared.SnapshotDelimiter, 2)
-	cName := fields[0]
-	snapName := fmt.Sprintf("snapshot-%s", fields[1])
-
-	snapshots, err := s.zfsListSnapshots(fmt.Sprintf("containers/%s", cName))
+	snaps, err := container.Snapshots()
 	if err != nil {
 		return err
 	}
 
-	if snapshots[len(snapshots)-1] != snapName {
+	if snaps[len(snaps)-1].Name() != sourceContainer.Name() {
 		return fmt.Errorf("ZFS can only restore from the latest snapshot. Delete newer snapshots or copy the snapshot into a new container instead.")
 	}
 
@@ -1088,7 +1080,7 @@ func (s *storageZfs) zfsGetPoolUsers() ([]string, error) {
 }
 
 // Global functions
-func storageZFSSetPoolNameConfig(d *Daemon, poolname string) error {
+func storageZFSValidatePoolName(d *Daemon, key string, value string) error {
 	s := storageZfs{}
 
 	// Confirm the backend is working
@@ -1098,20 +1090,15 @@ func storageZFSSetPoolNameConfig(d *Daemon, poolname string) error {
 	}
 
 	// Confirm the new pool exists and is compatible
-	if poolname != "" {
-		err = s.zfsCheckPool(poolname)
+	if value != "" {
+		err = s.zfsCheckPool(value)
 		if err != nil {
 			return fmt.Errorf("Invalid ZFS pool: %v", err)
 		}
 	}
 
-	// Check if we're switching pools
-	oldPoolname, err := d.ConfigValueGet("storage.zfs_pool_name")
-	if err != nil {
-		return err
-	}
-
 	// Confirm the old pool isn't in use anymore
+	oldPoolname := daemonConfig["storage.zfs_pool_name"].Get()
 	if oldPoolname != "" {
 		s.zfsPool = oldPoolname
 
@@ -1124,13 +1111,6 @@ func storageZFSSetPoolNameConfig(d *Daemon, poolname string) error {
 			return fmt.Errorf("Can not change ZFS config. Images or containers are still using the ZFS pool: %v", users)
 		}
 	}
-	s.zfsPool = poolname
-
-	// All good, set the new pool name
-	err = d.ConfigValueSet("storage.zfs_pool_name", poolname)
-	if err != nil {
-		return err
-	}
 
 	return nil
 }

From a90871a7da18798022593dd3132efc4368bca940 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 27 Apr 2016 13:19:38 -0400
Subject: [PATCH 0028/1193] Fix daemonConfig handling of storage
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Storage functions unfortunately have the habit of reading the daemon
configuration, so we need to at least temporarily set daemonConfig to
the right value...

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go        | 10 +++++-----
 lxd/daemon_config.go | 25 +++++++++++--------------
 2 files changed, 16 insertions(+), 19 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 4e3f63549..8a150cc59 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -344,27 +344,27 @@ func (d *Daemon) createCmd(version string, c Command) {
 	})
 }
 
-func (d *Daemon) SetupStorageDriver(driver string) error {
+func (d *Daemon) SetupStorageDriver() error {
 	var err error
 
 	lvmVgName := daemonConfig["storage.lvm_vg_name"].Get()
 	zfsPoolName := daemonConfig["storage.zfs_pool_name"].Get()
 
-	if driver == "lvm" || lvmVgName != "" {
+	if lvmVgName != "" {
 		d.Storage, err = newStorage(d, storageTypeLvm)
 		if err != nil {
 			shared.Logf("Could not initialize storage type LVM: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
-	} else if driver == "zfs" || zfsPoolName != "" {
+	} else if zfsPoolName != "" {
 		d.Storage, err = newStorage(d, storageTypeZfs)
 		if err != nil {
 			shared.Logf("Could not initialize storage type ZFS: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
-	} else if driver == "btrfs" || d.BackingFs == "btrfs" {
+	} else if d.BackingFs == "btrfs" {
 		d.Storage, err = newStorage(d, storageTypeBtrfs)
 		if err != nil {
 			shared.Logf("Could not initialize storage type btrfs: %s - falling back to dir", err)
@@ -743,7 +743,7 @@ func (d *Daemon) Init() error {
 
 	/* Setup the storage driver */
 	if !d.MockMode {
-		err = d.SetupStorageDriver("")
+		err = d.SetupStorageDriver()
 		if err != nil {
 			return fmt.Errorf("Failed to setup storage: %s", err)
 		}
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index a1e59bfa6..44f6058e6 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -252,23 +252,20 @@ func daemonConfigSetPassword(d *Daemon, key string, value string) (string, error
 }
 
 func daemonConfigSetStorage(d *Daemon, key string, value string) (string, error) {
-	driver := ""
-
-	// Guess the driver name from the key
-	switch key {
-	case "storage.lvm_vg_name":
-		driver = "lvm"
-	case "storage.zfs_pool_name":
-		driver = "zfs"
-	}
+	// The storage driver looks at daemonConfig so just set it temporarily
+	daemonConfigLock.Lock()
+	oldValue := daemonConfig[key].Get()
+	daemonConfig[key].currentValue = value
+	daemonConfigLock.Unlock()
 
-	// Should never actually hit this
-	if driver == "" {
-		return "", fmt.Errorf("Invalid storage key: %s", key)
-	}
+	defer func() {
+		daemonConfigLock.Lock()
+		daemonConfig[key].currentValue = oldValue
+		daemonConfigLock.Unlock()
+	}()
 
 	// Update the current storage driver
-	err := d.SetupStorageDriver(driver)
+	err := d.SetupStorageDriver()
 	if err != nil {
 		return "", err
 	}

From fe66c05c411a2cdec0880ad12ee9791a7c80c342 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 27 Apr 2016 17:34:08 +0000
Subject: [PATCH 0029/1193] don't remove config file on forkmigrate

068313ad07d8971cd148a345463310227fcdad39 moved the config file to a
non-temp file, so let's not delete it.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index aee88c1b3..fdaab35a3 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -712,8 +712,6 @@ func MigrateContainer(args []string) error {
 	configPath := args[3]
 	imagesDir := args[4]
 
-	defer os.Remove(configPath)
-
 	c, err := lxc.NewContainer(name, lxcpath)
 	if err != nil {
 		return err

From f4a82b1e35f5b289dbe00d314d7ab5d56aa8f446 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 27 Apr 2016 17:24:03 -0400
Subject: [PATCH 0030/1193] lvm: Fix following config change
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_lvm.go | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 5fb501d26..7a4784fc9 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -819,8 +819,14 @@ func (s *storageLvm) createDefaultThinPool() (string, error) {
 func (s *storageLvm) createThinLV(lvname string) (string, error) {
 	var err error
 
+	vgname := daemonConfig["storage.lvm_vg_name"].Get()
 	poolname := daemonConfig["storage.lvm_thinpool_name"].Get()
-	if poolname == "" {
+	exists, err := storageLVMThinpoolExists(vgname, poolname)
+	if err != nil {
+		return "", err
+	}
+
+	if !exists {
 		poolname, err = s.createDefaultThinPool()
 		if err != nil {
 			return "", fmt.Errorf("Error creating LVM thin pool: %v", err)

From 4a4e148a86eb4b6ac5901b3c599ae88683f2c9ad Mon Sep 17 00:00:00 2001
From: Hiroki Kiyohara <hirokiky at gmail.com>
Date: Thu, 28 Apr 2016 10:35:36 +0900
Subject: [PATCH 0031/1193] Fixed Markdown Syntaxt

Signed-off-by: Hiroki KIYOHARA <hirokiky at gmail.com>
---
 doc/rest-api.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index a41a17877..7a5f859e6 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -604,6 +604,8 @@ HTTP code for this should be 202 (Accepted).
  * Operation: sync
  * Return: dict representing current state
 
+Output:
+
     {
         "type": "sync",
         "status": "Success",
@@ -1283,6 +1285,7 @@ Input:
  * Return: dict representing an alias description and target
 
 Output:
+
     {
         "name": "test",
         "description": "my description",

From 1bfd3b0b31629887cf0495177e08ea41d7fb0fa5 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 28 Apr 2016 08:41:42 -0600
Subject: [PATCH 0032/1193] don't fail early when removing disks

We should try to unmount /everything/, no matter what.

Closes #1964

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 70f483f5c..009dac1af 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4005,9 +4005,10 @@ func (c *containerLXC) removeDiskDevices() error {
 		_ = syscall.Unmount(filepath.Join(c.DevicesPath(), f.Name()), syscall.MNT_DETACH)
 
 		// Remove the entry
-		err := os.Remove(filepath.Join(c.DevicesPath(), f.Name()))
+		diskPath := filepath.Join(c.DevicesPath(), f.Name())
+		err := os.Remove(diskPath)
 		if err != nil {
-			return err
+			shared.Log.Error("Failed to remove disk device path", log.Ctx{"err": err, "path": diskPath})
 		}
 	}
 

From 727e91297122b002bf03a1d2a0bb063ebb405b2a Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 28 Apr 2016 11:34:06 -0600
Subject: [PATCH 0033/1193] devices cleanup: don't recursively delete devices

the remove{Unix,Char}Devices when successful should leave this directory
empty, and when they're not succesful the recursiveness has the potential
to delete external data if things aren't unmounted correctly.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 009dac1af..ffcd6fe39 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1773,7 +1773,7 @@ func (c *containerLXC) cleanup() {
 	SeccompDeleteProfile(c)
 
 	// Remove the devices path
-	os.RemoveAll(c.DevicesPath())
+	os.Remove(c.DevicesPath())
 
 	// Remove the shmounts path
 	os.RemoveAll(shared.VarPath("shmounts", c.Name()))

From 1223ba34a0e21ad402ce9f1ab8c5b7643b970d62 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 28 Apr 2016 11:51:24 -0600
Subject: [PATCH 0034/1193] as with disk devices, don't fail when some unix
 devices fail to be deleted

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ffcd6fe39..5ec9e8e05 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3555,9 +3555,10 @@ func (c *containerLXC) removeUnixDevices() error {
 		}
 
 		// Remove the entry
-		err := os.Remove(filepath.Join(c.DevicesPath(), f.Name()))
+		devicePath := filepath.Join(c.DevicesPath(), f.Name())
+		err := os.Remove(devicePath)
 		if err != nil {
-			return err
+			shared.Log.Error("failed removing unix device", log.Ctx{"err": err, "path": devicePath})
 		}
 	}
 

From 7c06c363bb22e97a5a624618d27e31dd3152256e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 27 Apr 2016 20:19:10 -0400
Subject: [PATCH 0035/1193] Use the same key check for unix-char and unix-block
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go | 19 +------------------
 1 file changed, 1 insertion(+), 18 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index dd10b3065..89b7ac11a 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -112,24 +112,7 @@ func containerValidDeviceConfigKey(t, k string) bool {
 	}
 
 	switch t {
-	case "unix-char":
-		switch k {
-		case "gid":
-			return true
-		case "major":
-			return true
-		case "minor":
-			return true
-		case "mode":
-			return true
-		case "path":
-			return true
-		case "uid":
-			return true
-		default:
-			return false
-		}
-	case "unix-block":
+	case "unix-char", "unix-block":
 		switch k {
 		case "gid":
 			return true

From 2fe4c0a19becdc116809fec4f692298c621a7d56 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 28 Apr 2016 18:45:36 -0400
Subject: [PATCH 0036/1193] Allow removing when fs object no longer exists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This allows for the removal of container, image or snapshot that have
been manually removed from the filesystem.

Closes #1967

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_btrfs.go |  18 +++++--
 lxd/storage_dir.go   |   4 ++
 lxd/storage_zfs.go   | 139 +++++++++++++++++++++++++++------------------------
 3 files changed, 91 insertions(+), 70 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 5093d4ce8..e9903c0f5 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -122,10 +122,12 @@ func (s *storageBtrfs) ContainerDelete(container container) error {
 	}
 
 	// Then the directory (if it still exists).
-	err := os.RemoveAll(cPath)
-	if err != nil {
-		s.log.Error("ContainerDelete: failed", log.Ctx{"cPath": cPath, "err": err})
-		return fmt.Errorf("Error cleaning up %s: %s", cPath, err)
+	if shared.PathExists(cPath) {
+		err := os.RemoveAll(cPath)
+		if err != nil {
+			s.log.Error("ContainerDelete: failed", log.Ctx{"cPath": cPath, "err": err})
+			return fmt.Errorf("Error cleaning up %s: %s", cPath, err)
+		}
 	}
 
 	return nil
@@ -423,7 +425,13 @@ func (s *storageBtrfs) ImageDelete(fingerprint string) error {
 	imagePath := shared.VarPath("images", fingerprint)
 	subvol := fmt.Sprintf("%s.btrfs", imagePath)
 
-	return s.subvolDelete(subvol)
+	if s.isSubvolume(subvol) {
+		if err := s.subvolsDelete(subvol); err != nil {
+			return err
+		}
+	}
+
+	return nil
 }
 
 func (s *storageBtrfs) subvolCreate(subvol string) error {
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 652d7ebe4..f0c892ee1 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -82,6 +82,10 @@ func (s *storageDir) ContainerCanRestore(container container, sourceContainer co
 func (s *storageDir) ContainerDelete(container container) error {
 	cPath := container.Path()
 
+	if !shared.PathExists(cPath) {
+		return nil
+	}
+
 	err := os.RemoveAll(cPath)
 	if err != nil {
 		// RemovaAll fails on very long paths, so attempt an rm -Rf
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 9e7ae3ac0..db6e6d515 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -178,61 +178,63 @@ func (s *storageZfs) ContainerCanRestore(container container, sourceContainer co
 func (s *storageZfs) ContainerDelete(container container) error {
 	fs := fmt.Sprintf("containers/%s", container.Name())
 
-	removable := true
-	snaps, err := s.zfsListSnapshots(fs)
-	if err != nil {
-		return err
-	}
-
-	for _, snap := range snaps {
-		var err error
-		removable, err = s.zfsSnapshotRemovable(fs, snap)
+	if s.zfsExists(fs) {
+		removable := true
+		snaps, err := s.zfsListSnapshots(fs)
 		if err != nil {
 			return err
 		}
 
-		if !removable {
-			break
-		}
-	}
+		for _, snap := range snaps {
+			var err error
+			removable, err = s.zfsSnapshotRemovable(fs, snap)
+			if err != nil {
+				return err
+			}
 
-	if removable {
-		origin, err := s.zfsGet(fs, "origin")
-		if err != nil {
-			return err
+			if !removable {
+				break
+			}
 		}
-		origin = strings.TrimPrefix(origin, fmt.Sprintf("%s/", s.zfsPool))
 
-		err = s.zfsDestroy(fs)
-		if err != nil {
-			return err
-		}
+		if removable {
+			origin, err := s.zfsGet(fs, "origin")
+			if err != nil {
+				return err
+			}
+			origin = strings.TrimPrefix(origin, fmt.Sprintf("%s/", s.zfsPool))
 
-		err = s.zfsCleanup(origin)
-		if err != nil {
-			return err
-		}
-	} else {
-		err := s.zfsSet(fs, "mountpoint", "none")
-		if err != nil {
-			return err
-		}
+			err = s.zfsDestroy(fs)
+			if err != nil {
+				return err
+			}
 
-		err = s.zfsRename(fs, fmt.Sprintf("deleted/containers/%s", uuid.NewRandom().String()))
-		if err != nil {
-			return err
+			err = s.zfsCleanup(origin)
+			if err != nil {
+				return err
+			}
+		} else {
+			err := s.zfsSet(fs, "mountpoint", "none")
+			if err != nil {
+				return err
+			}
+
+			err = s.zfsRename(fs, fmt.Sprintf("deleted/containers/%s", uuid.NewRandom().String()))
+			if err != nil {
+				return err
+			}
 		}
 	}
 
 	if shared.PathExists(shared.VarPath(fs)) {
-		os.Remove(shared.VarPath(fs))
+		err := os.Remove(shared.VarPath(fs))
 		if err != nil {
 			return err
 		}
 	}
 
 	if shared.PathExists(shared.VarPath(fs) + ".zfs") {
-		os.Remove(shared.VarPath(fs) + ".zfs")
+		err := os.Remove(shared.VarPath(fs) + ".zfs")
 		if err != nil {
 			return err
 		}
@@ -448,27 +450,32 @@ func (s *storageZfs) ContainerSnapshotDelete(snapshotContainer container) error
 	cName := fields[0]
 	snapName := fmt.Sprintf("snapshot-%s", fields[1])
 
-	removable, err := s.zfsSnapshotRemovable(fmt.Sprintf("containers/%s", cName), snapName)
-	if removable {
-		err = s.zfsSnapshotDestroy(fmt.Sprintf("containers/%s", cName), snapName)
-		if err != nil {
-			return err
+	if s.zfsExists(fmt.Sprintf("containers/%s@%s", cName, snapName)) {
+		removable, err := s.zfsSnapshotRemovable(fmt.Sprintf("containers/%s", cName), snapName)
+		if removable {
+			err = s.zfsSnapshotDestroy(fmt.Sprintf("containers/%s", cName), snapName)
+			if err != nil {
+				return err
+			}
+		} else {
+			err = s.zfsSnapshotRename(fmt.Sprintf("containers/%s", cName), snapName, fmt.Sprintf("copy-%s", uuid.NewRandom().String()))
+			if err != nil {
+				return err
+			}
 		}
-	} else {
-		err = s.zfsSnapshotRename(fmt.Sprintf("containers/%s", cName), snapName, fmt.Sprintf("copy-%s", uuid.NewRandom().String()))
+	}
+
+	snapPath := shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", cName, fields[1]))
+	if shared.PathExists(snapPath) {
+		err := os.Remove(snapPath)
 		if err != nil {
 			return err
 		}
 	}
 
-	err = os.Remove(shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", cName, fields[1])))
-	if err != nil {
-		return err
-	}
-
 	parent := shared.VarPath(fmt.Sprintf("snapshots/%s", cName))
 	if ok, _ := shared.PathIsEmpty(parent); ok {
-		err = os.Remove(parent)
+		err := os.Remove(parent)
 		if err != nil {
 			return err
 		}
@@ -613,32 +620,34 @@ func (s *storageZfs) ImageCreate(fingerprint string) error {
 func (s *storageZfs) ImageDelete(fingerprint string) error {
 	fs := fmt.Sprintf("images/%s", fingerprint)
 
-	removable, err := s.zfsSnapshotRemovable(fs, "readonly")
-
-	if err != nil {
-		return err
-	}
-
-	if removable {
-		err := s.zfsDestroy(fs)
-		if err != nil {
-			return err
-		}
-	} else {
-		err := s.zfsSet(fs, "mountpoint", "none")
+	if s.zfsExists(fs) {
+		removable, err := s.zfsSnapshotRemovable(fs, "readonly")
 		if err != nil {
 			return err
 		}
 
-		err = s.zfsRename(fs, fmt.Sprintf("deleted/%s", fs))
-		if err != nil {
-			return err
+		if removable {
+			err := s.zfsDestroy(fs)
+			if err != nil {
+				return err
+			}
+		} else {
+			err := s.zfsSet(fs, "mountpoint", "none")
+			if err != nil {
+				return err
+			}
+
+			err = s.zfsRename(fs, fmt.Sprintf("deleted/%s", fs))
+			if err != nil {
+				return err
+			}
 		}
 	}
 
 	if shared.PathExists(shared.VarPath(fs + ".zfs")) {
 		os.Remove(shared.VarPath(fs + ".zfs"))
 	}
+
 	return nil
 }
 

From 5801fd588321255ef1c4063f80d89452317ed633 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 28 Apr 2016 23:36:44 -0400
Subject: [PATCH 0037/1193] Do proper logfile expiry
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1966

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 10 ++++++
 lxd/daemon.go        | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 109 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5ec9e8e05..eb0022061 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1088,6 +1088,16 @@ func (c *containerLXC) startCommon() (string, error) {
 		delete(c.expandedConfig, k)
 	}
 
+	// Rotate the log file
+	logfile := c.LogFilePath()
+	if shared.PathExists(logfile) {
+		os.Remove(logfile + ".old")
+		err := os.Rename(logfile, logfile+".old")
+		if err != nil {
+			return "", err
+		}
+	}
+
 	// Generate the LXC config
 	configPath := filepath.Join(c.LogPath(), "lxc.conf")
 	err = c.c.SaveConfigFile(configPath)
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 8a150cc59..b7a2e4bb5 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -9,6 +9,7 @@ import (
 	"encoding/pem"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"net"
 	"net/http"
 	"net/url"
@@ -749,6 +750,22 @@ func (d *Daemon) Init() error {
 		}
 	}
 
+	/* Log expiry */
+	go func() {
+		t := time.NewTicker(24 * time.Hour)
+		for {
+			shared.Debugf("Expiring log files")
+
+			err := d.ExpireLogs()
+			if err != nil {
+				shared.Log.Error("Failed to expire logs", log.Ctx{"err": err})
+			}
+
+			shared.Debugf("Done expiring log files")
+			<-t.C
+		}
+	}()
+
 	/* set the initial proxy function based on config values in the DB */
 	d.proxy = shared.ProxyFromConfig(
 		daemonConfig["core.proxy_https"].Get(),
@@ -1100,6 +1117,88 @@ func (d *Daemon) PasswordCheck(password string) error {
 	return nil
 }
 
+func (d *Daemon) ExpireLogs() error {
+	entries, err := ioutil.ReadDir(shared.LogPath())
+	if err != nil {
+		return err
+	}
+
+	result, err := dbContainersList(d.db, cTypeRegular)
+	if err != nil {
+		return err
+	}
+
+	newestFile := func(path string, dir os.FileInfo) time.Time {
+		newest := dir.ModTime()
+
+		entries, err := ioutil.ReadDir(path)
+		if err != nil {
+			return newest
+		}
+
+		for _, entry := range entries {
+			if entry.ModTime().After(newest) {
+				newest = entry.ModTime()
+			}
+		}
+
+		return newest
+	}
+
+	for _, entry := range entries {
+		// Check if the container still exists
+		if shared.StringInSlice(entry.Name(), result) {
+			// Remove any log file which wasn't modified in the past 48 hours
+			logs, err := ioutil.ReadDir(shared.LogPath(entry.Name()))
+			if err != nil {
+				return err
+			}
+
+			for _, logfile := range logs {
+				path := shared.LogPath(entry.Name(), logfile.Name())
+
+				// Always keep the LXC config
+				if logfile.Name() == "lxc.conf" {
+					continue
+				}
+
+				// Deal with directories (snapshots)
+				if logfile.IsDir() {
+					newest := newestFile(path, logfile)
+					if time.Since(newest).Hours() >= 48 {
+						os.RemoveAll(path)
+						if err != nil {
+							return err
+						}
+					}
+
+					continue
+				}
+
+				// Individual files
+				if time.Since(logfile.ModTime()).Hours() >= 48 {
+					err := os.Remove(path)
+					if err != nil {
+						return err
+					}
+				}
+			}
+		} else {
+			// Empty directory if unchanged in the past 24 hours
+			path := shared.LogPath(entry.Name())
+			newest := newestFile(path, entry)
+			if time.Since(newest).Hours() >= 24 {
+				err := os.RemoveAll(path)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
 type lxdHttpServer struct {
 	r *mux.Router
 	d *Daemon

From a7bfb324f0b3a42afa8fb226805cae01296a8d15 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 28 Apr 2016 23:39:45 -0400
Subject: [PATCH 0038/1193] Make logging a bit more consistent
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/images.go b/lxd/images.go
index 1a7879613..632b753c8 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -876,6 +876,8 @@ func autoUpdateImages(d *Daemon) {
 			shared.Log.Error("Error deleting image", log.Ctx{"err": err, "fp": fp})
 		}
 	}
+
+	shared.Debugf("Done updating images")
 }
 
 func pruneExpiredImages(d *Daemon) {

From e9181f53c32b1154f90dbbcbff58454e08319b33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 1 May 2016 10:30:17 -0400
Subject: [PATCH 0039/1193] zfs: Don't ignore errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_zfs.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index db6e6d515..8c0907f50 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -645,7 +645,10 @@ func (s *storageZfs) ImageDelete(fingerprint string) error {
 	}
 
 	if shared.PathExists(shared.VarPath(fs + ".zfs")) {
-		os.Remove(shared.VarPath(fs + ".zfs"))
+		err := os.Remove(shared.VarPath(fs + ".zfs"))
+		if err != nil {
+			return err
+		}
 	}
 
 	return nil

From f46bafacf8fc15a4849f12476b32cda10ad103c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 1 May 2016 11:51:27 -0400
Subject: [PATCH 0040/1193] Properly update the mode, uid and gid on existing
 files
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1975

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go            |  2 +-
 lxd/container.go     |  2 +-
 lxd/container_lxc.go | 11 +++++++++--
 lxd/nsexec.go        | 41 ++++++++++++++++++++++++++++++++++++++---
 shared/util.go       | 18 ++++++++++--------
 5 files changed, 59 insertions(+), 15 deletions(-)

diff --git a/client.go b/client.go
index 7a1e163fa..0254b5b49 100644
--- a/client.go
+++ b/client.go
@@ -1662,7 +1662,7 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode os.
 	return err
 }
 
-func (c *Client) PullFile(container string, p string) (int, int, os.FileMode, io.ReadCloser, error) {
+func (c *Client) PullFile(container string, p string) (int, int, int, io.ReadCloser, error) {
 	if c.Remote.Public {
 		return 0, 0, 0, nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxd/container.go b/lxd/container.go
index 89b7ac11a..b46041015 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -327,7 +327,7 @@ type container interface {
 
 	// File handling
 	FilePull(srcpath string, dstpath string) (int, int, os.FileMode, error)
-	FilePush(srcpath string, dstpath string, uid int, gid int, mode os.FileMode) error
+	FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error
 
 	// Command execution
 	Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index eb0022061..c2d683d6b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2927,7 +2927,10 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 	return uid, gid, os.FileMode(mode), nil
 }
 
-func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int, mode os.FileMode) error {
+func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error {
+	var rootUid = 0
+	var rootGid = 0
+
 	// Map uid and gid if needed
 	idmapset, err := c.LastIdmapSet()
 	if err != nil {
@@ -2936,6 +2939,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 
 	if idmapset != nil {
 		uid, gid = idmapset.ShiftIntoNs(uid, gid)
+		rootUid, rootGid = idmapset.ShiftIntoNs(0, 0)
 	}
 
 	// Setup container storage if needed
@@ -2956,7 +2960,10 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 		dstpath,
 		fmt.Sprintf("%d", uid),
 		fmt.Sprintf("%d", gid),
-		fmt.Sprintf("%d", mode&os.ModePerm),
+		fmt.Sprintf("%d", mode),
+		fmt.Sprintf("%d", rootUid),
+		fmt.Sprintf("%d", rootGid),
+		fmt.Sprintf("%d", int(os.FileMode(0640)&os.ModePerm)),
 	).CombinedOutput()
 
 	// Tear down container storage if needed
diff --git a/lxd/nsexec.go b/lxd/nsexec.go
index a25c73942..836adadfb 100644
--- a/lxd/nsexec.go
+++ b/lxd/nsexec.go
@@ -117,11 +117,12 @@ int dosetns(int pid, char *nstype) {
 	return 0;
 }
 
-int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, uid_t uid, gid_t gid, mode_t mode) {
+int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, mode_t defaultMode) {
 	int host_fd, container_fd;
 	int ret = -1;
 	int container_open_flags;
 	struct stat st;
+	int exists = 1;
 
 	host_fd = open(host, O_RDWR);
 	if (host_fd < 0) {
@@ -150,19 +151,41 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 		}
 	}
 
+	if (is_put && stat(container, &st) < 0)
+		exists = 0;
+
 	umask(0);
-	container_fd = open(container, container_open_flags, mode);
+	container_fd = open(container, container_open_flags, 0);
 	if (container_fd < 0) {
 		perror("error: open");
 		goto close_host;
 	}
 
 	if (is_put) {
+		if (!exists) {
+			if (mode == -1) {
+				mode = defaultMode;
+			}
+
+			if (uid == -1) {
+				uid = defaultUid;
+			}
+
+			if (gid == -1) {
+				gid = defaultGid;
+			}
+		}
+
 		if (copy(container_fd, host_fd) < 0) {
 			perror("error: copy");
 			goto close_container;
 		}
 
+		if (mode != -1 && fchmod(container_fd, mode) < 0) {
+			perror("error: chmod");
+			goto close_container;
+		}
+
 		if (fchown(container_fd, uid, gid) < 0) {
 			perror("error: chown");
 			goto close_container;
@@ -332,6 +355,9 @@ void forkdofile(char *buf, char *cur, bool is_put, ssize_t size) {
 	uid_t uid = 0;
 	gid_t gid = 0;
 	mode_t mode = 0;
+	uid_t defaultUid = 0;
+	gid_t defaultGid = 0;
+	mode_t defaultMode = 0;
 	char *command = cur, *rootfs = NULL, *source = NULL, *target = NULL;
 	pid_t pid;
 
@@ -356,9 +382,18 @@ void forkdofile(char *buf, char *cur, bool is_put, ssize_t size) {
 
 		ADVANCE_ARG_REQUIRED();
 		mode = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		defaultUid = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		defaultGid = atoi(cur);
+
+		ADVANCE_ARG_REQUIRED();
+		defaultMode = atoi(cur);
 	}
 
-	_exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, mode));
+	_exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, mode, defaultUid, defaultGid, defaultMode));
 }
 
 void forkgetnet(char *buf, char *cur, ssize_t size) {
diff --git a/shared/util.go b/shared/util.go
index ec2514060..e449532ca 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -96,24 +96,26 @@ func LogPath(path ...string) string {
 	return filepath.Join(items...)
 }
 
-func ParseLXDFileHeaders(headers http.Header) (uid int, gid int, mode os.FileMode) {
+func ParseLXDFileHeaders(headers http.Header) (uid int, gid int, mode int) {
 	uid, err := strconv.Atoi(headers.Get("X-LXD-uid"))
 	if err != nil {
-		uid = 0
+		uid = -1
 	}
 
 	gid, err = strconv.Atoi(headers.Get("X-LXD-gid"))
 	if err != nil {
-		gid = 0
+		gid = -1
 	}
 
-	/* Allow people to send stuff with a leading 0 for octal or a regular
-	 * int that represents the perms when redered in octal. */
-	rawMode, err := strconv.ParseInt(headers.Get("X-LXD-mode"), 0, 0)
+	mode, err = strconv.Atoi(headers.Get("X-LXD-mode"))
 	if err != nil {
-		rawMode = 0644
+		mode = -1
+	} else {
+		rawMode, err := strconv.ParseInt(headers.Get("X-LXD-mode"), 0, 0)
+		if err == nil {
+			mode = int(os.FileMode(rawMode) & os.ModePerm)
+		}
 	}
-	mode = os.FileMode(rawMode)
 
 	return uid, gid, mode
 }

From 6951b328aa9f0b2dac7dc2d49ad236677c4b5bb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 1 May 2016 12:26:46 -0400
Subject: [PATCH 0041/1193] Detect invalid certificate files
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1977

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/cert.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/shared/cert.go b/shared/cert.go
index 618ed0295..f28c016da 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -197,5 +197,9 @@ func ReadCert(fpath string) (*x509.Certificate, error) {
 	}
 
 	certBlock, _ := pem.Decode(cf)
+	if certBlock == nil {
+		return nil, fmt.Errorf("Invalid certificate file")
+	}
+
 	return x509.ParseCertificate(certBlock.Bytes)
 }

From 2ce0a7f5148e23ee5129a0043e0acf14b7b4d9fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 1 May 2016 15:59:46 -0500
Subject: [PATCH 0042/1193] apparmor: Fix broken disabled check
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index 45fe566c8..ce25c5040 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -178,6 +178,7 @@ func AAUnloadProfile(c container) error {
 // Parse the profile without loading it into the kernel.
 func AAParseProfile(c container) error {
 	if !aaAvailable {
+		return nil
 	}
 
 	return runApparmor(APPARMOR_CMD_PARSE, c)

From a65688d626cbffd711f9bee65677b7829f96075c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 1 May 2016 16:02:09 -0500
Subject: [PATCH 0043/1193] Allow on/off as boolean strings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_config.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 44f6058e6..c4c0a1413 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -55,7 +55,7 @@ func (k *daemonConfigKey) Validate(d *Daemon, value string) error {
 	}
 
 	// Validate booleans
-	if k.valueType == "bool" && !shared.StringInSlice(strings.ToLower(value), []string{"true", "false", "1", "0", "yes", "no"}) {
+	if k.valueType == "bool" && !shared.StringInSlice(strings.ToLower(value), []string{"true", "false", "1", "0", "yes", "no", "on", "off"}) {
 		return fmt.Errorf("Invalid value for a boolean: %s", value)
 	}
 
@@ -145,7 +145,7 @@ func (k *daemonConfigKey) GetBool() bool {
 	}
 
 	// Convert to boolean
-	if shared.StringInSlice(strings.ToLower(value), []string{"true", "1", "yes"}) {
+	if shared.StringInSlice(strings.ToLower(value), []string{"true", "1", "yes", "on"}) {
 		return true
 	}
 

From 0dd1635d992b0e6b028c8f324fbb3e187308d4ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 1 May 2016 16:25:24 -0500
Subject: [PATCH 0044/1193] Properly validate the container configuration keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1940

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go     | 120 ++++++++++++++++++++++++++++++++-------------------
 lxd/container_lxc.go |  38 ++++++----------
 lxd/containers.go    |   2 +-
 lxd/daemon_config.go |   6 +--
 lxd/main.go          |   6 +--
 shared/util.go       |   8 ++++
 6 files changed, 102 insertions(+), 78 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index b46041015..51524ab8d 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"strconv"
 	"strings"
 	"time"
 
@@ -37,73 +38,110 @@ func containerValidName(name string) error {
 	return nil
 }
 
-func containerValidConfigKey(k string) bool {
-	switch k {
+func containerValidConfigKey(key string, value string) error {
+	isInt64 := func(key string, value string) error {
+		if value == "" {
+			return nil
+		}
+
+		_, err := strconv.ParseInt(value, 10, 64)
+		if err != nil {
+			return fmt.Errorf("Invalid value for an integer: %s", value)
+		}
+
+		return nil
+	}
+
+	isBool := func(key string, value string) error {
+		if value == "" {
+			return nil
+		}
+
+		if !shared.StringInSlice(strings.ToLower(value), []string{"true", "false", "yes", "no", "1", "0", "on", "off"}) {
+			return fmt.Errorf("Invalid value for a boolean: %s", value)
+		}
+
+		return nil
+	}
+
+	isOneOf := func(key string, value string, valid []string) error {
+		if value == "" {
+			return nil
+		}
+
+		if !shared.StringInSlice(value, valid) {
+			return fmt.Errorf("Invalid value: %s (not one of %s)", value, valid)
+		}
+
+		return nil
+	}
+
+	switch key {
 	case "boot.autostart":
-		return true
+		return isBool(key, value)
 	case "boot.autostart.delay":
-		return true
+		return isInt64(key, value)
 	case "boot.autostart.priority":
-		return true
+		return isInt64(key, value)
 	case "limits.cpu":
-		return true
+		return nil
 	case "limits.cpu.allowance":
-		return true
+		return nil
 	case "limits.cpu.priority":
-		return true
+		return isInt64(key, value)
 	case "limits.disk.priority":
-		return true
+		return isInt64(key, value)
 	case "limits.memory":
-		return true
+		return nil
 	case "limits.memory.enforce":
-		return true
+		return isOneOf(key, value, []string{"soft", "hard"})
 	case "limits.memory.swap":
-		return true
+		return isBool(key, value)
 	case "limits.memory.swap.priority":
-		return true
+		return isInt64(key, value)
 	case "limits.network.priority":
-		return true
+		return isInt64(key, value)
 	case "limits.processes":
-		return true
+		return isInt64(key, value)
 	case "linux.kernel_modules":
-		return true
+		return nil
 	case "security.privileged":
-		return true
+		return isBool(key, value)
 	case "security.nesting":
-		return true
+		return isBool(key, value)
 	case "raw.apparmor":
-		return true
+		return nil
 	case "raw.lxc":
-		return true
+		return lxcValidConfig(value)
 	case "volatile.apply_template":
-		return true
+		return nil
 	case "volatile.base_image":
-		return true
+		return nil
 	case "volatile.last_state.idmap":
-		return true
+		return nil
 	case "volatile.last_state.power":
-		return true
+		return nil
 	}
 
-	if strings.HasPrefix(k, "volatile.") {
-		if strings.HasSuffix(k, ".hwaddr") {
-			return true
+	if strings.HasPrefix(key, "volatile.") {
+		if strings.HasSuffix(key, ".hwaddr") {
+			return nil
 		}
 
-		if strings.HasSuffix(k, ".name") {
-			return true
+		if strings.HasSuffix(key, ".name") {
+			return nil
 		}
 	}
 
-	if strings.HasPrefix(k, "environment.") {
-		return true
+	if strings.HasPrefix(key, "environment.") {
+		return nil
 	}
 
-	if strings.HasPrefix(k, "user.") {
-		return true
+	if strings.HasPrefix(key, "user.") {
+		return nil
 	}
 
-	return false
+	return fmt.Errorf("Bad key: %s", key)
 }
 
 func containerValidDeviceConfigKey(t, k string) bool {
@@ -187,20 +225,14 @@ func containerValidConfig(config map[string]string, profile bool, expanded bool)
 		return nil
 	}
 
-	for k, _ := range config {
+	for k, v := range config {
 		if profile && strings.HasPrefix(k, "volatile.") {
 			return fmt.Errorf("Volatile keys can only be set on containers.")
 		}
 
-		if k == "raw.lxc" {
-			err := lxcValidConfig(config["raw.lxc"])
-			if err != nil {
-				return err
-			}
-		}
-
-		if !containerValidConfigKey(k) {
-			return fmt.Errorf("Bad key: %s", k)
+		err := containerValidConfigKey(k, v)
+		if err != nil {
+			return err
 		}
 	}
 
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index c2d683d6b..2d8e22e2e 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -528,7 +528,7 @@ func (c *containerLXC) initLXC() error {
 					return err
 				}
 			} else {
-				if memorySwap != "false" && cgSwapAccounting {
+				if cgSwapAccounting && (memorySwap == "" || shared.IsTrue(memorySwap)) {
 					err = lxcSetConfigItem(cc, "lxc.cgroup.memory.limit_in_bytes", fmt.Sprintf("%d", valueInt))
 					if err != nil {
 						return err
@@ -547,7 +547,7 @@ func (c *containerLXC) initLXC() error {
 		}
 
 		// Configure the swappiness
-		if memorySwap == "false" {
+		if memorySwap != "" && !shared.IsTrue(memorySwap) {
 			err = lxcSetConfigItem(cc, "lxc.cgroup.memory.swappiness", "0")
 			if err != nil {
 				return err
@@ -773,9 +773,9 @@ func (c *containerLXC) initLXC() error {
 			devPath := filepath.Join(c.DevicesPath(), devName)
 
 			// Various option checks
-			isOptional := m["optional"] == "1" || m["optional"] == "true"
-			isReadOnly := m["readonly"] == "1" || m["readonly"] == "true"
-			isRecursive := m["recursive"] == "1" || m["recursive"] == "true"
+			isOptional := shared.IsTrue(m["optional"])
+			isReadOnly := shared.IsTrue(m["readonly"])
+			isRecursive := shared.IsTrue(m["recursive"])
 			isFile := !shared.IsDir(srcPath) && !deviceIsBlockdev(srcPath)
 
 			// Deal with a rootfs
@@ -2281,7 +2281,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 						return err
 					}
 				} else {
-					if memorySwap != "false" && cgSwapAccounting {
+					if cgSwapAccounting && (memorySwap == "" || shared.IsTrue(memorySwap)) {
 						err = c.CGroupSet("memory.limit_in_bytes", memory)
 						if err != nil {
 							undoChanges()
@@ -2305,7 +2305,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 				if key == "limits.memory.swap" || key == "limits.memory.swap.priority" {
 					memorySwap := c.expandedConfig["limits.memory.swap"]
 					memorySwapPriority := c.expandedConfig["limits.memory.swap.priority"]
-					if memorySwap == "false" {
+					if memorySwap != "" && !shared.IsTrue(memorySwap) {
 						err = c.CGroupSet("memory.swappiness", "0")
 						if err != nil {
 							undoChanges()
@@ -3881,9 +3881,9 @@ func (c *containerLXC) createDiskDevice(name string, m shared.Device) (string, e
 	devPath := filepath.Join(c.DevicesPath(), devName)
 
 	// Check if read-only
-	isOptional := m["optional"] == "1" || m["optional"] == "true"
-	isReadOnly := m["readonly"] == "1" || m["readonly"] == "true"
-	isRecursive := m["recursive"] == "1" || m["recursive"] == "true"
+	isOptional := shared.IsTrue(m["optional"])
+	isReadOnly := shared.IsTrue(m["readonly"])
+	isRecursive := shared.IsTrue(m["recursive"])
 	isFile := !shared.IsDir(srcPath) && !deviceIsBlockdev(srcPath)
 
 	// Check if the source exists
@@ -3940,7 +3940,7 @@ func (c *containerLXC) insertDiskDevice(name string, m shared.Device) error {
 		return fmt.Errorf("Can't insert device into stopped container")
 	}
 
-	isRecursive := m["recursive"] == "1" || m["recursive"] == "true"
+	isRecursive := shared.IsTrue(m["recursive"])
 
 	// Create the device on the host
 	devPath, err := c.createDiskDevice(name, m)
@@ -4363,23 +4363,11 @@ func (c *containerLXC) IsFrozen() bool {
 }
 
 func (c *containerLXC) IsNesting() bool {
-	switch strings.ToLower(c.expandedConfig["security.nesting"]) {
-	case "1":
-		return true
-	case "true":
-		return true
-	}
-	return false
+	return shared.IsTrue(c.expandedConfig["security.nesting"])
 }
 
 func (c *containerLXC) IsPrivileged() bool {
-	switch strings.ToLower(c.expandedConfig["security.privileged"]) {
-	case "1":
-		return true
-	case "true":
-		return true
-	}
-	return false
+	return shared.IsTrue(c.expandedConfig["security.privileged"])
 }
 
 func (c *containerLXC) IsRunning() bool {
diff --git a/lxd/containers.go b/lxd/containers.go
index b125768f7..16707efcb 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -110,7 +110,7 @@ func containersRestart(d *Daemon) error {
 		autoStart := config["boot.autostart"]
 		autoStartDelay := config["boot.autostart.delay"]
 
-		if lastState == "RUNNING" || autoStart == "true" {
+		if lastState == "RUNNING" || shared.IsTrue(autoStart) {
 			if c.IsRunning() {
 				continue
 			}
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index c4c0a1413..7a07a63a6 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -145,11 +145,7 @@ func (k *daemonConfigKey) GetBool() bool {
 	}
 
 	// Convert to boolean
-	if shared.StringInSlice(strings.ToLower(value), []string{"true", "1", "yes", "on"}) {
-		return true
-	}
-
-	return false
+	return shared.IsTrue(value)
 }
 
 func (k *daemonConfigKey) GetInt64() int64 {
diff --git a/lxd/main.go b/lxd/main.go
index 09f8a8b5a..076685328 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -514,7 +514,7 @@ func cmdActivateIfNeeded() error {
 			return err
 		}
 
-		if lastState == "RUNNING" || lastState == "Running" || autoStart == "true" {
+		if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) {
 			shared.Debugf("Daemon has auto-started containers, activating...")
 			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
 			return err
@@ -612,9 +612,9 @@ func cmdInit() error {
 			fmt.Printf(question)
 			input, _ := reader.ReadString('\n')
 			input = strings.TrimSuffix(input, "\n")
-			if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y", "true"}) {
+			if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y"}) {
 				return true
-			} else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n", "false"}) {
+			} else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n"}) {
 				return false
 			}
 
diff --git a/shared/util.go b/shared/util.go
index e449532ca..a71cb91cf 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -353,6 +353,14 @@ func IntInSlice(key int, list []int) bool {
 	return false
 }
 
+func IsTrue(value string) bool {
+	if StringInSlice(strings.ToLower(value), []string{"true", "1", "yes", "on"}) {
+		return true
+	}
+
+	return false
+}
+
 func IsOnSharedMount(pathName string) (bool, error) {
 	file, err := os.Open("/proc/self/mountinfo")
 	if err != nil {

From 0cafe522504638fb4b45724ccbfb4da8db6dc664 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 3 May 2016 20:20:44 +0000
Subject: [PATCH 0045/1193] don't alias rsync error condition

When rsyncing something, let's not mask err, so we can fail properly if the
rsync fails.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/rsync.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/rsync.go b/lxd/rsync.go
index 9bc3b79f2..1d043df44 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -141,7 +141,8 @@ func RsyncSend(path string, conn *websocket.Conn) error {
 		shared.Debugf("problem reading rsync stderr %s", err)
 	}
 
-	if err := cmd.Wait(); err != nil {
+	err = cmd.Wait()
+	if err != nil {
 		shared.Debugf("problem with rsync send of %s: %s: %s", path, err, string(output))
 	}
 

From 1e57d50c8e6e23e107c2d584808b215f2e88857d Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 4 May 2016 18:55:55 +0000
Subject: [PATCH 0046/1193] daemon: move execPath to a global

In the next patch we'll use this in the rsync code, and rather than have to
keep passing it around everywhere, let's just make it a global.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 26 +++++++++++++-------------
 lxd/daemon.go        |  9 +--------
 lxd/main.go          |  6 ++++++
 3 files changed, 20 insertions(+), 21 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 2d8e22e2e..678886053 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -419,12 +419,12 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup the hooks
-	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("%s callhook %s %d start", c.daemon.execPath, shared.VarPath(""), c.id))
+	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("%s callhook %s %d start", execPath, shared.VarPath(""), c.id))
 	if err != nil {
 		return err
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %d stop", c.daemon.execPath, shared.VarPath(""), c.id))
+	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %d stop", execPath, shared.VarPath(""), c.id))
 	if err != nil {
 		return err
 	}
@@ -1141,7 +1141,7 @@ func (c *containerLXC) Start(stateful bool) error {
 		}
 
 		out, err := exec.Command(
-			c.daemon.execPath,
+			execPath,
 			"forkmigrate",
 			c.name,
 			c.daemon.lxcpath,
@@ -1177,7 +1177,7 @@ func (c *containerLXC) Start(stateful bool) error {
 
 	// Start the LXC container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkstart",
 		c.name,
 		c.daemon.lxcpath,
@@ -1210,7 +1210,7 @@ func (c *containerLXC) StartFromMigration(imagesDir string) error {
 
 	// Start the LXC container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkmigrate",
 		c.name,
 		c.daemon.lxcpath,
@@ -1734,7 +1734,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 		}
 
 		out, err := exec.Command(
-			c.daemon.execPath,
+			execPath,
 			"forkmigrate",
 			c.name,
 			c.daemon.lxcpath,
@@ -2841,7 +2841,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 
 	// Get the file from the container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkgetfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
@@ -2952,7 +2952,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 
 	// Push the file to the container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkputfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
@@ -3007,7 +3007,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
 	}
 
-	args := []string{c.daemon.execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
+	args := []string{execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
 
 	args = append(args, "--")
 	args = append(args, "env")
@@ -3018,7 +3018,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	args = append(args, command...)
 
 	cmd := exec.Cmd{}
-	cmd.Path = c.daemon.execPath
+	cmd.Path = execPath
 	cmd.Args = args
 	cmd.Stdin = stdin
 	cmd.Stdout = stdout
@@ -3120,7 +3120,7 @@ func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
 
 	// Get the network state from the container
 	out, err := exec.Command(
-		c.daemon.execPath,
+		execPath,
 		"forkgetnet",
 		fmt.Sprintf("%d", pid)).CombinedOutput()
 
@@ -3317,7 +3317,7 @@ func (c *containerLXC) insertMount(source, target, fstype string, flags int) err
 	mntsrc := filepath.Join("/dev/.lxd-mounts", filepath.Base(tmpMount))
 	pidStr := fmt.Sprintf("%d", pid)
 
-	out, err := exec.Command(c.daemon.execPath, "forkmount", pidStr, mntsrc, target).CombinedOutput()
+	out, err := exec.Command(execPath, "forkmount", pidStr, mntsrc, target).CombinedOutput()
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
@@ -3347,7 +3347,7 @@ func (c *containerLXC) removeMount(mount string) error {
 
 	// Remove the mount from the container
 	pidStr := fmt.Sprintf("%d", pid)
-	out, err := exec.Command(c.daemon.execPath, "forkumount", pidStr, mount).CombinedOutput()
+	out, err := exec.Command(execPath, "forkumount", pidStr, mount).CombinedOutput()
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
diff --git a/lxd/daemon.go b/lxd/daemon.go
index b7a2e4bb5..49a6f7f5b 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -75,7 +75,6 @@ type Daemon struct {
 	pruneChan           chan bool
 	shutdownChan        chan bool
 	resetAutoUpdateChan chan bool
-	execPath            string
 
 	Storage storage
 
@@ -544,14 +543,8 @@ func (d *Daemon) Init() error {
 	d.shutdownChan = make(chan bool)
 
 	/* Set the executable path */
-	absPath, err := os.Readlink("/proc/self/exe")
-	if err != nil {
-		return err
-	}
-	d.execPath = absPath
-
 	/* Set the LVM environment */
-	err = os.Setenv("LVM_SUPPRESS_FD_WARNINGS", "1")
+	err := os.Setenv("LVM_SUPPRESS_FD_WARNINGS", "1")
 	if err != nil {
 		return err
 	}
diff --git a/lxd/main.go b/lxd/main.go
index 076685328..80c632d1c 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -50,9 +50,15 @@ var argVersion = gnuflag.Bool("version", false, "")
 // Global variables
 var debug bool
 var verbose bool
+var execPath string
 
 func init() {
 	rand.Seed(time.Now().UTC().UnixNano())
+	absPath, err := os.Readlink("/proc/self/exe")
+	if err != nil {
+		absPath = "bad-exec-path"
+	}
+	execPath = absPath
 }
 
 func main() {

From a365418b0b98e6e5d31d4e89a779e41bb4d4583f Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 4 May 2016 16:27:50 +0000
Subject: [PATCH 0047/1193] rsync: use custom netcat instead of nc -U

For whatever reason, it seemed (through checking via some combination of
strace and printing websocket frame lengths) that netcat was doing some
buffering of the socket contents and causing a hang in some cases. Instead,
let's use our own very dumb implementation of netcat instead, avoiding any
buffering.

Now that we are execing ourself to do rsync, we can't do any tests in go
test, because the test binary is not the lxd binary, and so we'll fork bomb
ourself. So, let's get rid of the rsync test. This doesn't get rid of test
coverage, since we're doing a cold migration in the test suite anyway.

Closes #1944

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/main.go       |   4 +++
 lxd/rsync.go      |  51 +++++++++++++++++++++++++-
 lxd/rsync_test.go | 106 ------------------------------------------------------
 3 files changed, 54 insertions(+), 107 deletions(-)
 delete mode 100644 lxd/rsync_test.go

diff --git a/lxd/main.go b/lxd/main.go
index 80c632d1c..27265b8ce 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -158,6 +158,8 @@ func run() error {
 		fmt.Printf("        Start a container\n")
 		fmt.Printf("    callhook\n")
 		fmt.Printf("        Call a container hook\n")
+		fmt.Printf("    netcat\n")
+		fmt.Printf("        Mirror a unix socket to stdin/stdout")
 	}
 
 	// Parse the arguments
@@ -233,6 +235,8 @@ func run() error {
 				fmt.Fprintf(os.Stderr, "error: %v\n", err)
 			}
 			os.Exit(ret)
+		case "netcat":
+			return Netcat(os.Args[1:])
 		}
 	}
 
diff --git a/lxd/rsync.go b/lxd/rsync.go
index 1d043df44..8f4931331 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -7,6 +7,7 @@ import (
 	"net"
 	"os"
 	"os/exec"
+	"sync"
 
 	"github.com/gorilla/websocket"
 
@@ -93,7 +94,7 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	 * command (i.e. the command to run on --server). However, we're
 	 * hardcoding that at the other end, so we can just ignore it.
 	 */
-	rsyncCmd := fmt.Sprintf("sh -c \"nc -U %s\"", f.Name())
+	rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, f.Name())
 	cmd := exec.Command(
 		"rsync",
 		"-arvP",
@@ -169,3 +170,51 @@ func rsyncRecvCmd(path string) *exec.Cmd {
 func RsyncRecv(path string, conn *websocket.Conn) error {
 	return rsyncWebsocket(path, rsyncRecvCmd(path), conn)
 }
+
+// Netcat is called with:
+//
+//    lxd netcat /path/to/unix/socket
+//
+// and does unbuffered netcatting of to socket to stdin/stdout. Any arguments
+// after the path to the unix socket are ignored, so that this can be passed
+// directly to rsync as the sync command.
+func Netcat(args []string) error {
+	if len(args) < 2 {
+		return fmt.Errorf("Bad arguments %q", args)
+	}
+
+	uAddr, err := net.ResolveUnixAddr("unix", args[1])
+	if err != nil {
+		return err
+	}
+
+	conn, err := net.DialUnix("unix", nil, uAddr)
+	if err != nil {
+		return err
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	go func() {
+		io.Copy(os.Stdout, conn)
+		f, _ := os.Create("/tmp/done_stdout")
+		f.Close()
+		conn.Close()
+		f, _ = os.Create("/tmp/done_close")
+		f.Close()
+		wg.Done()
+	}()
+
+	go func() {
+		io.Copy(conn, os.Stdin)
+		f, _ := os.Create("/tmp/done_stdin")
+		f.Close()
+	}()
+
+	f, _ := os.Create("/tmp/done_spawning_goroutines")
+	f.Close()
+	wg.Wait()
+
+	return nil
+}
diff --git a/lxd/rsync_test.go b/lxd/rsync_test.go
deleted file mode 100644
index 3aa94f3b3..000000000
--- a/lxd/rsync_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-package main
-
-import (
-	"io"
-	"io/ioutil"
-	"os"
-	"path"
-	"testing"
-
-	"github.com/lxc/lxd/shared"
-)
-
-const helloWorld = "hello world\n"
-
-func TestRsyncSendRecv(t *testing.T) {
-	source, err := ioutil.TempDir("", "lxd_test_source_")
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer os.RemoveAll(source)
-
-	sink, err := ioutil.TempDir("", "lxd_test_sink_")
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer os.RemoveAll(sink)
-
-	/* now, write something to rsync over */
-	f, err := os.Create(path.Join(source, "foo"))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	f.Write([]byte(helloWorld))
-	f.Close()
-
-	send, sendConn, _, err := rsyncSendSetup(shared.AddSlash(source))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	recv := rsyncRecvCmd(sink)
-
-	recvOut, err := recv.StdoutPipe()
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	recvIn, err := recv.StdinPipe()
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if err := recv.Start(); err != nil {
-		t.Error(err)
-		return
-	}
-
-	go func() {
-		defer sendConn.Close()
-		if _, err := io.Copy(sendConn, recvOut); err != nil {
-			t.Error(err)
-		}
-
-		if err := recv.Wait(); err != nil {
-			t.Error(err)
-		}
-
-	}()
-
-	/*
-	 * We close the socket in the above gofunc, but go tells us
-	 * https://github.com/golang/go/issues/4373 that this is an error
-	 * because we were reading from a socket that was closed. Thus, we
-	 * ignore it
-	 */
-	io.Copy(recvIn, sendConn)
-
-	if err := send.Wait(); err != nil {
-		t.Error(err)
-		return
-	}
-
-	f, err = os.Open(path.Join(sink, "foo"))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer f.Close()
-
-	buf, err := ioutil.ReadAll(f)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if string(buf) != helloWorld {
-		t.Errorf("expected %s got %s", helloWorld, buf)
-		return
-	}
-}

From 56a869cdd7982a390827dd3b2cea53c72f8d9b86 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 4 May 2016 21:24:40 +0000
Subject: [PATCH 0048/1193] state: fix state dir

We were using /containers/$container/rootfs/state as the directory to save
CRIU state in; instead, let's just use /containers/$container/state.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 678886053..867239408 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4494,5 +4494,13 @@ func (c *containerLXC) TemplatesPath() string {
 }
 
 func (c *containerLXC) StatePath() string {
-	return filepath.Join(c.RootfsPath(), "state")
+	/* FIXME: backwards compatibility: we used to use Join(RootfsPath(),
+	 * "state"), which was bad. Let's just check to see if that directory
+	 * exists.
+	 */
+	oldStatePath := filepath.Join(c.RootfsPath(), "state")
+	if shared.IsDir(oldStatePath) {
+		return oldStatePath
+	}
+	return filepath.Join(c.Path(), "state")
 }

From cbddd1cdee4ccbecb25fe7e415c9361ed2ababaf Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 5 May 2016 14:02:36 -0500
Subject: [PATCH 0049/1193] images: don't fail deleting when the storage delete
 fails

e.g. if someone deletes the zpool manually, they then can't delete an
image, because we can't detect the filesystem type for the path, because it
doesn't exist any more. Let's not fail so hard when we can't detect the
filesystem.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/images.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 632b753c8..5013787e5 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -911,12 +911,12 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	// look at the path
 	s, err := storageForImage(d, imgInfo)
 	if err != nil {
-		return err
-	}
-
-	// Remove the image from storage backend
-	if err = s.ImageDelete(imgInfo.Fingerprint); err != nil {
-		return err
+		shared.Log.Error("error detecting image storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
+	} else {
+		// Remove the image from storage backend
+		if err = s.ImageDelete(imgInfo.Fingerprint); err != nil {
+			shared.Log.Error("error deleting the image from storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
+		}
 	}
 
 	// Remove main image file

From c6473d0632621a0c06779a9bcf65b7f8240e5800 Mon Sep 17 00:00:00 2001
From: Hiroaki Nakamura <hnakamur at gmail.com>
Date: Sat, 7 May 2016 11:26:41 +0900
Subject: [PATCH 0050/1193] Improve messages in the Japanese translation

Signed-off-by: Hiroaki Nakamura <hnakamur at gmail.com>
---
 po/ja.po | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/po/ja.po b/po/ja.po
index fe4589f43..c7da0b7d2 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -178,7 +178,7 @@ msgid ""
 "\n"
 "lxc %s <name> [<name>...]"
 msgstr ""
-"コンテナの状態を %s に変更します。\n"
+"1つまたは複数のコンテナの状態を %s に変更します。\n"
 "\n"
 "lxc %s <name> [<name>...]"
 
@@ -192,7 +192,7 @@ msgstr "カラムレイアウト"
 
 #: lxc/init.go:134 lxc/init.go:135 lxc/launch.go:40 lxc/launch.go:41
 msgid "Config key/value to apply to the new container"
-msgstr "設定キー/値の組を新しいコンテナに適用しました"
+msgstr "新しいコンテナに適用するキー/値の設定"
 
 #: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:215
 #, c-format
@@ -359,7 +359,7 @@ msgid ""
 "Mode defaults to non-interactive, interactive mode is selected if both stdin "
 "AND stdout are terminals (stderr is ignored)."
 msgstr ""
-"コンテナで指定したコマンドを実行します。\n"
+"指定したコマンドをコンテナ内で実行します。\n"
 "\n"
 "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
 "EDITOR=/usr/bin/vim]... <command>\n"
@@ -438,7 +438,7 @@ msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要が
 
 #: lxc/main.go:57
 msgid "Ignore aliases when determining what command to run."
-msgstr "コマンドを実行する際にエイリアスを無視します。"
+msgstr "どのコマンドを実行するか決める際にエイリアスを無視します。"
 
 #: lxc/action.go:40
 msgid "Ignore the container state (only for start)."
@@ -856,7 +856,7 @@ msgid ""
 "<source> in the case of pull, <target> in the case of push and <file> in the "
 "case of edit are <container name>/<path>"
 msgstr ""
-"コンテナ上のファイルの管理\n"
+"コンテナ上のファイルを管理します。\n"
 "\n"
 "lxc file pull <source> [<source>...] <target>\n"
 "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] "
@@ -1206,7 +1206,7 @@ msgstr "再度エディタを起動するには Enter キーを押します"
 
 #: lxc/help.go:65
 msgid "Print debug information."
-msgstr "デバッグ情報を表示します"
+msgstr "デバッグ情報を表示します。"
 
 #: lxc/help.go:64
 msgid "Print less common commands."
@@ -1253,7 +1253,7 @@ msgstr "プロファイル %s が %s から削除されました"
 
 #: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
 msgid "Profile to apply to the new container"
-msgstr "プロファイルが新しいコンテナに適用されました"
+msgstr "新しいコンテナに適用するプロファイル"
 
 #: lxc/profile.go:253
 #, c-format

From 53a1808000b6e92277a936385d60a8d47669dc43 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 9 May 2016 08:09:45 -0600
Subject: [PATCH 0051/1193] add more checks for the criu binary

In particular, stateful stop/snapshot didn't have a check.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container.go     |  4 ++++
 lxd/container_lxc.go | 17 +++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/lxd/container.go b/lxd/container.go
index 51524ab8d..9c196ca4e 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -507,6 +507,10 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 			return nil, fmt.Errorf("Container not running, cannot do stateful snapshot")
 		}
 
+		if err := findCriu("snapshot"); err != nil {
+			return nil, err
+		}
+
 		stateDir := sourceContainer.StatePath()
 		err := os.MkdirAll(stateDir, 0700)
 		if err != nil {
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 867239408..6aa3510ab 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1134,6 +1134,10 @@ func (c *containerLXC) Start(stateful bool) error {
 			return fmt.Errorf("Container has no existing state to restore.")
 		}
 
+		if err := findCriu("snapshot"); err != nil {
+			return err
+		}
+
 		if !c.IsPrivileged() {
 			if err := c.IdmapSet().ShiftRootfs(c.StatePath()); err != nil {
 				return err
@@ -1344,6 +1348,10 @@ func (c *containerLXC) setupStopping() *sync.WaitGroup {
 func (c *containerLXC) Stop(stateful bool) error {
 	// Handle stateful stop
 	if stateful {
+		if err := findCriu("snapshot"); err != nil {
+			return err
+		}
+
 		// Cleanup any existing state
 		stateDir := c.StatePath()
 		os.RemoveAll(stateDir)
@@ -1676,6 +1684,15 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 		return err
 	}
 
+	/* let's also check for CRIU if necessary, before doing a bunch of
+	 * filesystem manipulations
+	 */
+	if shared.PathExists(c.StatePath()) {
+		if err := findCriu("snapshot"); err != nil {
+			return err
+		}
+	}
+
 	// Stop the container
 	wasRunning := false
 	if c.IsRunning() {

From 685a912b829053682a5c5542f727e835e5e76006 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 9 May 2016 08:32:03 -0600
Subject: [PATCH 0052/1193] rework (live) migration tests

In particular, same-host-different-lxd migration doesn't work right now
because of some cgroup path issues. CRIU will need to learn how to rewrite
mount paths to support this use case, but it's not a super high priority
right now.

Instead, let's use stateful snapshot/restore which will work, so that we
can at least test the CRIU machenery.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 test/suites/migration.sh | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/test/suites/migration.sh b/test/suites/migration.sh
index f0928060b..da0c9282a 100644
--- a/test/suites/migration.sh
+++ b/test/suites/migration.sh
@@ -64,8 +64,12 @@ test_migration() {
     return
   fi
 
-  lxc_remote launch testimage migratee
+  lxc_remote launch testimage l1:migratee
 
-  lxc_remote move l1:migratee l2:migratee
-  lxc_remote stop l2:migratee --force
+  # let the container do some interesting things
+  sleep 1s
+
+  lxc_remote stop --stateful l1:migratee
+  lxc_remote start l1:migratee
+  lxc_remote stop --force l1:migratee
 }

From 239697da9d201530fef1dd75ba00a3714d33729f Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 6 May 2016 13:32:53 -0500
Subject: [PATCH 0053/1193] doc: make it explicit that devices on create are
 optional

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 doc/rest-api.md | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 7a5f859e6..4f50f904d 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -356,6 +356,12 @@ Input (container based on a local image with the "ubuntu/devel" alias):
         "profiles": ["default"],                                            # List of profiles
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
+        "devices": {                                                        # optional list of devices the container should have
+            "rootfs": {
+                "path": "/dev/kvm",
+                "type": "unix-char"
+            },
+        },
         "source": {"type": "image",                                         # Can be: "image", "migration", "copy" or "none"
                    "alias": "ubuntu/devel"},                                # Name of the alias
     }
@@ -368,6 +374,12 @@ Input (container based on a local image identified by its fingerprint):
         "profiles": ["default"],                                            # List of profiles
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
+        "devices": {                                                        # optional list of devices the container should have
+            "rootfs": {
+                "path": "/dev/kvm",
+                "type": "unix-char"
+            },
+        },
         "source": {"type": "image",                                         # Can be: "image", "migration", "copy" or "none"
                    "fingerprint": "SHA-256"},                               # Fingerprint
     }
@@ -380,6 +392,12 @@ Input (container based on most recent match based on image properties):
         "profiles": ["default"],                                            # List of profiles
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
+        "devices": {                                                        # optional list of devices the container should have
+            "rootfs": {
+                "path": "/dev/kvm",
+                "type": "unix-char"
+            },
+        },
         "source": {"type": "image",                                         # Can be: "image", "migration", "copy" or "none"
                    "properties": {                                          # Properties
                         "os": "ubuntu",
@@ -396,6 +414,12 @@ Input (container without a pre-populated rootfs, useful when attaching to an exi
         "profiles": ["default"],                                            # List of profiles
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
+        "devices": {                                                        # optional list of devices the container should have
+            "rootfs": {
+                "path": "/dev/kvm",
+                "type": "unix-char"
+            },
+        },
         "source": {"type": "none"},                                         # Can be: "image", "migration", "copy" or "none"
     }
 
@@ -407,6 +431,12 @@ Input (using a public remote image):
         "profiles": ["default"],                                            # List of profiles
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
+        "devices": {                                                        # optional list of devices the container should have
+            "rootfs": {
+                "path": "/dev/kvm",
+                "type": "unix-char"
+            },
+        },
         "source": {"type": "image",                                         # Can be: "image", "migration", "copy" or "none"
                    "mode": "pull",                                          # One of "local" (default) or "pull"
                    "server": "https://10.0.2.3:8443",                       # Remote server (pull mode only)
@@ -424,6 +454,12 @@ Input (using a private remote image after having obtained a secret for that imag
         "profiles": ["default"],                                            # List of profiles
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
+        "devices": {                                                        # optional list of devices the container should have
+            "rootfs": {
+                "path": "/dev/kvm",
+                "type": "unix-char"
+            },
+        },
         "source": {"type": "image",                                         # Can be: "image", "migration", "copy" or "none"
                    "mode": "pull",                                          # One of "local" (default) or "pull"
                    "server": "https://10.0.2.3:8443",                       # Remote server (pull mode only)
@@ -440,6 +476,12 @@ Input (using a remote container, sent over the migration websocket):
         "profiles": ["default"],                                                        # List of profiles
         "ephemeral": true,                                                              # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                                  # Config override.
+        "devices": {                                                                    # optional list of devices the container should have
+            "rootfs": {
+                "path": "/dev/kvm",
+                "type": "unix-char"
+            },
+        },
         "source": {"type": "migration",                                                 # Can be: "image", "migration", "copy" or "none"
                    "mode": "pull",                                                      # Only "pull" is supported for now
                    "operation": "https://10.0.2.3:8443/1.0/operations/<UUID>",          # Full URL to the remote operation (pull mode only)

From 49212e3203cc564021a0f7f2daf1e158f1ff518c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 May 2016 18:21:57 -0400
Subject: [PATCH 0054/1193] Properly record the source of all image copies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2010

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go | 32 +++++++++++++++-----------------
 1 file changed, 15 insertions(+), 17 deletions(-)

diff --git a/client.go b/client.go
index 0254b5b49..78171dad2 100644
--- a/client.go
+++ b/client.go
@@ -580,27 +580,25 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 		return err
 	}
 
-	if c.Remote.Protocol != "simplestreams" {
-		if !info.Public {
-			var secret string
+	if c.Remote.Protocol != "simplestreams" && !info.Public {
+		var secret string
 
-			resp, err := c.post("images/"+image+"/secret", nil, Async)
-			if err != nil {
-				return err
-			}
-
-			op, err := resp.MetadataAsOperation()
-			if err != nil {
-				return err
-			}
+		resp, err := c.post("images/"+image+"/secret", nil, Async)
+		if err != nil {
+			return err
+		}
 
-			secret, err = op.Metadata.GetString("secret")
-			if err != nil {
-				return err
-			}
+		op, err := resp.MetadataAsOperation()
+		if err != nil {
+			return err
+		}
 
-			source["secret"] = secret
+		secret, err = op.Metadata.GetString("secret")
+		if err != nil {
+			return err
 		}
+
+		source["secret"] = secret
 		source["fingerprint"] = image
 	}
 

From cab8b0564c83673f364ec8df030b80674451d69c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 May 2016 18:51:27 -0400
Subject: [PATCH 0055/1193] Don't mark containers as ERROR while being created
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1988

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage.go | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/lxd/storage.go b/lxd/storage.go
index 68f30be1d..a17b2053d 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -234,6 +234,9 @@ func newStorageWithConfig(d *Daemon, sType storageType, config map[string]interf
 }
 
 func storageForFilename(d *Daemon, filename string) (storage, error) {
+	var filesystem string
+	var err error
+
 	config := make(map[string]interface{})
 	storageType := storageTypeDir
 
@@ -241,9 +244,11 @@ func storageForFilename(d *Daemon, filename string) (storage, error) {
 		return newStorageWithConfig(d, storageTypeMock, config)
 	}
 
-	filesystem, err := filesystemDetect(filename)
-	if err != nil {
-		return nil, fmt.Errorf("couldn't detect filesystem for '%s': %v", filename, err)
+	if shared.PathExists(filename) {
+		filesystem, err = filesystemDetect(filename)
+		if err != nil {
+			return nil, fmt.Errorf("couldn't detect filesystem for '%s': %v", filename, err)
+		}
 	}
 
 	if shared.PathExists(filename + ".lv") {

From 30894b97bca76d897596334dd10bc5e1de3ce849 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 May 2016 19:07:33 -0400
Subject: [PATCH 0056/1193] Cleanup events sent for operations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This fixes the following two issues:
 - No-change events being emitted on websocket connects
 - Out of order events on operation completion

Closes #1992

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/operations.go | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/lxd/operations.go b/lxd/operations.go
index 63e9ea90a..b50d3c790 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -129,9 +129,11 @@ func (op *operation) Run() (chan error, error) {
 			op.done()
 			chanRun <- nil
 
+			op.lock.Lock()
 			shared.Debugf("Success for %s operation: %s", op.class.String(), op.id)
 			_, md, _ := op.Render()
 			eventSend("operation", md)
+			op.lock.Unlock()
 		}(op, chanRun)
 	}
 	op.lock.Unlock()
@@ -224,22 +226,16 @@ func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error
 			chanConnect <- err
 
 			shared.Debugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err)
-			_, md, _ := op.Render()
-			eventSend("operation", md)
 			return
 		}
 
 		chanConnect <- nil
 
 		shared.Debugf("Handled %s operation: %s", op.class.String(), op.id)
-		_, md, _ := op.Render()
-		eventSend("operation", md)
 	}(op, chanConnect)
 	op.lock.Unlock()
 
 	shared.Debugf("Connected %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	eventSend("operation", md)
 
 	return chanConnect, nil
 }

From 044048e13beb5a8183b5797ab9775c6a2a78ffee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 May 2016 12:07:06 -0400
Subject: [PATCH 0057/1193] Fix ZFS refcounting issues
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1916
Closes #2013

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_zfs.go | 61 ++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 48 insertions(+), 13 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 8c0907f50..2b7dbe39c 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -773,29 +773,64 @@ func (s *storageZfs) zfsDestroy(path string) error {
 
 func (s *storageZfs) zfsCleanup(path string) error {
 	if strings.HasPrefix(path, "deleted/") {
+		// Cleanup of filesystems kept for refcount reason
 		removablePath, err := s.zfsSnapshotRemovable(path, "")
 		if err != nil {
 			return err
 		}
 
+		// Confirm that there are no more clones
 		if removablePath {
-			subPath := strings.SplitN(path, "@", 2)[0]
-
-			origin, err := s.zfsGet(subPath, "origin")
-			if err != nil {
-				return err
-			}
-			origin = strings.TrimPrefix(origin, fmt.Sprintf("%s/", s.zfsPool))
-
-			err = s.zfsDestroy(subPath)
-			if err != nil {
-				return err
+			if strings.Contains(path, "@") {
+				// Cleanup snapshots
+				err = s.zfsDestroy(path)
+				if err != nil {
+					return err
+				}
+
+				// Check if the parent can now be deleted
+				subPath := strings.SplitN(path, "@", 2)[0]
+				snaps, err := s.zfsListSnapshots(subPath)
+				if err != nil {
+					return err
+				}
+
+				if len(snaps) == 0 {
+					err := s.zfsCleanup(subPath)
+					if err != nil {
+						return err
+					}
+				}
+			} else {
+				// Cleanup filesystems
+				origin, err := s.zfsGet(path, "origin")
+				if err != nil {
+					return err
+				}
+				origin = strings.TrimPrefix(origin, fmt.Sprintf("%s/", s.zfsPool))
+
+				err = s.zfsDestroy(path)
+				if err != nil {
+					return err
+				}
+
+				// Attempt to remove its parent
+				if origin != "-" {
+					err := s.zfsCleanup(origin)
+					if err != nil {
+						return err
+					}
+				}
 			}
 
-			s.zfsCleanup(origin)
-
 			return nil
 		}
+	} else if strings.HasPrefix(path, "containers") {
+		// Just remove the copy- snapshot for copies of active containers
+		err := s.zfsDestroy(path)
+		if err != nil {
+			return err
+		}
 	}
 
 	return nil

From 3248084fecfdcf2bff6cdc2c128dabc3ddb68586 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 11 May 2016 17:37:51 +0000
Subject: [PATCH 0058/1193] propagate snapshot config when copying a snapshot

Closes #2017

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go   | 25 +++++++++++++++++++++++++
 lxc/copy.go | 37 +++++++++++++++++++++++++++++--------
 2 files changed, 54 insertions(+), 8 deletions(-)

diff --git a/client.go b/client.go
index 78171dad2..e90324e4a 100644
--- a/client.go
+++ b/client.go
@@ -1815,6 +1815,31 @@ func (c *Client) ListSnapshots(container string) ([]shared.SnapshotInfo, error)
 	return result, nil
 }
 
+func (c *Client) SnapshotInfo(snapName string) (*shared.SnapshotInfo, error) {
+	if c.Remote.Public {
+		return nil, fmt.Errorf("This function isn't supported by public remotes.")
+	}
+
+	pieces := strings.SplitN(snapName, shared.SnapshotDelimiter, 2)
+	if len(pieces) != 2 {
+		return nil, fmt.Errorf("invalid snapshot name %s", snapName)
+	}
+
+	qUrl := fmt.Sprintf("containers/%s/snapshots/%s", pieces[0], pieces[1])
+	resp, err := c.get(qUrl)
+	if err != nil {
+		return nil, err
+	}
+
+	var result shared.SnapshotInfo
+
+	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+		return nil, err
+	}
+
+	return &result, nil
+}
+
 func (c *Client) GetServerConfigString() ([]string, error) {
 	var resp []string
 
diff --git a/lxc/copy.go b/lxc/copy.go
index a451919b7..74631c491 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -47,7 +47,12 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 		return err
 	}
 
-	status := &shared.ContainerInfo{}
+	var status struct {
+		Architecture string
+		Devices      shared.Devices
+		Config       map[string]string
+		Profiles     []string
+	}
 
 	// TODO: presumably we want to do this for copying snapshots too? We
 	// need to think a bit more about how we track the baseImage in the
@@ -56,18 +61,34 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 	baseImage := ""
 
 	if !shared.IsSnapshot(sourceName) {
-		status, err = source.ContainerInfo(sourceName)
+		result, err := source.ContainerInfo(sourceName)
+		if err != nil {
+			return err
+		}
+
+		status.Architecture = result.Architecture
+		status.Devices = result.Devices
+		status.Config = result.Config
+		status.Profiles = result.Profiles
+
+	} else {
+		result, err := source.SnapshotInfo(sourceName)
 		if err != nil {
 			return err
 		}
 
-		baseImage = status.Config["volatile.base_image"]
+		status.Architecture = result.Architecture
+		status.Devices = result.Devices
+		status.Config = result.Config
+		status.Profiles = result.Profiles
+	}
+
+	baseImage = status.Config["volatile.base_image"]
 
-		if !keepVolatile {
-			for k := range status.Config {
-				if strings.HasPrefix(k, "volatile") {
-					delete(status.Config, k)
-				}
+	if !keepVolatile {
+		for k := range status.Config {
+			if strings.HasPrefix(k, "volatile") {
+				delete(status.Config, k)
 			}
 		}
 	}

From 7ac910cc5c1290c4949e7a526ba740755eda7879 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 11 May 2016 11:49:50 -0600
Subject: [PATCH 0059/1193] implement `lxc config show` for snapshots

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/config.go | 41 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 34 insertions(+), 7 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index 4bf7cc500..e824b4e61 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -341,16 +341,43 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 			brief := config.Brief()
 			data, err = yaml.Marshal(&brief)
 		} else {
-			config, err := d.ContainerInfo(container)
-			if err != nil {
-				return err
-			}
+			var brief shared.BriefContainerInfo
+			if shared.IsSnapshot(container) {
+				config, err := d.SnapshotInfo(container)
+				if err != nil {
+					return err
+				}
 
-			brief := config.Brief()
-			if c.expanded {
-				brief = config.BriefExpanded()
+				brief = shared.BriefContainerInfo{
+					Profiles:  config.Profiles,
+					Config:    config.Config,
+					Devices:   config.Devices,
+					Ephemeral: config.Ephemeral,
+				}
+				if c.expanded {
+					brief = shared.BriefContainerInfo{
+						Profiles:  config.Profiles,
+						Config:    config.ExpandedConfig,
+						Devices:   config.ExpandedDevices,
+						Ephemeral: config.Ephemeral,
+					}
+				}
+			} else {
+				config, err := d.ContainerInfo(container)
+				if err != nil {
+					return err
+				}
+
+				brief = config.Brief()
+				if c.expanded {
+					brief = config.BriefExpanded()
+				}
 			}
+
 			data, err = yaml.Marshal(&brief)
+			if err != nil {
+				return err
+			}
 		}
 
 		fmt.Printf("%s", data)

From f87e774212862298593ed55f969028af6b58eccb Mon Sep 17 00:00:00 2001
From: Daniel Middleton <monokal at users.noreply.github.com>
Date: Sat, 14 May 2016 12:51:36 +0100
Subject: [PATCH 0060/1193] Add Unix socket example to REST API usage.

Signed-off-by: Daniel Middleton d at monokal.io
---
 README.md | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 50037e250..e8d3d681f 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,22 @@ After you've got LXD installed and a session with the right permissions, you
 can take your [first steps](#first-steps).
 
 ## Using the REST API
-Here's a simple example of REST API usage via cURL:
+The LXD REST API can be used locally via unauthenticated Unix socket or remotely via SSL encapsulated TCP.
+
+#### via Unix socket
+```bash
+curl --unix-socket /var/lib/lxd/unix.socket \
+    -H "Content-Type: application/json" \
+    -X POST \
+    -d @hello-ubuntu.json \
+    "https://127.0.0.1:8443/1.0/containers"
+```
+
+#### via TCP
+TCP requires some additional configuration and is not enabled by default.
+```bash
+lxc config set core.https_address "[::]:8443"
+```
 ```bash
 curl -k -L -I \
     --cert ~/.config/lxc/client.crt \
@@ -39,7 +54,8 @@ curl -k -L -I \
     -d @hello-ubuntu.json \
     "https://127.0.0.1:8443/1.0/containers"
 ```
-where `hello-ubuntu.json` could contain:
+#### JSON payload
+The `hello-ubuntu.json` file referenced above could contain something like:
 ```json
 {
     "name":"some-ubuntu",

From 7d65940b33f106b7baecb64e7bb19509db5cfd0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 16 May 2016 16:41:43 -0400
Subject: [PATCH 0061/1193] Release LXD 2.0.1
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/flex.go b/shared/flex.go
index 7730eaac7..18fdbcbe4 100644
--- a/shared/flex.go
+++ b/shared/flex.go
@@ -3,7 +3,7 @@
  */
 package shared
 
-var Version = "2.0.0"
+var Version = "2.0.1"
 var UserAgent = "LXD " + Version
 
 /*

From 6f5b69cd208a97fc40803ac2f23cfd6116a41b1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 24 May 2016 14:48:21 -0400
Subject: [PATCH 0062/1193] CVE-2016-1581: Fix bad permissions on zfs.img
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When using a file backed zpool as created by "lxd init", the created
file was world readable, which would allow an untrusted user to read
container data.

This fix makes sure that zfs.img is always created 600 from now on and
also fixes the permission of any zfs.img file at LXD startup time.

Reported-by: Robie Basak
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go               |  2 +-
 lxd/db_update.go        | 19 +++++++++++++++++++
 lxd/main.go             |  5 +++++
 test/main.sh            |  4 ++++
 test/suites/security.sh | 26 ++++++++++++++++++++++++++
 5 files changed, 55 insertions(+), 1 deletion(-)
 create mode 100644 test/suites/security.sh

diff --git a/lxd/db.go b/lxd/db.go
index ca6d5d4ce..4b71cea93 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -34,7 +34,7 @@ type Profile struct {
 // Profiles will contain a list of all Profiles.
 type Profiles []Profile
 
-const DB_CURRENT_VERSION int = 29
+const DB_CURRENT_VERSION int = 30
 
 // CURRENT_SCHEMA contains the current SQLite SQL Schema.
 const CURRENT_SCHEMA string = `
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 4b0df51f9..509d1490c 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -15,6 +15,19 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
+func dbUpdateFromV29(db *sql.DB) error {
+	if shared.PathExists(shared.VarPath("zfs.img")) {
+		err := os.Chmod(shared.VarPath("zfs.img"), 0600)
+		if err != nil {
+			return err
+		}
+	}
+
+	stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
+	_, err := db.Exec(stmt, 30)
+	return err
+}
+
 func dbUpdateFromV28(db *sql.DB) error {
 	stmt := `
 INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "aadisable", 2 FROM profiles WHERE name="docker";
@@ -999,6 +1012,12 @@ func dbUpdate(d *Daemon, prevVersion int) error {
 			return err
 		}
 	}
+	if prevVersion < 30 {
+		err = dbUpdateFromV29(db)
+		if err != nil {
+			return err
+		}
+	}
 
 	return nil
 }
diff --git a/lxd/main.go b/lxd/main.go
index 27265b8ce..156decbc2 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -855,6 +855,11 @@ they otherwise would.
 				return fmt.Errorf("Failed to open %s: %s", storageDevice, err)
 			}
 
+			err = f.Chmod(0600)
+			if err != nil {
+				return fmt.Errorf("Failed to chmod %s: %s", storageDevice, err)
+			}
+
 			err = f.Truncate(int64(storageLoopSize * 1024 * 1024 * 1024))
 			if err != nil {
 				return fmt.Errorf("Failed to create sparse file %s: %s", storageDevice, err)
diff --git a/test/main.sh b/test/main.sh
index 5b5f486e7..fa22aa3d4 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -377,6 +377,10 @@ echo "==> TEST: basic usage"
 TEST_CURRENT=test_basic_usage
 test_basic_usage
 
+echo "==> TEST: security"
+TEST_CURRENT=test_security
+test_security
+
 echo "==> TEST: images (and cached image expiry)"
 TEST_CURRENT=test_image_expiry
 test_image_expiry
diff --git a/test/suites/security.sh b/test/suites/security.sh
new file mode 100644
index 000000000..ae9e9eb97
--- /dev/null
+++ b/test/suites/security.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+test_security() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
+  # CVE-2016-1581
+  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+    chmod +x "${LXD_INIT_DIR}"
+    spawn_lxd "${LXD_INIT_DIR}"
+
+    ZFS_POOL="lxdtest-$(basename "${LXD_DIR}")-init"
+    LXD_DIR=${LXD_INIT_DIR} lxd init --storage-backend zfs --storage-create-loop 1 --storage-pool "${ZFS_POOL}" --auto
+
+    PERM=$(stat -c %a "${LXD_INIT_DIR}/zfs.img")
+    if [ "${PERM}" != "600" ]; then
+      echo "Bad zfs.img permissions: ${PERM}"
+      zpool destroy "${ZFS_POOL}"
+      false
+    fi
+
+    zpool destroy "${ZFS_POOL}"
+    kill_lxd "${LXD_INIT_DIR}"
+  fi
+}

From 9b7f4b4d190c15b841d0e049da40a31f29fd8ebb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 24 May 2016 18:02:01 -0400
Subject: [PATCH 0063/1193] CVE-2016-1582: Bad permissions after remap
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When switching an unprivileged container into privileged mode, the
container directory remains owned by the container uid/gid and has a 755
directory mode rather than the expected 700.

This allows traversal by a host user to reach potentially now
root-owned setuid paths.

Reported-by: Robie Basak
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go    | 23 +++++++++++++++++++++++
 lxd/db.go               |  2 +-
 lxd/db_update.go        | 42 ++++++++++++++++++++++++++++++++++++++++++
 lxd/storage_lvm.go      | 21 +++++++++++++--------
 test/suites/security.sh | 34 ++++++++++++++++++++++++++++++++++
 5 files changed, 113 insertions(+), 9 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 6aa3510ab..588e0f354 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -978,6 +978,29 @@ func (c *containerLXC) startCommon() (string, error) {
 			}
 		}
 
+		var mode os.FileMode
+		var uid int
+		var gid int
+
+		if c.IsPrivileged() {
+			mode = 0700
+		} else {
+			mode = 0755
+			if idmap != nil {
+				uid, gid = idmap.ShiftIntoNs(0, 0)
+			}
+		}
+
+		err = os.Chmod(c.Path(), mode)
+		if err != nil {
+			return "", err
+		}
+
+		err = os.Chown(c.Path(), uid, gid)
+		if err != nil {
+			return "", err
+		}
+
 		err = c.StorageStop()
 		if err != nil {
 			return "", err
diff --git a/lxd/db.go b/lxd/db.go
index 4b71cea93..abd450ff1 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -34,7 +34,7 @@ type Profile struct {
 // Profiles will contain a list of all Profiles.
 type Profiles []Profile
 
-const DB_CURRENT_VERSION int = 30
+const DB_CURRENT_VERSION int = 31
 
 // CURRENT_SCHEMA contains the current SQLite SQL Schema.
 const CURRENT_SCHEMA string = `
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 509d1490c..0726fcf74 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -4,17 +4,53 @@ import (
 	"database/sql"
 	"encoding/hex"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"strconv"
 	"strings"
+	"syscall"
 
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
+func dbUpdateFromV30(db *sql.DB) error {
+	entries, err := ioutil.ReadDir(shared.VarPath("containers"))
+	if err != nil {
+		return err
+	}
+
+	for _, entry := range entries {
+		if !shared.IsDir(shared.VarPath("containers", entry.Name(), "rootfs")) {
+			continue
+		}
+
+		info, err := os.Stat(shared.VarPath("containers", entry.Name(), "rootfs"))
+		if err != nil {
+			return err
+		}
+
+		if int(info.Sys().(*syscall.Stat_t).Uid) == 0 {
+			err := os.Chmod(shared.VarPath("containers", entry.Name()), 0700)
+			if err != nil {
+				return err
+			}
+
+			err = os.Chown(shared.VarPath("containers", entry.Name()), 0, 0)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
+	_, err = db.Exec(stmt, 31)
+	return err
+}
+
 func dbUpdateFromV29(db *sql.DB) error {
 	if shared.PathExists(shared.VarPath("zfs.img")) {
 		err := os.Chmod(shared.VarPath("zfs.img"), 0600)
@@ -1018,6 +1054,12 @@ func dbUpdate(d *Daemon, prevVersion int) error {
 			return err
 		}
 	}
+	if prevVersion < 31 {
+		err = dbUpdateFromV30(db)
+		if err != nil {
+			return err
+		}
+	}
 
 	return nil
 }
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 7a4784fc9..2c729a5b5 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -301,14 +301,7 @@ func (s *storageLvm) ContainerCreateFromImage(
 		return fmt.Errorf("Error creating container directory: %v", err)
 	}
 
-	var mode os.FileMode
-	if container.IsPrivileged() {
-		mode = 0700
-	} else {
-		mode = 0755
-	}
-
-	err = os.Chmod(destPath, mode)
+	err = os.Chmod(destPath, 0700)
 	if err != nil {
 		return err
 	}
@@ -335,6 +328,18 @@ func (s *storageLvm) ContainerCreateFromImage(
 		return fmt.Errorf("Error mounting snapshot LV: %v", err)
 	}
 
+	var mode os.FileMode
+	if container.IsPrivileged() {
+		mode = 0700
+	} else {
+		mode = 0755
+	}
+
+	err = os.Chmod(destPath, mode)
+	if err != nil {
+		return err
+	}
+
 	if !container.IsPrivileged() {
 		if err = s.shiftRootfs(container); err != nil {
 			err2 := tryUnmount(destPath, 0)
diff --git a/test/suites/security.sh b/test/suites/security.sh
index ae9e9eb97..ce2524f7e 100644
--- a/test/suites/security.sh
+++ b/test/suites/security.sh
@@ -23,4 +23,38 @@ test_security() {
     zpool destroy "${ZFS_POOL}"
     kill_lxd "${LXD_INIT_DIR}"
   fi
+
+  # CVE-2016-1582
+  lxc launch testimage test-priv -c security.privileged=true
+
+  PERM=$(stat -L -c %a "${LXD_DIR}/containers/test-priv")
+  if [ "${PERM}" != "700" ]; then
+    echo "Bad container permissions: ${PERM}"
+    false
+  fi
+
+  lxc config set test-priv security.privileged false
+  lxc restart test-priv --force
+  lxc config set test-priv security.privileged true
+  lxc restart test-priv --force
+
+  PERM=$(stat -L -c %a "${LXD_DIR}/containers/test-priv")
+  if [ "${PERM}" != "700" ]; then
+    echo "Bad container permissions: ${PERM}"
+    false
+  fi
+
+  lxc delete test-priv --force
+
+  lxc launch testimage test-unpriv
+  lxc config set test-unpriv security.privileged true
+  lxc restart test-unpriv --force
+
+  PERM=$(stat -L -c %a "${LXD_DIR}/containers/test-unpriv")
+  if [ "${PERM}" != "700" ]; then
+    echo "Bad container permissions: ${PERM}"
+    false
+  fi
+
+  lxc delete test-unpriv --force
 }

From 65928918fe72633e6ede2c37a6a841b3e65995a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 25 May 2016 16:25:39 -0400
Subject: [PATCH 0064/1193] Release LXD 2.0.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/flex.go b/shared/flex.go
index 18fdbcbe4..6128c08db 100644
--- a/shared/flex.go
+++ b/shared/flex.go
@@ -3,7 +3,7 @@
  */
 package shared
 
-var Version = "2.0.1"
+var Version = "2.0.2"
 var UserAgent = "LXD " + Version
 
 /*

From 6342e59206d1276d5c0ddf2e9adc3a5802ae0e22 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 19 May 2016 19:41:41 +0000
Subject: [PATCH 0065/1193] better bash completion

* add completion of global and container config keys
* ignore --arguments when completing
* add completion of remote and profile names where it makes sense (but
  still always prefer completing local container names in commands like
  `lxc start`)
* do a better job of completing subcommands like `lxc config trust` and
  `lxc config device`

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 config/bash/lxd-client | 241 ++++++++++++++++++++++++++++++++++---------------
 1 file changed, 170 insertions(+), 71 deletions(-)

diff --git a/config/bash/lxd-client b/config/bash/lxd-client
index 631d7e85e..ac14dea3e 100644
--- a/config/bash/lxd-client
+++ b/config/bash/lxd-client
@@ -4,7 +4,7 @@ _have lxc && {
     _lxd_names()
     {
       COMPREPLY=( $( compgen -W \
-        "$( lxc list --fast | tail -n +4 | awk '{print $2}' | egrep -v '^(\||^$)' )" "$cur" )
+        "$( lxc list --fast | tail -n +4 | awk '{print $2}' | egrep -v '^(\||^$)' ) $1" "$cur" )
       )
     }
 
@@ -15,85 +15,184 @@ _have lxc && {
       )
     }
 
-    local cur prev
+    _lxd_remotes()
+    {
+      COMPREPLY=( $( compgen -W \
+        "$( lxc remote list | tail -n +4 | awk '{print $2}' | egrep -v '^(\||^$)' )" "$cur" )
+      )
+    }
+
+    _lxd_profiles()
+    {
+      COMPREPLY=( $( compgen -W "$( lxc profile list )" "$cur" ) )
+    }
 
     COMPREPLY=()
-    cur=${COMP_WORDS[COMP_CWORD]}
-    prev=${COMP_WORDS[COMP_CWORD-1]}
+    # ignore special --foo args
+    if [[ ${COMP_WORDS[COMP_CWORD]} == -* ]]; then
+      return 0
+    fi
+
     lxc_cmds="config copy delete exec file help image info init launch \
       list move profile publish remote restart restore snapshot start stop \
       version"
 
+    global_keys="core.https_address core.https_allowd_origin \
+      core.https_allowed_methods core.https_allowed_headers core.proxy_https \
+      core.proxy_http core.proxy_ignore_host core.trust_password \
+      storage.lvm_vg_name storage.lvm_thinpool_name storage.lvm_fstype \
+      storage.lvm_volume_size storage.zfs_pool_name
+      storage.zfs_remove_snapshots images.compression_algorithm \
+      images.remot_cache_expiry images.auto_update_interval \
+      images.auto_update_cached"
+
+    container_keys="boot.autostart boot.autostart.delay boot.autostart.priority \
+      limits.cpu limits.cpu.allowance limits.cpu.priority limits.disk.priority \
+      limits.memory limits.memory.enforce limits.memory.swap \
+      limits.memory.swap.priority limits.network.priority limits.processes \
+      linux.kernel_modules raw.apparmor raw.lxc security.nesting \
+      security.privileged volatile.apply_template volatile.base_image \
+      volatile.last_state.idmap volatile.last_state.power user.network_mode \
+      user.meta-data user.user-data user.vendor-data"
+
     if [ $COMP_CWORD -eq 1 ]; then
-      COMPREPLY=( $(compgen -W "$lxc_cmds" -- $cur) )
-    elif [ $COMP_CWORD -eq 2 ]; then
-      case "$prev" in
-        "config")
-          COMPREPLY=( $(compgen -W "device edit get set show trust" -- $cur) )
-          ;;
-        "copy")
-          _lxd_names
-          ;;
-        "delete")
-          _lxd_names
-          ;;
-        "exec")
-          _lxd_names
-          ;;
-        "file")
-          COMPREPLY=( $(compgen -W "pull push edit" -- $cur) )
-          ;;
-        "help")
-          COMPREPLY=( $(compgen -W "$lxc_cmds" -- $cur) )
-          ;;
-        "image")
-          COMPREPLY=( $(compgen -W "import copy delete edit export info list show alias" -- $cur) )
-          ;;
-        "info")
-          _lxd_names
-          ;;
-        "init")
-          _lxd_images
-          ;;
-        "launch")
-          _lxd_images
-          ;;
-        "move")
-          _lxd_names
-          ;;
-        "profile")
-          COMPREPLY=( $(compgen -W \
-            "list show create edit copy get set delete apply device" -- $cur) )
-          ;;
-        "publish")
-          _lxd_names
-          ;;
-        "remote")
-          COMPREPLY=( $(compgen -W \
-            "add remove list rename set-url set-default get-default" -- $cur) )
-          ;;
-        "restart")
-          _lxd_names
-          ;;
-        "restore")
-          _lxd_names
-          ;;
-        "snapshot")
-          _lxd_names
-          ;;
-        "start")
-          # should check if containers are stopped
-          _lxd_names
-          ;;
-        "stop")
-          # should check if containers are started
-          _lxd_names
-          ;;
-        *)
-          ;;
-      esac
+      COMPREPLY=( $(compgen -W "$lxc_cmds" -- ${COMP_WORDS[COMP_CWORD]}) )
+      return 0
     fi
 
+    local no_dashargs
+    cur=${COMP_WORDS[COMP_CWORD]}
+
+    no_dashargs=(${COMP_WORDS[@]//-*})
+    pos=$((COMP_CWORD - (${#COMP_WORDS[@]} - ${#no_dashargs[@]})))
+    if [ -z "$cur" ]; then
+      pos=$(($pos + 1))
+    fi
+
+    case ${no_dashargs[1]} in
+      "config")
+        case $pos in
+          2)
+            COMPREPLY=( $(compgen -W "device edit get set show trust" -- $cur) )
+            ;;
+          3)
+            case ${no_dashargs[2]} in
+              "trust")
+                COMPREPLY=( $(compgen -W "list add remove" -- $cur) )
+                ;;
+              "device")
+                COMPREPLY=( $(compgen -W "add get set unset list show remove" -- $cur) )
+                ;;
+              "show"|"edit")
+                _lxd_names
+                ;;
+              "get"|"set"|"unset")
+                _lxd_names "$global_keys"
+                ;;
+            esac
+            ;;
+          4)
+            case ${no_dashargs[2]} in
+              "trust")
+                _lxd_remotes
+                ;;
+              "device")
+                _lxd_names
+                ;;
+              "get"|"set"|"unset")
+                COMPREPLY=( $(compgen -W "$container_keys" -- $cur) )
+                ;;
+            esac
+            ;;
+        esac
+        ;;
+      "copy")
+        if [ $pos -lt 4 ]; then
+          _lxd_names
+        fi
+        ;;
+      "delete")
+        _lxd_names
+        ;;
+      "exec")
+        _lxd_names
+        ;;
+      "file")
+        COMPREPLY=( $(compgen -W "pull push edit" -- $cur) )
+        ;;
+      "help")
+        COMPREPLY=( $(compgen -W "$lxc_cmds" -- $cur) )
+        ;;
+      "image")
+        COMPREPLY=( $(compgen -W "import copy delete edit export info list show alias" -- $cur) )
+        ;;
+      "info")
+        _lxd_names
+        ;;
+      "init")
+        _lxd_images
+        ;;
+      "launch")
+        _lxd_images
+        ;;
+      "move")
+        _lxd_names
+        ;;
+      "profile")
+        case $pos in
+          2)
+            COMPREPLY=( $(compgen -W "list copy delete apply device edit get set show" -- $cur) )
+            ;;
+          3)
+            case ${no_dashargs[2]} in
+              "device")
+                COMPREPLY=( $(compgen -W "add get set unset list show remove" -- $cur) )
+                ;;
+              *)
+                _lxd_profiles
+                ;;
+            esac
+            ;;
+          4)
+            case ${no_dashargs[2]} in
+              "device")
+                _lxd_profiles
+                ;;
+              *)
+                COMPREPLY=( $(compgen -W "$container_keys" -- $cur) )
+                ;;
+            esac
+            ;;
+        esac
+        ;;
+      "publish")
+        _lxd_names
+        ;;
+      "remote")
+        COMPREPLY=( $(compgen -W \
+          "add remove list rename set-url set-default get-default" -- $cur) )
+        ;;
+      "restart")
+        _lxd_names
+        ;;
+      "restore")
+        _lxd_names
+        ;;
+      "snapshot")
+        _lxd_names
+        ;;
+      "start")
+        # should check if containers are stopped
+        _lxd_names
+        ;;
+      "stop")
+        # should check if containers are started
+        _lxd_names
+        ;;
+      *)
+        ;;
+    esac
+
     return 0
   }
 

From c1bee6268aac5760ca7df9d1fac6a221137e4980 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 24 May 2016 16:53:27 -0600
Subject: [PATCH 0066/1193] use default buffer size for WebsocketUpgrader

Let's use the default for this now so we do less copying. In my tests this
doesn't make a huge difference, but it does shave 2 seconds off of a 50
second or so transfer on average. Presumably bumping this higher when we
know we're going to be dumping a lot of data is a good idea, but that's for
a later set when I've done more experiments.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/operation.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/shared/operation.go b/shared/operation.go
index dabd72059..fa955e809 100644
--- a/shared/operation.go
+++ b/shared/operation.go
@@ -8,9 +8,7 @@ import (
 )
 
 var WebsocketUpgrader = websocket.Upgrader{
-	ReadBufferSize:  1024,
-	WriteBufferSize: 1024,
-	CheckOrigin:     func(r *http.Request) bool { return true },
+	CheckOrigin: func(r *http.Request) bool { return true },
 }
 
 type Operation struct {

From 55bb6c772af7fec717e51180608ba067b18e2adc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 24 May 2016 14:34:01 -0400
Subject: [PATCH 0067/1193] Add missing linebreak to lxd help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index 156decbc2..3968e1800 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -159,7 +159,7 @@ func run() error {
 		fmt.Printf("    callhook\n")
 		fmt.Printf("        Call a container hook\n")
 		fmt.Printf("    netcat\n")
-		fmt.Printf("        Mirror a unix socket to stdin/stdout")
+		fmt.Printf("        Mirror a unix socket to stdin/stdout\n")
 	}
 
 	// Parse the arguments

From d5b1db3a49a80b1645d289160b3a33d7e816886a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 25 May 2016 11:35:31 -0400
Subject: [PATCH 0068/1193] zfs: Improve block device detection
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When attempting to find the right block device for a zpool, only
consider actual block devices.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/devices.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index 0c95dc8ae..529450d7a 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -723,11 +723,11 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 						devices = append(devices, dev)
 					}
 				}
-			} else if shared.PathExists(fmt.Sprintf("/dev/%s", fields[0])) {
+			} else if deviceIsBlockdev(fmt.Sprintf("/dev/%s", fields[0])) {
 				path = fmt.Sprintf("/dev/%s", fields[0])
-			} else if shared.PathExists(fmt.Sprintf("/dev/disk/by-id/%s", fields[0])) {
+			} else if deviceIsBlockdev(fmt.Sprintf("/dev/disk/by-id/%s", fields[0])) {
 				path = fmt.Sprintf("/dev/disk/by-id/%s", fields[0])
-			} else if shared.PathExists(fmt.Sprintf("/dev/mapper/%s", fields[0])) {
+			} else if deviceIsBlockdev(fmt.Sprintf("/dev/mapper/%s", fields[0])) {
 				path = fmt.Sprintf("/dev/mapper/%s", fields[0])
 			} else {
 				continue

From 059e373b61eb1f16e356fa903141f421fff11f45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 26 May 2016 18:12:43 -0400
Subject: [PATCH 0069/1193] zfs: Mount if not mounted
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1888

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_zfs.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 2b7dbe39c..8071d515f 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -79,6 +79,13 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 
 // Things we don't need to care about
 func (s *storageZfs) ContainerStart(container container) error {
+	fs := fmt.Sprintf("containers/%s", container.Name())
+
+	// Just in case the container filesystem got unmounted
+	if !shared.IsMountPoint(shared.VarPath(fs)) {
+		s.zfsMount(fs)
+	}
+
 	return nil
 }
 

From 96ff947f3ff5ffa0ae9b64e16ddd615d47092c9c Mon Sep 17 00:00:00 2001
From: Rene Fragoso <ctrlrsf at gmail.com>
Date: Fri, 27 May 2016 10:26:15 -0400
Subject: [PATCH 0070/1193] Add push file method that does not send file owner
 and mode

This method is similar to PushFile, but it does not accept file owner
and mode parameters, and thus does not send any permissions to the lxd
server when pushing the file. By not sending the file permissions to lxd,
lxd will not change the file owner and permissions after file is copied.

This new method is required because PushFile could not be modified
without changing its API to accomplish the same thing. Although uid
and gid could be set to -1, mode is an os.FileMode (uint32) and
setting it to -1 would overflow it.

Signed-off-by: Rene Fragoso <ctrlrsf at gmail.com>
---
 client.go | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/client.go b/client.go
index e90324e4a..b364f9439 100644
--- a/client.go
+++ b/client.go
@@ -1660,6 +1660,29 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode os.
 	return err
 }
 
+func (c *Client) PushFileEdit(container string, p string, buf io.ReadSeeker) error {
+	if c.Remote.Public {
+		return fmt.Errorf("This function isn't supported by public remotes.")
+	}
+
+	query := url.Values{"path": []string{p}}
+	uri := c.url(shared.APIVersion, "containers", container, "files") + "?" + query.Encode()
+
+	req, err := http.NewRequest("POST", uri, buf)
+	if err != nil {
+		return err
+	}
+	req.Header.Set("User-Agent", shared.UserAgent)
+
+	raw, err := c.Http.Do(req)
+	if err != nil {
+		return err
+	}
+
+	_, err = HoistResponse(raw, Sync)
+	return err
+}
+
 func (c *Client) PullFile(container string, p string) (int, int, int, io.ReadCloser, error) {
 	if c.Remote.Public {
 		return 0, 0, 0, nil, fmt.Errorf("This function isn't supported by public remotes.")

From 35d6bdc5149669f51f7791cf67a478e6555bda88 Mon Sep 17 00:00:00 2001
From: Rene Fragoso <ctrlrsf at gmail.com>
Date: Fri, 27 May 2016 11:24:02 -0400
Subject: [PATCH 0071/1193] lxc file edit should use push method that doesn't
 send file owner and mode

The lxc file edit command needs to call a different/new method that pushes
a file without changing its owner and permissions.

Changes:

- Added pushEditedFile() which parses arguments and calls PushFileEdit()
  instead of normal PushFile().
- change edit() to call pushEditedFile() instead of push()

Signed-off-by: Rene Fragoso <ctrlrsf at gmail.com>
---
 lxc/file.go | 47 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 45 insertions(+), 2 deletions(-)

diff --git a/lxc/file.go b/lxc/file.go
index d96bc4c15..7ced3a5eb 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -153,6 +153,49 @@ func (c *fileCmd) push(config *lxd.Config, args []string) error {
 	return nil
 }
 
+func (c *fileCmd) pushEditedFile(config *lxd.Config, args []string) error {
+	if len(args) < 2 {
+		return errArgs
+	}
+
+	target := args[len(args)-1]
+	pathSpec := strings.SplitN(target, "/", 2)
+
+	if len(pathSpec) != 2 {
+		return fmt.Errorf(i18n.G("Invalid target %s"), target)
+	}
+
+	targetPath := pathSpec[1]
+	remote, container := config.ParseRemoteAndContainer(pathSpec[0])
+
+	d, err := lxd.NewClient(config, remote)
+	if err != nil {
+		return err
+	}
+
+	sourcefilename := args[0]
+
+	/* Make sure file is accessible by us before trying to push */
+	var file *os.File
+	if sourcefilename == "-" {
+		file = os.Stdin
+	} else {
+		file, err = os.Open(sourcefilename)
+		if err != nil {
+			return err
+		}
+	}
+
+	defer file.Close()
+
+	err = d.PushFileEdit(container, targetPath, file)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (c *fileCmd) pull(config *lxd.Config, args []string) error {
 	if len(args) < 2 {
 		return errArgs
@@ -235,7 +278,7 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
 
 	// If stdin isn't a terminal, read text from it
 	if !termios.IsTerminal(int(syscall.Stdin)) {
-		return c.push(config, append([]string{os.Stdin.Name()}, args[0]))
+		return c.pushEditedFile(config, append([]string{os.Stdin.Name()}, args[0]))
 	}
 
 	// Create temp file
@@ -256,7 +299,7 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	err = c.push(config, append([]string{fname}, args[0]))
+	err = c.pushEditedFile(config, append([]string{fname}, args[0]))
 	if err != nil {
 		return err
 	}

From c831604a8bdf2ef1f22765fb8fb1778f321d7375 Mon Sep 17 00:00:00 2001
From: Rene Fragoso <ctrlrsf at gmail.com>
Date: Fri, 27 May 2016 11:33:48 -0400
Subject: [PATCH 0072/1193] Add test for "lxc file edit" target file owner and
 permission

Signed-off-by: Rene Fragoso <ctrlrsf at gmail.com>
---
 test/suites/basic.sh | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 7bd2fb889..a4f52e27b 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -212,6 +212,14 @@ test_basic_usage() {
   lxc exec foo /bin/cat /root/in1 | grep abc
   lxc exec foo -- /bin/rm -f root/in1
 
+  # test lxc file edit doesn't change target file's owner and permissions
+  echo "content" | lxc file push - foo/tmp/edit_test
+  lxc exec foo -- chown 55.55 /tmp/edit_test
+  lxc exec foo -- chmod 555 /tmp/edit_test
+  echo "new content" | lxc file edit foo/tmp/edit_test
+  [ "$(lxc exec foo -- cat /tmp/edit_test)" = "new content" ]
+  [ "$(lxc exec foo -- stat -c \"%u %g %a\" /tmp/edit_test)" = "55 55 555" ]
+
   # make sure stdin is chowned to our container root uid (Issue #590)
   [ -t 0 ] && [ -t 1 ] && lxc exec foo -- chown 1000:1000 /proc/self/fd/0
 

From 76334f29a22327dd2d2b53941eeb0305ccde6d4e Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 31 May 2016 09:55:40 -0600
Subject: [PATCH 0073/1193] GET of a nonexistent file 404s

Closes #2059

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_file.go    | 2 +-
 lxd/container_lxc.go     | 3 +++
 test/suites/filemanip.sh | 5 +++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/lxd/container_file.go b/lxd/container_file.go
index 06590a5b2..7ac2085c6 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -52,7 +52,7 @@ func containerFileGet(c container, path string, r *http.Request) Response {
 	// Pul the file from the container
 	uid, gid, mode, err := c.FilePull(path, temp.Name())
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	headers := map[string]string{
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 588e0f354..fd0ee4c4b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2909,6 +2909,9 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 
 		// Extract errors
 		if strings.HasPrefix(line, "error: ") {
+			if strings.HasSuffix(line, "No such file or directory") {
+				return -1, -1, 0, os.ErrNotExist
+			}
 			return -1, -1, 0, fmt.Errorf(strings.TrimPrefix(line, "error: "))
 		}
 
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index f4b7b0534..07ca1488e 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -2,6 +2,7 @@
 
 test_filemanip() {
   ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
 
   lxc launch testimage filemanip
   lxc exec filemanip -- ln -s /tmp/ /tmp/outside
@@ -10,5 +11,9 @@ test_filemanip() {
   [ ! -f /tmp/main.sh ]
   lxc exec filemanip -- ls /tmp/main.sh
 
+  # missing files should return 404
+  err=$(my_curl -o /dev/null -w "%{http_code}" -X GET "https://${LXD_ADDR}/1.0/containers/filemanip/files?path=/tmp/foo")
+  [ "${err}" -eq "404" ]
+
   lxc delete filemanip -f
 }

From a628545e2608a1c97b3634f1965bfcfb93550e94 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 31 May 2016 17:21:23 +0000
Subject: [PATCH 0074/1193] fork file handler: print the errno to stdout as
 well

That way we can handle specific errors as necessary.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 12 ++++++++++--
 lxd/nsexec.go        | 40 ++++++++++++++++++++++++----------------
 2 files changed, 34 insertions(+), 18 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fd0ee4c4b..2f48cfac7 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2901,6 +2901,8 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 	gid := -1
 	mode := -1
 
+	var errStr string
+
 	// Process forkgetfile response
 	for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
 		if line == "" {
@@ -2909,10 +2911,16 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 
 		// Extract errors
 		if strings.HasPrefix(line, "error: ") {
-			if strings.HasSuffix(line, "No such file or directory") {
+			errStr = strings.TrimPrefix(line, "error: ")
+			continue
+		}
+
+		if strings.HasPrefix(line, "errno: ") {
+			errno := strings.TrimPrefix(line, "errno: ")
+			if errno == "2" {
 				return -1, -1, 0, os.ErrNotExist
 			}
-			return -1, -1, 0, fmt.Errorf(strings.TrimPrefix(line, "error: "))
+			return -1, -1, 0, fmt.Errorf(errStr)
 		}
 
 		// Extract the uid
diff --git a/lxd/nsexec.go b/lxd/nsexec.go
index 836adadfb..30e2d1ac7 100644
--- a/lxd/nsexec.go
+++ b/lxd/nsexec.go
@@ -48,6 +48,14 @@ package main
 //
 #define CMDLINE_SIZE (8 * PATH_MAX)
 
+void error(char *msg)
+{
+	int old_errno = errno;
+
+	perror(msg);
+	fprintf(stderr, "errno: %d\n", old_errno);
+}
+
 int mkdir_p(const char *dir, mode_t mode)
 {
 	const char *tmp = dir;
@@ -77,19 +85,19 @@ int copy(int target, int source)
 	char buf[1024];
 
 	if (ftruncate(target, 0) < 0) {
-		perror("error: truncate");
+		error("error: truncate");
 		return -1;
 	}
 
 	while ((n = read(source, buf, 1024)) > 0) {
 		if (write(target, buf, n) != n) {
-			perror("error: write");
+			error("error: write");
 			return -1;
 		}
 	}
 
 	if (n < 0) {
-		perror("error: read");
+		error("error: read");
 		return -1;
 	}
 
@@ -103,12 +111,12 @@ int dosetns(int pid, char *nstype) {
 	sprintf(buf, "/proc/%d/ns/%s", pid, nstype);
 	mntns = open(buf, O_RDONLY);
 	if (mntns < 0) {
-		perror("error: open mntns");
+		error("error: open mntns");
 		return -1;
 	}
 
 	if (setns(mntns, 0) < 0) {
-		perror("error: setns");
+		error("error: setns");
 		close(mntns);
 		return -1;
 	}
@@ -126,7 +134,7 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 
 	host_fd = open(host, O_RDWR);
 	if (host_fd < 0) {
-		perror("error: open");
+		error("error: open");
 		return -1;
 	}
 
@@ -136,17 +144,17 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 
 	if (pid > 0) {
 		if (dosetns(pid, "mnt") < 0) {
-			perror("error: setns");
+			error("error: setns");
 			goto close_host;
 		}
 	} else {
 		if (chroot(rootfs) < 0) {
-			perror("error: chroot");
+			error("error: chroot");
 			goto close_host;
 		}
 
 		if (chdir("/") < 0) {
-			perror("error: chdir");
+			error("error: chdir");
 			goto close_host;
 		}
 	}
@@ -157,7 +165,7 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 	umask(0);
 	container_fd = open(container, container_open_flags, 0);
 	if (container_fd < 0) {
-		perror("error: open");
+		error("error: open");
 		goto close_host;
 	}
 
@@ -177,17 +185,17 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 		}
 
 		if (copy(container_fd, host_fd) < 0) {
-			perror("error: copy");
+			error("error: copy");
 			goto close_container;
 		}
 
 		if (mode != -1 && fchmod(container_fd, mode) < 0) {
-			perror("error: chmod");
+			error("error: chmod");
 			goto close_container;
 		}
 
 		if (fchown(container_fd, uid, gid) < 0) {
-			perror("error: chown");
+			error("error: chown");
 			goto close_container;
 		}
 
@@ -196,7 +204,7 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 		ret = copy(host_fd, container_fd);
 
 		if (fstat(container_fd, &st) < 0) {
-			perror("error: stat");
+			error("error: stat");
 			goto close_container;
 		}
 
@@ -416,14 +424,14 @@ __attribute__((constructor)) void init(void) {
 
 	cmdline = open("/proc/self/cmdline", O_RDONLY);
 	if (cmdline < 0) {
-		perror("error: open");
+		error("error: open");
 		_exit(232);
 	}
 
 	memset(buf, 0, sizeof(buf));
 	if ((size = read(cmdline, buf, sizeof(buf)-1)) < 0) {
 		close(cmdline);
-		perror("error: read");
+		error("error: read");
 		_exit(232);
 	}
 	close(cmdline);

From 20e99aed44984b10d8fb9811fec4d3e0e37b3cfd Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 23 May 2016 15:21:23 -0600
Subject: [PATCH 0075/1193] use some buffering for zfs/btrfs send

For now, let's use a 4MB buffer.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go             |  4 ++--
 lxd/container_exec.go |  2 +-
 lxd/storage_btrfs.go  |  2 +-
 lxd/storage_zfs.go    |  2 +-
 shared/network.go     | 10 +++++++---
 shared/util.go        | 22 ++++++++++++++++------
 6 files changed, 28 insertions(+), 14 deletions(-)

diff --git a/client.go b/client.go
index b364f9439..5f4b74021 100644
--- a/client.go
+++ b/client.go
@@ -1446,7 +1446,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 			return -1, err
 		}
 
-		shared.WebsocketSendStream(conn, stdin)
+		shared.WebsocketSendStream(conn, stdin, -1)
 		<-shared.WebsocketRecvStream(stdout, conn)
 		conn.Close()
 
@@ -1460,7 +1460,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		}
 		defer conns[0].Close()
 
-		dones[0] = shared.WebsocketSendStream(conns[0], stdin)
+		dones[0] = shared.WebsocketSendStream(conns[0], stdin, -1)
 
 		outputs := []io.WriteCloser{stdout, stderr}
 		for i := 1; i < 3; i++ {
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 2e706050d..71aee790d 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -204,7 +204,7 @@ func (s *execWs) Do(op *operation) error {
 					<-shared.WebsocketRecvStream(ttys[i], s.conns[i])
 					ttys[i].Close()
 				} else {
-					<-shared.WebsocketSendStream(s.conns[i], ptys[i])
+					<-shared.WebsocketSendStream(s.conns[i], ptys[i], -1)
 					ptys[i].Close()
 					wgEOF.Done()
 				}
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index e9903c0f5..79c062122 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -850,7 +850,7 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
 		return err
 	}
 
-	<-shared.WebsocketSendStream(conn, stdout)
+	<-shared.WebsocketSendStream(conn, stdout, 4*1024*1024)
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 8071d515f..3d6bee884 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -1205,7 +1205,7 @@ func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zf
 		return err
 	}
 
-	<-shared.WebsocketSendStream(conn, stdout)
+	<-shared.WebsocketSendStream(conn, stdout, 4*1024*1024)
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
diff --git a/shared/network.go b/shared/network.go
index f64e5fe45..08e70191a 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -115,7 +115,7 @@ func IsLoopback(iface *net.Interface) bool {
 	return int(iface.Flags&net.FlagLoopback) > 0
 }
 
-func WebsocketSendStream(conn *websocket.Conn, r io.Reader) chan bool {
+func WebsocketSendStream(conn *websocket.Conn, r io.Reader, bufferSize int) chan bool {
 	ch := make(chan bool)
 
 	if r == nil {
@@ -124,7 +124,7 @@ func WebsocketSendStream(conn *websocket.Conn, r io.Reader) chan bool {
 	}
 
 	go func(conn *websocket.Conn, r io.Reader) {
-		in := ReaderToChannel(r)
+		in := ReaderToChannel(r, bufferSize)
 		for {
 			buf, ok := <-in
 			if !ok {
@@ -244,7 +244,11 @@ func WebsocketMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser) (c
 	}(conn, w)
 
 	go func(conn *websocket.Conn, r io.ReadCloser) {
-		in := ReaderToChannel(r)
+		/* For now, we don't need to adjust buffer sizes in
+		 * WebsocketMirror, since it's used for interactive things like
+		 * exec.
+		 */
+		in := ReaderToChannel(r, -1)
 		for {
 			buf, ok := <-in
 			if !ok {
diff --git a/shared/util.go b/shared/util.go
index a71cb91cf..5b9376a2f 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -129,16 +129,26 @@ func ReadToJSON(r io.Reader, req interface{}) error {
 	return json.Unmarshal(buf, req)
 }
 
-func ReaderToChannel(r io.Reader) <-chan []byte {
+func ReaderToChannel(r io.Reader, bufferSize int) <-chan []byte {
+	if bufferSize <= 128*1024 {
+		bufferSize = 128 * 1024
+	}
+
 	ch := make(chan ([]byte))
 
 	go func() {
+		readSize := 128 * 1024
+		offset := 0
+		buf := make([]byte, bufferSize)
+
 		for {
-			/* io.Copy uses a 32KB buffer, so we might as well too. */
-			buf := make([]byte, 32*1024)
-			nr, err := r.Read(buf)
-			if nr > 0 {
-				ch <- buf[0:nr]
+			read := buf[offset : offset+readSize]
+			nr, err := r.Read(read)
+			offset += nr
+			if offset+readSize >= bufferSize || err != nil {
+				ch <- buf[0:offset]
+				offset = 0
+				buf = make([]byte, bufferSize)
 			}
 
 			if err != nil {

From 3653f79b090eb1ec8853aac5b0d5f9ea21993b60 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 24 May 2016 16:06:13 +0000
Subject: [PATCH 0076/1193] add a test for ReaderToChannel

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/util_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/shared/util_test.go b/shared/util_test.go
index 33e12b758..253fcd260 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -1,8 +1,10 @@
 package shared
 
 import (
+	"bytes"
 	"fmt"
 	"io/ioutil"
+	"math/rand"
 	"os"
 	"strings"
 	"testing"
@@ -100,3 +102,43 @@ func TestReadLastNLines(t *testing.T) {
 		}
 	}
 }
+
+func TestReaderToChannel(t *testing.T) {
+	buf := make([]byte, 64*1024*1024)
+	rand.Read(buf)
+
+	offset := 0
+	finished := false
+
+	ch := ReaderToChannel(bytes.NewBuffer(buf), -1)
+	for {
+		data, ok := <-ch
+		if len(data) > 0 {
+			for i := 0; i < len(data); i++ {
+				if buf[offset+i] != data[i] {
+					t.Error(fmt.Sprintf("byte %d didn't match", offset+i))
+					return
+				}
+			}
+
+			offset += len(data)
+			if offset > len(buf) {
+				t.Error("read too much data")
+				return
+			}
+
+			if offset == len(buf) {
+				finished = true
+			}
+		}
+
+		if !ok {
+			if !finished {
+				t.Error("connection closed too early")
+				return
+			} else {
+				break
+			}
+		}
+	}
+}

From a21b89305bb414126bdf533532b6a52017e0aef3 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 1 Jun 2016 12:16:59 -0600
Subject: [PATCH 0077/1193] don't try to update /var/lib/lxd/containers in go
 tests

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/db_update.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index 0726fcf74..c7e915975 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -17,7 +17,13 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-func dbUpdateFromV30(db *sql.DB) error {
+func dbUpdateFromV30(db *sql.DB, mockMode bool) error {
+	if mockMode {
+		stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
+		_, err := db.Exec(stmt, 31)
+		return err
+	}
+
 	entries, err := ioutil.ReadDir(shared.VarPath("containers"))
 	if err != nil {
 		return err
@@ -1055,7 +1061,7 @@ func dbUpdate(d *Daemon, prevVersion int) error {
 		}
 	}
 	if prevVersion < 31 {
-		err = dbUpdateFromV30(db)
+		err = dbUpdateFromV30(db, d.MockMode)
 		if err != nil {
 			return err
 		}

From 465654fb2e1ea1c3c622e164b56e5a5cb988c81d Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 1 Jun 2016 22:16:18 +0000
Subject: [PATCH 0078/1193] don't try to chmod zfs.img when testing db upgrades

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/db_update.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index c7e915975..d8056d89d 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -57,7 +57,13 @@ func dbUpdateFromV30(db *sql.DB, mockMode bool) error {
 	return err
 }
 
-func dbUpdateFromV29(db *sql.DB) error {
+func dbUpdateFromV29(db *sql.DB, mockMode bool) error {
+	if mockMode {
+		stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
+		_, err := db.Exec(stmt, 30)
+		return err
+	}
+
 	if shared.PathExists(shared.VarPath("zfs.img")) {
 		err := os.Chmod(shared.VarPath("zfs.img"), 0600)
 		if err != nil {
@@ -1055,7 +1061,7 @@ func dbUpdate(d *Daemon, prevVersion int) error {
 		}
 	}
 	if prevVersion < 30 {
-		err = dbUpdateFromV29(db)
+		err = dbUpdateFromV29(db, d.MockMode)
 		if err != nil {
 			return err
 		}

From 98bc871a8ff156c2babef4a2a5a4a8785a183503 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 27 May 2016 20:07:48 -0400
Subject: [PATCH 0079/1193] Fix parsing of <FQDN>:<PORT>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/remote.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index e5573b54d..a01b0f65a 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -113,7 +113,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 
 	// Fix broken URL parser
 	if !strings.Contains(addr, "://") && remoteURL.Scheme != "" && remoteURL.Scheme != "unix" && remoteURL.Host == "" {
-		remoteURL.Host = remoteURL.Scheme
+		remoteURL.Host = addr
 		remoteURL.Scheme = ""
 	}
 

From 879f471a6f270f8b429e408a1a0009aaa8a53604 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 1 Jun 2016 23:06:58 +0000
Subject: [PATCH 0080/1193] use crypto/rand instead of math/rand for testing

We don't really need good crypto data here, but the nice math/rand.Read API
wasn't added until go 1.6, and we still test on 1.5, so let's just use this
for now.

Some day in the future we can revert this and have our tests go fast again.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/util_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/util_test.go b/shared/util_test.go
index 253fcd260..fb94d9f0f 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -4,7 +4,7 @@ import (
 	"bytes"
 	"fmt"
 	"io/ioutil"
-	"math/rand"
+	"crypto/rand"
 	"os"
 	"strings"
 	"testing"

From f9c24d2adfcee104a249ba841d9956354bc66aeb Mon Sep 17 00:00:00 2001
From: Rene Fragoso <ctrlrsf at gmail.com>
Date: Thu, 2 Jun 2016 09:51:28 -0400
Subject: [PATCH 0081/1193] Remove edit push functions and use flag instead

Signed-off-by: Rene Fragoso <ctrlrsf at gmail.com>
---
 client.go   | 31 ++++++-----------------
 lxc/file.go | 82 +++++++++++++++++--------------------------------------------
 2 files changed, 29 insertions(+), 84 deletions(-)

diff --git a/client.go b/client.go
index 5f4b74021..3f2431e97 100644
--- a/client.go
+++ b/client.go
@@ -1633,7 +1633,7 @@ func (c *Client) ProfileConfig(name string) (*shared.ProfileConfig, error) {
 	return &ct, nil
 }
 
-func (c *Client) PushFile(container string, p string, gid int, uid int, mode os.FileMode, buf io.ReadSeeker) error {
+func (c *Client) PushFile(container string, p string, gid int, uid int, mode string, buf io.ReadSeeker) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1647,32 +1647,15 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode os.
 	}
 	req.Header.Set("User-Agent", shared.UserAgent)
 
-	req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", mode.Perm()))
-	req.Header.Set("X-LXD-uid", strconv.FormatUint(uint64(uid), 10))
-	req.Header.Set("X-LXD-gid", strconv.FormatUint(uint64(gid), 10))
-
-	raw, err := c.Http.Do(req)
-	if err != nil {
-		return err
+	if mode != "" {
+		req.Header.Set("X-LXD-mode", mode)
 	}
-
-	_, err = HoistResponse(raw, Sync)
-	return err
-}
-
-func (c *Client) PushFileEdit(container string, p string, buf io.ReadSeeker) error {
-	if c.Remote.Public {
-		return fmt.Errorf("This function isn't supported by public remotes.")
+	if uid != -1 {
+		req.Header.Set("X-LXD-uid", strconv.FormatUint(uint64(uid), 10))
 	}
-
-	query := url.Values{"path": []string{p}}
-	uri := c.url(shared.APIVersion, "containers", container, "files") + "?" + query.Encode()
-
-	req, err := http.NewRequest("POST", uri, buf)
-	if err != nil {
-		return err
+	if gid != -1 {
+		req.Header.Set("X-LXD-gid", strconv.FormatUint(uint64(gid), 10))
 	}
-	req.Header.Set("User-Agent", shared.UserAgent)
 
 	raw, err := c.Http.Do(req)
 	if err != nil {
diff --git a/lxc/file.go b/lxc/file.go
index 7ced3a5eb..40d5994d1 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -45,7 +45,7 @@ func (c *fileCmd) flags() {
 	gnuflag.StringVar(&c.mode, "mode", "", i18n.G("Set the file's perms on push"))
 }
 
-func (c *fileCmd) push(config *lxd.Config, args []string) error {
+func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string) error {
 	if len(args) < 2 {
 		return errArgs
 	}
@@ -125,74 +125,36 @@ func (c *fileCmd) push(config *lxd.Config, args []string) error {
 			fpath = path.Join(fpath, path.Base(f.Name()))
 		}
 
-		if c.mode == "" || c.uid == -1 || c.gid == -1 {
-			fMode, fUid, fGid, err := c.getOwner(f)
-			if err != nil {
-				return err
-			}
+		if send_file_perms {
+			if c.mode == "" || c.uid == -1 || c.gid == -1 {
+				fMode, fUid, fGid, err := c.getOwner(f)
+				if err != nil {
+					return err
+				}
 
-			if c.mode == "" {
-				mode = fMode
-			}
+				if c.mode == "" {
+					mode = fMode
+				}
 
-			if c.uid == -1 {
-				uid = fUid
-			}
+				if c.uid == -1 {
+					uid = fUid
+				}
 
-			if c.gid == -1 {
-				gid = fGid
+				if c.gid == -1 {
+					gid = fGid
+				}
 			}
-		}
 
-		err = d.PushFile(container, fpath, gid, uid, mode, f)
-		if err != nil {
-			return err
+			err = d.PushFile(container, fpath, gid, uid, fmt.Sprintf("%04o", mode.Perm()), f)
+		} else {
+			err = d.PushFile(container, fpath, -1, -1, "", f)
 		}
-	}
-
-	return nil
-}
-
-func (c *fileCmd) pushEditedFile(config *lxd.Config, args []string) error {
-	if len(args) < 2 {
-		return errArgs
-	}
-
-	target := args[len(args)-1]
-	pathSpec := strings.SplitN(target, "/", 2)
-
-	if len(pathSpec) != 2 {
-		return fmt.Errorf(i18n.G("Invalid target %s"), target)
-	}
 
-	targetPath := pathSpec[1]
-	remote, container := config.ParseRemoteAndContainer(pathSpec[0])
-
-	d, err := lxd.NewClient(config, remote)
-	if err != nil {
-		return err
-	}
-
-	sourcefilename := args[0]
-
-	/* Make sure file is accessible by us before trying to push */
-	var file *os.File
-	if sourcefilename == "-" {
-		file = os.Stdin
-	} else {
-		file, err = os.Open(sourcefilename)
 		if err != nil {
 			return err
 		}
 	}
 
-	defer file.Close()
-
-	err = d.PushFileEdit(container, targetPath, file)
-	if err != nil {
-		return err
-	}
-
 	return nil
 }
 
@@ -278,7 +240,7 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
 
 	// If stdin isn't a terminal, read text from it
 	if !termios.IsTerminal(int(syscall.Stdin)) {
-		return c.pushEditedFile(config, append([]string{os.Stdin.Name()}, args[0]))
+		return c.push(config, false, append([]string{os.Stdin.Name()}, args[0]))
 	}
 
 	// Create temp file
@@ -299,7 +261,7 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	err = c.pushEditedFile(config, append([]string{fname}, args[0]))
+	err = c.push(config, false, append([]string{fname}, args[0]))
 	if err != nil {
 		return err
 	}
@@ -314,7 +276,7 @@ func (c *fileCmd) run(config *lxd.Config, args []string) error {
 
 	switch args[0] {
 	case "push":
-		return c.push(config, args[1:])
+		return c.push(config, true, args[1:])
 	case "pull":
 		return c.pull(config, args[1:])
 	case "edit":

From c2f574ec51f764e45e4168b939e8647e8113c224 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Thu, 2 Jun 2016 08:35:14 -0700
Subject: [PATCH 0082/1193] Check for zero byte send in ReaderToChannel

Run gofmt on shared/util_test.go to resolve a test failure.

Fixes #2072

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 shared/util.go      | 2 +-
 shared/util_test.go | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/shared/util.go b/shared/util.go
index 5b9376a2f..04398dbb6 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -145,7 +145,7 @@ func ReaderToChannel(r io.Reader, bufferSize int) <-chan []byte {
 			read := buf[offset : offset+readSize]
 			nr, err := r.Read(read)
 			offset += nr
-			if offset+readSize >= bufferSize || err != nil {
+			if offset > 0 && (offset+readSize >= bufferSize || err != nil) {
 				ch <- buf[0:offset]
 				offset = 0
 				buf = make([]byte, bufferSize)
diff --git a/shared/util_test.go b/shared/util_test.go
index fb94d9f0f..19d07e9b9 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -2,9 +2,9 @@ package shared
 
 import (
 	"bytes"
+	"crypto/rand"
 	"fmt"
 	"io/ioutil"
-	"crypto/rand"
 	"os"
 	"strings"
 	"testing"

From 66886764e2c34a1377679430fc898a283d4f12ed Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 1 Jun 2016 21:26:02 +0000
Subject: [PATCH 0083/1193] probably fix a concurrent websocket write

Consider,

t=2016-05-31T20:49:07+0000 lvl=dbug msg=MigrationSink driver=storage/dir live=false container=nonlive objects=[nonlive/snap0]
t=2016-05-31T20:49:07+0000 lvl=dbug msg="sending write barrier"
t=2016-05-31T20:49:07+0000 lvl=dbug msg="Got message barrier, resetting stream"
t=2016-05-31T20:49:07+0000 lvl=dbug msg="sending write barrier"
t=2016-05-31T20:49:07+0000 lvl=dbug msg="Got message barrier, resetting stream"
panic: concurrent write to websocket connection

goroutine 560 [running]:
panic(0x9b9da0, 0xc42012a000)
	/lxd/build/tmp.xTgWRy05x1/go/golang/src/runtime/panic.go:500 +0x1a1
github.com/gorilla/websocket.(*Conn).flushFrame(0xc42000bb00, 0xc42034e601, 0x0, 0x0, 0x0, 0xc42034e778, 0xc42046c001)
	/lxd/build/tmp.xTgWRy05x1/go/src/github.com/gorilla/websocket/conn.go:481 +0x414
github.com/gorilla/websocket.(*Conn).NextWriter(0xc42000bb00, 0x2, 0xc42034e778, 0x1044e01, 0xc42028aa20, 0x0)
	/lxd/build/tmp.xTgWRy05x1/go/src/github.com/gorilla/websocket/conn.go:407 +0x1a1
github.com/lxc/lxd/shared.WebsocketMirror.func2(0xc420147180, 0xc42000bb00, 0x10482e0, 0xc42028aa20)
	/lxd/build/tmp.xTgWRy05x1/go/src/github.com/lxc/lxd/shared/network.go:257 +0x125
created by github.com/lxc/lxd/shared.WebsocketMirror
	/lxd/build/tmp.xTgWRy05x1/go/src/github.com/lxc/lxd/shared/network.go:274 +0xe2
error: Get https://127.0.0.1:22441/1.0/operations/eb592bbb-aa8c-4a0f-a57f-cac9285fedba/wait: Unable to connect to: 127.0.0.1:22441

One way this could happen is that the goroute that is sending the snapshot
signals readDone, and then the storage driver starts sending the next bits over
the websocket before the message barrier is actually sent. This seems really
unlikley to happen since the goroutine sending the message needs to be starved
for a long time, but given that we've only seen this stack trace once in the
last few months it seems quite rare.

There may be other ways for this to occur, but this is the only one I can see
right now.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/network.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/network.go b/shared/network.go
index 08e70191a..fa08b42a0 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -252,10 +252,10 @@ func WebsocketMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser) (c
 		for {
 			buf, ok := <-in
 			if !ok {
-				readDone <- true
 				r.Close()
 				Debugf("sending write barrier")
 				conn.WriteMessage(websocket.TextMessage, []byte{})
+				readDone <- true
 				return
 			}
 			w, err := conn.NextWriter(websocket.BinaryMessage)

From c9648ca41560d54ae1983fb357649fb5f20448b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 2 Jun 2016 23:47:51 -0400
Subject: [PATCH 0084/1193] patches: Add new table and schema update
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go        |  8 +++++++-
 lxd/db_update.go | 19 +++++++++++++++++++
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/lxd/db.go b/lxd/db.go
index abd450ff1..17fc1379a 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -34,7 +34,7 @@ type Profile struct {
 // Profiles will contain a list of all Profiles.
 type Profiles []Profile
 
-const DB_CURRENT_VERSION int = 31
+const DB_CURRENT_VERSION int = 32
 
 // CURRENT_SCHEMA contains the current SQLite SQL Schema.
 const CURRENT_SCHEMA string = `
@@ -135,6 +135,12 @@ CREATE TABLE IF NOT EXISTS images_source (
     alias VARCHAR(255) NOT NULL,
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
 );
+CREATE TABLE IF NOT EXISTS patches (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    applied_at DATETIME NOT NULL,
+    UNIQUE (name)
+);
 CREATE TABLE IF NOT EXISTS profiles (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
diff --git a/lxd/db_update.go b/lxd/db_update.go
index d8056d89d..95ed4d36a 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -17,6 +17,19 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
+func dbUpdateFromV31(db *sql.DB) error {
+	stmt := `
+CREATE TABLE IF NOT EXISTS patches (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    applied_at DATETIME NOT NULL,
+    UNIQUE (name)
+);
+INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
+	_, err := db.Exec(stmt, 32)
+	return err
+}
+
 func dbUpdateFromV30(db *sql.DB, mockMode bool) error {
 	if mockMode {
 		stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
@@ -1072,6 +1085,12 @@ func dbUpdate(d *Daemon, prevVersion int) error {
 			return err
 		}
 	}
+	if prevVersion < 32 {
+		err = dbUpdateFromV31(db)
+		if err != nil {
+			return err
+		}
+	}
 
 	return nil
 }

From 5e04551de1dbccd1266b8b33e15b33518f9b8271 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 3 Jun 2016 00:57:06 -0400
Subject: [PATCH 0085/1193] patches: Initial implementation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go     |  8 ++++++-
 lxd/db_patches.go | 30 +++++++++++++++++++++++++
 lxd/patches.go    | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 102 insertions(+), 1 deletion(-)
 create mode 100644 lxd/db_patches.go
 create mode 100644 lxd/patches.go

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 49a6f7f5b..9e5aed7ff 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -735,12 +735,18 @@ func (d *Daemon) Init() error {
 		return err
 	}
 
-	/* Setup the storage driver */
 	if !d.MockMode {
+		/* Setup the storage driver */
 		err = d.SetupStorageDriver()
 		if err != nil {
 			return fmt.Errorf("Failed to setup storage: %s", err)
 		}
+
+		/* Apply all patches */
+		err = patchesApplyAll(d)
+		if err != nil {
+			return err
+		}
 	}
 
 	/* Log expiry */
diff --git a/lxd/db_patches.go b/lxd/db_patches.go
new file mode 100644
index 000000000..cd2876aac
--- /dev/null
+++ b/lxd/db_patches.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+	"database/sql"
+	"fmt"
+)
+
+func dbPatches(db *sql.DB) ([]string, error) {
+	inargs := []interface{}{}
+	outfmt := []interface{}{""}
+
+	query := fmt.Sprintf("SELECT name FROM patches")
+	result, err := dbQueryScan(db, query, inargs, outfmt)
+	if err != nil {
+		return []string{}, err
+	}
+
+	response := []string{}
+	for _, r := range result {
+		response = append(response, r[0].(string))
+	}
+
+	return response, nil
+}
+
+func dbPatchesMarkApplied(db *sql.DB, patch string) error {
+	stmt := `INSERT INTO patches (name, applied_at) VALUES (?, strftime("%s"));`
+	_, err := db.Exec(stmt, patch)
+	return err
+}
diff --git a/lxd/patches.go b/lxd/patches.go
new file mode 100644
index 000000000..b0174c719
--- /dev/null
+++ b/lxd/patches.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+	"github.com/lxc/lxd/shared"
+)
+
+/* Patches are one-time actions that are sometimes needed to update
+   existing container configuration or move things around on the
+   filesystem.
+
+   Those patches are applied at startup time after the database schema
+   has been fully updated. Patches can therefore assume a working database.
+
+   At the time the patches are applied, the containers aren't started
+   yet and the daemon isn't listening to requests.
+
+   DO NOT use this mechanism for database update. Schema updates must be
+   done through the separate schema update mechanism.
+
+
+   Only append to the patches list, never remove entries and never re-order them.
+*/
+
+var patches = []patch{}
+
+type patch struct {
+	name string
+	run  func(name string, d *Daemon) error
+}
+
+func (p *patch) apply(d *Daemon) error {
+	shared.Debugf("Applying patch: %s", p.name)
+
+	err := p.run(p.name, d)
+	if err != nil {
+		return err
+	}
+
+	err = dbPatchesMarkApplied(d.db, p.name)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func patchesApplyAll(d *Daemon) error {
+	appliedPatches, err := dbPatches(d.db)
+	if err != nil {
+		return err
+	}
+
+	for _, patch := range patches {
+		if shared.StringInSlice(patch.name, appliedPatches) {
+			continue
+		}
+
+		err := patch.apply(d)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

From 4f7323bd4a4f5162bbdd5eb20d57b9e9e199bdc0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 3 Jun 2016 01:03:05 -0400
Subject: [PATCH 0086/1193] Tiny optimization in db_update
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_update.go | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index 95ed4d36a..73ec86731 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -892,12 +892,14 @@ INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
 func dbUpdate(d *Daemon, prevVersion int) error {
 	db := d.db
 
-	if prevVersion < 0 || prevVersion > DB_CURRENT_VERSION {
-		return fmt.Errorf("Bad database version: %d", prevVersion)
-	}
 	if prevVersion == DB_CURRENT_VERSION {
 		return nil
 	}
+
+	if prevVersion < 0 || prevVersion > DB_CURRENT_VERSION {
+		return fmt.Errorf("Bad database version: %d", prevVersion)
+	}
+
 	var err error
 	if prevVersion < 1 {
 		err = dbUpdateFromV0(db)

From 602ebbc72fcd6e9df5b5f315217a43df641ff3d4 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Fri, 3 Jun 2016 07:11:38 -0700
Subject: [PATCH 0087/1193] Add support for os.SyscallError in client

Modify the client's error handling to support os.SyscallErrors.  Add
an exported function to the client to handle detection of the special
case errors so that the core logic can be reused by other projects,
e.g. juju.  Generation of the error message is kept out of the client
so as to avoid adding i18n messages into other projects' output.

Update the 'LXD socket not found...' error message to include a prompt
about LXD not being installed.

Add a message to the 'funcs starting with empty line' test to help
users with bad habits understand what they did wrong.

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 client.go                      | 42 +++++++++++++++++++++++++++++++
 client_test.go                 | 52 +++++++++++++++++++++++++++++++++++++++
 lxc/main.go                    | 32 +++++++-----------------
 po/de.po                       |  2 +-
 po/fr.po                       |  2 +-
 po/ja.po                       |  2 +-
 po/lxd.pot                     | 56 +++++++++++++++++++++---------------------
 test/suites/static_analysis.sh |  2 +-
 8 files changed, 135 insertions(+), 55 deletions(-)
 create mode 100644 client_test.go

diff --git a/client.go b/client.go
index 3f2431e97..725f3118a 100644
--- a/client.go
+++ b/client.go
@@ -19,6 +19,7 @@ import (
 	"path/filepath"
 	"strconv"
 	"strings"
+	"syscall"
 
 	"github.com/gorilla/websocket"
 
@@ -500,6 +501,47 @@ func (c *Client) GetServerConfig() (*Response, error) {
 	return c.baseGet(c.url(shared.APIVersion))
 }
 
+// GetLocalLXDErr determines whether or not an error is likely due to a
+// local LXD configuration issue, and if so, returns the underlying error.
+// GetLocalLXDErr can be used to provide customized error messages to help
+// the user identify basic system issues, e.g. LXD daemon not running.
+//
+// Returns syscall.ENOENT, syscall.ECONNREFUSED or syscall.EACCES when a
+// local LXD configuration issue is detected, nil otherwise.
+func GetLocalLXDErr(err error) error {
+	t, ok := err.(*url.Error)
+	if !ok {
+		return nil
+	}
+
+	u, ok := t.Err.(*net.OpError)
+	if !ok {
+		return nil
+	}
+
+	if u.Op == "dial" && u.Net == "unix" {
+		var lxdErr error
+
+		sysErr, ok := u.Err.(*os.SyscallError)
+		if ok {
+			lxdErr = sysErr.Err
+		} else {
+			// syscall.Errno may be returned on some systems, e.g. CentOS
+			lxdErr, ok = u.Err.(syscall.Errno)
+			if !ok {
+				return nil
+			}
+		}
+
+		switch lxdErr {
+		case syscall.ENOENT, syscall.ECONNREFUSED, syscall.EACCES:
+			return lxdErr
+		}
+	}
+
+	return nil
+}
+
 func (c *Client) AmTrusted() bool {
 	resp, err := c.GetServerConfig()
 	if err != nil {
diff --git a/client_test.go b/client_test.go
new file mode 100644
index 000000000..9ba658e46
--- /dev/null
+++ b/client_test.go
@@ -0,0 +1,52 @@
+package lxd
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"syscall"
+	"testing"
+)
+
+func assertNoError(t *testing.T, err error, msg string) {
+	if err != nil {
+		t.Fatalf("Error: %s, action: %s", err, msg)
+	}
+}
+
+func TestLocalLXDError(t *testing.T) {
+	f, err := ioutil.TempFile("", "lxd-test.socket")
+	assertNoError(t, err, "ioutil.TempFile to create fake socket file")
+	defer os.RemoveAll(f.Name())
+
+	c := &Client{
+		Name:   "test",
+		Config: DefaultConfig,
+		Remote: &RemoteConfig{
+			Addr:   fmt.Sprintf("unix:/%s", f.Name()),
+			Static: true,
+			Public: false,
+		},
+	}
+	runTest := func(exp error) {
+		lxdErr := GetLocalLXDErr(connectViaUnix(c, c.Remote))
+		if lxdErr != exp {
+			t.Fatalf("GetLocalLXDErr returned the wrong error, EXPECTED: %s, ACTUAL: %s", exp, lxdErr)
+		}
+	}
+
+	// The fake socket file should mimic a socket with nobody listening.
+	runTest(syscall.ECONNREFUSED)
+
+	// Remove R/W permissions to mimic the user not having lxd group permissions.
+	// Skip this test for root, as root ignores permissions and connect will fail
+	// with ECONNREFUSED instead of EACCES.
+	if os.Geteuid() != 0 {
+		assertNoError(t, f.Chmod(0100), "f.Chmod on fake socket file")
+		runTest(syscall.EACCES)
+	}
+
+	// Remove the fake socket to mimic LXD not being installed.
+	assertNoError(t, os.RemoveAll(f.Name()), "osRemoveAll on fake socket file")
+	runTest(syscall.ENOENT)
+}
diff --git a/lxc/main.go b/lxc/main.go
index fa49434f0..3208ad1ad 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -2,8 +2,6 @@ package main
 
 import (
 	"fmt"
-	"net"
-	"net/url"
 	"os"
 	"os/exec"
 	"path"
@@ -21,28 +19,16 @@ var configPath string
 
 func main() {
 	if err := run(); err != nil {
-		// The action we take depends on the error we get.
 		msg := fmt.Sprintf(i18n.G("error: %v"), err)
-		switch t := err.(type) {
-		case *url.Error:
-			switch u := t.Err.(type) {
-			case *net.OpError:
-				if u.Op == "dial" && u.Net == "unix" {
-					switch errno := u.Err.(type) {
-					case syscall.Errno:
-						switch errno {
-						case syscall.ENOENT:
-							msg = i18n.G("LXD socket not found; is LXD running?")
-						case syscall.ECONNREFUSED:
-							msg = i18n.G("Connection refused; is LXD running?")
-						case syscall.EACCES:
-							msg = i18n.G("Permisson denied, are you in the lxd group?")
-						default:
-							msg = fmt.Sprintf("%d %s", uintptr(errno), errno.Error())
-						}
-					}
-				}
-			}
+
+		lxdErr := lxd.GetLocalLXDErr(err)
+		switch lxdErr {
+		case syscall.ENOENT:
+			msg = i18n.G("LXD socket not found; is LXD installed and running?")
+		case syscall.ECONNREFUSED:
+			msg = i18n.G("Connection refused; is LXD running?")
+		case syscall.EACCES:
+			msg = i18n.G("Permisson denied, are you in the lxd group?")
 		}
 
 		fmt.Fprintln(os.Stderr, fmt.Sprintf("%s", msg))
diff --git a/po/de.po b/po/de.po
index 38d1d3ed4..81b8e1104 100644
--- a/po/de.po
+++ b/po/de.po
@@ -542,7 +542,7 @@ msgid "Keep the image up to date after initial copy"
 msgstr ""
 
 #: lxc/main.go:35
-msgid "LXD socket not found; is LXD running?"
+msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
 #: lxc/launch.go:22
diff --git a/po/fr.po b/po/fr.po
index 9f74b9d5c..90f536b97 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -467,7 +467,7 @@ msgid "Keep the image up to date after initial copy"
 msgstr ""
 
 #: lxc/main.go:35
-msgid "LXD socket not found; is LXD running?"
+msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
 #: lxc/launch.go:22
diff --git a/po/ja.po b/po/ja.po
index c7da0b7d2..4a65d634e 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -509,7 +509,7 @@ msgid "Keep the image up to date after initial copy"
 msgstr "最初にコピーした後も常にイメージを最新の状態に保つ"
 
 #: lxc/main.go:35
-msgid "LXD socket not found; is LXD running?"
+msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
 #: lxc/launch.go:22
diff --git a/po/lxd.pot b/po/lxd.pot
index 54ff04255..4114d7e35 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-05-11 18:51-0400\n"
+        "POT-Creation-Date: 2016-06-13 19:11-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -187,12 +187,12 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:190
+#: lxc/config.go:527 lxc/config.go:592 lxc/image.go:687 lxc/profile.go:190
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
 
-#: lxc/main.go:37
+#: lxc/main.go:29
 msgid   "Connection refused; is LXD running?"
 msgstr  ""
 
@@ -271,12 +271,12 @@ msgid   "Delete containers or container snapshots.\n"
         "Destroy containers or snapshots with any attached data (configuration, snapshots, ...)."
 msgstr  ""
 
-#: lxc/config.go:617
+#: lxc/config.go:644
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:804
+#: lxc/config.go:831
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
@@ -289,11 +289,11 @@ msgstr  ""
 msgid   "EXPIRY DATE"
 msgstr  ""
 
-#: lxc/main.go:55
+#: lxc/main.go:41
 msgid   "Enables debug mode."
 msgstr  ""
 
-#: lxc/main.go:54
+#: lxc/main.go:40
 msgid   "Enables verbose mode."
 msgstr  ""
 
@@ -353,7 +353,7 @@ msgstr  ""
 msgid   "Force the removal of stopped containers."
 msgstr  ""
 
-#: lxc/main.go:56
+#: lxc/main.go:42
 msgid   "Force using the local unix socket."
 msgstr  ""
 
@@ -361,7 +361,7 @@ msgstr  ""
 msgid   "Format"
 msgstr  ""
 
-#: lxc/main.go:138
+#: lxc/main.go:124
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
@@ -377,11 +377,11 @@ msgstr  ""
 msgid   "ISSUE DATE"
 msgstr  ""
 
-#: lxc/main.go:146
+#: lxc/main.go:132
 msgid   "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr  ""
 
-#: lxc/main.go:57
+#: lxc/main.go:43
 msgid   "Ignore aliases when determining what command to run."
 msgstr  ""
 
@@ -421,7 +421,7 @@ msgstr  ""
 msgid   "Invalid configuration key"
 msgstr  ""
 
-#: lxc/file.go:190
+#: lxc/file.go:195
 #, c-format
 msgid   "Invalid source %s"
 msgstr  ""
@@ -439,8 +439,8 @@ msgstr  ""
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
-#: lxc/main.go:35
-msgid   "LXD socket not found; is LXD running?"
+#: lxc/main.go:27
+msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
 #: lxc/launch.go:22
@@ -691,7 +691,7 @@ msgid   "Monitor activity on the LXD server.\n"
         "lxc monitor --type=logging"
 msgstr  ""
 
-#: lxc/file.go:178
+#: lxc/file.go:183
 msgid   "More than one file to download, but target is not a directory"
 msgstr  ""
 
@@ -742,7 +742,7 @@ msgstr  ""
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
-#: lxc/help.go:63 lxc/main.go:122
+#: lxc/help.go:63 lxc/main.go:108
 msgid   "Options:"
 msgstr  ""
 
@@ -791,7 +791,7 @@ msgstr  ""
 msgid   "Path to an alternate server directory."
 msgstr  ""
 
-#: lxc/main.go:39
+#: lxc/main.go:31
 msgid   "Permisson denied, are you in the lxd group?"
 msgstr  ""
 
@@ -810,7 +810,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:501 lxc/config.go:566 lxc/image.go:688
+#: lxc/config.go:528 lxc/config.go:593 lxc/image.go:688
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -1018,7 +1018,7 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/config.go:645 lxc/config.go:657 lxc/config.go:690 lxc/config.go:708 lxc/config.go:746 lxc/config.go:764
+#: lxc/config.go:672 lxc/config.go:684 lxc/config.go:717 lxc/config.go:735 lxc/config.go:773 lxc/config.go:791
 msgid   "The device doesn't exist"
 msgstr  ""
 
@@ -1039,7 +1039,7 @@ msgstr  ""
 msgid   "Timestamps:"
 msgstr  ""
 
-#: lxc/main.go:147
+#: lxc/main.go:133
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
@@ -1078,7 +1078,7 @@ msgstr  ""
 msgid   "Uploaded: %s"
 msgstr  ""
 
-#: lxc/main.go:122
+#: lxc/main.go:108
 #, c-format
 msgid   "Usage: %s"
 msgstr  ""
@@ -1107,7 +1107,7 @@ msgstr  ""
 msgid   "YES"
 msgstr  ""
 
-#: lxc/main.go:66
+#: lxc/main.go:52
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
@@ -1119,7 +1119,7 @@ msgstr  ""
 msgid   "bad result type from action"
 msgstr  ""
 
-#: lxc/copy.go:78
+#: lxc/copy.go:99
 msgid   "can't copy to the same container name"
 msgstr  ""
 
@@ -1143,12 +1143,12 @@ msgstr  ""
 msgid   "enabled"
 msgstr  ""
 
-#: lxc/main.go:25 lxc/main.go:159
+#: lxc/main.go:22 lxc/main.go:145
 #, c-format
 msgid   "error: %v"
 msgstr  ""
 
-#: lxc/help.go:40 lxc/main.go:117
+#: lxc/help.go:40 lxc/main.go:103
 #, c-format
 msgid   "error: unknown command: %s"
 msgstr  ""
@@ -1161,7 +1161,7 @@ msgstr  ""
 msgid   "no"
 msgstr  ""
 
-#: lxc/copy.go:101
+#: lxc/copy.go:122
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
@@ -1169,7 +1169,7 @@ msgstr  ""
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:266 lxc/main.go:270
+#: lxc/main.go:252 lxc/main.go:256
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
@@ -1211,7 +1211,7 @@ msgstr  ""
 msgid   "unreachable return reached"
 msgstr  ""
 
-#: lxc/main.go:199
+#: lxc/main.go:185
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 148d5d18f..301e3ef29 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -20,7 +20,7 @@ test_static_analysis() {
     ## Functions starting by empty line
     OUT=$(grep -r "^$" -B1 . | grep "func " | grep -v "}$" || true)
     if [ -n "${OUT}" ]; then
-      echo "${OUT}"
+      echo "ERROR: Functions must not start with an empty line: ${OUT}"
       false
     fi
 

From cdc0c946ea81d4f25f30f0d3177d9b031f19d79d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 3 Jun 2016 14:54:32 -0400
Subject: [PATCH 0088/1193] patches: Fix DB test, we now have 17 tables
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/database_update.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/database_update.sh b/test/suites/database_update.sh
index d89d7575f..e178ee927 100644
--- a/test/suites/database_update.sh
+++ b/test/suites/database_update.sh
@@ -11,7 +11,7 @@ test_database_update(){
   spawn_lxd "${LXD_MIGRATE_DIR}"
 
   # Assert there are enough tables.
-  expected_tables=16
+  expected_tables=17
   tables=$(sqlite3 "${MIGRATE_DB}" ".dump" | grep -c "CREATE TABLE")
   [ "${tables}" -eq "${expected_tables}" ] || { echo "FAIL: Wrong number of tables after database migration. Found: ${tables}, expected ${expected_tables}"; false; }
 

From b1e270929f60b701a5e3a2be89f2fba7b1382ee6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 3 Jun 2016 15:00:46 -0400
Subject: [PATCH 0089/1193] Makefile: go get has become worse, now need 3 runs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Ran into that with the recent bugfix releases, make dist was failing,
doing one more go get fixed it...

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index edc11ed30..2b08f8ea4 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,8 @@ ARCHIVE=lxd-$(VERSION).tar
 
 .PHONY: default
 default:
-	# Must run twice due to go get race
+	# Must a few times due to go get race
+	-go get -t -v -d ./...
 	-go get -t -v -d ./...
 	-go get -t -v -d ./...
 	go install -v $(DEBUG) ./...
@@ -20,7 +21,8 @@ default:
 
 .PHONY: client
 client:
-	# Must run twice due to go get race
+	# Must a few times due to go get race
+	-go get -t -v -d ./...
 	-go get -t -v -d ./...
 	-go get -t -v -d ./...
 	go install -v $(DEBUG) ./lxc
@@ -28,7 +30,8 @@ client:
 
 .PHONY: update
 update:
-	# Must run twice due to go get race
+	# Must a few times due to go get race
+	-go get -t -v -d -u ./...
 	-go get -t -v -d -u ./...
 	go get -t -v -d -u ./...
 	@echo "Dependencies updated"

From a52f3a906b2498c2f54ebba81138b66fb4ac847f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 7 Jun 2016 16:36:18 -0400
Subject: [PATCH 0090/1193] Tweak TLS cipher list a bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - This drops the ECDSA ciphers from the announced list by the server as
   since our certificate is RSA, those cannot be used.

 - We also now allow connections using CBC-SHA instead of GCM-SHA256 as
   this is needed by some older clients.

Closes #2034

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go     | 4 ++--
 shared/network.go | 4 +++-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 9e5aed7ff..d1ca0232b 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -800,8 +800,8 @@ func (d *Daemon) Init() error {
 			MinVersion:         tls.VersionTLS12,
 			MaxVersion:         tls.VersionTLS12,
 			CipherSuites: []uint16{
-				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
-				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
 			PreferServerCipherSuites: true,
 		}
 		tlsConfig.BuildNameToCertificate()
diff --git a/shared/network.go b/shared/network.go
index fa08b42a0..2d44433d6 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -39,7 +39,9 @@ func initTLSConfig() *tls.Config {
 		MaxVersion: tls.VersionTLS12,
 		CipherSuites: []uint16{
 			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
-			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
 		PreferServerCipherSuites: true,
 	}
 }

From 1107c39393dd1980b305008e4db9a61a4464d1d2 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Wed, 8 Jun 2016 07:59:17 -0700
Subject: [PATCH 0091/1193] Fix a panic in 'remote add'

Use strings.TrimPrefix to strip 'unix://' from the URL/path in both
remoteCmd.addServer() and connectViaUnix.  This resolves the direct
cause of the panic and makes the handling of relative paths consistent
in both flows.

Add a shared util IsUnixSocket() and use it when determining whether
a path without an explicit scheme should be treated as HTTPS or UNIX.

Remove an extra '/' from the path used for TestLocalLXDError, adding
a single slash makes an absolute path look like a relative path with
a 'unix://' scheme.

Fixes #2089

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 client.go      |  6 +-----
 client_test.go |  2 +-
 lxc/remote.go  | 16 +++-------------
 shared/util.go | 10 ++++++++++
 4 files changed, 15 insertions(+), 19 deletions(-)

diff --git a/client.go b/client.go
index 725f3118a..baf4a5aa3 100644
--- a/client.go
+++ b/client.go
@@ -242,11 +242,7 @@ func connectViaUnix(c *Client, remote *RemoteConfig) error {
 		//   unix:///path/to/socket
 		//   unix:/path/to/socket
 		//   unix:path/to/socket
-		path := strings.TrimPrefix(remote.Addr, "unix:")
-		if strings.HasPrefix(path, "///") {
-			// translate unix:///path/to, to just "/path/to"
-			path = path[2:]
-		}
+		path := strings.TrimPrefix(strings.TrimPrefix(remote.Addr, "unix:"), "//")
 		raddr, err := net.ResolveUnixAddr("unix", path)
 		if err != nil {
 			return nil, err
diff --git a/client_test.go b/client_test.go
index 9ba658e46..e7b12a0dd 100644
--- a/client_test.go
+++ b/client_test.go
@@ -23,7 +23,7 @@ func TestLocalLXDError(t *testing.T) {
 		Name:   "test",
 		Config: DefaultConfig,
 		Remote: &RemoteConfig{
-			Addr:   fmt.Sprintf("unix:/%s", f.Name()),
+			Addr:   fmt.Sprintf("unix:%s", f.Name()),
 			Static: true,
 			Public: false,
 		},
diff --git a/lxc/remote.go b/lxc/remote.go
index a01b0f65a..8bd435c28 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -126,7 +126,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 	} else if addr[0] == '/' {
 		rScheme = "unix"
 	} else {
-		if !shared.PathExists(addr) {
+		if !shared.IsUnixSocket(addr) {
 			rScheme = "https"
 		} else {
 			rScheme = "unix"
@@ -148,17 +148,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 	}
 
 	if rScheme == "unix" {
-		if addr[0:5] == "unix:" {
-			if addr[0:7] == "unix://" {
-				if len(addr) > 8 {
-					rHost = addr[8:]
-				} else {
-					rHost = ""
-				}
-			} else {
-				rHost = addr[6:]
-			}
-		}
+		rHost = strings.TrimPrefix(strings.TrimPrefix(addr, "unix:"), "//")
 		rPort = ""
 	}
 
@@ -181,7 +171,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 		return err
 	}
 
-	if len(addr) > 5 && addr[0:5] == "unix:" {
+	if strings.HasPrefix(addr, "unix:") {
 		// NewClient succeeded so there was a lxd there (we fingered
 		// it) so just accept it
 		return nil
diff --git a/shared/util.go b/shared/util.go
index 04398dbb6..ab6d0610f 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -70,6 +70,16 @@ func IsDir(name string) bool {
 	return stat.IsDir()
 }
 
+// IsUnixSocket returns true if the given path is either a Unix socket
+// or a symbolic link pointing at a Unix socket.
+func IsUnixSocket(path string) bool {
+	stat, err := os.Stat(path)
+	if err != nil {
+		return false
+	}
+	return (stat.Mode() & os.ModeSocket) == os.ModeSocket
+}
+
 // VarPath returns the provided path elements joined by a slash and
 // appended to the end of $LXD_DIR, which defaults to /var/lib/lxd.
 func VarPath(path ...string) string {

From 20383689846daf0288c60c1d1eff1cf77e932a52 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Wed, 8 Jun 2016 10:26:52 -0700
Subject: [PATCH 0092/1193] Add target path to rootfs tarball in image export

The split image path didn't pass in 'target' to the Join call.

Add an integration test to verify exporting split images.  Add an
option to the import-busybox script to control whether or not to
set the filename for split images.  Change the effective filename
to be 'busybox', previously it was being set to 'busybox.rootfs'.

Fixes #1980

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 client.go                |  2 +-
 test/deps/import-busybox | 19 ++++++++++++-------
 test/suites/basic.sh     | 30 +++++++++++++++++++++++++++++-
 3 files changed, 42 insertions(+), 9 deletions(-)

diff --git a/client.go b/client.go
index baf4a5aa3..ba7266c2a 100644
--- a/client.go
+++ b/client.go
@@ -785,7 +785,7 @@ func (c *Client) ExportImage(image string, target string) (string, error) {
 			return "", fmt.Errorf("Invalid multipart image")
 		}
 
-		rootfsTarf, err := os.OpenFile(filepath.Join(part.FileName()), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+		rootfsTarf, err := os.OpenFile(filepath.Join(target, part.FileName()), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
 		if err != nil {
 			return "", err
 		}
diff --git a/test/deps/import-busybox b/test/deps/import-busybox
index 69cf4b811..91a9cfe60 100755
--- a/test/deps/import-busybox
+++ b/test/deps/import-busybox
@@ -108,7 +108,7 @@ class LXD(object):
             return [image.split("/1.0/images/")[-1]
                     for image in data['metadata']]
 
-    def images_upload(self, path, filename, public):
+    def images_upload(self, path, public, filename=None):
         headers = {}
         if public:
             headers['X-LXD-public'] = "1"
@@ -121,16 +121,16 @@ class LXD(object):
         else:
             meta_path, rootfs_path = path
             boundary = str(uuid.uuid1())
+            filename_entry = " filename=%s" % filename if filename else ""
 
             upload_path = os.path.join(self.workdir, "upload")
             body = open(upload_path, "wb+")
             for name, path in [("metadata", meta_path),
                                ("rootfs", rootfs_path)]:
-                filename = os.path.basename(path)
                 body.write(bytes("--%s\r\n" % boundary, "utf-8"))
                 body.write(bytes("Content-Disposition: form-data; "
-                                 "name=%s; filename=%s\r\n" %
-                                 (name, filename), "utf-8"))
+                                 "name=%s;%s\r\n" % (name, filename_entry),
+                                 "utf-8"))
                 body.write(b"Content-Type: application/octet-stream\r\n")
                 body.write(b"\r\n")
                 with open(path, "rb") as fd:
@@ -314,8 +314,11 @@ if __name__ == "__main__":
             if fingerprint in lxd.images_list():
                 parser.exit(1, "This image is already in the store.\n")
 
-            r = lxd.images_upload((meta_path, rootfs_path),
-                                  meta_path.split("/")[-1], args.public)
+            if args.filename:
+                r = lxd.images_upload((meta_path, rootfs_path), args.public,
+                                      meta_path.split("/")[-1])
+            else:
+                r = lxd.images_upload((meta_path, rootfs_path), args.public)
             print("Image imported as: %s" % r['fingerprint'])
         else:
             path = busybox.create_tarball()
@@ -326,7 +329,7 @@ if __name__ == "__main__":
             if fingerprint in lxd.images_list():
                 parser.exit(1, "This image is already in the store.\n")
 
-            r = lxd.images_upload(path, path.split("/")[-1], args.public)
+            r = lxd.images_upload(path, args.public)
             print("Image imported as: %s" % r['fingerprint'])
 
         setup_alias(args.alias, fingerprint)
@@ -338,6 +341,8 @@ if __name__ == "__main__":
                         default=False, help="Make the image public")
     parser.add_argument("--split", action="store_true",
                         default=False, help="Whether to create a split image")
+    parser.add_argument("--filename", action="store_true",
+                        default=False, help="Set the split image's filename")
     parser.set_defaults(func=import_busybox)
 
     # Call the function
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index a4f52e27b..dfc1ac04f 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -24,7 +24,7 @@ test_basic_usage() {
     name=${sum}.tar.xz
   fi
   [ "${sum}" = "$(sha256sum "${LXD_DIR}/${name}" | cut -d' ' -f1)" ]
-
+  
   # Test an alias with slashes
   lxc image show "${sum}"
   lxc image alias create a/b/ "${sum}"
@@ -58,6 +58,34 @@ test_basic_usage() {
   [ "${sum}" = "$(sha256sum "${LXD_DIR}/testimage.tar.xz" | cut -d' ' -f1)" ]
   rm "${LXD_DIR}/testimage.tar.xz"
 
+
+  # Test image export with a split image.
+  deps/import-busybox --split --alias splitimage
+
+  sum=$(lxc image info splitimage | grep ^Fingerprint | cut -d' ' -f2)
+
+  lxc image export splitimage "${LXD_DIR}"
+  [ "${sum}" = "$(cat "${LXD_DIR}/meta-${sum}.tar.xz" "${LXD_DIR}/${sum}.tar.xz" | sha256sum | cut -d' ' -f1)" ]
+  
+  # Delete the split image and exported files
+  rm "${LXD_DIR}/${sum}.tar.xz"
+  rm "${LXD_DIR}/meta-${sum}.tar.xz"
+  lxc image delete splitimage
+
+  # Redo the split image export test, this time with the --filename flag
+  # to tell import-busybox to set the 'busybox' filename in the upload.
+  # The sum should remain the same as its the same image.
+  deps/import-busybox --split --filename --alias splitimage
+
+  lxc image export splitimage "${LXD_DIR}"
+  [ "${sum}" = "$(cat "${LXD_DIR}/meta-busybox.tar.xz" "${LXD_DIR}/busybox.tar.xz" | sha256sum | cut -d' ' -f1)" ]
+  
+  # Delete the split image and exported files
+  rm "${LXD_DIR}/busybox.tar.xz"
+  rm "${LXD_DIR}/meta-busybox.tar.xz"
+  lxc image delete splitimage
+
+
   # Test container creation
   lxc init testimage foo
   lxc list | grep foo | grep STOPPED

From a8022a92482ec11f8c324b5f165a3138ab1031e3 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Thu, 9 Jun 2016 09:46:41 -0700
Subject: [PATCH 0093/1193] Check all alias args to support subcommand aliases

Fixes #2095

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 lxc/main.go | 21 +++++++--------------
 1 file changed, 7 insertions(+), 14 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index 3208ad1ad..5c61e3c68 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -190,26 +190,19 @@ func expandAlias(config *lxd.Config, origArgs []string) ([]string, bool) {
 	aliasValue := []string{}
 
 	for k, v := range config.Aliases {
-		matches := false
+		foundAlias = true
 		for i, key := range strings.Split(k, " ") {
-			if len(origArgs) <= i+1 {
-				break
-			}
-
-			if origArgs[i+1] == key {
-				matches = true
-				aliasKey = strings.Split(k, " ")
-				aliasValue = strings.Split(v, " ")
+			if len(origArgs) <= i+1 || origArgs[i+1] != key {
+				foundAlias = false
 				break
 			}
 		}
 
-		if !matches {
-			continue
+		if foundAlias {
+			aliasKey = strings.Split(k, " ")
+			aliasValue = strings.Split(v, " ")
+			break
 		}
-
-		foundAlias = true
-		break
 	}
 
 	if !foundAlias {

From a70f8c4468179d229a7fb6c306944089f816756a Mon Sep 17 00:00:00 2001
From: Tuomas Suutari <tuomas.suutari at gmail.com>
Date: Fri, 10 Jun 2016 00:20:33 +0300
Subject: [PATCH 0094/1193] doc: Fix typo in "unix-block" description

Fix the description of the "unix-block" device type to talk about block
devices rather than character devices.

Signed-off-by: Tuomas Suutari <tuomas.suutari at gmail.com>
---
 doc/configuration.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/configuration.md b/doc/configuration.md
index 599c3b5c6..c7e67dded 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -233,7 +233,7 @@ gid         | int       | 0                 | no        | GID of the device owne
 mode        | int       | 0660              | no        | Mode of the device in the container
 
 ### Type: unix-block
-Unix block device entries simply make the requested character device
+Unix block device entries simply make the requested block device
 appear in the container's /dev and allow read/write operations to it.
 
 The following properties exist:

From 6eda03f7050b069ac26cdda2f168197c493f4f04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 10 Jun 2016 15:16:30 -0400
Subject: [PATCH 0095/1193] patches: Add a comment
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/patches.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/patches.go b/lxd/patches.go
index b0174c719..11030b4be 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -63,3 +63,5 @@ func patchesApplyAll(d *Daemon) error {
 
 	return nil
 }
+
+// Patches begin here

From 78a471e103ae8d617d2a052e91232c3a17c61c5e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 10 Jun 2016 18:24:55 -0400
Subject: [PATCH 0096/1193] Include the certificate fingerprint in server info
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2098

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go        | 10 ++++++++++
 shared/server.go | 27 ++++++++++++++-------------
 2 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/client.go b/client.go
index ba7266c2a..09e75fc86 100644
--- a/client.go
+++ b/client.go
@@ -2,6 +2,7 @@ package lxd
 
 import (
 	"bytes"
+	"crypto/sha256"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/json"
@@ -1597,6 +1598,15 @@ func (c *Client) ServerStatus() (*shared.ServerState, error) {
 		return nil, err
 	}
 
+	// Fill in certificate fingerprint if not provided
+	if ss.Environment.CertificateFingerprint == "" && ss.Environment.Certificate != "" {
+		pemCertificate, _ := pem.Decode([]byte(ss.Environment.Certificate))
+		if pemCertificate != nil {
+			digest := sha256.Sum256(pemCertificate.Bytes)
+			ss.Environment.CertificateFingerprint = fmt.Sprintf("%x", digest)
+		}
+	}
+
 	return &ss, nil
 }
 
diff --git a/shared/server.go b/shared/server.go
index e557c9d90..b16f1c295 100644
--- a/shared/server.go
+++ b/shared/server.go
@@ -1,19 +1,20 @@
 package shared
 
 type ServerStateEnvironment struct {
-	Addresses          []string `json:"addresses"`
-	Architectures      []string `json:"architectures"`
-	Certificate        string   `json:"certificate"`
-	Driver             string   `json:"driver"`
-	DriverVersion      string   `json:"driver_version"`
-	Kernel             string   `json:"kernel"`
-	KernelArchitecture string   `json:"kernel_architecture"`
-	KernelVersion      string   `json:"kernel_version"`
-	Server             string   `json:"server"`
-	ServerPid          int      `json:"server_pid"`
-	ServerVersion      string   `json:"server_version"`
-	Storage            string   `json:"storage"`
-	StorageVersion     string   `json:"storage_version"`
+	Addresses              []string `json:"addresses"`
+	Architectures          []string `json:"architectures"`
+	Certificate            string   `json:"certificate"`
+	CertificateFingerprint string   `json:"certificate_fingerprint"`
+	Driver                 string   `json:"driver"`
+	DriverVersion          string   `json:"driver_version"`
+	Kernel                 string   `json:"kernel"`
+	KernelArchitecture     string   `json:"kernel_architecture"`
+	KernelVersion          string   `json:"kernel_version"`
+	Server                 string   `json:"server"`
+	ServerPid              int      `json:"server_pid"`
+	ServerVersion          string   `json:"server_version"`
+	Storage                string   `json:"storage"`
+	StorageVersion         string   `json:"storage_version"`
 }
 
 type ServerState struct {

From 036822f287d11ae7770c4c274c7a4ad34ef4dcd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 13 Jun 2016 13:48:06 -0400
Subject: [PATCH 0097/1193] Normalize the URLs in the client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This avoids generating URLs which Go then drops on the floor.

Closes #2112

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/client.go b/client.go
index 09e75fc86..508989be2 100644
--- a/client.go
+++ b/client.go
@@ -476,17 +476,33 @@ func (c *Client) websocket(operation string, secret string) (*websocket.Conn, er
 }
 
 func (c *Client) url(elem ...string) string {
+	// Normalize the URL
 	path := strings.Join(elem, "/")
+	entries := []string{}
+	fields := strings.Split(path, "/")
+	for i, entry := range fields {
+		if entry == "" && i+1 < len(fields) {
+			continue
+		}
+
+		entries = append(entries, entry)
+	}
+	path = strings.Join(entries, "/")
+
+	// Assemble the final URL
 	uri := c.BaseURL + "/" + path
 
+	// Aliases may contain a trailing slash
 	if strings.HasPrefix(path, "1.0/images/aliases") {
 		return uri
 	}
 
+	// File paths may contain a trailing slash
 	if strings.Contains(path, "?") {
 		return uri
 	}
 
+	// Nothing else should contain a trailing slash
 	return strings.TrimSuffix(uri, "/")
 }
 

From 2cc549b686fa37ea44243d17b6b503e62db367de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 13 Jun 2016 16:35:11 -0400
Subject: [PATCH 0098/1193] lvm: Don't call lvextend on new LVM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_lvm.go | 62 ++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 39 insertions(+), 23 deletions(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 2c729a5b5..43273ba32 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -778,14 +778,28 @@ func (s *storageLvm) ImageDelete(fingerprint string) error {
 
 func (s *storageLvm) createDefaultThinPool() (string, error) {
 	thinPoolName := daemonConfig["storage.lvm_thinpool_name"].Get()
+	isRecent, err := s.lvmVersionIsAtLeast("2.02.99")
+	if err != nil {
+		return "", fmt.Errorf("Error checking LVM version: %v", err)
+	}
 
-	// Create a tiny 1G thinpool
-	output, err := tryExec(
-		"lvcreate",
-		"--poolmetadatasize", "1G",
-		"-L", "1G",
-		"--thinpool",
-		fmt.Sprintf("%s/%s", s.vgName, thinPoolName))
+	// Create the thin pool
+	var output []byte
+	if isRecent {
+		output, err = tryExec(
+			"lvcreate",
+			"--poolmetadatasize", "1G",
+			"-l", "100%FREE",
+			"--thinpool",
+			fmt.Sprintf("%s/%s", s.vgName, thinPoolName))
+	} else {
+		output, err = tryExec(
+			"lvcreate",
+			"--poolmetadatasize", "1G",
+			"-L", "1G",
+			"--thinpool",
+			fmt.Sprintf("%s/%s", s.vgName, thinPoolName))
+	}
 
 	if err != nil {
 		s.log.Error(
@@ -799,23 +813,25 @@ func (s *storageLvm) createDefaultThinPool() (string, error) {
 			"Could not create LVM thin pool named %s", thinPoolName)
 	}
 
-	// Grow it to the maximum VG size (two step process required by old LVM)
-	output, err = tryExec(
-		"lvextend",
-		"--alloc", "anywhere",
-		"-l", "100%FREE",
-		fmt.Sprintf("%s/%s", s.vgName, thinPoolName))
-
-	if err != nil {
-		s.log.Error(
-			"Could not grow thin pool",
-			log.Ctx{
-				"name":   thinPoolName,
-				"err":    err,
-				"output": string(output)})
+	if !isRecent {
+		// Grow it to the maximum VG size (two step process required by old LVM)
+		output, err = tryExec(
+			"lvextend",
+			"--alloc", "anywhere",
+			"-l", "100%FREE",
+			fmt.Sprintf("%s/%s", s.vgName, thinPoolName))
 
-		return "", fmt.Errorf(
-			"Could not grow LVM thin pool named %s", thinPoolName)
+		if err != nil {
+			s.log.Error(
+				"Could not grow thin pool",
+				log.Ctx{
+					"name":   thinPoolName,
+					"err":    err,
+					"output": string(output)})
+
+			return "", fmt.Errorf(
+				"Could not grow LVM thin pool named %s", thinPoolName)
+		}
 	}
 
 	return thinPoolName, nil

From 2b9e0e638ebe4e829f0983ec890e16d6ffa0d960 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 13 Jun 2016 15:11:18 -0600
Subject: [PATCH 0099/1193] show remote in the --show-log example provided on
 error

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/action.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/action.go b/lxc/action.go
index b91ad1ce4..1cf6a1386 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -90,7 +90,7 @@ func (c *actionCmd) run(config *lxd.Config, args []string) error {
 		}
 
 		if err := d.WaitForSuccess(resp.Operation); err != nil {
-			return fmt.Errorf("%s\n"+i18n.G("Try `lxc info --show-log %s` for more info"), err, name)
+			return fmt.Errorf("%s\n"+i18n.G("Try `lxc info --show-log %s` for more info"), err, nameArg)
 		}
 	}
 	return nil

From 26404a098eca76ac1c05cfe2b1acff563d96bc0f Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 1 Jun 2016 10:40:55 -0600
Subject: [PATCH 0100/1193] c/r: switch to the new ->migrate API

We'll use this in the next patch to enable use of new liblxc features.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container.go     |  5 ++---
 lxd/container_lxc.go | 14 +++++++++-----
 lxd/migrate.go       |  3 +--
 3 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 9c196ca4e..cf7884f9b 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -341,7 +341,7 @@ type container interface {
 
 	// Snapshots & migration
 	Restore(sourceContainer container) error
-	Checkpoint(opts lxc.CheckpointOptions) error
+	Migrate(cmd uint, stateDir string, stop bool) error
 	StartFromMigration(imagesDir string) error
 	Snapshots() ([]container, error)
 
@@ -527,8 +527,7 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 		 * after snapshotting will fail.
 		 */
 
-		opts := lxc.CheckpointOptions{Directory: stateDir, Stop: false, Verbose: true}
-		err = sourceContainer.Checkpoint(opts)
+		err = sourceContainer.Migrate(lxc.MIGRATE_DUMP, stateDir, false)
 		err2 := CollectCRIULogFile(sourceContainer, stateDir, "snapshot", "dump")
 		if err2 != nil {
 			shared.Log.Warn("failed to collect criu log file", log.Ctx{"error": err2})
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 2f48cfac7..6f3f11729 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1385,8 +1385,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		}
 
 		// Checkpoint
-		opts := lxc.CheckpointOptions{Directory: stateDir, Stop: true, Verbose: true}
-		err = c.Checkpoint(opts)
+		err = c.Migrate(lxc.MIGRATE_DUMP, stateDir, true)
 		err2 := CollectCRIULogFile(c, stateDir, "snapshot", "dump")
 		if err2 != nil {
 			shared.Log.Warn("failed to collect criu log file", log.Ctx{"error": err2})
@@ -2712,14 +2711,19 @@ func (c *containerLXC) Export(w io.Writer) error {
 	return tw.Close()
 }
 
-func (c *containerLXC) Checkpoint(opts lxc.CheckpointOptions) error {
-	// Load the go-lxc struct
+func (c *containerLXC) Migrate(cmd uint, stateDir string, stop bool) error {
 	err := c.initLXC()
 	if err != nil {
 		return err
 	}
 
-	return c.c.Checkpoint(opts)
+	opts := lxc.MigrateOptions{
+		Stop: stop,
+		Directory: stateDir,
+		Verbose: true,
+	}
+
+	return c.c.Migrate(cmd, opts)
 }
 
 func (c *containerLXC) TemplateApply(trigger string) error {
diff --git a/lxd/migrate.go b/lxd/migrate.go
index fdaab35a3..627e6d04a 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -368,8 +368,7 @@ func (s *migrationSourceWs) Do(op *operation) error {
 		}
 		defer os.RemoveAll(checkpointDir)
 
-		opts := lxc.CheckpointOptions{Stop: true, Directory: checkpointDir, Verbose: true}
-		err = s.container.Checkpoint(opts)
+		err = s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, true)
 
 		if err2 := CollectCRIULogFile(s.container, checkpointDir, "migration", "dump"); err2 != nil {
 			shared.Debugf("Error collecting checkpoint log file %s", err)

From 05d273a561be94936a47438caf789bf89ffeb720 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 14 Jun 2016 17:40:34 +0000
Subject: [PATCH 0101/1193] simplify checkpoint/restore code everywhere

Some problems:

* We had various entry points for migration, each which collected logs in
  various different and inconsistent ways.
* We also had the StartFromMigrate call, and a Migrate() to which you could
  pass lxc.MIGRATE_RESTORE, which wasn't an obvious API.
* at each point we had a check that did the rootfs shifting if necessary
* we had to do findCriu everywhere manually

Now that we have a Migrate() call, let's just route everything through
that, and handle all of this in a uniform way.

Note that some findCriu calls are still prudent to do e.g. in snapshot
restore, before we actually do all the filesystem work to restore stuff if
the snapshot is stateful. I've left those sorts of calls in.

Note: this is a modified version (dropping the preserveInodes bits) from
31f1e20ca1c97e5a0b857502697fb1f9c6a8b5af.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container.go     |  12 +--
 lxd/container_lxc.go | 234 +++++++++++++++++++++++++++++----------------------
 lxd/migrate.go       |  93 +-------------------
 3 files changed, 137 insertions(+), 202 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index cf7884f9b..166e53d04 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -11,8 +11,6 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 
 	"github.com/lxc/lxd/shared"
-
-	log "gopkg.in/inconshreveable/log15.v2"
 )
 
 // Helper functions
@@ -341,8 +339,7 @@ type container interface {
 
 	// Snapshots & migration
 	Restore(sourceContainer container) error
-	Migrate(cmd uint, stateDir string, stop bool) error
-	StartFromMigration(imagesDir string) error
+	Migrate(cmd uint, stateDir string, function string, stop bool) error
 	Snapshots() ([]container, error)
 
 	// Config handling
@@ -527,12 +524,7 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 		 * after snapshotting will fail.
 		 */
 
-		err = sourceContainer.Migrate(lxc.MIGRATE_DUMP, stateDir, false)
-		err2 := CollectCRIULogFile(sourceContainer, stateDir, "snapshot", "dump")
-		if err2 != nil {
-			shared.Log.Warn("failed to collect criu log file", log.Ctx{"error": err2})
-		}
-
+		err = sourceContainer.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", false)
 		if err != nil {
 			os.RemoveAll(sourceContainer.StatePath())
 			return nil, err
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 6f3f11729..5f374773b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"archive/tar"
+	"bufio"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -1157,30 +1158,7 @@ func (c *containerLXC) Start(stateful bool) error {
 			return fmt.Errorf("Container has no existing state to restore.")
 		}
 
-		if err := findCriu("snapshot"); err != nil {
-			return err
-		}
-
-		if !c.IsPrivileged() {
-			if err := c.IdmapSet().ShiftRootfs(c.StatePath()); err != nil {
-				return err
-			}
-		}
-
-		out, err := exec.Command(
-			execPath,
-			"forkmigrate",
-			c.name,
-			c.daemon.lxcpath,
-			configPath,
-			c.StatePath()).CombinedOutput()
-		if string(out) != "" {
-			for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-				shared.Debugf("forkmigrate: %s", line)
-			}
-		}
-		CollectCRIULogFile(c, c.StatePath(), "snapshot", "restore")
-
+		err := c.Migrate(lxc.MIGRATE_RESTORE, c.StatePath(), "snapshot", false)
 		if err != nil && !c.IsRunning() {
 			return err
 		}
@@ -1228,41 +1206,6 @@ func (c *containerLXC) Start(stateful bool) error {
 	return nil
 }
 
-func (c *containerLXC) StartFromMigration(imagesDir string) error {
-	// Run the shared start code
-	configPath, err := c.startCommon()
-	if err != nil {
-		return err
-	}
-
-	// Start the LXC container
-	out, err := exec.Command(
-		execPath,
-		"forkmigrate",
-		c.name,
-		c.daemon.lxcpath,
-		configPath,
-		imagesDir).CombinedOutput()
-
-	if string(out) != "" {
-		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.Debugf("forkmigrate: %s", line)
-		}
-	}
-
-	if err != nil && !c.IsRunning() {
-		return fmt.Errorf(
-			"Error calling 'lxd forkmigrate %s %s %s %s': err='%v'",
-			c.name,
-			c.daemon.lxcpath,
-			filepath.Join(c.LogPath(), "lxc.conf"),
-			imagesDir,
-			err)
-	}
-
-	return nil
-}
-
 func (c *containerLXC) OnStart() error {
 	// Make sure we can't call go-lxc functions by mistake
 	c.fromHook = true
@@ -1371,10 +1314,6 @@ func (c *containerLXC) setupStopping() *sync.WaitGroup {
 func (c *containerLXC) Stop(stateful bool) error {
 	// Handle stateful stop
 	if stateful {
-		if err := findCriu("snapshot"); err != nil {
-			return err
-		}
-
 		// Cleanup any existing state
 		stateDir := c.StatePath()
 		os.RemoveAll(stateDir)
@@ -1385,12 +1324,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		}
 
 		// Checkpoint
-		err = c.Migrate(lxc.MIGRATE_DUMP, stateDir, true)
-		err2 := CollectCRIULogFile(c, stateDir, "snapshot", "dump")
-		if err2 != nil {
-			shared.Log.Warn("failed to collect criu log file", log.Ctx{"error": err2})
-		}
-
+		err = c.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", true)
 		if err != nil {
 			return err
 		}
@@ -1761,32 +1695,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 	// If the container wasn't running but was stateful, should we restore
 	// it as running?
 	if shared.PathExists(c.StatePath()) {
-		configPath, err := c.startCommon()
-		if err != nil {
-			return err
-		}
-
-		if !c.IsPrivileged() {
-			if err := c.IdmapSet().ShiftRootfs(c.StatePath()); err != nil {
-				return err
-			}
-		}
-
-		out, err := exec.Command(
-			execPath,
-			"forkmigrate",
-			c.name,
-			c.daemon.lxcpath,
-			configPath,
-			c.StatePath()).CombinedOutput()
-		if string(out) != "" {
-			for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-				shared.Debugf("forkmigrate: %s", line)
-			}
-		}
-		CollectCRIULogFile(c, c.StatePath(), "snapshot", "restore")
-
-		if err != nil {
+		if err := c.Migrate(lxc.MIGRATE_RESTORE, c.StatePath(), "snapshot", false); err != nil {
 			return err
 		}
 
@@ -2711,19 +2620,140 @@ func (c *containerLXC) Export(w io.Writer) error {
 	return tw.Close()
 }
 
-func (c *containerLXC) Migrate(cmd uint, stateDir string, stop bool) error {
-	err := c.initLXC()
+func collectCRIULogFile(c container, imagesDir string, function string, method string) error {
+	t := time.Now().Format(time.RFC3339)
+	newPath := shared.LogPath(c.Name(), fmt.Sprintf("%s_%s_%s.log", function, method, t))
+	return shared.FileCopy(filepath.Join(imagesDir, fmt.Sprintf("%s.log", method)), newPath)
+}
+
+func getCRIULogErrors(imagesDir string, method string) (string, error) {
+	f, err := os.Open(path.Join(imagesDir, fmt.Sprintf("%s.log", method)))
+	if err != nil {
+		return "", err
+	}
+
+	defer f.Close()
+
+	scanner := bufio.NewScanner(f)
+	ret := []string{}
+	for scanner.Scan() {
+		line := scanner.Text()
+		if strings.Contains(line, "Error") {
+			ret = append(ret, scanner.Text())
+		}
+	}
+
+	return strings.Join(ret, "\n"), nil
+}
+
+func findCriu(host string) error {
+	_, err := exec.LookPath("criu")
 	if err != nil {
+		return fmt.Errorf("CRIU is required for live migration but its binary couldn't be found on the %s server. Is it installed in LXD's path?", host)
+	}
+
+	return nil
+}
+
+func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop bool) error {
+	if err := findCriu(function); err != nil {
 		return err
 	}
 
-	opts := lxc.MigrateOptions{
-		Stop: stop,
-		Directory: stateDir,
-		Verbose: true,
+	prettyCmd := ""
+	switch cmd {
+	case lxc.MIGRATE_PRE_DUMP:
+		prettyCmd = "pre-dump"
+	case lxc.MIGRATE_DUMP:
+		prettyCmd = "dump"
+	case lxc.MIGRATE_RESTORE:
+		prettyCmd = "restore"
+	default:
+		prettyCmd = "unknown"
+		shared.Log.Warn("unknown migrate call", log.Ctx{"cmd": cmd})
+	}
+
+	var migrateErr error
+
+	/* For restore, we need an extra fork so that we daemonize monitor
+	 * instead of having it be a child of LXD, so let's hijack the command
+	 * here and do the extra fork.
+	 */
+	if cmd == lxc.MIGRATE_RESTORE {
+		// Run the shared start
+		_, err := c.startCommon()
+		if err != nil {
+			return err
+		}
+
+		/*
+		 * For unprivileged containers we need to shift the
+		 * perms on the images images so that they can be
+		 * opened by the process after it is in its user
+		 * namespace.
+		 */
+		if !c.IsPrivileged() {
+			if err := c.IdmapSet().ShiftRootfs(stateDir); err != nil {
+				return err
+			}
+		}
+
+		configPath := filepath.Join(c.LogPath(), "lxc.conf")
+
+		var out []byte
+		out, migrateErr = exec.Command(
+			execPath,
+			"forkmigrate",
+			c.name,
+			c.daemon.lxcpath,
+			configPath,
+			stateDir).CombinedOutput()
+
+		if string(out) != "" {
+			for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+				shared.Debugf("forkmigrate: %s", line)
+			}
+		}
+
+		if migrateErr != nil && !c.IsRunning() {
+			migrateErr = fmt.Errorf(
+				"Error calling 'lxd forkmigrate %s %s %s %s': err='%v' out='%v'",
+				c.name,
+				c.daemon.lxcpath,
+				filepath.Join(c.LogPath(), "lxc.conf"),
+				stateDir,
+				err,
+				string(out))
+		}
+
+	} else {
+		err := c.initLXC()
+		if err != nil {
+			return err
+		}
+
+		opts := lxc.MigrateOptions{
+			Stop:      stop,
+			Directory: stateDir,
+			Verbose:   true,
+		}
+
+		migrateErr = c.c.Migrate(cmd, opts)
+	}
+
+	collectErr := collectCRIULogFile(c, stateDir, function, prettyCmd)
+	if collectErr != nil {
+		shared.Log.Error("Error collecting checkpoint log file", log.Ctx{"err": collectErr})
+	}
+
+	if migrateErr != nil {
+		log, err2 := getCRIULogErrors(stateDir, prettyCmd)
+		if err2 == nil {
+			migrateErr = fmt.Errorf("%s %s failed\n%s", function, prettyCmd, log)
+		}
 	}
 
-	return c.c.Migrate(cmd, opts)
+	return migrateErr
 }
 
 func (c *containerLXC) TemplateApply(trigger string) error {
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 627e6d04a..5528aa198 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -6,18 +6,13 @@
 package main
 
 import (
-	"bufio"
 	"fmt"
 	"io/ioutil"
 	"net/http"
 	"net/url"
 	"os"
-	"os/exec"
-	"path"
-	"path/filepath"
 	"strings"
 	"sync"
-	"time"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/gorilla/websocket"
@@ -71,15 +66,6 @@ func (c *migrationFields) send(m proto.Message) error {
 	return shared.WriteAll(w, data)
 }
 
-func findCriu(host string) error {
-	_, err := exec.LookPath("criu")
-	if err != nil {
-		return fmt.Errorf("CRIU is required for live migration but its binary couldn't be found on the %s server. Is it installed in LXD's path?", host)
-	}
-
-	return nil
-}
-
 func (c *migrationFields) recv(m proto.Message) error {
 	mt, r, err := c.controlConn.NextReader()
 	if err != nil {
@@ -158,32 +144,6 @@ func (c *migrationFields) controlChannel() <-chan MigrationControl {
 	return ch
 }
 
-func CollectCRIULogFile(c container, imagesDir string, function string, method string) error {
-	t := time.Now().Format(time.RFC3339)
-	newPath := shared.LogPath(c.Name(), fmt.Sprintf("%s_%s_%s.log", function, method, t))
-	return shared.FileCopy(filepath.Join(imagesDir, fmt.Sprintf("%s.log", method)), newPath)
-}
-
-func GetCRIULogErrors(imagesDir string, method string) (string, error) {
-	f, err := os.Open(path.Join(imagesDir, fmt.Sprintf("%s.log", method)))
-	if err != nil {
-		return "", err
-	}
-
-	defer f.Close()
-
-	scanner := bufio.NewScanner(f)
-	ret := []string{}
-	for scanner.Scan() {
-		line := scanner.Text()
-		if strings.Contains(line, "Error") {
-			ret = append(ret, scanner.Text())
-		}
-	}
-
-	return strings.Join(ret, "\n"), nil
-}
-
 type migrationSourceWs struct {
 	migrationFields
 
@@ -368,24 +328,8 @@ func (s *migrationSourceWs) Do(op *operation) error {
 		}
 		defer os.RemoveAll(checkpointDir)
 
-		err = s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, true)
-
-		if err2 := CollectCRIULogFile(s.container, checkpointDir, "migration", "dump"); err2 != nil {
-			shared.Debugf("Error collecting checkpoint log file %s", err)
-		}
-
+		err = s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true)
 		if err != nil {
-			driver.Cleanup()
-			log, err2 := GetCRIULogErrors(checkpointDir, "dump")
-
-			/* couldn't find the CRIU log file which means we
-			 * didn't even get that far; give back the liblxc
-			 * error. */
-			if err2 != nil {
-				log = err.Error()
-			}
-
-			err = fmt.Errorf("checkpoint failed:\n%s", log)
 			s.sendControl(err)
 			return err
 		}
@@ -601,36 +545,12 @@ func (c *migrationSink) do() error {
 				return
 			}
 
-			defer func() {
-				err := CollectCRIULogFile(c.container, imagesDir, "migration", "restore")
-				/*
-				 * If the checkpoint fails, we won't have any log to collect,
-				 * so don't warn about that.
-				 */
-				if err != nil && !os.IsNotExist(err) {
-					shared.Debugf("Error collectiong migration log file %s", err)
-				}
-
-				os.RemoveAll(imagesDir)
-			}()
+			defer os.RemoveAll(imagesDir)
 
 			if err := RsyncRecv(shared.AddSlash(imagesDir), c.criuConn); err != nil {
 				restore <- err
 				return
 			}
-
-			/*
-			 * For unprivileged containers we need to shift the
-			 * perms on the images images so that they can be
-			 * opened by the process after it is in its user
-			 * namespace.
-			 */
-			if !c.container.IsPrivileged() {
-				if err := c.container.IdmapSet().ShiftRootfs(imagesDir); err != nil {
-					restore <- err
-					return
-				}
-			}
 		}
 
 		err := <-fsTransfer
@@ -640,15 +560,8 @@ func (c *migrationSink) do() error {
 		}
 
 		if c.live {
-			err := c.container.StartFromMigration(imagesDir)
+			err = c.container.Migrate(lxc.MIGRATE_RESTORE, imagesDir, "migration", false)
 			if err != nil {
-				log, err2 := GetCRIULogErrors(imagesDir, "restore")
-				/* restore failed before CRIU was invoked, give
-				 * back the liblxc error */
-				if err2 != nil {
-					log = err.Error()
-				}
-				err = fmt.Errorf("restore failed:\n%s", log)
 				restore <- err
 				return
 			}

From 0a114712362cb40bde592814be79a612e67af984 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 16 Jun 2016 08:32:00 -0600
Subject: [PATCH 0102/1193] similar to lxc delete, add a -f shortcut to lxc
 stop

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/action.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxc/action.go b/lxc/action.go
index 1cf6a1386..dfb304430 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -34,6 +34,7 @@ lxc %s <name> [<name>...]`), c.name, c.name)
 func (c *actionCmd) flags() {
 	if c.hasTimeout {
 		gnuflag.IntVar(&c.timeout, "timeout", -1, i18n.G("Time to wait for the container before killing it."))
+		gnuflag.BoolVar(&c.force, "f", false, i18n.G("Force the container to shutdown."))
 		gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the container to shutdown."))
 	}
 	gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Store the container state (only for stop)."))

From cb34263da5487cbfaba0ed5301c93c22a33f9182 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jun 2016 15:51:56 -0400
Subject: [PATCH 0103/1193] Better handle PEM decoding errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2119

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config.go          |  4 +++
 lxd/certificates.go    |  5 +++
 lxd/containers_post.go |  3 ++
 lxd/daemon.go          |  6 ++++
 po/lxd.pot             | 82 ++++++++++++++++++++++++++------------------------
 shared/network.go      |  4 +++
 6 files changed, 65 insertions(+), 39 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index e824b4e61..3923cf6f7 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -250,6 +250,10 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 				fp := cert.Fingerprint[0:12]
 
 				certBlock, _ := pem.Decode([]byte(cert.Certificate))
+				if certBlock == nil {
+					return fmt.Errorf(i18n.G("Invalid certificate"))
+				}
+
 				cert, err := x509.ParseCertificate(certBlock.Bytes)
 				if err != nil {
 					return err
diff --git a/lxd/certificates.go b/lxd/certificates.go
index df3b70b55..a0502aaee 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -69,6 +69,11 @@ func readSavedClientCAList(d *Daemon) {
 
 	for _, dbCert := range dbCerts {
 		certBlock, _ := pem.Decode([]byte(dbCert.Certificate))
+		if certBlock == nil {
+			shared.Logf("Error decoding certificate for %s: %s", dbCert.Name, err)
+			continue
+		}
+
 		cert, err := x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
 			shared.Logf("Error reading certificate for %s: %s", dbCert.Name, err)
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 25e37ca82..795fdbd07 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -253,6 +253,9 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 		var cert *x509.Certificate
 		if req.Source.Certificate != "" {
 			certBlock, _ := pem.Decode([]byte(req.Source.Certificate))
+			if certBlock == nil {
+				return fmt.Errorf("Invalid certificate")
+			}
 
 			cert, err = x509.ParseCertificate(certBlock.Bytes)
 			if err != nil {
diff --git a/lxd/daemon.go b/lxd/daemon.go
index d1ca0232b..0c81e3744 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -111,6 +111,9 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 	var cert *x509.Certificate
 	if certificate != "" {
 		certBlock, _ := pem.Decode([]byte(certificate))
+		if certBlock == nil {
+			return nil, fmt.Errorf("Invalid certificate")
+		}
 
 		cert, err = x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
@@ -163,6 +166,9 @@ func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, er
 	var cert *x509.Certificate
 	if certificate != "" {
 		certBlock, _ := pem.Decode([]byte(certificate))
+		if certBlock == nil {
+			return nil, fmt.Errorf("Invalid certificate")
+		}
 
 		cert, err = x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
diff --git a/po/lxd.pot b/po/lxd.pot
index 4114d7e35..2ae7b2258 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-06-13 19:11-0400\n"
+        "POT-Creation-Date: 2016-06-27 17:34-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -106,7 +106,7 @@ msgstr  ""
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:256
+#: lxc/remote.go:246
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
@@ -141,7 +141,7 @@ msgstr  ""
 msgid   "Bytes sent"
 msgstr  ""
 
-#: lxc/config.go:270
+#: lxc/config.go:274
 msgid   "COMMON NAME"
 msgstr  ""
 
@@ -163,7 +163,7 @@ msgstr  ""
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:206
+#: lxc/remote.go:196
 #, c-format
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
@@ -175,7 +175,7 @@ msgid   "Changes state of one or more containers to %s.\n"
         "lxc %s <name> [<name>...]"
 msgstr  ""
 
-#: lxc/remote.go:279
+#: lxc/remote.go:269
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
@@ -187,7 +187,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:527 lxc/config.go:592 lxc/image.go:687 lxc/profile.go:190
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:687 lxc/profile.go:190
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -225,7 +225,7 @@ msgstr  ""
 msgid   "Copying the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:221
+#: lxc/remote.go:211
 msgid   "Could not create server cert dir"
 msgstr  ""
 
@@ -271,12 +271,12 @@ msgid   "Delete containers or container snapshots.\n"
         "Destroy containers or snapshots with any attached data (configuration, snapshots, ...)."
 msgstr  ""
 
-#: lxc/config.go:644
+#: lxc/config.go:648
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:831
+#: lxc/config.go:835
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
@@ -285,7 +285,7 @@ msgstr  ""
 msgid   "EPHEMERAL"
 msgstr  ""
 
-#: lxc/config.go:272
+#: lxc/config.go:276
 msgid   "EXPIRY DATE"
 msgstr  ""
 
@@ -326,7 +326,7 @@ msgstr  ""
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:269 lxc/image.go:605 lxc/image.go:634
+#: lxc/config.go:273 lxc/image.go:605 lxc/image.go:634
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -345,7 +345,7 @@ msgid   "Fingers the LXD instance to check if it is up and working.\n"
         "lxc finger <remote>"
 msgstr  ""
 
-#: lxc/action.go:37
+#: lxc/action.go:37 lxc/action.go:38
 msgid   "Force the container to shutdown."
 msgstr  ""
 
@@ -373,7 +373,7 @@ msgstr  ""
 msgid   "IPV6"
 msgstr  ""
 
-#: lxc/config.go:271
+#: lxc/config.go:275
 msgid   "ISSUE DATE"
 msgstr  ""
 
@@ -385,7 +385,7 @@ msgstr  ""
 msgid   "Ignore aliases when determining what command to run."
 msgstr  ""
 
-#: lxc/action.go:40
+#: lxc/action.go:41
 msgid   "Ignore the container state (only for start)."
 msgstr  ""
 
@@ -417,6 +417,10 @@ msgstr  ""
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
 
+#: lxc/config.go:254
+msgid   "Invalid certificate"
+msgstr  ""
+
 #: lxc/init.go:30 lxc/init.go:35
 msgid   "Invalid configuration key"
 msgstr  ""
@@ -705,15 +709,15 @@ msgid   "Move containers within or in between lxd instances.\n"
         "    Rename a local container.\n"
 msgstr  ""
 
-#: lxc/action.go:63
+#: lxc/action.go:64
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:380 lxc/remote.go:363
+#: lxc/list.go:380 lxc/remote.go:353
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/remote.go:337 lxc/remote.go:342
+#: lxc/remote.go:327 lxc/remote.go:332
 msgid   "NO"
 msgstr  ""
 
@@ -726,11 +730,11 @@ msgstr  ""
 msgid   "New alias to define at target"
 msgstr  ""
 
-#: lxc/config.go:281
+#: lxc/config.go:285
 msgid   "No certificate provided to add"
 msgstr  ""
 
-#: lxc/config.go:304
+#: lxc/config.go:308
 msgid   "No fingerprint specified."
 msgstr  ""
 
@@ -767,11 +771,11 @@ msgstr  ""
 msgid   "PROFILES"
 msgstr  ""
 
-#: lxc/remote.go:365
+#: lxc/remote.go:355
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:606 lxc/remote.go:366
+#: lxc/image.go:606 lxc/remote.go:356
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -810,7 +814,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:528 lxc/config.go:593 lxc/image.go:688
+#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:688
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -914,15 +918,15 @@ msgstr  ""
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:357
 msgid   "STATIC"
 msgstr  ""
 
-#: lxc/remote.go:214
+#: lxc/remote.go:204
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:276
+#: lxc/remote.go:266
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
@@ -994,7 +998,7 @@ msgstr  ""
 msgid   "Stopping container failed!"
 msgstr  ""
 
-#: lxc/action.go:39
+#: lxc/action.go:40
 msgid   "Store the container state (only for stop)."
 msgstr  ""
 
@@ -1018,7 +1022,7 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/config.go:672 lxc/config.go:684 lxc/config.go:717 lxc/config.go:735 lxc/config.go:773 lxc/config.go:791
+#: lxc/config.go:676 lxc/config.go:688 lxc/config.go:721 lxc/config.go:739 lxc/config.go:777 lxc/config.go:795
 msgid   "The device doesn't exist"
 msgstr  ""
 
@@ -1048,7 +1052,7 @@ msgstr  ""
 msgid   "Transferring image: %d%%"
 msgstr  ""
 
-#: lxc/action.go:93 lxc/launch.go:132
+#: lxc/action.go:94 lxc/launch.go:132
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1065,7 +1069,7 @@ msgstr  ""
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:364
+#: lxc/remote.go:354
 msgid   "URL"
 msgstr  ""
 
@@ -1103,7 +1107,7 @@ msgstr  ""
 msgid   "Whether to show the expanded configuration"
 msgstr  ""
 
-#: lxc/remote.go:339 lxc/remote.go:344
+#: lxc/remote.go:329 lxc/remote.go:334
 msgid   "YES"
 msgstr  ""
 
@@ -1115,7 +1119,7 @@ msgstr  ""
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
-#: lxc/action.go:89
+#: lxc/action.go:90
 msgid   "bad result type from action"
 msgstr  ""
 
@@ -1123,11 +1127,11 @@ msgstr  ""
 msgid   "can't copy to the same container name"
 msgstr  ""
 
-#: lxc/remote.go:327
+#: lxc/remote.go:317
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:353
+#: lxc/remote.go:343
 msgid   "default"
 msgstr  ""
 
@@ -1165,31 +1169,31 @@ msgstr  ""
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
-#: lxc/remote.go:207
+#: lxc/remote.go:197
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:252 lxc/main.go:256
+#: lxc/main.go:245 lxc/main.go:249
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:389
+#: lxc/remote.go:379
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:319 lxc/remote.go:381 lxc/remote.go:416 lxc/remote.go:432
+#: lxc/remote.go:309 lxc/remote.go:371 lxc/remote.go:406 lxc/remote.go:422
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:302
+#: lxc/remote.go:292
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
-#: lxc/remote.go:323 lxc/remote.go:385 lxc/remote.go:420
+#: lxc/remote.go:313 lxc/remote.go:375 lxc/remote.go:410
 #, c-format
 msgid   "remote %s is static and cannot be modified"
 msgstr  ""
diff --git a/shared/network.go b/shared/network.go
index 2d44433d6..626a9cee2 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -102,6 +102,10 @@ func GetTLSConfigMem(tlsClientCert string, tlsClientKey string, tlsRemoteCertPEM
 	if tlsRemoteCertPEM != "" {
 		// Ignore any content outside of the PEM bytes we care about
 		certBlock, _ := pem.Decode([]byte(tlsRemoteCertPEM))
+		if certBlock == nil {
+			return nil, fmt.Errorf("Invalid remote certificate")
+		}
+
 		var err error
 		tlsRemoteCert, err = x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {

From e4883211b1d15f38a8beb055fa6d4e93452cd19f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jun 2016 15:56:59 -0400
Subject: [PATCH 0104/1193] Fail to add an existing certificate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/certificates.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/certificates.go b/lxd/certificates.go
index a0502aaee..f40ae0272 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -141,7 +141,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 	fingerprint := certGenerateFingerprint(cert)
 	for _, existingCert := range d.clientCerts {
 		if fingerprint == certGenerateFingerprint(&existingCert) {
-			return EmptySyncResponse
+			return BadRequest(fmt.Errorf("Certificate already in trust store"))
 		}
 	}
 

From 9bfb97b4c3d60f8107541987b8afef4de4a4ec9c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jun 2016 16:25:13 -0400
Subject: [PATCH 0105/1193] Fix failure to restore on btrfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2058

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_btrfs.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 79c062122..5f7da3a64 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -249,7 +249,7 @@ func (s *storageBtrfs) ContainerRestore(
 	} else {
 		// Remove the backup, we made
 		if s.isSubvolume(sourceBackupPath) {
-			return s.subvolDelete(sourceBackupPath)
+			return s.subvolsDelete(sourceBackupPath)
 		}
 		os.RemoveAll(sourceBackupPath)
 	}

From 0e19ae7f6d51f1269697ce3823db4900f6404749 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jun 2016 16:40:08 -0400
Subject: [PATCH 0106/1193] Set Location on sync POST requests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2092

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/certificates.go | 17 ++++++++---------
 lxd/images.go       |  4 ++--
 lxd/profiles.go     |  4 ++--
 lxd/response.go     | 16 +++++++++++++++-
 4 files changed, 27 insertions(+), 14 deletions(-)

diff --git a/lxd/certificates.go b/lxd/certificates.go
index f40ae0272..021b48f9a 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -96,20 +96,25 @@ func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
 }
 
 func certificatesPost(d *Daemon, r *http.Request) Response {
+	// Parse the request
 	req := certificatesPostBody{}
-
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
 		return BadRequest(err)
 	}
 
+	// Access check
+	if !d.isTrustedClient(r) && d.PasswordCheck(req.Password) != nil {
+		return Forbidden
+	}
+
 	if req.Type != "client" {
 		return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
 	}
 
+	// Extract the certificate
 	var cert *x509.Certificate
 	var name string
 	if req.Certificate != "" {
-
 		data, err := base64.StdEncoding.DecodeString(req.Certificate)
 		if err != nil {
 			return BadRequest(err)
@@ -120,9 +125,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 			return BadRequest(err)
 		}
 		name = req.Name
-
 	} else if r.TLS != nil {
-
 		if len(r.TLS.PeerCertificates) < 1 {
 			return BadRequest(fmt.Errorf("No client certificate provided"))
 		}
@@ -145,10 +148,6 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 		}
 	}
 
-	if !d.isTrustedClient(r) && d.PasswordCheck(req.Password) != nil {
-		return Forbidden
-	}
-
 	err := saveCert(d, name, cert)
 	if err != nil {
 		return SmartError(err)
@@ -156,7 +155,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 
 	d.clientCerts = append(d.clientCerts, *cert)
 
-	return EmptySyncResponse
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", shared.APIVersion, fingerprint))
 }
 
 var certificatesCmd = Command{
diff --git a/lxd/images.go b/lxd/images.go
index 5013787e5..401b00f4e 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1085,7 +1085,7 @@ func aliasesPost(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
-	return EmptySyncResponse
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, req.Name))
 }
 
 func aliasesGet(d *Daemon, r *http.Request) Response {
@@ -1197,7 +1197,7 @@ func aliasPost(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, req.Name))
 }
 
 func imageExport(d *Daemon, r *http.Request) Response {
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 6cbbf0f6a..746bd3251 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -83,7 +83,7 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}
 
-	return EmptySyncResponse
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name))
 }
 
 var profilesCmd = Command{
@@ -253,7 +253,7 @@ func profilePost(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
-	return EmptySyncResponse
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name))
 }
 
 // The handler for the delete operation.
diff --git a/lxd/response.go b/lxd/response.go
index 59a7a667f..bb746ca81 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -40,6 +40,7 @@ type Response interface {
 type syncResponse struct {
 	success  bool
 	metadata interface{}
+	location string
 }
 
 func (r *syncResponse) Render(w http.ResponseWriter) error {
@@ -48,6 +49,11 @@ func (r *syncResponse) Render(w http.ResponseWriter) error {
 		status = shared.Failure
 	}
 
+	if r.location != "" {
+		w.Header().Set("Location", r.location)
+		w.WriteHeader(201)
+	}
+
 	resp := syncResp{Type: lxd.Sync, Status: status.String(), StatusCode: status, Metadata: r.metadata}
 	return WriteJSON(w, resp)
 }
@@ -64,7 +70,15 @@ func (r *syncResponse) String() string {
 	return "failure"
 }
 
-var EmptySyncResponse = &syncResponse{true, make(map[string]interface{})}
+func SyncResponse(success bool, metadata interface{}) Response {
+	return &syncResponse{success: success, metadata: metadata}
+}
+
+func SyncResponseLocation(success bool, metadata interface{}, location string) Response {
+	return &syncResponse{success: success, metadata: metadata, location: location}
+}
+
+var EmptySyncResponse = &syncResponse{success: true, metadata: make(map[string]interface{})}
 
 // File transfer response
 type fileResponseEntry struct {

From d7b882d8ff2b4e22fc0a5742b0d701cee6121f0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jun 2016 18:10:19 -0400
Subject: [PATCH 0107/1193] Clarify ZFS snapshot shortcomings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2055

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/storage-backends.md | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/doc/storage-backends.md b/doc/storage-backends.md
index 4b4ce0eb2..7c33328d8 100644
--- a/doc/storage-backends.md
+++ b/doc/storage-backends.md
@@ -63,3 +63,11 @@ rsync is used to transfer the container content across.
    one. You can however create new containers from older snapshots which
    makes it possible to confirm the snapshots is indeed what you want to
    restore before you remove the newer snapshots.
+
+   Also note that container copies use ZFS snapshots, so you also cannot
+   restore a container to a snapshot taken before the last copy without
+   having to also delete container copies.
+
+   Copying the wanted snapshot into a new container and then deleting
+   the old container does however work, at the cost of losing any other
+   snapshot the container may have had.

From 27dd8dcb18bf0a2521bf5dd819c6d20239906495 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jun 2016 18:48:15 -0400
Subject: [PATCH 0108/1193] test: 201 is a valid return code for alias creation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/deps/import-busybox | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/deps/import-busybox b/test/deps/import-busybox
index 91a9cfe60..d78d777a5 100755
--- a/test/deps/import-busybox
+++ b/test/deps/import-busybox
@@ -83,7 +83,7 @@ class LXD(object):
 
         status, data = self.rest_call("/1.0/images/aliases", data, "POST")
 
-        if status != 200:
+        if status not in (200, 201):
             raise Exception("Failed to create alias: %s" % name)
 
     def aliases_remove(self, name):

From bd4d1852c9f98cac55b5e40db58920881ec7eadd Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Mon, 20 Jun 2016 13:45:26 -0700
Subject: [PATCH 0109/1193] Use defer to undo changes on failed update

Defer the function to undo changes and track whether or not the changes
should be kept using a closure over a bool.  This eliminates the need to
explicitly call undoChanges() in every failing return path.

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 lxd/container_lxc.go | 81 ++++++++++++++--------------------------------------
 1 file changed, 21 insertions(+), 60 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5f374773b..8bcb91079 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1992,18 +1992,24 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
-	// Define a function which reverts everything
-	undoChanges := func() {
-		c.architecture = oldArchitecture
-		c.ephemeral = oldEphemeral
-		c.expandedConfig = oldExpandedConfig
-		c.expandedDevices = oldExpandedDevices
-		c.localConfig = oldLocalConfig
-		c.localDevices = oldLocalDevices
-		c.profiles = oldProfiles
-		c.initLXC()
-		deviceTaskSchedulerTrigger("container", c.name, "changed")
-	}
+	// Define a function which reverts everything.  Defer this function
+	// so that it doesn't need to be explicitly called in every failing
+	// return path.  Track whether or not we want to undo the changes
+	// using a closure.
+	undoChanges := true
+	defer func() {
+		if undoChanges {
+			c.architecture = oldArchitecture
+			c.ephemeral = oldEphemeral
+			c.expandedConfig = oldExpandedConfig
+			c.expandedDevices = oldExpandedDevices
+			c.localConfig = oldLocalConfig
+			c.localDevices = oldLocalDevices
+			c.profiles = oldProfiles
+			c.initLXC()
+			deviceTaskSchedulerTrigger("container", c.name, "changed")
+		}
+	}()
 
 	// Apply the various changes
 	c.architecture = args.Architecture
@@ -2015,19 +2021,16 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	// Expand the config and refresh the LXC config
 	err = c.expandConfig()
 	if err != nil {
-		undoChanges()
 		return err
 	}
 
 	err = c.expandDevices()
 	if err != nil {
-		undoChanges()
 		return err
 	}
 
 	err = c.initLXC()
 	if err != nil {
-		undoChanges()
 		return err
 	}
 
@@ -2055,14 +2058,12 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	// Do some validation of the config diff
 	err = containerValidConfig(c.expandedConfig, false, true)
 	if err != nil {
-		undoChanges()
 		return err
 	}
 
 	// Do some validation of the devices diff
 	err = containerValidDevices(c.expandedDevices, false, true)
 	if err != nil {
-		undoChanges()
 		return err
 	}
 
@@ -2071,7 +2072,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		if key == "raw.apparmor" || key == "security.nesting" {
 			err = AAParseProfile(c)
 			if err != nil {
-				undoChanges()
 				return err
 			}
 		}
@@ -2090,13 +2090,11 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		if m["size"] != oldRootfsSize {
 			size, err := shared.ParseByteSizeString(m["size"])
 			if err != nil {
-				undoChanges()
 				return err
 			}
 
 			err = c.storage.ContainerSetQuota(c, size)
 			if err != nil {
-				undoChanges()
 				return err
 			}
 		}
@@ -2122,7 +2120,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 
 		if oldRootfs["source"] != newRootfs["source"] {
-			undoChanges()
 			return fmt.Errorf("Cannot change the rootfs path of a running container")
 		}
 
@@ -2134,7 +2131,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 				// Update the AppArmor profile
 				err = AALoadProfile(c)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			} else if key == "linux.kernel_modules" && value != "" {
@@ -2142,7 +2138,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 					module = strings.TrimPrefix(module, " ")
 					out, err := exec.Command("modprobe", module).CombinedOutput()
 					if err != nil {
-						undoChanges()
 						return fmt.Errorf("Failed to load kernel module '%s': %s", module, out)
 					}
 				}
@@ -2193,7 +2188,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 				} else {
 					valueInt, err := shared.ParseByteSizeString(memory)
 					if err != nil {
-						undoChanges()
 						return err
 					}
 					memory = fmt.Sprintf("%d", valueInt)
@@ -2203,20 +2197,17 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 				if cgSwapAccounting {
 					err = c.CGroupSet("memory.memsw.limit_in_bytes", "-1")
 					if err != nil {
-						undoChanges()
 						return err
 					}
 				}
 
 				err = c.CGroupSet("memory.limit_in_bytes", "-1")
 				if err != nil {
-					undoChanges()
 					return err
 				}
 
 				err = c.CGroupSet("memory.soft_limit_in_bytes", "-1")
 				if err != nil {
-					undoChanges()
 					return err
 				}
 
@@ -2225,25 +2216,21 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 					// Set new limit
 					err = c.CGroupSet("memory.soft_limit_in_bytes", memory)
 					if err != nil {
-						undoChanges()
 						return err
 					}
 				} else {
 					if cgSwapAccounting && (memorySwap == "" || shared.IsTrue(memorySwap)) {
 						err = c.CGroupSet("memory.limit_in_bytes", memory)
 						if err != nil {
-							undoChanges()
 							return err
 						}
 						err = c.CGroupSet("memory.memsw.limit_in_bytes", memory)
 						if err != nil {
-							undoChanges()
 							return err
 						}
 					} else {
 						err = c.CGroupSet("memory.limit_in_bytes", memory)
 						if err != nil {
-							undoChanges()
 							return err
 						}
 					}
@@ -2256,7 +2243,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 					if memorySwap != "" && !shared.IsTrue(memorySwap) {
 						err = c.CGroupSet("memory.swappiness", "0")
 						if err != nil {
-							undoChanges()
 							return err
 						}
 					} else {
@@ -2264,14 +2250,12 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 						if memorySwapPriority != "" {
 							priority, err = strconv.Atoi(memorySwapPriority)
 							if err != nil {
-								undoChanges()
 								return err
 							}
 						}
 
 						err = c.CGroupSet("memory.swappiness", fmt.Sprintf("%d", 60-10+priority))
 						if err != nil {
-							undoChanges()
 							return err
 						}
 					}
@@ -2293,25 +2277,21 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 				// Apply new CPU limits
 				cpuShares, cpuCfsQuota, cpuCfsPeriod, err := deviceParseCPU(c.expandedConfig["limits.cpu.allowance"], c.expandedConfig["limits.cpu.priority"])
 				if err != nil {
-					undoChanges()
 					return err
 				}
 
 				err = c.CGroupSet("cpu.shares", cpuShares)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 
 				err = c.CGroupSet("cpu.cfs_period_us", cpuCfsPeriod)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 
 				err = c.CGroupSet("cpu.cfs_quota_us", cpuCfsQuota)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			} else if key == "limits.processes" {
@@ -2322,19 +2302,16 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 				if value == "" {
 					err = c.CGroupSet("pids.max", "max")
 					if err != nil {
-						undoChanges()
 						return err
 					}
 				} else {
 					valueInt, err := strconv.ParseInt(value, 10, 64)
 					if err != nil {
-						undoChanges()
 						return err
 					}
 
 					err = c.CGroupSet("pids.max", fmt.Sprintf("%d", valueInt))
 					if err != nil {
-						undoChanges()
 						return err
 					}
 				}
@@ -2346,19 +2323,16 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
 				err = c.removeUnixDevice(k, m)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			} else if m["type"] == "disk" && m["path"] != "/" {
 				err = c.removeDiskDevice(k, m)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			} else if m["type"] == "nic" {
 				err = c.removeNetworkDevice(k, m)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			}
@@ -2368,19 +2342,16 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
 				err = c.insertUnixDevice(k, m)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			} else if m["type"] == "disk" && m["path"] != "/" {
 				err = c.insertDiskDevice(k, m)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			} else if m["type"] == "nic" {
 				err = c.insertNetworkDevice(k, m)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			}
@@ -2393,7 +2364,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			} else if m["type"] == "nic" {
 				err = c.setNetworkLimits(k, m)
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			}
@@ -2403,32 +2373,27 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		if updateDiskLimit && cgBlkioController {
 			diskLimits, err := c.getDiskLimits()
 			if err != nil {
-				undoChanges()
 				return err
 			}
 
 			for block, limit := range diskLimits {
 				err = c.CGroupSet("blkio.throttle.read_bps_device", fmt.Sprintf("%s %d", block, limit.readBps))
 				if err != nil {
-					undoChanges()
 					return err
 				}
 
 				err = c.CGroupSet("blkio.throttle.read_iops_device", fmt.Sprintf("%s %d", block, limit.readIops))
 				if err != nil {
-					undoChanges()
 					return err
 				}
 
 				err = c.CGroupSet("blkio.throttle.write_bps_device", fmt.Sprintf("%s %d", block, limit.writeBps))
 				if err != nil {
-					undoChanges()
 					return err
 				}
 
 				err = c.CGroupSet("blkio.throttle.write_iops_device", fmt.Sprintf("%s %d", block, limit.writeIops))
 				if err != nil {
-					undoChanges()
 					return err
 				}
 			}
@@ -2438,50 +2403,46 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	// Finally, apply the changes to the database
 	tx, err := dbBegin(c.daemon.db)
 	if err != nil {
-		undoChanges()
 		return err
 	}
 
 	err = dbContainerConfigClear(tx, c.id)
 	if err != nil {
 		tx.Rollback()
-		undoChanges()
 		return err
 	}
 
 	err = dbContainerConfigInsert(tx, c.id, args.Config)
 	if err != nil {
 		tx.Rollback()
-		undoChanges()
 		return err
 	}
 
 	err = dbContainerProfilesInsert(tx, c.id, args.Profiles)
 	if err != nil {
 		tx.Rollback()
-		undoChanges()
 		return err
 	}
 
 	err = dbDevicesAdd(tx, "container", int64(c.id), args.Devices)
 	if err != nil {
 		tx.Rollback()
-		undoChanges()
 		return err
 	}
 
 	err = dbContainerUpdate(tx, c.id, c.architecture, c.ephemeral)
 	if err != nil {
 		tx.Rollback()
-		undoChanges()
 		return err
 	}
 
 	if err := txCommit(tx); err != nil {
-		undoChanges()
 		return err
 	}
 
+	// Success, update the closure to mark that the changes should be kept.
+	undoChanges = false
+
 	return nil
 }
 

From 57350070cd155aa96540beb40a835a8329e1e826 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Jun 2016 11:36:44 -0400
Subject: [PATCH 0110/1193] Allow "none" as compression algorithm
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_config.go | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 7a07a63a6..98bee3c0d 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -175,7 +175,7 @@ func daemonConfigInit(db *sql.DB) error {
 
 		"images.auto_update_cached":    &daemonConfigKey{valueType: "bool", defaultValue: "true"},
 		"images.auto_update_interval":  &daemonConfigKey{valueType: "int", defaultValue: "6"},
-		"images.compression_algorithm": &daemonConfigKey{valueType: "string", validator: daemonConfigValidateCommand, defaultValue: "gzip"},
+		"images.compression_algorithm": &daemonConfigKey{valueType: "string", validator: daemonConfigValidateCompression, defaultValue: "gzip"},
 		"images.remote_cache_expiry":   &daemonConfigKey{valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry},
 
 		"storage.lvm_fstype":        &daemonConfigKey{valueType: "string", defaultValue: "ext4", validValues: []string{"ext4", "xfs"}},
@@ -311,7 +311,11 @@ func daemonConfigTriggerExpiry(d *Daemon, key string, value string) {
 	d.pruneChan <- true
 }
 
-func daemonConfigValidateCommand(d *Daemon, key string, value string) error {
+func daemonConfigValidateCompression(d *Daemon, key string, value string) error {
+	if value == "none" {
+		return nil
+	}
+
 	_, err := exec.LookPath(value)
 	return err
 }

From 6111055e0a4a7c77c92940c56b5a26c164fdaf40 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Jun 2016 12:41:38 -0400
Subject: [PATCH 0111/1193] init: actually unset the storage keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index 3968e1800..99d10b4ff 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -833,7 +833,7 @@ they otherwise would.
 	}
 
 	// Unset all storage keys, core.https_address and core.trust_password
-	for _, key := range []string{"core.https_address", "core.trust_password"} {
+	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
 		_, err = c.SetServerConfig(key, "")
 		if err != nil {
 			return err

From dfc6bd60dc7873270230e404f0531e92b0db263a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Jun 2016 18:42:20 -0400
Subject: [PATCH 0112/1193] Make devices cgroup config more readable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8bcb91079..ce5095511 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -368,7 +368,21 @@ func (c *containerLXC) initLXC() error {
 			return err
 		}
 
-		for _, dev := range []string{"c *:* m", "b *:* m", "c 5:0 rwm", "c 5:1 rwm", "c 1:5 rwm", "c 1:7 rwm", "c 1:3 rwm", "c 1:8 rwm", "c 1:9 rwm", "c 5:2 rwm", "c 136:* rwm"} {
+		devices := []string{
+			"b *:* m",     // Allow mknod of block devices
+			"c *:* m",     // Allow mknod of char devices
+			"c 136:* rwm", // /dev/pts devices
+			"c 1:3 rwm",   // /dev/null
+			"c 1:5 rwm",   // /dev/zero
+			"c 1:7 rwm",   // /dev/full
+			"c 1:8 rwm",   // /dev/random
+			"c 1:9 rwm",   // /dev/urandom
+			"c 5:0 rwm",   // /dev/tty
+			"c 5:1 rwm",   // /dev/console
+			"c 5:2 rwm",   // /dev/ptmx
+		}
+
+		for _, dev := range devices {
 			err = lxcSetConfigItem(cc, "lxc.cgroup.devices.allow", dev)
 			if err != nil {
 				return err

From 458eb15ac86a9b45cbc1190d677cba6066e1cfb2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Jun 2016 18:43:10 -0400
Subject: [PATCH 0113/1193] Setup /dev/fuse by default
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ce5095511..a05db3df3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -325,6 +325,7 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	bindMounts := []string{
+		"/dev/fuse",
 		"/proc/sys/fs/binfmt_misc",
 		"/sys/firmware/efi/efivars",
 		"/sys/fs/fuse/connections",
@@ -369,17 +370,18 @@ func (c *containerLXC) initLXC() error {
 		}
 
 		devices := []string{
-			"b *:* m",     // Allow mknod of block devices
-			"c *:* m",     // Allow mknod of char devices
-			"c 136:* rwm", // /dev/pts devices
-			"c 1:3 rwm",   // /dev/null
-			"c 1:5 rwm",   // /dev/zero
-			"c 1:7 rwm",   // /dev/full
-			"c 1:8 rwm",   // /dev/random
-			"c 1:9 rwm",   // /dev/urandom
-			"c 5:0 rwm",   // /dev/tty
-			"c 5:1 rwm",   // /dev/console
-			"c 5:2 rwm",   // /dev/ptmx
+			"b *:* m",      // Allow mknod of block devices
+			"c *:* m",      // Allow mknod of char devices
+			"c 136:* rwm",  // /dev/pts devices
+			"c 1:3 rwm",    // /dev/null
+			"c 1:5 rwm",    // /dev/zero
+			"c 1:7 rwm",    // /dev/full
+			"c 1:8 rwm",    // /dev/random
+			"c 1:9 rwm",    // /dev/urandom
+			"c 5:0 rwm",    // /dev/tty
+			"c 5:1 rwm",    // /dev/console
+			"c 5:2 rwm",    // /dev/ptmx
+			"c 10:229 rwm", // /dev/fuse
 		}
 
 		for _, dev := range devices {

From 2609f8e0e8bfd6b9994245dcedcfc7bbc3b331a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Jun 2016 19:23:43 -0400
Subject: [PATCH 0114/1193] Better handle bind mounts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index a05db3df3..eabe18675 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -343,9 +343,20 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	for _, mnt := range bindMounts {
-		err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s %s none rbind,create=dir,optional", mnt, strings.TrimPrefix(mnt, "/")))
-		if err != nil {
-			return err
+		if !shared.PathExists(mnt) {
+			continue
+		}
+
+		if shared.IsDir(mnt) {
+			err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s %s none rbind,create=dir,optional", mnt, strings.TrimPrefix(mnt, "/")))
+			if err != nil {
+				return err
+			}
+		} else {
+			err = lxcSetConfigItem(cc, "lxc.mount.entry", fmt.Sprintf("%s %s none bind,create=file,optional", mnt, strings.TrimPrefix(mnt, "/")))
+			if err != nil {
+				return err
+			}
 		}
 	}
 

From 4adf7cd654d620bf5c691fa1798bccb63edc60de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Jun 2016 20:34:37 -0400
Subject: [PATCH 0115/1193] Update repository URL for xgettext-go
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 2b08f8ea4..85a23e21a 100644
--- a/Makefile
+++ b/Makefile
@@ -82,7 +82,7 @@ update-po:
 	done
 
 update-pot:
-	go get -v -x github.com/ubuntu-core/snappy/i18n/xgettext-go/
+	go get -v -x github.com/snapcore/snapd/i18n/xgettext-go/
 	xgettext-go -o po/$(DOMAIN).pot --add-comments-tag=TRANSLATORS: --sort-output --package-name=$(DOMAIN) --msgid-bugs-address=lxc-devel at lists.linuxcontainers.org --keyword=i18n.G --keyword-plural=i18n.NG *.go shared/*.go lxc/*.go lxd/*.go
 
 

From b21bb9873fcd4874aad6c6e24ecf6295c260a52d Mon Sep 17 00:00:00 2001
From: Nicolas Lastra <nicolas.san at gmail.com>
Date: Wed, 22 Jun 2016 12:58:19 -0400
Subject: [PATCH 0116/1193] Update README.md

The example to mount shared folder inside container from host was improved.

Closes #2123

Signed-off-by: Nicolas O. Lastra <nicolas.san at gmail.com>
---
 README.md | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index e8d3d681f..56599bf26 100644
--- a/README.md
+++ b/README.md
@@ -311,8 +311,16 @@ lxc-devel, and we can escalate to CRIU lists as necessary.
 
 Yes. The easiest way to do that is using a privileged container:
 
-    lxc launch ubuntu priv -c security.privileged=true
-    lxc config device add priv homedir disk source=/home/$USER path=/home/ubuntu
+1.a) create a container.
+
+    lxc launch ubuntu privilegedContainerName -c security.privileged=true
+    
+1.b) or, if your container already exists.
+
+        lxc config set privilegedContainerName security.privileged true
+2) then.
+
+    lxc config device add privilegedContainerName shareName disk source=/home/$USER path=/home/ubuntu
 
 #### How can I run docker inside a LXD container?
 

From 4647265ff00cd6d860c89102c7a4b7277e654b95 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Jun 2016 19:49:28 -0400
Subject: [PATCH 0117/1193] Add squashfs support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is now needed for Ubuntu 16.10 images.

Has been tested both directly on a host and inside a container.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go           | 41 ++++++++++++++++++++++++++---------------
 lxd/main.go             |  2 +-
 lxd/storage_btrfs.go    |  2 +-
 lxd/storage_dir.go      |  2 +-
 lxd/storage_lvm.go      | 12 ++++++------
 lxd/storage_zfs.go      |  2 +-
 shared/simplestreams.go | 41 +++++++++++++++++++++++++++++------------
 7 files changed, 65 insertions(+), 37 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 401b00f4e..76914e2c7 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -66,30 +66,41 @@ func detectCompression(fname string) ([]string, string, error) {
 		return []string{"--lzma", "-xf"}, ".tar.lzma", nil
 	case bytes.Equal(header[257:262], []byte{'u', 's', 't', 'a', 'r'}):
 		return []string{"-xf"}, ".tar", nil
+	case bytes.Equal(header[0:4], []byte{'h', 's', 'q', 's'}):
+		return []string{""}, ".squashfs", nil
 	default:
 		return []string{""}, "", fmt.Errorf("Unsupported compression.")
 	}
 
 }
 
-func untar(tarball string, path string) error {
-	extractArgs, _, err := detectCompression(tarball)
+func unpack(file string, path string) error {
+	extractArgs, extension, err := detectCompression(file)
 	if err != nil {
 		return err
 	}
 
-	command := "tar"
+	command := ""
 	args := []string{}
-	if runningInUserns {
-		args = append(args, "--wildcards")
-		args = append(args, "--exclude=dev/*")
-		args = append(args, "--exclude=./dev/*")
-		args = append(args, "--exclude=rootfs/dev/*")
-		args = append(args, "--exclude=rootfs/./dev/*")
+	if strings.HasPrefix(extension, ".tar") {
+		command = "tar"
+		if runningInUserns {
+			args = append(args, "--wildcards")
+			args = append(args, "--exclude=dev/*")
+			args = append(args, "--exclude=./dev/*")
+			args = append(args, "--exclude=rootfs/dev/*")
+			args = append(args, "--exclude=rootfs/./dev/*")
+		}
+		args = append(args, "-C", path, "--numeric-owner")
+		args = append(args, extractArgs...)
+		args = append(args, file)
+	} else if strings.HasPrefix(extension, ".squashfs") {
+		command = "unsquashfs"
+		args = append(args, "-f", "-d", path, "-n")
+		args = append(args, file)
+	} else {
+		return fmt.Errorf("Unsupported image format: %s", extension)
 	}
-	args = append(args, "-C", path, "--numeric-owner")
-	args = append(args, extractArgs...)
-	args = append(args, tarball)
 
 	output, err := exec.Command(command, args...).CombinedOutput()
 	if err != nil {
@@ -101,8 +112,8 @@ func untar(tarball string, path string) error {
 	return nil
 }
 
-func untarImage(imagefname string, destpath string) error {
-	err := untar(imagefname, destpath)
+func unpackImage(imagefname string, destpath string) error {
+	err := unpack(imagefname, destpath)
 	if err != nil {
 		return err
 	}
@@ -114,7 +125,7 @@ func untarImage(imagefname string, destpath string) error {
 			return fmt.Errorf("Error creating rootfs directory")
 		}
 
-		err = untar(imagefname+".rootfs", rootfsPath)
+		err = unpack(imagefname+".rootfs", rootfsPath)
 		if err != nil {
 			return err
 		}
diff --git a/lxd/main.go b/lxd/main.go
index 99d10b4ff..ec20ed2a4 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -333,7 +333,7 @@ func cmdDaemon() error {
 		go memProfiler(*argMemProfile)
 	}
 
-	neededPrograms := []string{"setfacl", "rsync", "tar", "xz"}
+	neededPrograms := []string{"setfacl", "rsync", "tar", "unsquashfs", "xz"}
 	for _, p := range neededPrograms {
 		_, err := exec.LookPath(p)
 		if err != nil {
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 5f7da3a64..08de5621f 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -413,7 +413,7 @@ func (s *storageBtrfs) ImageCreate(fingerprint string) error {
 		return err
 	}
 
-	if err := untarImage(imagePath, subvol); err != nil {
+	if err := unpackImage(imagePath, subvol); err != nil {
 		s.subvolDelete(subvol)
 		return err
 	}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index f0c892ee1..ef2f158b8 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -60,7 +60,7 @@ func (s *storageDir) ContainerCreateFromImage(
 	}
 
 	imagePath := shared.VarPath("images", imageFingerprint)
-	if err := untarImage(imagePath, container.Path()); err != nil {
+	if err := unpackImage(imagePath, container.Path()); err != nil {
 		s.ContainerDelete(container)
 		return err
 	}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 43273ba32..057235e22 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -732,28 +732,28 @@ func (s *storageLvm) ImageCreate(fingerprint string) error {
 	fstype := daemonConfig["storage.lvm_fstype"].Get()
 	err = tryMount(lvpath, tempLVMountPoint, fstype, 0, "discard")
 	if err != nil {
-		shared.Logf("Error mounting image LV for untarring: %v", err)
+		shared.Logf("Error mounting image LV for unpacking: %v", err)
 		return fmt.Errorf("Error mounting image LV: %v", err)
 	}
 
-	untarErr := untarImage(finalName, tempLVMountPoint)
+	unpackErr := unpackImage(finalName, tempLVMountPoint)
 
 	err = tryUnmount(tempLVMountPoint, 0)
 	if err != nil {
 		s.log.Warn("could not unmount LV. Will not remove",
 			log.Ctx{"lvpath": lvpath, "mountpoint": tempLVMountPoint, "err": err})
-		if untarErr == nil {
+		if unpackErr == nil {
 			return err
 		}
 
 		return fmt.Errorf(
 			"Error unmounting '%s' during cleanup of error %v",
-			tempLVMountPoint, untarErr)
+			tempLVMountPoint, unpackErr)
 	}
 
-	if untarErr != nil {
+	if unpackErr != nil {
 		s.removeLV(fingerprint)
-		return untarErr
+		return unpackErr
 	}
 
 	return nil
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 3d6bee884..78f688531 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -603,7 +603,7 @@ func (s *storageZfs) ImageCreate(fingerprint string) error {
 		return err
 	}
 
-	err = untarImage(imagePath, subvol)
+	err = unpackImage(imagePath, subvol)
 	if err != nil {
 		s.zfsDestroy(fs)
 		return err
diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index 7842e75fc..a3d07c148 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -115,14 +115,22 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 			found := 0
 			for _, item := range version.Items {
 				// Skip the files we don't care about
-				if !StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz"}) {
+				if !StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz", "squashfs"}) {
 					continue
 				}
 				found += 1
 
 				size += item.Size
-				if item.LXDHashSha256 != "" {
-					fingerprint = item.LXDHashSha256
+				if fingerprint == "" {
+					if item.LXDHashSha256SquashFs != "" {
+						fingerprint = item.LXDHashSha256SquashFs
+					}
+					if item.LXDHashSha256RootXz != "" {
+						fingerprint = item.LXDHashSha256RootXz
+					}
+					if item.LXDHashSha256 != "" {
+						fingerprint = item.LXDHashSha256
+					}
 				}
 
 				if item.FileType == "lxd.tar.xz" {
@@ -132,9 +140,16 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 					metaHash = item.HashSha256
 				}
 
-				if item.FileType == "root.tar.xz" {
-					rootfsPath = item.Path
-					rootfsHash = item.HashSha256
+				if rootfsPath == "" || rootfsHash == "" {
+					if item.FileType == "squashfs" {
+						rootfsPath = item.Path
+						rootfsHash = item.HashSha256
+					}
+
+					if item.FileType == "root.tar.xz" {
+						rootfsPath = item.Path
+						rootfsHash = item.HashSha256
+					}
 				}
 			}
 
@@ -199,12 +214,14 @@ type SimpleStreamsManifestProductVersion struct {
 }
 
 type SimpleStreamsManifestProductVersionItem struct {
-	Path          string `json:"path"`
-	FileType      string `json:"ftype"`
-	HashMd5       string `json:"md5"`
-	HashSha256    string `json:"sha256"`
-	LXDHashSha256 string `json:"combined_sha256"`
-	Size          int64  `json:"size"`
+	Path                  string `json:"path"`
+	FileType              string `json:"ftype"`
+	HashMd5               string `json:"md5"`
+	HashSha256            string `json:"sha256"`
+	LXDHashSha256         string `json:"combined_sha256"`
+	LXDHashSha256RootXz   string `json:"combined_rootxz_sha256"`
+	LXDHashSha256SquashFs string `json:"combined_squashfs_sha256"`
+	Size                  int64  `json:"size"`
 }
 
 type SimpleStreamsIndex struct {

From f9589e46267e67493eaf5c44537aa9025f06cfe1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Jun 2016 10:33:33 -0400
Subject: [PATCH 0118/1193] doc: Drop JSON example from configuration.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

It's outdated and we've got a bunch of up to date ones in rest-api.md

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/configuration.md | 39 ---------------------------------------
 1 file changed, 39 deletions(-)

diff --git a/doc/configuration.md b/doc/configuration.md
index c7e67dded..cb605fafa 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -263,42 +263,3 @@ network interface connected to LXD's default bridge (lxdbr0).
 
 The "default" profile is set for any new container created which doesn't
 specify a different profiles list.
-
-## JSON representation
-A representation of a container using all the different types of
-configurations would look like:
-
-    {
-        'name': "my-container",
-        'profiles': ["default"],
-        'architecture': 'x86_64',
-        'config': {
-            'limits.cpu': '3',
-            'security.privileged': 'true'
-        },
-        'devices': {
-            'nic-lxdbr0': {
-                'type': 'none'
-            },
-            'nic-mybr0': {
-                'type': 'nic',
-                'mtu': '9000',
-                'parent': 'mybr0'
-            },
-            'rootfs': {
-                'type': 'disk',
-                'path': '/',
-                'source': 'UUID=8f7fdf5e-dc60-4524-b9fe-634f82ac2fb6'
-            },
-        },
-        'status': {
-                    'status': "Running",
-                    'status_code': 103,
-                    'ips': [{'interface': "eth0",
-                             'protocol': "INET6",
-                             'address': "2001:470:b368:1020:1::2"},
-                            {'interface': "eth0",
-                             'protocol': "INET",
-                             'address': "172.16.15.30"}]}
-    }
-

From e04ea0e15efa7ebd9bfd11f7fb71fa4495722114 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Fri, 24 Jun 2016 09:43:54 -0700
Subject: [PATCH 0119/1193] Add function to consolidate Do's error handling

Add a function that can be called inline with return to handle all
cleanup if an error occurs after migration has been started.  This
fixes a missing call to driver.Cleanup() and hopefully reduces the
probability of a similar bug occuring in the future.

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 lxd/migrate.go | 38 ++++++++++++++++++--------------------
 1 file changed, 18 insertions(+), 20 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index 5528aa198..f39ca24cb 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -301,37 +301,39 @@ func (s *migrationSourceWs) Do(op *operation) error {
 		driver, _ = rsyncMigrationSource(s.container)
 	}
 
-	if err := driver.SendWhileRunning(s.fsConn); err != nil {
+	// All failure paths need to do a few things to correctly handle errors before returning.
+	// Unfortunately, handling errors is not well-suited to defer as the code depends on the
+	// status of driver and the error value.  The error value is especially tricky due to the
+	// common case of creating a new err variable (intentional or not) due to scoping and use
+	// of ":=".  Capturing err in a closure for use in defer would be fragile, which defeats
+	// the purpose of using defer.  An abort function reduces the odds of mishandling errors
+	// without introducing the fragility of closing on err.
+	abort := func(err error) error {
 		driver.Cleanup()
 		s.sendControl(err)
 		return err
 	}
 
+	if err := driver.SendWhileRunning(s.fsConn); err != nil {
+		return abort(err)
+	}
+
 	if s.live {
 		if header.Criu == nil {
-			driver.Cleanup()
-			err := fmt.Errorf("Got no CRIU socket type for live migration")
-			s.sendControl(err)
-			return err
+			return abort(fmt.Errorf("Got no CRIU socket type for live migration"))
 		} else if *header.Criu != CRIUType_CRIU_RSYNC {
-			driver.Cleanup()
-			err := fmt.Errorf("Formats other than criu rsync not understood")
-			s.sendControl(err)
-			return err
+			return abort(fmt.Errorf("Formats other than criu rsync not understood"))
 		}
 
 		checkpointDir, err := ioutil.TempDir("", "lxd_checkpoint_")
 		if err != nil {
-			driver.Cleanup()
-			s.sendControl(err)
-			return err
+			return abort(err)
 		}
 		defer os.RemoveAll(checkpointDir)
 
 		err = s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true)
 		if err != nil {
-			s.sendControl(err)
-			return err
+			return abort(err)
 		}
 
 		/*
@@ -342,15 +344,11 @@ func (s *migrationSourceWs) Do(op *operation) error {
 		 * p.haul's protocol, it will make sense to do these in parallel.
 		 */
 		if err := RsyncSend(shared.AddSlash(checkpointDir), s.criuConn); err != nil {
-			driver.Cleanup()
-			s.sendControl(err)
-			return err
+			return abort(err)
 		}
 
 		if err := driver.SendAfterCheckpoint(s.fsConn); err != nil {
-			driver.Cleanup()
-			s.sendControl(err)
-			return err
+			return abort(err)
 		}
 	}
 

From e8608dc08f4fa39d2daec5c78a4782010a4ba406 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 16 Jun 2016 16:40:08 -0400
Subject: [PATCH 0120/1193] Fix duplicate SyncResponse definition
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Caused by bad cherry-pick.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/response.go | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/lxd/response.go b/lxd/response.go
index bb746ca81..0bdc1e9e5 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -58,10 +58,6 @@ func (r *syncResponse) Render(w http.ResponseWriter) error {
 	return WriteJSON(w, resp)
 }
 
-func SyncResponse(success bool, metadata interface{}) Response {
-	return &syncResponse{success, metadata}
-}
-
 func (r *syncResponse) String() string {
 	if r.success {
 		return "success"

From 92c4ab22408971ffa6d2ac47946d9d6a5612848e Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 27 Jun 2016 16:44:32 -0600
Subject: [PATCH 0121/1193] improve error message

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index eabe18675..b7d60b4b8 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3964,7 +3964,7 @@ func (c *containerLXC) createDiskDevice(name string, m shared.Device) (string, e
 		if isOptional {
 			return "", nil
 		}
-		return "", fmt.Errorf("Source path doesn't exist")
+		return "", fmt.Errorf("Source path %s doesn't exist for device %s", srcPath, name)
 	}
 
 	// Create the devices directory if missing

From 26a850e27193114b18af2db5b9190cc6c656f55a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Jun 2016 10:59:26 -0400
Subject: [PATCH 0122/1193] Use the daemon provided fingerprint on image copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fallback on client provided fingerprint if no daemon provided
fingerprint can be found in operation metadata.

Closes #2162

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/client.go b/client.go
index 508989be2..622ce06a3 100644
--- a/client.go
+++ b/client.go
@@ -697,6 +697,8 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 		go dest.Monitor([]string{"operation"}, handler)
 	}
 
+	fingerprint := info.Fingerprint
+
 	for _, addr := range addresses {
 		sourceUrl := "https://" + addr
 
@@ -710,11 +712,18 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 
 		operation = resp.Operation
 
-		err = dest.WaitForSuccess(resp.Operation)
+		op, err := dest.WaitForSuccessOp(resp.Operation)
 		if err != nil {
 			return err
 		}
 
+		if op.Metadata != nil {
+			value, err := op.Metadata.GetString("fingerprint")
+			if err == nil {
+				fingerprint = value
+			}
+		}
+
 		break
 	}
 
@@ -726,7 +735,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 	if copy_aliases {
 		for _, alias := range info.Aliases {
 			dest.DeleteAlias(alias.Name)
-			err = dest.PostAlias(alias.Name, alias.Description, info.Fingerprint)
+			err = dest.PostAlias(alias.Name, alias.Description, fingerprint)
 			if err != nil {
 				return fmt.Errorf("Error adding alias %s: %s", alias.Name, err)
 			}
@@ -736,7 +745,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 	/* add new aliases */
 	for _, alias := range aliases {
 		dest.DeleteAlias(alias)
-		err = dest.PostAlias(alias, alias, info.Fingerprint)
+		err = dest.PostAlias(alias, alias, fingerprint)
 		if err != nil {
 			return fmt.Errorf("Error adding alias %s: %s\n", alias, err)
 		}
@@ -1847,6 +1856,19 @@ func (c *Client) WaitForSuccess(waitURL string) error {
 	return fmt.Errorf(op.Err)
 }
 
+func (c *Client) WaitForSuccessOp(waitURL string) (*shared.Operation, error) {
+	op, err := c.WaitFor(waitURL)
+	if err != nil {
+		return nil, err
+	}
+
+	if op.StatusCode == shared.Success {
+		return op, nil
+	}
+
+	return op, fmt.Errorf(op.Err)
+}
+
 func (c *Client) RestoreSnapshot(container string, snapshotName string, stateful bool) (*Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")

From 326b02d5c5efbd889376303d13ab2555e8812fd9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Jun 2016 19:16:38 -0400
Subject: [PATCH 0123/1193] doc: Fix certificates json examples
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/rest-api.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 4f50f904d..6c020989f 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -296,7 +296,7 @@ Input:
     {
         "type": "client",                       # Certificate type (keyring), currently only client
         "certificate": "PEM certificate",       # If provided, a valid x509 certificate. If not, the client certificate of the connection will be used
-        "name": "foo"                           # An optional name for the certificate. If nothing is provided, the host in the TLS header for the request is used.
+        "name": "foo",                          # An optional name for the certificate. If nothing is provided, the host in the TLS header for the request is used.
         "password": "server-trust-password"     # The trust password for that server (only required if untrusted)
     }
 
@@ -311,7 +311,8 @@ Output:
 
     {
         "type": "client",
-        "certificate": "PEM certificate"
+        "certificate": "PEM certificate",
+        "name": "foo",
         "fingerprint": "SHA256 Hash of the raw certificate"
     }
 

From d546580679d7aec03cfe2f218688290bfbf4d8d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Jun 2016 14:20:43 -0400
Subject: [PATCH 0124/1193] Release LXD 2.0.3
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/flex.go b/shared/flex.go
index 6128c08db..50a681ee8 100644
--- a/shared/flex.go
+++ b/shared/flex.go
@@ -3,7 +3,7 @@
  */
 package shared
 
-var Version = "2.0.2"
+var Version = "2.0.3"
 var UserAgent = "LXD " + Version
 
 /*

From 2cf00624130ad6f10738bb933dc58466866f3283 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Jun 2016 14:25:34 -0400
Subject: [PATCH 0125/1193] Makefile: Also have "make dist" run multiple go get
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Makefile b/Makefile
index 85a23e21a..487f51209 100644
--- a/Makefile
+++ b/Makefile
@@ -58,6 +58,8 @@ dist:
 	rm -Rf lxd-$(VERSION) $(ARCHIVE) $(ARCHIVE).gz
 	mkdir -p lxd-$(VERSION)/dist
 	-GOPATH=$(shell pwd)/lxd-$(VERSION)/dist go get -t -v -d ./...
+	-GOPATH=$(shell pwd)/lxd-$(VERSION)/dist go get -t -v -d ./...
+	-GOPATH=$(shell pwd)/lxd-$(VERSION)/dist go get -t -v -d ./...
 	GOPATH=$(shell pwd)/lxd-$(VERSION)/dist go get -t -v -d ./...
 	rm -rf $(shell pwd)/lxd-$(VERSION)/dist/src/github.com/lxc/lxd
 	ln -s ../../../.. ./lxd-$(VERSION)/dist/src/github.com/lxc/lxd

From 93124401cb9949172b5d1d5befb76ba27beddca2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 29 Jun 2016 17:15:57 -0400
Subject: [PATCH 0126/1193] Improve check for invalid physical devices
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

And drop the rather odd error message :)

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b7d60b4b8..1ab6aad39 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3875,8 +3875,8 @@ func (c *containerLXC) insertNetworkDevice(name string, m shared.Device) error {
 		return nil
 	}
 
-	if m["hwaddr"] == "" || m["name"] == "" {
-		return fmt.Errorf("wtf? hwaddr=%s name=%s", m["hwaddr"], m["name"])
+	if m["parent"] != "" && !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", m["parent"])) {
+		return fmt.Errorf("Parent device '%s' doesn't exist", m["parent"])
 	}
 
 	// Return empty list if not running

From c36cb1105d433cc013d1a086798a88692ddf5bd2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 30 Jun 2016 13:30:28 -0400
Subject: [PATCH 0127/1193] simplestreams: list images available as both
 squashfs and tar.xz
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index a3d07c148..973b9bb6a 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -153,7 +153,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 				}
 			}
 
-			if found != 2 || size == 0 || filename == "" || fingerprint == "" {
+			if found < 2 || size == 0 || filename == "" || fingerprint == "" {
 				// Invalid image
 				continue
 			}

From 5ebaaa5aea585b224e75a9ed7aaea9e80eff6876 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 1 Jul 2016 08:55:17 -0600
Subject: [PATCH 0128/1193] remove unused field from finger cmd

Closes #2170

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/finger.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lxc/finger.go b/lxc/finger.go
index 3fea385ae..2f0749fe3 100644
--- a/lxc/finger.go
+++ b/lxc/finger.go
@@ -5,9 +5,7 @@ import (
 	"github.com/lxc/lxd/shared/i18n"
 )
 
-type fingerCmd struct {
-	httpAddr string
-}
+type fingerCmd struct{}
 
 func (c *fingerCmd) showByDefault() bool {
 	return false

From 268c3dfafc2b6fd19be5d0dec218241799e0214e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 30 Jun 2016 16:02:35 -0400
Subject: [PATCH 0129/1193] apparmor: Don't depend on the LXC apparmor profile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Rather than depending on the LXC apparmor profile, just generate a
complete profile for each container.

This will allow some flexibility, for example, having completely
different profiles for privileged and unprivileged containers, or have
profiles vary based on kernel version.

Related to #1942

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 228 insertions(+), 11 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index ce25c5040..d9a3d8a67 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -50,17 +50,234 @@ const NESTING_AA_PROFILE = `
 const DEFAULT_AA_PROFILE = `
 #include <tunables/global>
 profile "%s" flags=(attach_disconnected,mediate_deleted) {
-    #include <abstractions/lxc/container-base>
-
-    # Special exception for cgroup namespaces
-    %s
-
-    # user input raw.apparmor below here
-    %s
-
-    # nesting support goes here if needed
-    %s
-    change_profile -> "%s",
+  network,
+  capability,
+  file,
+  umount,
+
+  # dbus, signal, ptrace and unix are only supported by recent apparmor
+  # versions. Comment them if the apparmor parser doesn't recognize them.
+
+  # This also needs additional rules to reach outside of the container via
+  # DBus, so just let all of DBus within the container.
+  dbus,
+
+  # Allow us to receive signals from anywhere. Note: if per-container profiles
+  # are supported, for container isolation this should be changed to something
+  # like:
+  #   signal (receive) peer=unconfined,
+  #   signal (receive) peer=/usr/bin/lxc-start,
+  signal (receive),
+
+  # Allow us to send signals to ourselves
+  signal peer=@{profile_name},
+
+  # Allow other processes to read our /proc entries, futexes, perf tracing and
+  # kcmp for now (they will need 'read' in the first place). Administrators can
+  # override with:
+  #   deny ptrace (readby) ...
+  ptrace (readby),
+
+  # Allow other processes to trace us by default (they will need 'trace' in
+  # the first place). Administrators can override with:
+  #   deny ptrace (tracedby) ...
+  ptrace (tracedby),
+
+  # Allow us to ptrace ourselves
+  ptrace peer=@{profile_name},
+
+  # Allow receive via unix sockets from anywhere. Note: if per-container
+  # profiles are supported, for container isolation this should be changed to
+  # something like:
+  #   unix (receive) peer=(label=unconfined),
+  unix (receive),
+
+  # Allow all unix in the container
+  unix peer=(label=@{profile_name}),
+
+  # ignore DENIED message on / remount
+  deny mount options=(ro, remount) -> /,
+  deny mount options=(ro, remount, silent) -> /,
+
+  # allow tmpfs mounts everywhere
+  mount fstype=tmpfs,
+
+  # allow hugetlbfs mounts everywhere
+  mount fstype=hugetlbfs,
+
+  # allow mqueue mounts everywhere
+  mount fstype=mqueue,
+
+  # allow fuse mounts everywhere
+  mount fstype=fuse,
+  mount fstype=fuse.*,
+
+  # deny access under /proc/bus to avoid e.g. messing with pci devices directly
+  deny @{PROC}/bus/** wklx,
+
+  # deny writes in /proc/sys/fs but allow binfmt_misc to be mounted
+  mount fstype=binfmt_misc -> /proc/sys/fs/binfmt_misc/,
+  deny @{PROC}/sys/fs/** wklx,
+
+  # allow efivars to be mounted, writing to it will be blocked though
+  mount fstype=efivarfs -> /sys/firmware/efi/efivars/,
+
+  # block some other dangerous paths
+  deny @{PROC}/kcore rwklx,
+  deny @{PROC}/kmem rwklx,
+  deny @{PROC}/mem rwklx,
+  deny @{PROC}/sysrq-trigger rwklx,
+
+  # deny writes in /sys except for /sys/fs/cgroup, also allow
+  # fusectl, securityfs and debugfs to be mounted there (read-only)
+  mount fstype=fusectl -> /sys/fs/fuse/connections/,
+  mount fstype=securityfs -> /sys/kernel/security/,
+  mount fstype=debugfs -> /sys/kernel/debug/,
+  deny mount fstype=debugfs -> /var/lib/ureadahead/debugfs/,
+  mount fstype=proc -> /proc/,
+  mount fstype=sysfs -> /sys/,
+  mount options=(rw, nosuid, nodev, noexec, remount) -> /sys/,
+  deny /sys/firmware/efi/efivars/** rwklx,
+  deny /sys/kernel/security/** rwklx,
+  mount options=(move) /sys/fs/cgroup/cgmanager/ -> /sys/fs/cgroup/cgmanager.lower/,
+  mount options=(ro, nosuid, nodev, noexec, remount, strictatime) -> /sys/fs/cgroup/,
+
+  # deny reads from debugfs
+  deny /sys/kernel/debug/{,**} rwklx,
+
+  # allow paths to be made slave, shared, private or unbindable
+  # FIXME: This currently doesn't work due to the apparmor parser treating those as allowing all mounts.
+#  mount options=(rw,make-slave) -> **,
+#  mount options=(rw,make-rslave) -> **,
+#  mount options=(rw,make-shared) -> **,
+#  mount options=(rw,make-rshared) -> **,
+#  mount options=(rw,make-private) -> **,
+#  mount options=(rw,make-rprivate) -> **,
+#  mount options=(rw,make-unbindable) -> **,
+#  mount options=(rw,make-runbindable) -> **,
+
+  # allow bind-mounts of anything except /proc, /sys and /dev
+  mount options=(rw,bind) /[^spd]*{,/**},
+  mount options=(rw,bind) /d[^e]*{,/**},
+  mount options=(rw,bind) /de[^v]*{,/**},
+  mount options=(rw,bind) /dev/.[^l]*{,/**},
+  mount options=(rw,bind) /dev/.l[^x]*{,/**},
+  mount options=(rw,bind) /dev/.lx[^c]*{,/**},
+  mount options=(rw,bind) /dev/.lxc?*{,/**},
+  mount options=(rw,bind) /dev/[^.]*{,/**},
+  mount options=(rw,bind) /dev?*{,/**},
+  mount options=(rw,bind) /p[^r]*{,/**},
+  mount options=(rw,bind) /pr[^o]*{,/**},
+  mount options=(rw,bind) /pro[^c]*{,/**},
+  mount options=(rw,bind) /proc?*{,/**},
+  mount options=(rw,bind) /s[^y]*{,/**},
+  mount options=(rw,bind) /sy[^s]*{,/**},
+  mount options=(rw,bind) /sys?*{,/**},
+
+  # allow moving mounts except for /proc, /sys and /dev
+  mount options=(rw,move) /[^spd]*{,/**},
+  mount options=(rw,move) /d[^e]*{,/**},
+  mount options=(rw,move) /de[^v]*{,/**},
+  mount options=(rw,move) /dev/.[^l]*{,/**},
+  mount options=(rw,move) /dev/.l[^x]*{,/**},
+  mount options=(rw,move) /dev/.lx[^c]*{,/**},
+  mount options=(rw,move) /dev/.lxc?*{,/**},
+  mount options=(rw,move) /dev/[^.]*{,/**},
+  mount options=(rw,move) /dev?*{,/**},
+  mount options=(rw,move) /p[^r]*{,/**},
+  mount options=(rw,move) /pr[^o]*{,/**},
+  mount options=(rw,move) /pro[^c]*{,/**},
+  mount options=(rw,move) /proc?*{,/**},
+  mount options=(rw,move) /s[^y]*{,/**},
+  mount options=(rw,move) /sy[^s]*{,/**},
+  mount options=(rw,move) /sys?*{,/**},
+
+  # generated by: lxc-generate-aa-rules.py container-rules.base
+  deny /proc/sys/[^kn]*{,/**} wklx,
+  deny /proc/sys/k[^e]*{,/**} wklx,
+  deny /proc/sys/ke[^r]*{,/**} wklx,
+  deny /proc/sys/ker[^n]*{,/**} wklx,
+  deny /proc/sys/kern[^e]*{,/**} wklx,
+  deny /proc/sys/kerne[^l]*{,/**} wklx,
+  deny /proc/sys/kernel/[^smhd]*{,/**} wklx,
+  deny /proc/sys/kernel/d[^o]*{,/**} wklx,
+  deny /proc/sys/kernel/do[^m]*{,/**} wklx,
+  deny /proc/sys/kernel/dom[^a]*{,/**} wklx,
+  deny /proc/sys/kernel/doma[^i]*{,/**} wklx,
+  deny /proc/sys/kernel/domai[^n]*{,/**} wklx,
+  deny /proc/sys/kernel/domain[^n]*{,/**} wklx,
+  deny /proc/sys/kernel/domainn[^a]*{,/**} wklx,
+  deny /proc/sys/kernel/domainna[^m]*{,/**} wklx,
+  deny /proc/sys/kernel/domainnam[^e]*{,/**} wklx,
+  deny /proc/sys/kernel/domainname?*{,/**} wklx,
+  deny /proc/sys/kernel/h[^o]*{,/**} wklx,
+  deny /proc/sys/kernel/ho[^s]*{,/**} wklx,
+  deny /proc/sys/kernel/hos[^t]*{,/**} wklx,
+  deny /proc/sys/kernel/host[^n]*{,/**} wklx,
+  deny /proc/sys/kernel/hostn[^a]*{,/**} wklx,
+  deny /proc/sys/kernel/hostna[^m]*{,/**} wklx,
+  deny /proc/sys/kernel/hostnam[^e]*{,/**} wklx,
+  deny /proc/sys/kernel/hostname?*{,/**} wklx,
+  deny /proc/sys/kernel/m[^s]*{,/**} wklx,
+  deny /proc/sys/kernel/ms[^g]*{,/**} wklx,
+  deny /proc/sys/kernel/msg*/** wklx,
+  deny /proc/sys/kernel/s[^he]*{,/**} wklx,
+  deny /proc/sys/kernel/se[^m]*{,/**} wklx,
+  deny /proc/sys/kernel/sem*/** wklx,
+  deny /proc/sys/kernel/sh[^m]*{,/**} wklx,
+  deny /proc/sys/kernel/shm*/** wklx,
+  deny /proc/sys/kernel?*{,/**} wklx,
+  deny /proc/sys/n[^e]*{,/**} wklx,
+  deny /proc/sys/ne[^t]*{,/**} wklx,
+  deny /proc/sys/net?*{,/**} wklx,
+  deny /sys/[^fdc]*{,/**} wklx,
+  deny /sys/c[^l]*{,/**} wklx,
+  deny /sys/cl[^a]*{,/**} wklx,
+  deny /sys/cla[^s]*{,/**} wklx,
+  deny /sys/clas[^s]*{,/**} wklx,
+  deny /sys/class/[^n]*{,/**} wklx,
+  deny /sys/class/n[^e]*{,/**} wklx,
+  deny /sys/class/ne[^t]*{,/**} wklx,
+  deny /sys/class/net?*{,/**} wklx,
+  deny /sys/class?*{,/**} wklx,
+  deny /sys/d[^e]*{,/**} wklx,
+  deny /sys/de[^v]*{,/**} wklx,
+  deny /sys/dev[^i]*{,/**} wklx,
+  deny /sys/devi[^c]*{,/**} wklx,
+  deny /sys/devic[^e]*{,/**} wklx,
+  deny /sys/device[^s]*{,/**} wklx,
+  deny /sys/devices/[^v]*{,/**} wklx,
+  deny /sys/devices/v[^i]*{,/**} wklx,
+  deny /sys/devices/vi[^r]*{,/**} wklx,
+  deny /sys/devices/vir[^t]*{,/**} wklx,
+  deny /sys/devices/virt[^u]*{,/**} wklx,
+  deny /sys/devices/virtu[^a]*{,/**} wklx,
+  deny /sys/devices/virtua[^l]*{,/**} wklx,
+  deny /sys/devices/virtual/[^n]*{,/**} wklx,
+  deny /sys/devices/virtual/n[^e]*{,/**} wklx,
+  deny /sys/devices/virtual/ne[^t]*{,/**} wklx,
+  deny /sys/devices/virtual/net?*{,/**} wklx,
+  deny /sys/devices/virtual?*{,/**} wklx,
+  deny /sys/devices?*{,/**} wklx,
+  deny /sys/f[^s]*{,/**} wklx,
+  deny /sys/fs/[^c]*{,/**} wklx,
+  deny /sys/fs/c[^g]*{,/**} wklx,
+  deny /sys/fs/cg[^r]*{,/**} wklx,
+  deny /sys/fs/cgr[^o]*{,/**} wklx,
+  deny /sys/fs/cgro[^u]*{,/**} wklx,
+  deny /sys/fs/cgrou[^p]*{,/**} wklx,
+  deny /sys/fs/cgroup?*{,/**} wklx,
+  deny /sys/fs?*{,/**} wklx,
+
+  # Special exception for cgroup namespaces
+%s
+
+  # user input raw.apparmor below here
+  %s
+
+  # nesting support goes here if needed
+%s
+  change_profile -> "%s",
 }`
 
 func AAProfileFull(c container) string {

From 77a9ae32b3adf428fa3cb2e18282a519eb8b2de3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 30 Jun 2016 18:34:50 -0400
Subject: [PATCH 0130/1193] apparmor: Setup a more modular apparmor profile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Related to #1942

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go | 51 +++++++++++++++++++++++++--------------------------
 1 file changed, 25 insertions(+), 26 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index d9a3d8a67..8ed85d3ba 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -48,8 +48,6 @@ const NESTING_AA_PROFILE = `
 `
 
 const DEFAULT_AA_PROFILE = `
-#include <tunables/global>
-profile "%s" flags=(attach_disconnected,mediate_deleted) {
   network,
   capability,
   file,
@@ -268,17 +266,7 @@ profile "%s" flags=(attach_disconnected,mediate_deleted) {
   deny /sys/fs/cgrou[^p]*{,/**} wklx,
   deny /sys/fs/cgroup?*{,/**} wklx,
   deny /sys/fs?*{,/**} wklx,
-
-  # Special exception for cgroup namespaces
-%s
-
-  # user input raw.apparmor below here
-  %s
-
-  # nesting support goes here if needed
-%s
-  change_profile -> "%s",
-}`
+`
 
 func AAProfileFull(c container) string {
 	lxddir := shared.VarPath("")
@@ -295,28 +283,39 @@ func AAProfileShort(c container) string {
 	return fmt.Sprintf("lxd-%s", c.Name())
 }
 
-func AAProfileCgns() string {
-	if shared.PathExists("/proc/self/ns/cgroup") {
-		return "  mount fstype=cgroup -> /sys/fs/cgroup/**,"
-	}
-	return ""
-}
-
 // getProfileContent generates the apparmor profile template from the given
 // container. This includes the stock lxc includes as well as stuff from
 // raw.apparmor.
 func getAAProfileContent(c container) string {
-	rawApparmor, ok := c.ExpandedConfig()["raw.apparmor"]
-	if !ok {
-		rawApparmor = ""
+	profile := strings.TrimLeft(DEFAULT_AA_PROFILE, "\n")
+
+	// Apply cgns bits
+	if shared.PathExists("/proc/self/ns/cgroup") {
+		profile += "\n  # Cgroup namespace support\n"
+		profile += "  mount fstype=cgroup -> /sys/fs/cgroup/**,\n"
 	}
 
-	nesting := ""
+	// Apply nesting bits
 	if c.IsNesting() {
-		nesting = NESTING_AA_PROFILE
+		profile += "\n  # Container nesting support\n"
+		profile += strings.TrimLeft(NESTING_AA_PROFILE, "\n")
+		profile += fmt.Sprintf("  change_profile -> \"%s\",\n", AAProfileFull(c))
 	}
 
-	return fmt.Sprintf(DEFAULT_AA_PROFILE, AAProfileFull(c), AAProfileCgns(), rawApparmor, nesting, AAProfileFull(c))
+	// Append raw.apparmor
+	rawApparmor, ok := c.ExpandedConfig()["raw.apparmor"]
+	if ok {
+		profile += "\n  # User input (raw.apparmor)\n"
+		for _, line := range strings.Split(strings.Trim(rawApparmor, "\n"), "\n") {
+			profile += fmt.Sprintf("  %s\n", line)
+		}
+	}
+
+	return fmt.Sprintf(`#include <tunables/global>
+profile "%s" flags=(attach_disconnected,mediate_deleted) {
+%s
+}
+`, AAProfileFull(c), strings.Trim(profile, "\n"))
 }
 
 func runApparmor(command string, c container) error {

From e8c1895dea12b88c4a39d10022d66135691c4d7a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 30 Jun 2016 18:41:50 -0400
Subject: [PATCH 0131/1193] apparmor: Rename main two chunks of rules
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Related to #1942

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go | 54 +++++++++++++++++++++++++++---------------------------
 1 file changed, 27 insertions(+), 27 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index 8ed85d3ba..49cb67182 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -23,31 +23,7 @@ const (
 
 var aaPath = shared.VarPath("security", "apparmor")
 
-const NESTING_AA_PROFILE = `
-  pivot_root,
-  mount /var/lib/lxd/shmounts/ -> /var/lib/lxd/shmounts/,
-  mount none -> /var/lib/lxd/shmounts/,
-  mount fstype=proc -> /usr/lib/*/lxc/**,
-  mount fstype=sysfs -> /usr/lib/*/lxc/**,
-  mount options=(rw,bind),
-  mount options=(rw,rbind),
-  deny /dev/.lxd/proc/** rw,
-  deny /dev/.lxd/sys/** rw,
-  mount options=(rw,make-rshared),
-
-  # there doesn't seem to be a way to ask for:
-  # mount options=(ro,nosuid,nodev,noexec,remount,bind),
-  # as we always get mount to $cdir/proc/sys with those flags denied
-  # So allow all mounts until that is straightened out:
-  mount,
-  mount options=bind /var/lib/lxd/shmounts/** -> /var/lib/lxd/**,
-  # lxc-container-default-with-nesting also inherited these
-  # from start-container, and seems to need them.
-  ptrace,
-  signal,
-`
-
-const DEFAULT_AA_PROFILE = `
+const AA_PROFILE_BASE = `
   network,
   capability,
   file,
@@ -268,6 +244,30 @@ const DEFAULT_AA_PROFILE = `
   deny /sys/fs?*{,/**} wklx,
 `
 
+const AA_PROFILE_NESTING = `
+  pivot_root,
+  mount /var/lib/lxd/shmounts/ -> /var/lib/lxd/shmounts/,
+  mount none -> /var/lib/lxd/shmounts/,
+  mount fstype=proc -> /usr/lib/*/lxc/**,
+  mount fstype=sysfs -> /usr/lib/*/lxc/**,
+  mount options=(rw,bind),
+  mount options=(rw,rbind),
+  deny /dev/.lxd/proc/** rw,
+  deny /dev/.lxd/sys/** rw,
+  mount options=(rw,make-rshared),
+
+  # there doesn't seem to be a way to ask for:
+  # mount options=(ro,nosuid,nodev,noexec,remount,bind),
+  # as we always get mount to $cdir/proc/sys with those flags denied
+  # So allow all mounts until that is straightened out:
+  mount,
+  mount options=bind /var/lib/lxd/shmounts/** -> /var/lib/lxd/**,
+  # lxc-container-default-with-nesting also inherited these
+  # from start-container, and seems to need them.
+  ptrace,
+  signal,
+`
+
 func AAProfileFull(c container) string {
 	lxddir := shared.VarPath("")
 	if len(c.Name())+len(lxddir)+7 >= 253 {
@@ -287,7 +287,7 @@ func AAProfileShort(c container) string {
 // container. This includes the stock lxc includes as well as stuff from
 // raw.apparmor.
 func getAAProfileContent(c container) string {
-	profile := strings.TrimLeft(DEFAULT_AA_PROFILE, "\n")
+	profile := strings.TrimLeft(AA_PROFILE_BASE, "\n")
 
 	// Apply cgns bits
 	if shared.PathExists("/proc/self/ns/cgroup") {
@@ -298,7 +298,7 @@ func getAAProfileContent(c container) string {
 	// Apply nesting bits
 	if c.IsNesting() {
 		profile += "\n  # Container nesting support\n"
-		profile += strings.TrimLeft(NESTING_AA_PROFILE, "\n")
+		profile += strings.TrimLeft(AA_PROFILE_NESTING, "\n")
 		profile += fmt.Sprintf("  change_profile -> \"%s\",\n", AAProfileFull(c))
 	}
 

From dfa6609d4fe42efd92cfa1d1254093c4f6202f35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 5 Jul 2016 17:10:37 +0100
Subject: [PATCH 0132/1193] apparmor: Add feature detection and clean things a
 bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go | 89 +++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 58 insertions(+), 31 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index 49cb67182..22e5f5ec7 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -24,23 +24,14 @@ const (
 var aaPath = shared.VarPath("security", "apparmor")
 
 const AA_PROFILE_BASE = `
-  network,
+  ### Base profile
   capability,
+  dbus,
   file,
+  network,
   umount,
 
-  # dbus, signal, ptrace and unix are only supported by recent apparmor
-  # versions. Comment them if the apparmor parser doesn't recognize them.
-
-  # This also needs additional rules to reach outside of the container via
-  # DBus, so just let all of DBus within the container.
-  dbus,
-
-  # Allow us to receive signals from anywhere. Note: if per-container profiles
-  # are supported, for container isolation this should be changed to something
-  # like:
-  #   signal (receive) peer=unconfined,
-  #   signal (receive) peer=/usr/bin/lxc-start,
+  # Allow us to receive signals from anywhere.
   signal (receive),
 
   # Allow us to send signals to ourselves
@@ -60,15 +51,6 @@ const AA_PROFILE_BASE = `
   # Allow us to ptrace ourselves
   ptrace peer=@{profile_name},
 
-  # Allow receive via unix sockets from anywhere. Note: if per-container
-  # profiles are supported, for container isolation this should be changed to
-  # something like:
-  #   unix (receive) peer=(label=unconfined),
-  unix (receive),
-
-  # Allow all unix in the container
-  unix peer=(label=@{profile_name}),
-
   # ignore DENIED message on / remount
   deny mount options=(ro, remount) -> /,
   deny mount options=(ro, remount, silent) -> /,
@@ -246,14 +228,18 @@ const AA_PROFILE_BASE = `
 
 const AA_PROFILE_NESTING = `
   pivot_root,
+  ptrace,
+  signal,
+
+  deny /dev/.lxd/proc/** rw,
+  deny /dev/.lxd/sys/** rw,
+
   mount /var/lib/lxd/shmounts/ -> /var/lib/lxd/shmounts/,
   mount none -> /var/lib/lxd/shmounts/,
   mount fstype=proc -> /usr/lib/*/lxc/**,
   mount fstype=sysfs -> /usr/lib/*/lxc/**,
   mount options=(rw,bind),
   mount options=(rw,rbind),
-  deny /dev/.lxd/proc/** rw,
-  deny /dev/.lxd/sys/** rw,
   mount options=(rw,make-rshared),
 
   # there doesn't seem to be a way to ask for:
@@ -262,10 +248,6 @@ const AA_PROFILE_NESTING = `
   # So allow all mounts until that is straightened out:
   mount,
   mount options=bind /var/lib/lxd/shmounts/** -> /var/lib/lxd/**,
-  # lxc-container-default-with-nesting also inherited these
-  # from start-container, and seems to need them.
-  ptrace,
-  signal,
 `
 
 func AAProfileFull(c container) string {
@@ -289,15 +271,27 @@ func AAProfileShort(c container) string {
 func getAAProfileContent(c container) string {
 	profile := strings.TrimLeft(AA_PROFILE_BASE, "\n")
 
+	// Apply new features
+	if aaParserSupports("unix") {
+		profile += `
+  ### Feature: unix
+  # Allow receive via unix sockets from anywhere
+  unix (receive),
+
+  # Allow all unix in the container
+  unix peer=(label=@{profile_name}),
+`
+	}
+
 	// Apply cgns bits
 	if shared.PathExists("/proc/self/ns/cgroup") {
-		profile += "\n  # Cgroup namespace support\n"
+		profile += "\n  ### Feature: cgroup namespace\n"
 		profile += "  mount fstype=cgroup -> /sys/fs/cgroup/**,\n"
 	}
 
 	// Apply nesting bits
 	if c.IsNesting() {
-		profile += "\n  # Container nesting support\n"
+		profile += "\n  ### Configuration: nesting\n"
 		profile += strings.TrimLeft(AA_PROFILE_NESTING, "\n")
 		profile += fmt.Sprintf("  change_profile -> \"%s\",\n", AAProfileFull(c))
 	}
@@ -305,7 +299,7 @@ func getAAProfileContent(c container) string {
 	// Append raw.apparmor
 	rawApparmor, ok := c.ExpandedConfig()["raw.apparmor"]
 	if ok {
-		profile += "\n  # User input (raw.apparmor)\n"
+		profile += "\n  ### Configuration: raw.apparmor\n"
 		for _, line := range strings.Split(strings.Trim(rawApparmor, "\n"), "\n") {
 			profile += fmt.Sprintf("  %s\n", line)
 		}
@@ -421,3 +415,36 @@ func aaProfile() string {
 	}
 	return ""
 }
+
+func aaParserSupports(feature string) bool {
+	out, err := exec.Command("apparmor_parser", "--version").CombinedOutput()
+	if err != nil {
+		return false
+	}
+
+	major := 0
+	minor := 0
+	micro := 0
+
+	_, err = fmt.Sscanf(strings.Split(string(out), "\n")[0], "AppArmor parser version %d.%d.%d", &major, &minor, &micro)
+	if err != nil {
+		return false
+	}
+
+	switch feature {
+	case "unix":
+		if major < 2 {
+			return false
+		}
+
+		if major == 2 && minor < 10 {
+			return false
+		}
+
+		if major == 2 && minor == 10 && micro < 95 {
+			return false
+		}
+	}
+
+	return true
+}

From bc6985ab68f38bf686663e16b75348e383eb2d3a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 5 Jul 2016 11:08:57 +0100
Subject: [PATCH 0133/1193] Make lxc-to-lxd work inside virtualenv
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2175

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 8574e459a..df8fbf581 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -1,4 +1,4 @@
-#!/usr/bin/python3
+#!/usr/bin/env python3
 
 import argparse
 import json

From 472e650df709f91c1d24182ba36f425a21daa197 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 5 Jul 2016 11:11:26 +0100
Subject: [PATCH 0134/1193] Allow /dev/net/tun by default
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

It's perfectly safe inside both privileged and unprivileged containers
so there's no reason to have people jump through hoops for that.

Related to #2172

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 1ab6aad39..7e605d9da 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -326,6 +326,7 @@ func (c *containerLXC) initLXC() error {
 
 	bindMounts := []string{
 		"/dev/fuse",
+		"/dev/net/tun",
 		"/proc/sys/fs/binfmt_misc",
 		"/sys/firmware/efi/efivars",
 		"/sys/fs/fuse/connections",
@@ -393,6 +394,7 @@ func (c *containerLXC) initLXC() error {
 			"c 5:1 rwm",    // /dev/console
 			"c 5:2 rwm",    // /dev/ptmx
 			"c 10:229 rwm", // /dev/fuse
+			"c 10:200 rwm", // /dev/net/tun
 		}
 
 		for _, dev := range devices {

From bf18553c4d7489f249df1b3eb9e2bdeb03c2db73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 5 Jul 2016 11:59:24 +0100
Subject: [PATCH 0135/1193] lxd-bridge-proxy: Remove unused code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd-bridge/lxd-bridge-proxy/main.go | 12 +-----------
 1 file changed, 1 insertion(+), 11 deletions(-)

diff --git a/lxd-bridge/lxd-bridge-proxy/main.go b/lxd-bridge/lxd-bridge-proxy/main.go
index 48b3a0b36..a73b74e31 100644
--- a/lxd-bridge/lxd-bridge-proxy/main.go
+++ b/lxd-bridge/lxd-bridge-proxy/main.go
@@ -2,24 +2,14 @@ package main
 
 import (
 	"flag"
-	"fmt"
 	"log"
 	"net/http"
 	"net/http/httputil"
 )
 
-func NewProxy() *httputil.ReverseProxy {
-	director := func(req *http.Request) {
-		if req.Method == "CONNECT" {
-			fmt.Printf("CONNECT: %s\n", req.Host)
-		}
-	}
-	return &httputil.ReverseProxy{Director: director}
-}
-
 func main() {
 	addr := flag.String("addr", "[fe80::1%lxdbr0]:13128", "proxy listen address")
 	flag.Parse()
 
-	log.Fatal(http.ListenAndServe(*addr, NewProxy()))
+	log.Fatal(http.ListenAndServe(*addr, &httputil.ReverseProxy{}))
 }

From cb0c1524172289abd9ce317a4201a2ea613ba7b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 5 Jul 2016 12:07:44 +0100
Subject: [PATCH 0136/1193] Don't unfreeze a container on stop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2164

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/action.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/action.go b/lxc/action.go
index dfb304430..6858bc9cd 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -64,7 +64,7 @@ func (c *actionCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("Must supply container name for: ")+"\"%s\"", nameArg)
 		}
 
-		if c.action == shared.Start || c.action == shared.Stop {
+		if c.action == shared.Start {
 			current, err := d.ContainerInfo(name)
 			if err != nil {
 				return err

From d5a99459ffca49da8180094894332246a69524ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 5 Jul 2016 12:12:07 +0100
Subject: [PATCH 0137/1193] Unfreeze frozen container on shutdown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2164

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_state.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/container_state.go b/lxd/container_state.go
index c8baa39a5..fb0f8152c 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -88,6 +88,13 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 			}
 		} else {
 			do = func(op *operation) error {
+				if c.IsFrozen() {
+					err := c.Unfreeze()
+					if err != nil {
+						return err
+					}
+				}
+
 				err = c.Shutdown(time.Duration(raw.Timeout) * time.Second)
 				if err != nil {
 					return err

From 9391d5a39f68f164dd374a47f40a3a5e3efeda0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 5 Jul 2016 14:03:59 +0100
Subject: [PATCH 0138/1193] Fix ZFS volume size on 32bit architectures
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Lets just use int64 everywhere in the init code.

Closes #2158

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxd/main.go b/lxd/main.go
index ec20ed2a4..9da7563cb 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -35,11 +35,11 @@ var argHelp = gnuflag.Bool("help", false, "")
 var argLogfile = gnuflag.String("logfile", "", "")
 var argMemProfile = gnuflag.String("memprofile", "", "")
 var argNetworkAddress = gnuflag.String("network-address", "", "")
-var argNetworkPort = gnuflag.Int("network-port", -1, "")
+var argNetworkPort = gnuflag.Int64("network-port", -1, "")
 var argPrintGoroutinesEvery = gnuflag.Int("print-goroutines-every", -1, "")
 var argStorageBackend = gnuflag.String("storage-backend", "", "")
 var argStorageCreateDevice = gnuflag.String("storage-create-device", "", "")
-var argStorageCreateLoop = gnuflag.Int("storage-create-loop", -1, "")
+var argStorageCreateLoop = gnuflag.Int64("storage-create-loop", -1, "")
 var argStoragePool = gnuflag.String("storage-pool", "", "")
 var argSyslog = gnuflag.Bool("syslog", false, "")
 var argTimeout = gnuflag.Int("timeout", -1, "")
@@ -590,11 +590,11 @@ func cmdInit() error {
 	var defaultPrivileged int // controls whether we set security.privileged=true
 	var storageBackend string // dir or zfs
 	var storageMode string    // existing, loop or device
-	var storageLoopSize int   // Size in GB
+	var storageLoopSize int64 // Size in GB
 	var storageDevice string  // Path
 	var storagePool string    // pool name
 	var networkAddress string // Address
-	var networkPort int       // Port
+	var networkPort int64     // Port
 	var trustPassword string  // Trust password
 
 	// Detect userns
@@ -645,12 +645,12 @@ func cmdInit() error {
 		}
 	}
 
-	askInt := func(question string, min int, max int) int {
+	askInt := func(question string, min int64, max int64) int64 {
 		for {
 			fmt.Printf(question)
 			input, _ := reader.ReadString('\n')
 			input = strings.TrimSuffix(input, "\n")
-			intInput, err := strconv.Atoi(input)
+			intInput, err := strconv.ParseInt(input, 10, 64)
 
 			if err == nil && (min == -1 || intInput >= min) && (max == -1 || intInput <= max) {
 				return intInput

From f4a5b354469bfb67f6ec7506f0bc3180e885a635 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 5 Jul 2016 15:07:48 +0100
Subject: [PATCH 0139/1193] zfs: Only delete copy- snapshots on delete
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2127

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_zfs.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 78f688531..adb1caf21 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -832,7 +832,7 @@ func (s *storageZfs) zfsCleanup(path string) error {
 
 			return nil
 		}
-	} else if strings.HasPrefix(path, "containers") {
+	} else if strings.HasPrefix(path, "containers") && strings.Contains(path, "@copy-") {
 		// Just remove the copy- snapshot for copies of active containers
 		err := s.zfsDestroy(path)
 		if err != nil {

From e9fc5418a3b396c149481e02dd349e85fdbdf041 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 6 Jul 2016 15:35:51 -0600
Subject: [PATCH 0140/1193] use named args for actionCmds

We'll take advantage of this in the next patch making the diff much
shorter.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/main.go | 55 +++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 37 insertions(+), 18 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index 5c61e3c68..a7fe8ffe4 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -156,29 +156,48 @@ type command interface {
 }
 
 var commands = map[string]command{
-	"config":   &configCmd{},
-	"copy":     &copyCmd{},
-	"delete":   &deleteCmd{},
-	"exec":     &execCmd{},
-	"file":     &fileCmd{},
-	"finger":   &fingerCmd{},
-	"help":     &helpCmd{},
-	"image":    &imageCmd{},
-	"info":     &infoCmd{},
-	"init":     &initCmd{},
-	"launch":   &launchCmd{},
-	"list":     &listCmd{},
-	"monitor":  &monitorCmd{},
-	"move":     &moveCmd{},
-	"pause":    &actionCmd{shared.Freeze, false, false, "pause", -1, false, false, false},
+	"config":  &configCmd{},
+	"copy":    &copyCmd{},
+	"delete":  &deleteCmd{},
+	"exec":    &execCmd{},
+	"file":    &fileCmd{},
+	"finger":  &fingerCmd{},
+	"help":    &helpCmd{},
+	"image":   &imageCmd{},
+	"info":    &infoCmd{},
+	"init":    &initCmd{},
+	"launch":  &launchCmd{},
+	"list":    &listCmd{},
+	"monitor": &monitorCmd{},
+	"move":    &moveCmd{},
+	"pause": &actionCmd{
+		action:  shared.Freeze,
+		name:    "pause",
+	},
 	"profile":  &profileCmd{},
 	"publish":  &publishCmd{},
 	"remote":   &remoteCmd{},
-	"restart":  &actionCmd{shared.Restart, true, true, "restart", -1, false, false, false},
+	"restart":  &actionCmd{
+		action: shared.Restart,
+		hasTimeout: true,
+		visible: true,
+		name: "restart",
+		timeout: -1,
+	},
 	"restore":  &restoreCmd{},
 	"snapshot": &snapshotCmd{},
-	"start":    &actionCmd{shared.Start, false, true, "start", -1, false, false, false},
-	"stop":     &actionCmd{shared.Stop, true, true, "stop", -1, false, false, false},
+	"start":    &actionCmd{
+		action: shared.Start,
+		visible: true,
+		name: "start",
+	},
+	"stop":     &actionCmd{
+		action: shared.Stop,
+		hasTimeout: true,
+		visible: true,
+		name: "stop",
+		timeout: -1,
+	},
 	"version":  &versionCmd{},
 }
 

From 7b3882dda56f0ae2d519038c9ebc36ebbe00de0b Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 6 Jul 2016 16:27:09 -0600
Subject: [PATCH 0141/1193] add some additional help to `lxc pause`

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/action.go | 23 ++++++++++++++---------
 lxc/main.go   | 39 ++++++++++++++++++++-------------------
 2 files changed, 34 insertions(+), 28 deletions(-)

diff --git a/lxc/action.go b/lxc/action.go
index 6858bc9cd..8fece381f 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -10,14 +10,15 @@ import (
 )
 
 type actionCmd struct {
-	action     shared.ContainerAction
-	hasTimeout bool
-	visible    bool
-	name       string
-	timeout    int
-	force      bool
-	stateful   bool
-	stateless  bool
+	action         shared.ContainerAction
+	hasTimeout     bool
+	visible        bool
+	name           string
+	timeout        int
+	force          bool
+	stateful       bool
+	stateless      bool
+	additionalHelp string
 }
 
 func (c *actionCmd) showByDefault() bool {
@@ -25,10 +26,14 @@ func (c *actionCmd) showByDefault() bool {
 }
 
 func (c *actionCmd) usage() string {
+	if c.additionalHelp != "" {
+		c.additionalHelp = fmt.Sprintf("\n\n%s", c.additionalHelp)
+	}
+
 	return fmt.Sprintf(i18n.G(
 		`Changes state of one or more containers to %s.
 
-lxc %s <name> [<name>...]`), c.name, c.name)
+lxc %s <name> [<name>...]%s`), c.name, c.name, c.additionalHelp)
 }
 
 func (c *actionCmd) flags() {
diff --git a/lxc/main.go b/lxc/main.go
index a7fe8ffe4..a049f7fa8 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -171,34 +171,35 @@ var commands = map[string]command{
 	"monitor": &monitorCmd{},
 	"move":    &moveCmd{},
 	"pause": &actionCmd{
-		action:  shared.Freeze,
-		name:    "pause",
+		action:         shared.Freeze,
+		name:           "pause",
+		additionalHelp: i18n.G("The opposite of `lxc pause` is `lxc start`."),
 	},
-	"profile":  &profileCmd{},
-	"publish":  &publishCmd{},
-	"remote":   &remoteCmd{},
-	"restart":  &actionCmd{
-		action: shared.Restart,
+	"profile": &profileCmd{},
+	"publish": &publishCmd{},
+	"remote":  &remoteCmd{},
+	"restart": &actionCmd{
+		action:     shared.Restart,
 		hasTimeout: true,
-		visible: true,
-		name: "restart",
-		timeout: -1,
+		visible:    true,
+		name:       "restart",
+		timeout:    -1,
 	},
 	"restore":  &restoreCmd{},
 	"snapshot": &snapshotCmd{},
-	"start":    &actionCmd{
-		action: shared.Start,
+	"start": &actionCmd{
+		action:  shared.Start,
 		visible: true,
-		name: "start",
+		name:    "start",
 	},
-	"stop":     &actionCmd{
-		action: shared.Stop,
+	"stop": &actionCmd{
+		action:     shared.Stop,
 		hasTimeout: true,
-		visible: true,
-		name: "stop",
-		timeout: -1,
+		visible:    true,
+		name:       "stop",
+		timeout:    -1,
 	},
-	"version":  &versionCmd{},
+	"version": &versionCmd{},
 }
 
 var errArgs = fmt.Errorf(i18n.G("wrong number of subcommand arguments"))

From f356a14c43fe2f079c59d06c3e54201ff04410ec Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 6 Jul 2016 19:50:35 -0600
Subject: [PATCH 0142/1193] make i18n

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 po/lxd.pot | 30 +++++++++++++++++-------------
 1 file changed, 17 insertions(+), 13 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index 2ae7b2258..2f754ba60 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-06-27 17:34-0400\n"
+        "POT-Creation-Date: 2016-08-11 11:20-0600\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -168,11 +168,11 @@ msgstr  ""
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
 
-#: lxc/action.go:28
+#: lxc/action.go:33
 #, c-format
 msgid   "Changes state of one or more containers to %s.\n"
         "\n"
-        "lxc %s <name> [<name>...]"
+        "lxc %s <name> [<name>...]%s"
 msgstr  ""
 
 #: lxc/remote.go:269
@@ -339,13 +339,13 @@ msgstr  ""
 msgid   "Fingerprint: %s"
 msgstr  ""
 
-#: lxc/finger.go:17
+#: lxc/finger.go:15
 msgid   "Fingers the LXD instance to check if it is up and working.\n"
         "\n"
         "lxc finger <remote>"
 msgstr  ""
 
-#: lxc/action.go:37 lxc/action.go:38
+#: lxc/action.go:42 lxc/action.go:43
 msgid   "Force the container to shutdown."
 msgstr  ""
 
@@ -385,7 +385,7 @@ msgstr  ""
 msgid   "Ignore aliases when determining what command to run."
 msgstr  ""
 
-#: lxc/action.go:41
+#: lxc/action.go:46
 msgid   "Ignore the container state (only for start)."
 msgstr  ""
 
@@ -709,7 +709,7 @@ msgid   "Move containers within or in between lxd instances.\n"
         "    Rename a local container.\n"
 msgstr  ""
 
-#: lxc/action.go:64
+#: lxc/action.go:69
 msgid   "Must supply container name for: "
 msgstr  ""
 
@@ -998,7 +998,7 @@ msgstr  ""
 msgid   "Stopping container failed!"
 msgstr  ""
 
-#: lxc/action.go:40
+#: lxc/action.go:45
 msgid   "Store the container state (only for stop)."
 msgstr  ""
 
@@ -1031,11 +1031,15 @@ msgstr  ""
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
 
+#: lxc/main.go:176
+msgid   "The opposite of `lxc pause` is `lxc start`."
+msgstr  ""
+
 #: lxc/publish.go:62
 msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
 
-#: lxc/action.go:36
+#: lxc/action.go:41
 msgid   "Time to wait for the container before killing it."
 msgstr  ""
 
@@ -1052,7 +1056,7 @@ msgstr  ""
 msgid   "Transferring image: %d%%"
 msgstr  ""
 
-#: lxc/action.go:94 lxc/launch.go:132
+#: lxc/action.go:99 lxc/launch.go:132
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1119,7 +1123,7 @@ msgstr  ""
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
-#: lxc/action.go:90
+#: lxc/action.go:95
 msgid   "bad result type from action"
 msgstr  ""
 
@@ -1173,7 +1177,7 @@ msgstr  ""
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:245 lxc/main.go:249
+#: lxc/main.go:265 lxc/main.go:269
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
@@ -1215,7 +1219,7 @@ msgstr  ""
 msgid   "unreachable return reached"
 msgstr  ""
 
-#: lxc/main.go:185
+#: lxc/main.go:205
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 

From 2ec2ec160ad5ae8b06fb35590f63b1264373a66e Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 7 Jul 2016 14:59:25 +0000
Subject: [PATCH 0143/1193] migration: fix tempdir handling

If we fail to create the tempdir, we don't need to delete it. We *do*
however, need to inform the main thread that the restore failed.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index f39ca24cb..48a14dd84 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -539,7 +539,7 @@ func (c *migrationSink) do() error {
 			var err error
 			imagesDir, err = ioutil.TempDir("", "lxd_restore_")
 			if err != nil {
-				os.RemoveAll(imagesDir)
+				restore <- err
 				return
 			}
 

From 5978167b3732df1026e6a52e29daadcb0cebdde2 Mon Sep 17 00:00:00 2001
From: Travis McLane <tmclane at gmail.com>
Date: Thu, 7 Jul 2016 11:54:41 -0500
Subject: [PATCH 0144/1193] enable IPV6 name resolution

Signed-off-by: Travis McLane <tmclane at gmail.com>
---
 lxd-bridge/lxd-bridge | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
index 2778509ba..f3c5f00a2 100755
--- a/lxd-bridge/lxd-bridge
+++ b/lxd-bridge/lxd-bridge
@@ -133,7 +133,7 @@ start() {
         if [ "${LXD_IPV6_NAT}" = "true" ]; then
             ip6tables "${use_iptables_lock}" -t nat -A POSTROUTING -s "${LXD_IPV6_NETWORK}" ! -d "${LXD_IPV6_NETWORK}" -j MASQUERADE
         fi
-        LXD_IPV6_ARG="--dhcp-range=${LXD_IPV6_ADDR},ra-only --listen-address ${LXD_IPV6_ADDR}"
+        LXD_IPV6_ARG="--dhcp-range=${LXD_IPV6_ADDR},ra-stateless,ra-names --listen-address ${LXD_IPV6_ADDR}"
     fi
 
     iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 67 -j ACCEPT

From 79ddded6fc77404c9566f6235a2ce32ab79fd812 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 8 Jul 2016 15:43:20 -0400
Subject: [PATCH 0145/1193] Don't share http client with go routines
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2186

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/lxc/list.go b/lxc/list.go
index e0e809ff1..d1639f455 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -209,6 +209,11 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 	for i := 0; i < threads; i++ {
 		cStatesWg.Add(1)
 		go func() {
+			d, err := lxd.NewClient(&d.Config, d.Name)
+			if err != nil {
+				return
+			}
+
 			for {
 				cName, more := <-cStatesQueue
 				if !more {
@@ -229,6 +234,11 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 
 		cSnapshotsWg.Add(1)
 		go func() {
+			d, err := lxd.NewClient(&d.Config, d.Name)
+			if err != nil {
+				return
+			}
+
 			for {
 				cName, more := <-cSnapshotsQueue
 				if !more {

From 98a41d86f0099548438c65a66f6758632de07cc5 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 7 Jul 2016 22:28:32 +0000
Subject: [PATCH 0146/1193] make client.websocket a public API

We'll use this in the next patch.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/client.go b/client.go
index 622ce06a3..439e6832b 100644
--- a/client.go
+++ b/client.go
@@ -469,7 +469,7 @@ func (c *Client) delete(base string, args interface{}, rtype ResponseType) (*Res
 	return HoistResponse(resp, rtype)
 }
 
-func (c *Client) websocket(operation string, secret string) (*websocket.Conn, error) {
+func (c *Client) Websocket(operation string, secret string) (*websocket.Conn, error) {
 	query := url.Values{"secret": []string{secret}}
 	url := c.BaseWSURL + path.Join(operation, "websocket") + "?" + query.Encode()
 	return WebsocketDial(c.websocketDialer, url)
@@ -1496,7 +1496,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 	if controlHandler != nil {
 		var control *websocket.Conn
 		if wsControl, ok := fds["control"]; ok {
-			control, err = c.websocket(resp.Operation, wsControl.(string))
+			control, err = c.Websocket(resp.Operation, wsControl.(string))
 			if err != nil {
 				return -1, err
 			}
@@ -1505,7 +1505,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 			go controlHandler(c, control)
 		}
 
-		conn, err := c.websocket(resp.Operation, fds["0"].(string))
+		conn, err := c.Websocket(resp.Operation, fds["0"].(string))
 		if err != nil {
 			return -1, err
 		}
@@ -1518,7 +1518,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		conns := make([]*websocket.Conn, 3)
 		dones := make([]chan bool, 3)
 
-		conns[0], err = c.websocket(resp.Operation, fds[strconv.Itoa(0)].(string))
+		conns[0], err = c.Websocket(resp.Operation, fds[strconv.Itoa(0)].(string))
 		if err != nil {
 			return -1, err
 		}
@@ -1528,7 +1528,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 
 		outputs := []io.WriteCloser{stdout, stderr}
 		for i := 1; i < 3; i++ {
-			conns[i], err = c.websocket(resp.Operation, fds[strconv.Itoa(i)].(string))
+			conns[i], err = c.Websocket(resp.Operation, fds[strconv.Itoa(i)].(string))
 			if err != nil {
 				return -1, err
 			}

From d854dd523dc6739b7a299bdda9632c4ff7a24405 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 8 Jul 2016 15:58:19 -0400
Subject: [PATCH 0147/1193] Add comment to iptables rules
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2125

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd-bridge/lxd-bridge | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
index f3c5f00a2..67a7829c8 100755
--- a/lxd-bridge/lxd-bridge
+++ b/lxd-bridge/lxd-bridge
@@ -113,7 +113,7 @@ start() {
     if [ -n "${LXD_IPV4_ADDR}" ] && [ -n "${LXD_IPV4_NETMASK}" ] && [ -n "${LXD_IPV4_NETWORK}" ]; then
         echo 1 > /proc/sys/net/ipv4/ip_forward
         if [ "${LXD_IPV4_NAT}" = "true" ]; then
-            iptables "${use_iptables_lock}" -t nat -A POSTROUTING -s "${LXD_IPV4_NETWORK}" ! -d "${LXD_IPV4_NETWORK}" -j MASQUERADE
+            iptables "${use_iptables_lock}" -t nat -A POSTROUTING -s "${LXD_IPV4_NETWORK}" ! -d "${LXD_IPV4_NETWORK}" -j MASQUERADE -m comment --comment "managed by lxd-bridge"
         fi
         LXD_IPV4_ARG="--listen-address ${LXD_IPV4_ADDR} --dhcp-range ${LXD_IPV4_DHCP_RANGE} --dhcp-lease-max=${LXD_IPV4_DHCP_MAX}"
     fi
@@ -131,18 +131,18 @@ start() {
 
         ip -6 addr add dev "${LXD_BRIDGE}" "${LXD_IPV6_ADDR}/${LXD_IPV6_MASK}"
         if [ "${LXD_IPV6_NAT}" = "true" ]; then
-            ip6tables "${use_iptables_lock}" -t nat -A POSTROUTING -s "${LXD_IPV6_NETWORK}" ! -d "${LXD_IPV6_NETWORK}" -j MASQUERADE
+            ip6tables "${use_iptables_lock}" -t nat -A POSTROUTING -s "${LXD_IPV6_NETWORK}" ! -d "${LXD_IPV6_NETWORK}" -j MASQUERADE -m comment --comment "managed by lxd-bridge"
         fi
         LXD_IPV6_ARG="--dhcp-range=${LXD_IPV6_ADDR},ra-stateless,ra-names --listen-address ${LXD_IPV6_ADDR}"
     fi
 
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 67 -j ACCEPT
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 67 -j ACCEPT
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 53 -j ACCEPT
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 53 -j ACCEPT
-    iptables "${use_iptables_lock}" -I FORWARD -i "${LXD_BRIDGE}" -j ACCEPT
-    iptables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT
-    iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill
+    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+    iptables "${use_iptables_lock}" -I FORWARD -i "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
+    iptables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
+    iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
 
     LXD_DOMAIN_ARG=""
     if [ -n "${LXD_DOMAIN}" ]; then
@@ -182,20 +182,20 @@ stop() {
 
     if [ -d /sys/class/net/${LXD_BRIDGE} ]; then
         ifdown ${LXD_BRIDGE}
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 67 -j ACCEPT
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 67 -j ACCEPT
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 53 -j ACCEPT
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 53 -j ACCEPT
-        iptables ${use_iptables_lock} -D FORWARD -i ${LXD_BRIDGE} -j ACCEPT
-        iptables ${use_iptables_lock} -D FORWARD -o ${LXD_BRIDGE} -j ACCEPT
-        iptables ${use_iptables_lock} -t mangle -D POSTROUTING -o ${LXD_BRIDGE} -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill
+        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        iptables ${use_iptables_lock} -D FORWARD -i ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        iptables ${use_iptables_lock} -D FORWARD -o ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        iptables ${use_iptables_lock} -t mangle -D POSTROUTING -o ${LXD_BRIDGE} -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
 
         if [ -n "${LXD_IPV4_NETWORK}" ] && [ "${LXD_IPV4_NAT}" = "true" ]; then
-            iptables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV4_NETWORK} ! -d ${LXD_IPV4_NETWORK} -j MASQUERADE
+            iptables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV4_NETWORK} ! -d ${LXD_IPV4_NETWORK} -j MASQUERADE -m comment --comment "managed by lxd-bridge"
         fi
 
         if [ "${HAS_IPV6}" = "true" ] && [ -n "${LXD_IPV6_NETWORK}" ] && [ "${LXD_IPV6_NAT}" = "true" ]; then
-            ip6tables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV6_NETWORK} ! -d ${LXD_IPV6_NETWORK} -j MASQUERADE
+            ip6tables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV6_NETWORK} ! -d ${LXD_IPV6_NETWORK} -j MASQUERADE -m comment --comment "managed by lxd-bridge"
         fi
 
         if [ -e "${varrun}/dnsmasq.pid" ]; then

From d921959184649b71dce304c802efe2021d102e7a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 8 Jul 2016 16:18:32 -0400
Subject: [PATCH 0148/1193] Add sanity checks for common problems
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Beats having to read the LXC log.

Closes #2190

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7e605d9da..a19198b07 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -953,6 +953,24 @@ func (c *containerLXC) startCommon() (string, error) {
 		return "", fmt.Errorf("The container is already running")
 	}
 
+	// Sanity checks for devices
+	for name, m := range c.expandedDevices {
+		switch m["type"] {
+		case "disk":
+			if m["source"] != "" && !shared.PathExists(m["source"]) {
+				return "", fmt.Errorf("Missing source '%s' for disk '%s'", m["source"], name)
+			}
+		case "nic":
+			if m["parent"] != "" && !shared.PathExists(fmt.Sprintf("/sys/class/net/%s", m["parent"])) {
+				return "", fmt.Errorf("Missing parent '%s' for nic '%s'", m["parent"], name)
+			}
+		case "unix-char", "unix-block":
+			if m["path"] != "" && m["major"] == "" && m["minor"] == "" && !shared.PathExists(m["path"]) {
+				return "", fmt.Errorf("Missing source '%s' for device '%s'", m["path"], name)
+			}
+		}
+	}
+
 	// Load any required kernel modules
 	kernelModules := c.expandedConfig["linux.kernel_modules"]
 	if kernelModules != "" {

From 1087244d59cdf9141f052a9196973be6c0c69399 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 8 Jul 2016 16:49:57 -0400
Subject: [PATCH 0149/1193] Return more error information back to the user
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2190

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index a19198b07..f551d1d1c 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1235,6 +1235,7 @@ func (c *containerLXC) Start(stateful bool) error {
 		c.daemon.lxcpath,
 		configPath).CombinedOutput()
 
+	// Capture debug output
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
 			shared.Debugf("forkstart: %s", line)
@@ -1242,12 +1243,40 @@ func (c *containerLXC) Start(stateful bool) error {
 	}
 
 	if err != nil && !c.IsRunning() {
+		// Attempt to extract the LXC errors
+		log := ""
+		logPath := filepath.Join(c.LogPath(), "lxc.log")
+		if shared.PathExists(logPath) {
+			logContent, err := ioutil.ReadFile(logPath)
+			if err == nil {
+				for _, line := range strings.Split(string(logContent), "\n") {
+					fields := strings.Fields(line)
+					if len(fields) < 4 {
+						continue
+					}
+
+					// We only care about errors
+					if fields[2] != "ERROR" {
+						continue
+					}
+
+					// Prepend the line break
+					if len(log) == 0 {
+						log += "\n"
+					}
+
+					log += fmt.Sprintf("  %s\n", strings.Join(fields[0:], " "))
+				}
+			}
+		}
+
+		// Return the actual error
 		return fmt.Errorf(
-			"Error calling 'lxd forkstart %s %s %s': err='%v'",
+			"Error calling 'lxd forkstart %s %s %s': err='%v'%s",
 			c.name,
 			c.daemon.lxcpath,
 			filepath.Join(c.LogPath(), "lxc.conf"),
-			err)
+			err, log)
 	}
 
 	return nil

From 8696e1d1c09847438d2ebb51bbe145fa69ca6e24 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 12 Jul 2016 08:38:31 -0600
Subject: [PATCH 0150/1193] don't fail db upgrade if $LXD_DIR/containers
 doesn't exist

The user may not have actually started LXD or imported any containers, so
let's not fail in this case.

Launchpad #1602025

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/db_update.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index 73ec86731..ed376ee87 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -39,6 +39,13 @@ func dbUpdateFromV30(db *sql.DB, mockMode bool) error {
 
 	entries, err := ioutil.ReadDir(shared.VarPath("containers"))
 	if err != nil {
+		/* If the directory didn't exist before, the user had never
+		 * started containers, so we don't need to fix up permissions
+		 * on anything.
+		 */
+		if os.IsNotExist(err) {
+			return nil
+		}
 		return err
 	}
 

From bfdc6d596dcf375d0147eb6f1cd26e457f2ff8a7 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 12 Jul 2016 12:33:32 -0600
Subject: [PATCH 0151/1193] better errors when sanity checking devices

let's always include the path, so it's obvious which device is broken.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f551d1d1c..0fd24651a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3504,10 +3504,10 @@ func (c *containerLXC) createUnixDevice(name string, m shared.Device) (string, e
 		// If no major and minor are set, use those from the device on the host
 		_, major, minor, err = deviceGetAttributes(srcPath)
 		if err != nil {
-			return "", fmt.Errorf("Failed to get device attributes: %s", err)
+			return "", fmt.Errorf("Failed to get device attributes for %s: %s", m["path"], err)
 		}
 	} else if m["major"] == "" || m["minor"] == "" {
-		return "", fmt.Errorf("Both major and minor must be supplied for devices")
+		return "", fmt.Errorf("Both major and minor must be supplied for device: %s", m["path"])
 	} else {
 		major, err = strconv.Atoi(m["major"])
 		if err != nil {

From 6c2954509848237d41fbdbe5e21db815a28ae8ba Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 19 Jul 2016 09:25:50 -0600
Subject: [PATCH 0152/1193] list: fix concurrent read/write

We can't concurrently write, but we can't concurrently read either, so let's
extend the lock duration.

Closes #2183

fatal error: concurrent map read and map write

goroutine 1 [running]:
runtime.throw(0x9cd180, 0x21)
	/usr/lib/go-1.6/src/runtime/panic.go:530 +0x90 fp=0xc8200f0fa8 sp=0xc8200f0f90
runtime.mapaccess2_faststr(0x808280, 0xc8201db140, 0xc8201b43d0, 0xb, 0x0, 0xdbf920)
	/usr/lib/go-1.6/src/runtime/hashmap_fast.go:307 +0x5b fp=0xc8200f1008 sp=0xc8200f0fa8
main.(*listCmd).listContainers(0xc82000be30, 0xc8200d0700, 0xc8201e0000, 0x32, 0x50, 0xc820011a50, 0x1, 0x1, 0xc82008ff00, 0x3, ...)
	/build/lxd-ma7bPQ/lxd-2.0.2/obj-x86_64-linux-gnu/src/github.com/lxc/lxd/lxc/list.go:254 +0x651 fp=0xc8200f1750 sp=0xc8200f1008
main.(*listCmd).run(0xc82000be30, 0xc8200f2030, 0x0, 0x0, 0x0, 0x0, 0x0)
	/build/lxd-ma7bPQ/lxd-2.0.2/obj-x86_64-linux-gnu/src/github.com/lxc/lxd/lxc/list.go:401 +0x1324 fp=0xc8200f1ae8 sp=0xc8200f1750
main.run(0x0, 0x0)
	/build/lxd-ma7bPQ/lxd-2.0.2/obj-x86_64-linux-gnu/src/github.com/lxc/lxd/lxc/main.go:151 +0x1098 fp=0xc8200f1df0 sp=0xc8200f1ae8
main.main()
	/build/lxd-ma7bPQ/lxd-2.0.2/obj-x86_64-linux-gnu/src/github.com/lxc/lxd/lxc/main.go:23 +0x3a fp=0xc8200f1f40 sp=0xc8200f1df0
runtime.main()
	/usr/lib/go-1.6/src/runtime/proc.go:188 +0x2b0 fp=0xc8200f1f90 sp=0xc8200f1f40
runtime.goexit()
	/usr/lib/go-1.6/src/runtime/asm_amd64.s:1998 +0x1 fp=0xc8200f1f98 sp=0xc8200f1f90

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/list.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index d1639f455..5a165fe8f 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -259,6 +259,8 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 	}
 
 	for _, cInfo := range cinfos {
+		cStatesLock.Lock()
+		cSnapshotsLock.Lock()
 		for _, column := range columns {
 			if column.NeedsState && cInfo.IsActive() {
 				_, ok := cStates[cInfo.Name]
@@ -266,9 +268,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 					continue
 				}
 
-				cStatesLock.Lock()
 				cStates[cInfo.Name] = nil
-				cStatesLock.Unlock()
 
 				cStatesQueue <- cInfo.Name
 			}
@@ -279,13 +279,13 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 					continue
 				}
 
-				cSnapshotsLock.Lock()
 				cSnapshots[cInfo.Name] = nil
-				cSnapshotsLock.Unlock()
 
 				cSnapshotsQueue <- cInfo.Name
 			}
 		}
+		cStatesLock.Unlock()
+		cSnapshotsLock.Unlock()
 	}
 
 	close(cStatesQueue)

From f9f7e3733a95e3b0ef12b9d6fda8929f87e92d4a Mon Sep 17 00:00:00 2001
From: Elvis Espinal <elvisespinal at neoscloudllc.com>
Date: Wed, 20 Jul 2016 11:02:47 -0400
Subject: [PATCH 0153/1193] Fixed errors on api examples with curl

Testing the 1st example "talking over the socket", I got this error: curl: (35) gnutls_handshake() failed: An unexpected TLS packet was received. Because the url is pointing to the TCP and not the socket.
Testing the 2nd example "talking over TCP", I got these warnings:
      1-Warning: You can only select one HTTP request method! You asked for both POST
      2-Warning: (-d, --data) and HEAD (-I, --head).
With these changes both examples run without errors.

Signed-off-by: Elvis Espinal <elvisespinal at neoscloudllc.com>
---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 56599bf26..54ad4a27b 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ curl --unix-socket /var/lib/lxd/unix.socket \
     -H "Content-Type: application/json" \
     -X POST \
     -d @hello-ubuntu.json \
-    "https://127.0.0.1:8443/1.0/containers"
+    lxd/1.0/containers
 ```
 
 #### via TCP
@@ -46,7 +46,7 @@ TCP requires some additional configuration and is not enabled by default.
 lxc config set core.https_address "[::]:8443"
 ```
 ```bash
-curl -k -L -I \
+curl -k -L \
     --cert ~/.config/lxc/client.crt \
     --key ~/.config/lxc/client.key \
     -H "Content-Type: application/json" \

From 4438a8533e144acab1094abf03a95f27fb857074 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 19 Jul 2016 16:36:54 -0600
Subject: [PATCH 0154/1193] remove fuse device from docker profile

Closes #2213

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/db_profiles.go | 6 +-----
 lxd/db_test.go     | 2 +-
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index d97f974b6..c3a5ff192 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -130,16 +130,12 @@ func dbProfileCreateDocker(db *sql.DB) error {
 	config := map[string]string{
 		"security.nesting":     "true",
 		"linux.kernel_modules": "overlay, nf_nat"}
-	fusedev := map[string]string{
-		"path": "/dev/fuse",
-		"type": "unix-char",
-	}
 	aadisable := map[string]string{
 		"path":   "/sys/module/apparmor/parameters/enabled",
 		"type":   "disk",
 		"source": "/dev/null",
 	}
-	devices := map[string]shared.Device{"fuse": fusedev, "aadisable": aadisable}
+	devices := map[string]shared.Device{"aadisable": aadisable}
 
 	_, err = dbProfileCreate(db, "docker", "Profile supporting docker in containers", config, devices)
 	return err
diff --git a/lxd/db_test.go b/lxd/db_test.go
index 5a4d2a51c..d68f5996a 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -22,7 +22,7 @@ const DB_FIXTURES string = `
     INSERT INTO images_properties (image_id, type, key, value) VALUES (1, 0, 'thekey', 'some value');
     INSERT INTO profiles_config (profile_id, key, value) VALUES (3, 'thekey', 'thevalue');
     INSERT INTO profiles_devices (profile_id, name, type) VALUES (3, 'devicename', 1);
-    INSERT INTO profiles_devices_config (profile_device_id, key, value) VALUES (4, 'devicekey', 'devicevalue');
+    INSERT INTO profiles_devices_config (profile_device_id, key, value) VALUES (3, 'devicekey', 'devicevalue');
     `
 
 //  This Helper will initialize a test in-memory DB.

From 36f04bf6386982e02e20f84e97f9bc050e1f7db1 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Wed, 20 Jul 2016 10:18:48 -0700
Subject: [PATCH 0155/1193] Remove subvolume in zfs.ImageCreate error flow

Provide more context in the error message when unpack fails.

Fixes #2194

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 lxd/images.go      |  9 +++++++--
 lxd/storage_zfs.go | 21 +++++++++++++++------
 2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 76914e2c7..a5d6b9438 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -104,9 +104,14 @@ func unpack(file string, path string) error {
 
 	output, err := exec.Command(command, args...).CombinedOutput()
 	if err != nil {
+		co := string(output)
 		shared.Debugf("Unpacking failed")
-		shared.Debugf(string(output))
-		return err
+		shared.Debugf(co)
+
+		// Truncate the output to a single line for inclusion in the error
+		// message.  The first line isn't guaranteed to pinpoint the issue,
+		// but it's better than nothing and better than a multi-line message.
+		return fmt.Errorf("Unpack failed, %s.  %s", err, strings.SplitN(co, "\n", 2)[0])
 	}
 
 	return nil
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index adb1caf21..31e3f936e 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -603,22 +603,31 @@ func (s *storageZfs) ImageCreate(fingerprint string) error {
 		return err
 	}
 
+	cleanup := func(err error) error {
+		if zerr := s.zfsDestroy(fs); zerr != nil {
+			err = fmt.Errorf("%s  During cleanup: %s", err, zerr)
+		}
+		if shared.PathExists(subvol) {
+			if oserr := os.Remove(subvol); oserr != nil {
+				err = fmt.Errorf("%s  During cleanup: Failed to remove sub-volume %s, %s", err, subvol, oserr)
+			}
+		}
+		return err
+	}
+
 	err = unpackImage(imagePath, subvol)
 	if err != nil {
-		s.zfsDestroy(fs)
-		return err
+		return cleanup(err)
 	}
 
 	err = s.zfsSet(fs, "readonly", "on")
 	if err != nil {
-		s.zfsDestroy(fs)
-		return err
+		return cleanup(err)
 	}
 
 	err = s.zfsSnapshotCreate(fs, "readonly")
 	if err != nil {
-		s.zfsDestroy(fs)
-		return err
+		return cleanup(err)
 	}
 
 	return nil

From 8e749ee54e03b146c48ba3940ebe659165194876 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 22 Jul 2016 15:39:58 -0400
Subject: [PATCH 0156/1193] Add "lxc profile unset" to help message
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2227

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/profile.go |  1 +
 po/lxd.pot     | 41 +++++++++++++++++++++--------------------
 2 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/lxc/profile.go b/lxc/profile.go
index 0c6cdd13c..a1e7aa5e9 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -54,6 +54,7 @@ lxc profile create <profile>                   Create a profile.
 lxc profile copy <profile> <remote>            Copy the profile to the specified remote.
 lxc profile get <profile> <key>                Get profile configuration.
 lxc profile set <profile> <key> <value>        Set profile configuration.
+lxc profile unset <profile> <key>              Unset profile configuration.
 lxc profile delete <profile>                   Delete a profile.
 lxc profile edit <profile>
     Edit profile, either by launching external editor or reading STDIN.
diff --git a/po/lxd.pot b/po/lxd.pot
index 2f754ba60..f35782ce3 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-08-11 11:20-0600\n"
+        "POT-Creation-Date: 2016-08-11 11:22-0600\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -86,7 +86,7 @@ msgstr  ""
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/profile.go:226
+#: lxc/profile.go:227
 msgid   "(none)"
 msgstr  ""
 
@@ -98,7 +98,7 @@ msgstr  ""
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:378
+#: lxc/list.go:388
 msgid   "ARCHITECTURE"
 msgstr  ""
 
@@ -145,7 +145,7 @@ msgstr  ""
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:379
+#: lxc/list.go:389
 msgid   "CREATED AT"
 msgstr  ""
 
@@ -159,7 +159,7 @@ msgstr  ""
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:343
+#: lxc/profile.go:344
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
@@ -187,7 +187,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:687 lxc/profile.go:190
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:687 lxc/profile.go:191
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -281,7 +281,7 @@ msgstr  ""
 msgid   "Device %s removed from %s"
 msgstr  ""
 
-#: lxc/list.go:462
+#: lxc/list.go:472
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -365,11 +365,11 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:376
+#: lxc/list.go:386
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:377
+#: lxc/list.go:387
 msgid   "IPV6"
 msgstr  ""
 
@@ -523,6 +523,7 @@ msgid   "Manage configuration profiles.\n"
         "lxc profile copy <profile> <remote>            Copy the profile to the specified remote.\n"
         "lxc profile get <profile> <key>                Get profile configuration.\n"
         "lxc profile set <profile> <key> <value>        Set profile configuration.\n"
+        "lxc profile unset <profile> <key>              Unset profile configuration.\n"
         "lxc profile delete <profile>                   Delete a profile.\n"
         "lxc profile edit <profile>\n"
         "    Edit profile, either by launching external editor or reading STDIN.\n"
@@ -713,7 +714,7 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:380 lxc/remote.go:353
+#: lxc/list.go:390 lxc/remote.go:353
 msgid   "NAME"
 msgstr  ""
 
@@ -759,15 +760,15 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:464
+#: lxc/list.go:474
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:381
+#: lxc/list.go:391
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:382
+#: lxc/list.go:392
 msgid   "PROFILES"
 msgstr  ""
 
@@ -810,7 +811,7 @@ msgid   "Presents details on how to use LXD.\n"
         "lxd help [--all]"
 msgstr  ""
 
-#: lxc/profile.go:191
+#: lxc/profile.go:192
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
@@ -841,17 +842,17 @@ msgstr  ""
 msgid   "Processes: %d"
 msgstr  ""
 
-#: lxc/profile.go:228
+#: lxc/profile.go:229
 #, c-format
 msgid   "Profile %s applied to %s"
 msgstr  ""
 
-#: lxc/profile.go:142
+#: lxc/profile.go:143
 #, c-format
 msgid   "Profile %s created"
 msgstr  ""
 
-#: lxc/profile.go:212
+#: lxc/profile.go:213
 #, c-format
 msgid   "Profile %s deleted"
 msgstr  ""
@@ -910,11 +911,11 @@ msgstr  ""
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:383
+#: lxc/list.go:393
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:384
+#: lxc/list.go:394
 msgid   "STATE"
 msgstr  ""
 
@@ -1010,7 +1011,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:385
+#: lxc/list.go:395
 msgid   "TYPE"
 msgstr  ""
 

From 695c1a906d339e21393e2f41bf16f86db562da86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 22 Jul 2016 16:03:01 -0400
Subject: [PATCH 0157/1193] Fix limits.disk.priority when set to 0
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2230

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0fd24651a..38cb02a91 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -636,7 +636,13 @@ func (c *containerLXC) initLXC() error {
 				return err
 			}
 
-			err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.weight", fmt.Sprintf("%d", priorityInt*100))
+			// Minimum valid value is 10
+			priority := priorityInt * 100
+			if priority == 0 {
+				priority = 10
+			}
+
+			err = lxcSetConfigItem(cc, "lxc.cgroup.blkio.weight", fmt.Sprintf("%d", priority))
 			if err != nil {
 				return err
 			}
@@ -2231,7 +2237,13 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 					}
 				}
 
-				err = c.CGroupSet("blkio.weight", fmt.Sprintf("%d", priorityInt*100))
+				// Minimum valid value is 10
+				priority := priorityInt * 100
+				if priority == 0 {
+					priority = 10
+				}
+
+				err = c.CGroupSet("blkio.weight", fmt.Sprintf("%d", priority))
 				if err != nil {
 					return err
 				}

From ff6cf194258ac3c54c39cacd05da83f80ee5c70a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 22 Jul 2016 16:16:12 -0400
Subject: [PATCH 0158/1193] Fix simplestreams size reporting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This fixes reporting of image size when an image rootfs is available
both as .squashfs and .tar.xz.

Rather than add the size of all files, only add the size of the files
we'll actually use.

Closes #2223

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index 973b9bb6a..d2bab6e15 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -120,7 +120,6 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 				}
 				found += 1
 
-				size += item.Size
 				if fingerprint == "" {
 					if item.LXDHashSha256SquashFs != "" {
 						fingerprint = item.LXDHashSha256SquashFs
@@ -138,6 +137,8 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 					filename = fields[len(fields)-1]
 					metaPath = item.Path
 					metaHash = item.HashSha256
+
+					size += item.Size
 				}
 
 				if rootfsPath == "" || rootfsHash == "" {
@@ -150,6 +151,8 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 						rootfsPath = item.Path
 						rootfsHash = item.HashSha256
 					}
+
+					size += item.Size
 				}
 			}
 

From 75be05a61cab4f7bca0857170ca75023c49bfc4d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 22 Jul 2016 16:25:04 -0400
Subject: [PATCH 0159/1193] Better handle missing or invalid device types
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2210

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lxd/container.go b/lxd/container.go
index 166e53d04..ad5747f31 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -244,7 +244,15 @@ func containerValidDevices(devices shared.Devices, profile bool, expanded bool)
 	}
 
 	// Check each device individually
-	for _, m := range devices {
+	for name, m := range devices {
+		if m["type"] == "" {
+			return fmt.Errorf("Missing device type for device '%s'", name)
+		}
+
+		if !shared.StringInSlice(m["type"], []string{"none", "nic", "disk", "unix-char", "unix-block"}) {
+			return fmt.Errorf("Invalid device type for device '%s'", name)
+		}
+
 		for k, _ := range m {
 			if !containerValidDeviceConfigKey(m["type"], k) {
 				return fmt.Errorf("Invalid device configuration key for %s: %s", m["type"], k)

From 4b430d13bf60ce920de689451d6422750bc59ffc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 22 Jul 2016 16:34:13 -0400
Subject: [PATCH 0160/1193] Fix nic hotplug with openvswitch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2106

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 38cb02a91..4be5798ff 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3744,10 +3744,18 @@ func (c *containerLXC) createNetworkDevice(name string, m shared.Device) (string
 		}
 
 		if m["nictype"] == "bridged" {
-			err = exec.Command("ip", "link", "set", n1, "master", m["parent"]).Run()
-			if err != nil {
-				deviceRemoveInterface(n2)
-				return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
+			if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", m["parent"])) {
+				err = exec.Command("ip", "link", "set", n1, "master", m["parent"]).Run()
+				if err != nil {
+					deviceRemoveInterface(n2)
+					return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
+				}
+			} else {
+				err = exec.Command("ovs-vsctl", "add-port", m["parent"], n1).Run()
+				if err != nil {
+					deviceRemoveInterface(n2)
+					return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
+				}
 			}
 		}
 

From 773d357b3d35d14b15669932f9a02f9e9605997e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 25 Jul 2016 23:05:10 +0200
Subject: [PATCH 0161/1193] Document config_get in pongo templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/image-handling.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/image-handling.md b/doc/image-handling.md
index 29df7f8c6..34bb0871d 100644
--- a/doc/image-handling.md
+++ b/doc/image-handling.md
@@ -128,3 +128,6 @@ The "create\_only" key can be set to have LXD only only create missing files but
 As a general rule, you should never template a file which is owned by a
 package or is otherwise expected to be overwritten by normal operation
 of the container.
+
+For convenience the following functions are exported to pongo templates:
+ - config\_get("user.foo", "bar") => Returns the value of "user.foo" or "bar" if unset.

From aa1fedf74bc23662c4c0a0c77ee22772c6c1c3b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Jul 2016 14:16:23 +0200
Subject: [PATCH 0162/1193] simplestreams: Use the hashes in the right order
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2239

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams.go | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index d2bab6e15..c71c45a3c 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -123,11 +123,9 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 				if fingerprint == "" {
 					if item.LXDHashSha256SquashFs != "" {
 						fingerprint = item.LXDHashSha256SquashFs
-					}
-					if item.LXDHashSha256RootXz != "" {
+					} else if item.LXDHashSha256RootXz != "" {
 						fingerprint = item.LXDHashSha256RootXz
-					}
-					if item.LXDHashSha256 != "" {
+					} else if item.LXDHashSha256 != "" {
 						fingerprint = item.LXDHashSha256
 					}
 				}

From 3bc3f08f675bc4912addcd6ee1335798017bc708 Mon Sep 17 00:00:00 2001
From: diliprenkila <dilip.renkila278 at gmail.com>
Date: Mon, 25 Jul 2016 22:42:28 +0200
Subject: [PATCH 0163/1193] Added command to install squashfs-tools in
 README.md

Signed-off-by: Dilip Renkila <dilip.renkila278 at gmail.com>
---
 README.md                      | 2 +-
 scripts/vagrant/install-lxd.sh | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 54ad4a27b..05db5ebb5 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,7 @@ backend. Installing these tools adds a bit to initramfs and may slow down your
 host boot, but are needed if you'd like to use a particular backend:
 
     sudo apt-get install lvm2 thin-provisioning-tools
-    sudo apt-get install btrfs-tools
+    sudo apt-get install btrfs-tools squashfs-tools
 
 To run the testsuite, you'll also need:
 
diff --git a/scripts/vagrant/install-lxd.sh b/scripts/vagrant/install-lxd.sh
index 9bb326ac5..7e59f788f 100644
--- a/scripts/vagrant/install-lxd.sh
+++ b/scripts/vagrant/install-lxd.sh
@@ -9,7 +9,7 @@ sudo apt-get -y install xz-utils tar acl curl gettext \
 
 # install build dependencies
 sudo apt-get -y install lxc lxc-dev mercurial git pkg-config \
-    protobuf-compiler golang-goprotobuf-dev
+    protobuf-compiler golang-goprotobuf-dev squashfs-tools
 
 # setup env 
 [ -e uid_gid_setup ] || \

From 144fe9f7fcdcd9d8d03a2b4a0147df65518d273f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Jul 2016 14:35:57 +0200
Subject: [PATCH 0164/1193] README: shuffle packages a bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Move squashfs-tools to build dependencies.
 - Drop the protobuf stuff that's not needed to build lxd.
 - Replace lxc with liblxc1.
 - Add missing rsync dependency.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 05db5ebb5..44d09f188 100644
--- a/README.md
+++ b/README.md
@@ -83,14 +83,14 @@ via the LXD PPA:
     sudo apt-get install software-properties-common
     sudo add-apt-repository ppa:ubuntu-lxc/lxd-git-master
     sudo apt-get update
-    sudo apt-get install golang lxc lxc-dev mercurial git pkg-config protobuf-compiler golang-goprotobuf-dev xz-utils tar acl make
+    sudo apt-get install acl git golang liblxc1 lxc-dev make pkg-config rsync squashfs-tools tar xz-utils
 
 There are a few storage backends for LXD besides the default "directory"
 backend. Installing these tools adds a bit to initramfs and may slow down your
 host boot, but are needed if you'd like to use a particular backend:
 
     sudo apt-get install lvm2 thin-provisioning-tools
-    sudo apt-get install btrfs-tools squashfs-tools
+    sudo apt-get install btrfs-tools
 
 To run the testsuite, you'll also need:
 

From 90601b5023d2b6433f47fa7a9ac69a261cc964ba Mon Sep 17 00:00:00 2001
From: Dolph Mathews <dolph.mathews at gmail.com>
Date: Mon, 18 Jul 2016 13:23:02 -0700
Subject: [PATCH 0165/1193] Fix spelling: permisson -> permission

Signed-off-by: Dolph Mathews <dolph.mathews at gmail.com>
---
 lxc/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/main.go b/lxc/main.go
index a049f7fa8..0b9fd572b 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -28,7 +28,7 @@ func main() {
 		case syscall.ECONNREFUSED:
 			msg = i18n.G("Connection refused; is LXD running?")
 		case syscall.EACCES:
-			msg = i18n.G("Permisson denied, are you in the lxd group?")
+			msg = i18n.G("Permission denied, are you in the lxd group?")
 		}
 
 		fmt.Fprintln(os.Stderr, fmt.Sprintf("%s", msg))

From 4660c9b8b3b7e42ae636a24c69a2c480c51a60dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Jul 2016 14:42:32 +0200
Subject: [PATCH 0166/1193] Update PO template for previous commit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2211

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/lxd.pot | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index f35782ce3..3417dcb21 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-08-11 11:22-0600\n"
+        "POT-Creation-Date: 2016-08-11 11:25-0600\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -797,7 +797,7 @@ msgid   "Path to an alternate server directory."
 msgstr  ""
 
 #: lxc/main.go:31
-msgid   "Permisson denied, are you in the lxd group?"
+msgid   "Permission denied, are you in the lxd group?"
 msgstr  ""
 
 #: lxc/info.go:103

From f859c58ca081863ec0762ddeaa784c6bbe4ceebb Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Thu, 14 Jul 2016 12:36:00 +0300
Subject: [PATCH 0167/1193] Make --version option visible

Signed-off-by: anatoly techtonik techtonik at gmail.com
---
 lxc/help.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxc/help.go b/lxc/help.go
index 15672a53c..678e218ee 100644
--- a/lxc/help.go
+++ b/lxc/help.go
@@ -64,6 +64,7 @@ func (c *helpCmd) run(_ *lxd.Config, args []string) error {
 		fmt.Println("  --all              " + i18n.G("Print less common commands."))
 		fmt.Println("  --debug            " + i18n.G("Print debug information."))
 		fmt.Println("  --verbose          " + i18n.G("Print verbose information."))
+		fmt.Println("  --version          " + i18n.G("Show client version."))
 		fmt.Println()
 		fmt.Println(i18n.G("Environment:"))
 		fmt.Println("  LXD_CONF           " + i18n.G("Path to an alternate client configuration directory."))

From 50efd90956ab967f73ddc4bddcadb16e247b4051 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 26 Jul 2016 14:46:37 +0200
Subject: [PATCH 0168/1193] Update PO template for previous commit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2171

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/lxd.pot | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index 3417dcb21..391a1202a 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -297,7 +297,7 @@ msgstr  ""
 msgid   "Enables verbose mode."
 msgstr  ""
 
-#: lxc/help.go:68
+#: lxc/help.go:69
 msgid   "Environment:"
 msgstr  ""
 
@@ -678,7 +678,7 @@ msgstr  ""
 msgid   "Memory (peak)"
 msgstr  ""
 
-#: lxc/help.go:86
+#: lxc/help.go:87
 msgid   "Missing summary."
 msgstr  ""
 
@@ -788,11 +788,11 @@ msgstr  ""
 msgid   "Packets sent"
 msgstr  ""
 
-#: lxc/help.go:69
+#: lxc/help.go:70
 msgid   "Path to an alternate client configuration directory."
 msgstr  ""
 
-#: lxc/help.go:70
+#: lxc/help.go:71
 msgid   "Path to an alternate server directory."
 msgstr  ""
 
@@ -964,6 +964,10 @@ msgstr  ""
 msgid   "Show all commands (not just interesting ones)"
 msgstr  ""
 
+#: lxc/help.go:67
+msgid   "Show client version."
+msgstr  ""
+
 #: lxc/info.go:36
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""

From 2b96c49a34e1bd58a714b92e410f178c732b5b91 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 28 Jul 2016 22:56:48 +0200
Subject: [PATCH 0169/1193] Fix typo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 config/bash/lxd-client | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/bash/lxd-client b/config/bash/lxd-client
index ac14dea3e..21bfd4f8e 100644
--- a/config/bash/lxd-client
+++ b/config/bash/lxd-client
@@ -37,7 +37,7 @@ _have lxc && {
       list move profile publish remote restart restore snapshot start stop \
       version"
 
-    global_keys="core.https_address core.https_allowd_origin \
+    global_keys="core.https_address core.https_allowed_origin \
       core.https_allowed_methods core.https_allowed_headers core.proxy_https \
       core.proxy_http core.proxy_ignore_host core.trust_password \
       storage.lvm_vg_name storage.lvm_thinpool_name storage.lvm_fstype \

From f5c39f9c46db2349663814a03d761336d75373dc Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 1 Aug 2016 12:30:54 -0600
Subject: [PATCH 0170/1193] remember the return code in the non
 wait-for-websocket case

Closes #2243

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_exec.go | 9 ++++++++-
 test/suites/basic.sh  | 4 ++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 71aee790d..57b310bb7 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -14,6 +14,8 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+
+	log "gopkg.in/inconshreveable/log15.v2"
 )
 
 type commandPostContent struct {
@@ -329,7 +331,12 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		}
 		defer nullDev.Close()
 
-		_, cmdErr := c.Exec(post.Command, env, nil, nil, nil)
+		cmdResult, cmdErr := c.Exec(post.Command, env, nil, nil, nil)
+		metadata := shared.Jmap{"return": cmdResult}
+		err = op.UpdateMetadata(metadata)
+		if err != nil {
+			shared.Log.Error("error updating metadata for cmd", log.Ctx{"err": err, "cmd": post.Command})
+		}
 		return cmdErr
 	}
 
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index dfc1ac04f..fa2a1d54f 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -229,6 +229,10 @@ test_basic_usage() {
   lxc exec --env BEST_BAND=meshuggah foo env | grep meshuggah
   lxc exec foo ip link show | grep eth0
 
+  # check that we can get the return code for a non- wait-for-websocket exec
+  op=$(my_curl -X POST "https://${LXD_ADDR}/1.0/containers/foo/exec" -d '{"command": ["sleep", "1"], "environment": {}, "wait-for-websocket": false, "interactive": false}' | jq -r .operation)
+  [ "$(my_curl "https://${LXD_ADDR}${op}/wait" | jq -r .metadata.metadata.return)" != "null" ]
+
   # test file transfer
   echo abc > "${LXD_DIR}/in"
 

From c0e2c9ce8a4dd870c8c5a86d2655735c4abb6226 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 2 Aug 2016 08:58:00 -0600
Subject: [PATCH 0171/1193] add /dev/net/tun and /dev/fuse to docs

we supply these devices now, so let's document it

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 doc/configuration.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/doc/configuration.md b/doc/configuration.md
index cb605fafa..7fd3ea685 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -136,6 +136,8 @@ Those includes:
  - /dev/tty (character device)
  - /dev/random (character device)
  - /dev/urandom (character device)
+ - /dev/net/tun (character device)
+ - /dev/fuse (character device)
  - lo (network interface)
 
 Anything else has to be defined in the container configuration or in one

From 9e953f67496c1e1efc6d61ff67634da50e9cf881 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 2 Aug 2016 13:06:45 -0600
Subject: [PATCH 0172/1193] remove unused "name" argument from
 {create,remove}UnixDevice

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 4be5798ff..706df7d67 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1079,7 +1079,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	for k, m := range c.expandedDevices {
 		if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
 			// Unix device
-			devPath, err := c.createUnixDevice(k, m)
+			devPath, err := c.createUnixDevice(m)
 			if err != nil {
 				return "", err
 			}
@@ -2409,7 +2409,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		// Live update the devices
 		for k, m := range removeDevices {
 			if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
-				err = c.removeUnixDevice(k, m)
+				err = c.removeUnixDevice(m)
 				if err != nil {
 					return err
 				}
@@ -3501,7 +3501,7 @@ func (c *containerLXC) removeMount(mount string) error {
 }
 
 // Unix devices handling
-func (c *containerLXC) createUnixDevice(name string, m shared.Device) (string, error) {
+func (c *containerLXC) createUnixDevice(m shared.Device) (string, error) {
 	var err error
 	var major, minor int
 
@@ -3613,7 +3613,7 @@ func (c *containerLXC) insertUnixDevice(name string, m shared.Device) error {
 	}
 
 	// Create the device on the host
-	devPath, err := c.createUnixDevice(name, m)
+	devPath, err := c.createUnixDevice(m)
 	if err != nil {
 		return fmt.Errorf("Failed to setup device: %s", err)
 	}
@@ -3638,7 +3638,7 @@ func (c *containerLXC) insertUnixDevice(name string, m shared.Device) error {
 	return nil
 }
 
-func (c *containerLXC) removeUnixDevice(name string, m shared.Device) error {
+func (c *containerLXC) removeUnixDevice(m shared.Device) error {
 	// Check that the container is running
 	pid := c.InitPID()
 	if pid == -1 {

From 39cb22be8629883477bec76483a94ca520bd4024 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 2 Aug 2016 17:30:28 -0600
Subject: [PATCH 0173/1193] actually handle containers list error

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/devices.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/devices.go b/lxd/devices.go
index 529450d7a..ddbcc63e7 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -203,6 +203,10 @@ func deviceTaskBalance(d *Daemon) {
 
 	// Iterate through the containers
 	containers, err := dbContainersList(d.db, cTypeRegular)
+	if err != nil {
+		shared.Log.Error("problem loading containers list", log.Ctx{"err": err})
+		return
+	}
 	fixedContainers := map[int][]container{}
 	balancedContainers := map[container]int{}
 	for _, name := range containers {

From 275c601c043228267acbf68c848e2ba436f80aa0 Mon Sep 17 00:00:00 2001
From: Jason Travis <jtravis at tgen.org>
Date: Wed, 3 Aug 2016 07:47:58 -0700
Subject: [PATCH 0174/1193] Fix flag name in init error message

Signed-off-by: Jason Travis <jasontravis at nau.edu>
---
 lxd/main.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/main.go b/lxd/main.go
index 9da7563cb..e91124470 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -732,13 +732,13 @@ func cmdInit() error {
 
 		if *argStorageBackend == "dir" {
 			if *argStorageCreateLoop != -1 || *argStorageCreateDevice != "" || *argStoragePool != "" {
-				return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-pool may be used with the 'dir' backend.")
+				return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
 			}
 		}
 
 		if *argStorageBackend == "zfs" {
 			if *argStorageCreateLoop != -1 && *argStorageCreateDevice != "" {
-				return fmt.Errorf("Only one of --storage-create-device or --storage-create-pool can be specified with the 'zfs' backend.")
+				return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified with the 'zfs' backend.")
 			}
 
 			if *argStoragePool == "" {

From 7349aa3cc6d0065661b7db5eede5a56fdfeb93e8 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 4 Aug 2016 14:19:11 -0600
Subject: [PATCH 0175/1193] add some reasonable defaults to `lxd init`

This also adds some validation: namely that ip addresses are valid and that
any block device specified as a ZFS pool is indeed an actual block device.

Closes #1933

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/main.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 47 insertions(+), 15 deletions(-)

diff --git a/lxd/main.go b/lxd/main.go
index e91124470..b923b4ce3 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -617,11 +617,14 @@ func cmdInit() error {
 
 	reader := bufio.NewReader(os.Stdin)
 
-	askBool := func(question string) bool {
+	askBool := func(question string, default_ string) bool {
 		for {
 			fmt.Printf(question)
 			input, _ := reader.ReadString('\n')
 			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
 			if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y"}) {
 				return true
 			} else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n"}) {
@@ -632,11 +635,14 @@ func cmdInit() error {
 		}
 	}
 
-	askChoice := func(question string, choices []string) string {
+	askChoice := func(question string, choices []string, default_ string) string {
 		for {
 			fmt.Printf(question)
 			input, _ := reader.ReadString('\n')
 			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
 			if shared.StringInSlice(input, choices) {
 				return input
 			}
@@ -645,11 +651,14 @@ func cmdInit() error {
 		}
 	}
 
-	askInt := func(question string, min int64, max int64) int64 {
+	askInt := func(question string, min int64, max int64, default_ string) int64 {
 		for {
 			fmt.Printf(question)
 			input, _ := reader.ReadString('\n')
 			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
 			intInput, err := strconv.ParseInt(input, 10, 64)
 
 			if err == nil && (min == -1 || intInput >= min) && (max == -1 || intInput <= max) {
@@ -660,11 +669,21 @@ func cmdInit() error {
 		}
 	}
 
-	askString := func(question string) string {
+	askString := func(question string, default_ string, validate func(string) string) string {
 		for {
 			fmt.Printf(question)
 			input, _ := reader.ReadString('\n')
 			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			if validate != nil {
+				result := validate(input)
+				if result != "" {
+					fmt.Printf("Invalid input: %s\n\n", result)
+					continue
+				}
+			}
 			if len(input) != 0 {
 				return input
 			}
@@ -776,7 +795,7 @@ func cmdInit() error {
 			return fmt.Errorf("Init configuration is only valid with --auto")
 		}
 
-		storageBackend = askChoice("Name of the storage backend to use (dir or zfs): ", backendsSupported)
+		storageBackend = askChoice("Name of the storage backend to use (dir or zfs) [default=zfs]: ", backendsSupported, "zfs")
 
 		if !shared.StringInSlice(storageBackend, backendsSupported) {
 			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)
@@ -787,17 +806,23 @@ func cmdInit() error {
 		}
 
 		if storageBackend == "zfs" {
-			if askBool("Create a new ZFS pool (yes/no)? ") {
-				storagePool = askString("Name of the new ZFS pool: ")
-				if askBool("Would you like to use an existing block device (yes/no)? ") {
-					storageDevice = askString("Path to the existing block device: ")
+			if askBool("Create a new ZFS pool (yes/no) [defualt=yes]? ", "yes") {
+				storagePool = askString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
+				if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
+					deviceExists := func(path string) string {
+						if !shared.IsBlockdevPath(path) {
+							return fmt.Sprintf("'%s' is not a block device", path)
+						}
+						return ""
+					}
+					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
 					storageMode = "device"
 				} else {
-					storageLoopSize = askInt("Size in GB of the new loop device (1GB minimum): ", 1, -1)
+					storageLoopSize = askInt("Size in GB of the new loop device (1GB minimum) [default=10GB]: ", 1, -1, "10")
 					storageMode = "loop"
 				}
 			} else {
-				storagePool = askString("Name of the existing ZFS pool or dataset: ")
+				storagePool = askString("Name of the existing ZFS pool or dataset: ", "", nil)
 				storageMode = "existing"
 			}
 		}
@@ -814,16 +839,23 @@ in theory attack their parent container and gain more privileges than
 they otherwise would.
 
 `)
-			if askBool("Would you like to have your containers share their parent's allocation (yes/no)? ") {
+			if askBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
 				defaultPrivileged = 1
 			} else {
 				defaultPrivileged = 0
 			}
 		}
 
-		if askBool("Would you like LXD to be available over the network (yes/no)? ") {
-			networkAddress = askString("Address to bind LXD to (not including port): ")
-			networkPort = askInt("Port to bind LXD to (8443 recommended): ", 1, 65535)
+		if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
+			isIPAddress := func(s string) string {
+				if net.ParseIP(s) == nil {
+					return fmt.Sprintf("'%s' is not an IP address", s)
+				}
+				return ""
+			}
+
+			networkAddress = askString("Address to bind LXD to (not including port) [default=0.0.0.0]: ", "0.0.0.0", isIPAddress)
+			networkPort = askInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
 			trustPassword = askPassword("Trust password for new clients: ")
 		}
 	}

From d5e0303c8b6694e87255d19047fdbd9c0778ae7f Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 4 Aug 2016 21:58:53 +0000
Subject: [PATCH 0176/1193] relax constraints on WebsocketRecvStream args

We don't use the Close method any more, so let's make these Writers, not
WriteClosers.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/network.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/shared/network.go b/shared/network.go
index 626a9cee2..d6c8d4f44 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -157,10 +157,10 @@ func WebsocketSendStream(conn *websocket.Conn, r io.Reader, bufferSize int) chan
 	return ch
 }
 
-func WebsocketRecvStream(w io.WriteCloser, conn *websocket.Conn) chan bool {
+func WebsocketRecvStream(w io.Writer, conn *websocket.Conn) chan bool {
 	ch := make(chan bool)
 
-	go func(w io.WriteCloser, conn *websocket.Conn) {
+	go func(w io.Writer, conn *websocket.Conn) {
 		for {
 			mt, r, err := conn.NextReader()
 			if mt == websocket.CloseMessage {

From a365ed8f381f70e13280614124b5ee22ce2887b4 Mon Sep 17 00:00:00 2001
From: Eric <naisanza at gmail.com>
Date: Mon, 1 Aug 2016 20:15:26 -0400
Subject: [PATCH 0177/1193] Initial documentation for production use of LXD

A collection of server changes in order for LXD to function correctly
when many file operations are requried

Closes #2256

Signed-off-by: Eric Jaw naisanza at gmail.com
---
 doc/production-setup.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)
 create mode 100644 doc/production-setup.md

diff --git a/doc/production-setup.md b/doc/production-setup.md
new file mode 100644
index 000000000..1ddd94ab1
--- /dev/null
+++ b/doc/production-setup.md
@@ -0,0 +1,47 @@
+# Introduction
+So you've made it past trying out [LXD live online](https://linuxcontainers.org/lxd/try-it/), 
+or on a server scavanged from random parts. You like what you see, 
+and now you want to try doing some serious work with LXD.
+
+With the vanilla installation of Ubuntu Server 16.04, there will 
+need to be some modifications to the server configuration to avoid 
+common pitfalls when using containers that require tens of thousands 
+of file operations.
+
+
+## Common errors that may be encountered
+
+`Failed to allocate directory watch: Too many open files`
+
+`<Error> <Error>: Too many open files`
+
+`failed to open stream: Too many open files in...`
+
+
+# Server Changes
+## /etc/security/limits.conf
+
+Domain  | Type  | Item    | Value     | Default   | Description
+:-----  | :---  | :----   | :-------- | :-------- | :----------
+*       | soft  | nofile  | 1048576   | unset     | maximum number of open files
+*       | hard  | nofile  | 1048576   | unset     | maximum number of open files
+root    | soft  | nofile  | 1048576   | unset     | maximum number of open files
+root    | hard  | nofile  | 1048576   | unset     | maximum number of open files
+*       | soft  | memlock | unlimited | unset     | maximum locked-in-memory address space (KB)
+*       | hard  | memlock | unlimited | unset     | maximum locked-in-memory address space (KB)
+
+
+## /etc/sysctl.conf
+
+Parameter                       | Value     | Default | Description
+:-----                          | :---      | :---    | :---
+fs.inotify.max\_queued\_events  | 1048576   | 16384   | This specifies an upper limit on the number of events that can be queued to the corresponding inotify instance. [1]
+fs.inotify.max\_user\_instances | 1048576   | 128     | This specifies an upper limit on the number of inotify instances that can be created per real user ID. [1]
+fs.inotify.max\_user\_watches   | 1048576   | 8192    | This specifies an upper limit on the number of watches that can be created per real user ID. [1]
+vm.max\_map\_count              | 262144    | 65530   | This file contains the maximum number of memory map areas a process may have. Memory map areas are used as a side-effect of calling malloc, directly by mmap and mprotect, and also when loading shared libraries.
+
+
+Then, reboot the server.
+
+
+[1]: http://man7.org/linux/man-pages/man7/inotify.7.html

From b3cede9eea65f5f76935f150076ac1ffcf41130e Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 5 Aug 2016 00:28:21 +0000
Subject: [PATCH 0178/1193] wrap lxd init entered ip6 addresses in []

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/main.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/main.go b/lxd/main.go
index b923b4ce3..02dadf457 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -855,6 +855,9 @@ they otherwise would.
 			}
 
 			networkAddress = askString("Address to bind LXD to (not including port) [default=0.0.0.0]: ", "0.0.0.0", isIPAddress)
+			if net.ParseIP(networkAddress).To4() == nil {
+				networkAddress = fmt.Sprintf("[%s]", networkAddress)
+			}
 			networkPort = askInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
 			trustPassword = askPassword("Trust password for new clients: ")
 		}

From ce613c368e6dc5200e31ccd4b23c25339109e0e2 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 5 Aug 2016 00:28:55 +0000
Subject: [PATCH 0179/1193] fix typo

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index 02dadf457..982b65555 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -806,7 +806,7 @@ func cmdInit() error {
 		}
 
 		if storageBackend == "zfs" {
-			if askBool("Create a new ZFS pool (yes/no) [defualt=yes]? ", "yes") {
+			if askBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
 				storagePool = askString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
 				if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
 					deviceExists := func(path string) string {

From a460b65d3fbabb613b4ebb5ca0b31ae919f25596 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 6 Aug 2016 21:00:57 -0400
Subject: [PATCH 0180/1193] simplestreams: properly deal with unset expiry
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index c71c45a3c..1c1ef4450 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -180,6 +180,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 			}
 
 			// Attempt to parse the EOL
+			image.ExpiryDate = time.Unix(0, 0).UTC()
 			if product.SupportedEOL != "" {
 				eolDate, err := time.Parse(eolLayout, product.SupportedEOL)
 				if err == nil {

From 6547128e474ca5a061ee40b7865d4e715866171d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 7 Aug 2016 12:47:35 -0400
Subject: [PATCH 0181/1193] simplestreams: handle images without labels
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams.go | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index 1c1ef4450..a482086fd 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -160,6 +160,12 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 			}
 
 			// Generate the actual image entry
+			description := fmt.Sprintf("%s %s %s", product.OperatingSystem, product.ReleaseTitle, product.Architecture)
+			if version.Label != "" {
+				description = fmt.Sprintf("%s (%s)", description, version.Label)
+			}
+			description = fmt.Sprintf("%s (%s)", description, name)
+
 			image := ImageInfo{}
 			image.Architecture = architectureName
 			image.Public = true
@@ -176,7 +182,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 				"architecture": product.Architecture,
 				"label":        version.Label,
 				"serial":       name,
-				"description":  fmt.Sprintf("%s %s %s (%s) (%s)", product.OperatingSystem, product.ReleaseTitle, product.Architecture, version.Label, name),
+				"description":  description,
 			}
 
 			// Attempt to parse the EOL

From 497b5be210c2b69d76995ef8bc633ec1d838ba43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 7 Aug 2016 12:47:54 -0400
Subject: [PATCH 0182/1193] config: Switch to using simplestreams for images:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This allows for better caching on the client network and lower CPU usage
on the image server thanks to only using TLS for the metadata.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 config.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/config.go b/config.go
index 8c935c591..c7b720965 100644
--- a/config.go
+++ b/config.go
@@ -52,8 +52,9 @@ var LocalRemote = RemoteConfig{
 	Public: false}
 
 var ImagesRemote = RemoteConfig{
-	Addr:   "https://images.linuxcontainers.org",
-	Public: true}
+	Addr:     "https://images.linuxcontainers.org",
+	Public:   true,
+	Protocol: "simplestreams"}
 
 var UbuntuRemote = RemoteConfig{
 	Addr:     "https://cloud-images.ubuntu.com/releases",

From 196a7bf95a03e30c6318621ba8954cceb072edf6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 7 Aug 2016 12:57:03 -0400
Subject: [PATCH 0183/1193] simplestreams: Set proper user-agent
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams.go | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index a482086fd..f387c703c 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -502,8 +502,15 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 		}
 		defer out.Close()
 
-		resp, err := s.http.Get(url)
+		req, err := http.NewRequest("GET", url, nil)
 		if err != nil {
+			return err
+		}
+		req.Header.Set("User-Agent", UserAgent)
+
+		resp, err := s.http.Do(req)
+		if err != nil {
+			return err
 		}
 		defer resp.Body.Close()
 

From 4873520079854c29af3085a14cabba454b39ab9d Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Tue, 19 Jul 2016 10:14:59 -0700
Subject: [PATCH 0184/1193] Document image export target behavior and fix bugs

Add help documentation for the target of `image export`.

If the target is a file, append the appropriate file extension to the
user-provided filename.  Properly parse the Content-Disposition header
to retrieve the image's filename instead of assuming `filename` is the
only parameter in the header.

Fix a bug where specifying a filename (as opposed to a directory) would
create the file with incorrect permissions (664 instead of 600).

Fixes #2205

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 client.go             | 60 ++++++++++++++---------------------
 lxc/image.go          | 11 ++++++-
 po/lxd.pot            | 87 ++++++++++++++++++++++++++++-----------------------
 test/suites/basic.sh  |  7 ++++-
 test/suites/remote.sh |  6 ++--
 5 files changed, 91 insertions(+), 80 deletions(-)

diff --git a/client.go b/client.go
index 439e6832b..4192088ce 100644
--- a/client.go
+++ b/client.go
@@ -832,56 +832,44 @@ func (c *Client) ExportImage(image string, target string) (string, error) {
 	if target == "-" {
 		wr = os.Stdout
 		destpath = "stdout"
-	} else if fi, err := os.Stat(target); err == nil {
-		// file exists, so check if folder
-		switch mode := fi.Mode(); {
-		case mode.IsDir():
-			// save in directory, header content-disposition can not be null
-			// and will have a filename
-			cd := strings.Split(raw.Header["Content-Disposition"][0], "=")
-
-			// write filename from header
-			destpath = filepath.Join(target, cd[1])
-			f, err := os.Create(destpath)
-			defer f.Close()
-
-			if err != nil {
-				return "", err
-			}
-
-			wr = f
-
-		default:
-			// overwrite file
-			destpath = target
-			f, err := os.OpenFile(destpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
-			defer f.Close()
+	} else {
+		_, cdParams, err := mime.ParseMediaType(raw.Header.Get("Content-Disposition"))
+		if err != nil {
+			return "", err
+		}
+		filename, ok := cdParams["filename"]
+		if !ok {
+			return "", fmt.Errorf("No filename in Content-Disposition header.")
+		}
 
-			if err != nil {
-				return "", err
+		if shared.IsDir(target) {
+			// The target is a directory, use the filename verbatim from the
+			// Content-Disposition header
+			destpath = filepath.Join(target, filename)
+		} else {
+			// The target is a file, parse the extension from the source filename
+			// and append it to the target filename.
+			ext := filepath.Ext(filename)
+			if strings.HasSuffix(filename, fmt.Sprintf(".tar%s", ext)) {
+				ext = fmt.Sprintf(".tar%s", ext)
 			}
-
-			wr = f
+			destpath = fmt.Sprintf("%s%s", target, ext)
 		}
-	} else {
-		// write as simple file
-		destpath = target
-		f, err := os.Create(destpath)
-		defer f.Close()
 
-		wr = f
+		f, err := os.OpenFile(destpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
 		if err != nil {
 			return "", err
 		}
+		defer f.Close()
+
+		wr = f
 	}
 
 	_, err = io.Copy(wr, raw.Body)
-
 	if err != nil {
 		return "", err
 	}
 
-	// it streams to stdout or file, so no response returned
 	return destpath, nil
 }
 
diff --git a/lxc/image.go b/lxc/image.go
index 752b9c68f..dcd397d7d 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -120,9 +120,18 @@ lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--
 lxc image delete [remote:]<image>
     Delete an image from the LXD image store.
 
-lxc image export [remote:]<image>
+lxc image export [remote:]<image> [target]
     Export an image from the LXD image store into a distributable tarball.
 
+    The output target is optional and defaults to the working directory.
+    The target may be an existing directory, file name, or "-" to specify
+    stdout.  The target MUST be a directory when exporting a split image.
+    If the target is a directory, the image's name (each part's name for
+    split images) as found in the database will be used for the exported
+    image.  If the target is a file (not a directory and not stdout), then
+    the appropriate extension will be appended to the provided file name
+    based on the algorithm used to compress the image. 
+
 lxc image info [remote:]<image>
     Print everything LXD knows about a given image.
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 391a1202a..582b3ed4f 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-08-11 11:25-0600\n"
+        "POT-Creation-Date: 2016-08-11 11:46-0600\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -77,7 +77,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:583
+#: lxc/image.go:592
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -90,11 +90,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:604 lxc/image.go:633
+#: lxc/image.go:613 lxc/image.go:642
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:608
+#: lxc/image.go:617
 msgid   "ARCH"
 msgstr  ""
 
@@ -111,7 +111,7 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:347
+#: lxc/image.go:356
 msgid   "Aliases:"
 msgstr  ""
 
@@ -119,12 +119,12 @@ msgstr  ""
 msgid   "An environment variable of the form HOME=/home/foo"
 msgstr  ""
 
-#: lxc/image.go:330 lxc/info.go:90
+#: lxc/image.go:339 lxc/info.go:90
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:351
+#: lxc/image.go:360
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
@@ -187,7 +187,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:687 lxc/profile.go:191
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:696 lxc/profile.go:191
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -210,7 +210,7 @@ msgstr  ""
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:155
+#: lxc/image.go:164
 msgid   "Copy aliases from source"
 msgstr  ""
 
@@ -220,7 +220,7 @@ msgid   "Copy containers within or in between lxd instances.\n"
         "lxc copy [remote:]<source container> [remote:]<destination container> [--ephemeral|e]"
 msgstr  ""
 
-#: lxc/image.go:268
+#: lxc/image.go:277
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
@@ -245,7 +245,7 @@ msgid   "Create a read-only snapshot of a container.\n"
         "lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:335 lxc/info.go:92
+#: lxc/image.go:344 lxc/info.go:92
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -259,7 +259,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:607 lxc/image.go:635
+#: lxc/image.go:616 lxc/image.go:644
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -317,16 +317,16 @@ msgid   "Execute the specified command in a container.\n"
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
 
-#: lxc/image.go:339
+#: lxc/image.go:348
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:341
+#: lxc/image.go:350
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:273 lxc/image.go:605 lxc/image.go:634
+#: lxc/config.go:273 lxc/image.go:614 lxc/image.go:643
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -334,7 +334,7 @@ msgstr  ""
 msgid   "Fast mode (same as --columns=nsacPt"
 msgstr  ""
 
-#: lxc/image.go:328
+#: lxc/image.go:337
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -389,11 +389,11 @@ msgstr  ""
 msgid   "Ignore the container state (only for start)."
 msgstr  ""
 
-#: lxc/image.go:273
+#: lxc/image.go:282
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:419
+#: lxc/image.go:428
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
@@ -439,7 +439,7 @@ msgstr  ""
 msgid   "Ips:"
 msgstr  ""
 
-#: lxc/image.go:156
+#: lxc/image.go:165
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
@@ -506,7 +506,7 @@ msgstr  ""
 msgid   "Log:"
 msgstr  ""
 
-#: lxc/image.go:154
+#: lxc/image.go:163
 msgid   "Make image public"
 msgstr  ""
 
@@ -641,9 +641,18 @@ msgid   "Manipulate container images.\n"
         "lxc image delete [remote:]<image>\n"
         "    Delete an image from the LXD image store.\n"
         "\n"
-        "lxc image export [remote:]<image>\n"
+        "lxc image export [remote:]<image> [target]\n"
         "    Export an image from the LXD image store into a distributable tarball.\n"
         "\n"
+        "    The output target is optional and defaults to the working directory.\n"
+        "    The target may be an existing directory, file name, or \"-\" to specify\n"
+        "    stdout.  The target MUST be a directory when exporting a split image.\n"
+        "    If the target is a directory, the image's name (each part's name for\n"
+        "    split images) as found in the database will be used for the exported\n"
+        "    image.  If the target is a file (not a directory and not stdout), then\n"
+        "    the appropriate extension will be appended to the provided file name\n"
+        "    based on the algorithm used to compress the image. \n"
+        "\n"
         "lxc image info [remote:]<image>\n"
         "    Print everything LXD knows about a given image.\n"
         "\n"
@@ -727,7 +736,7 @@ msgstr  ""
 msgid   "Name: %s"
 msgstr  ""
 
-#: lxc/image.go:157 lxc/publish.go:33
+#: lxc/image.go:166 lxc/publish.go:33
 msgid   "New alias to define at target"
 msgstr  ""
 
@@ -743,7 +752,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:411
+#: lxc/image.go:420
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -751,7 +760,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:506
+#: lxc/image.go:515
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -776,7 +785,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:606 lxc/remote.go:356
+#: lxc/image.go:615 lxc/remote.go:356
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -815,7 +824,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:688
+#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:697
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -866,7 +875,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:343
+#: lxc/image.go:352
 msgid   "Properties:"
 msgstr  ""
 
@@ -874,7 +883,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:331
+#: lxc/image.go:340
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -907,7 +916,7 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:609
+#: lxc/image.go:618
 msgid   "SIZE"
 msgstr  ""
 
@@ -972,7 +981,7 @@ msgstr  ""
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/image.go:329
+#: lxc/image.go:338
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -981,7 +990,7 @@ msgstr  ""
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/image.go:353
+#: lxc/image.go:362
 msgid   "Source:"
 msgstr  ""
 
@@ -1048,7 +1057,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it."
 msgstr  ""
 
-#: lxc/image.go:332
+#: lxc/image.go:341
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -1056,7 +1065,7 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:402
+#: lxc/image.go:411
 #, c-format
 msgid   "Transferring image: %d%%"
 msgstr  ""
@@ -1074,7 +1083,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:610
+#: lxc/image.go:619
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1086,7 +1095,7 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:337
+#: lxc/image.go:346
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1148,11 +1157,11 @@ msgstr  ""
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:323
+#: lxc/image.go:332
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:325
+#: lxc/image.go:334
 msgid   "enabled"
 msgstr  ""
 
@@ -1170,7 +1179,7 @@ msgstr  ""
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:318 lxc/image.go:586
+#: lxc/image.go:327 lxc/image.go:595
 msgid   "no"
 msgstr  ""
 
@@ -1228,7 +1237,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:320 lxc/image.go:590
+#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:599
 msgid   "yes"
 msgstr  ""
 
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index fa2a1d54f..cbfb547c6 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -53,11 +53,16 @@ test_basic_usage() {
   lxc image import "${LXD_DIR}/testimage.tar.xz" --alias testimage
   rm "${LXD_DIR}/testimage.tar.xz"
 
-  # Test filename for image export (should be "out")
+  # Test filename for image export
   lxc image export testimage "${LXD_DIR}/"
   [ "${sum}" = "$(sha256sum "${LXD_DIR}/testimage.tar.xz" | cut -d' ' -f1)" ]
   rm "${LXD_DIR}/testimage.tar.xz"
 
+  # Test custom filename for image export
+  lxc image export testimage "${LXD_DIR}/foo"
+  [ "${sum}" = "$(sha256sum "${LXD_DIR}/foo.tar.xz" | cut -d' ' -f1)" ]
+  rm "${LXD_DIR}/foo.tar.xz"
+
 
   # Test image export with a split image.
   deps/import-busybox --split --alias splitimage
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index ee04ad1b6..f731d2ec8 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -83,10 +83,10 @@ test_remote_usage() {
   lxc_remote remote add lxd2 "${LXD2_ADDR}" --accept-certificate --password foo
 
   # we need a public image on localhost
-  lxc_remote image export localhost:testimage "${LXD_DIR}/foo.img"
+  img=$(lxc_remote image export localhost:testimage "${LXD_DIR}/foo" | grep -o "foo.*")
   lxc_remote image delete localhost:testimage
-  sum=$(sha256sum "${LXD_DIR}/foo.img" | cut -d' ' -f1)
-  lxc_remote image import "${LXD_DIR}/foo.img" localhost: --public
+  sum=$(sha256sum "${LXD_DIR}/${img}" | cut -d' ' -f1)
+  lxc_remote image import "${LXD_DIR}/${img}" localhost: --public
   lxc_remote image alias create localhost:testimage "${sum}"
 
   lxc_remote image delete "lxd2:${sum}" || true

From e8f20706858b39a1c588aafb7a9f31f94df4927d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 22 Jul 2016 16:12:23 -0400
Subject: [PATCH 0185/1193] Document and validate limits.*.priority values
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2231

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/configuration.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/doc/configuration.md b/doc/configuration.md
index 7fd3ea685..465dd09bd 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -71,13 +71,13 @@ boot.autostart.priority     | integer   | 0             | n/a           | What o
 environment.\*              | string    | -             | yes (exec)    | key/value environment variables to export to the container and set on exec
 limits.cpu                  | string    | - (all)       | yes           | Number or range of CPUs to expose to the container
 limits.cpu.allowance        | string    | 100%          | yes           | How much of the CPU can be used. Can be a percentage (e.g. 50%) for a soft limit or hard a chunk of time (25ms/100ms)
-limits.cpu.priority         | integer   | 10 (maximum)  | yes           | CPU scheduling priority compared to other containers sharing the same CPUs (overcommit)
-limits.disk.priority        | integer   | 5 (medium)    | yes           | When under load, how much priority to give to the container's I/O requests
+limits.cpu.priority         | integer   | 10 (maximum)  | yes           | CPU scheduling priority compared to other containers sharing the same CPUs (overcommit) (integer between 0 and 10)
+limits.disk.priority        | integer   | 5 (medium)    | yes           | When under load, how much priority to give to the container's I/O requests (integer between 0 and 10)
 limits.memory               | string    | - (all)       | yes           | Percentage of the host's memory or fixed value in bytes (supports kB, MB, GB, TB, PB and EB suffixes)
 limits.memory.enforce       | string    | hard          | yes           | If hard, container can't exceed its memory limit. If soft, the container can exceed its memory limit when extra host memory is available.
 limits.memory.swap          | boolean   | true          | yes           | Whether to allow some of the container's memory to be swapped out to disk
-limits.memory.swap.priority | integer   | 10 (maximum)  | yes           | The higher this is set, the least likely the container is to be swapped to disk
-limits.network.priority     | integer   | 0 (minimum)   | yes           | When under load, how much priority to give to the container's network requests
+limits.memory.swap.priority | integer   | 10 (maximum)  | yes           | The higher this is set, the least likely the container is to be swapped to disk (integer between 0 and 10)
+limits.network.priority     | integer   | 0 (minimum)   | yes           | When under load, how much priority to give to the container's network requests (integer between 0 and 10)
 limits.processes            | integer   | - (max)       | yes           | Maximum number of processes that can run in the container
 linux.kernel\_modules       | string    | -             | yes           | Comma separated list of kernel modules to load before starting the container
 raw.apparmor                | blob      | -             | yes           | Apparmor profile entries to be appended to the generated profile

From 493d3e06b3bdc0a0ff770ed0d256392cdb212563 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 4 Aug 2016 10:58:08 -0600
Subject: [PATCH 0186/1193] alphabetize device processing

Unfortunately, there's not a way to range over a custom type, and there's
no way to sort a map, so we end up with this verbosity :(

Closes #2233

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 36 ++++++++++++++++++++++++------------
 lxd/networks.go      |  5 ++++-
 shared/devices.go    | 16 ++++++++++++++++
 3 files changed, 44 insertions(+), 13 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 706df7d67..4278edcb6 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -117,7 +117,8 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 
 	// Look for a rootfs entry
 	rootfs := false
-	for _, m := range c.expandedDevices {
+	for _, name := range c.expandedDevices.DeviceNames() {
+		m := c.expandedDevices[name]
 		if m["type"] == "disk" && m["path"] == "/" {
 			rootfs = true
 			break
@@ -649,7 +650,8 @@ func (c *containerLXC) initLXC() error {
 		}
 
 		hasDiskLimits := false
-		for _, m := range c.expandedDevices {
+		for _, name := range c.expandedDevices.DeviceNames() {
+			m := c.expandedDevices[name]
 			if m["type"] != "disk" {
 				continue
 			}
@@ -715,7 +717,8 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup devices
-	for k, m := range c.expandedDevices {
+	for _, k := range c.expandedDevices.DeviceNames() {
+		m := c.expandedDevices[k]
 		if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
 			// Prepare all the paths
 			srcPath := m["path"]
@@ -960,7 +963,8 @@ func (c *containerLXC) startCommon() (string, error) {
 	}
 
 	// Sanity checks for devices
-	for name, m := range c.expandedDevices {
+	for _, name := range c.expandedDevices.DeviceNames() {
+		m := c.expandedDevices[name]
 		switch m["type"] {
 		case "disk":
 			if m["source"] != "" && !shared.PathExists(m["source"]) {
@@ -1076,7 +1080,8 @@ func (c *containerLXC) startCommon() (string, error) {
 	c.removeDiskDevices()
 
 	// Create the devices
-	for k, m := range c.expandedDevices {
+	for _, k := range c.expandedDevices.DeviceNames() {
+		m := c.expandedDevices[k]
 		if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
 			// Unix device
 			devPath, err := c.createUnixDevice(m)
@@ -1123,7 +1128,8 @@ func (c *containerLXC) startCommon() (string, error) {
 
 	// Cleanup any leftover volatile entries
 	netNames := []string{}
-	for k, v := range c.expandedDevices {
+	for _, k := range c.expandedDevices.DeviceNames() {
+		v := c.expandedDevices[k]
 		if v["type"] == "nic" {
 			netNames = append(netNames, k)
 		}
@@ -1344,7 +1350,8 @@ func (c *containerLXC) OnStart() error {
 	}
 
 	// Apply network limits
-	for name, m := range c.expandedDevices {
+	for _, name := range c.expandedDevices.DeviceNames() {
+		m := c.expandedDevices[name]
 		if m["type"] != "nic" {
 			continue
 		}
@@ -2194,7 +2201,8 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 
 		var newRootfs shared.Device
-		for _, m := range c.expandedDevices {
+		for _, name := range c.expandedDevices.DeviceNames() {
+			m := c.expandedDevices[name]
 			if m["type"] == "disk" && m["path"] == "/" {
 				newRootfs = m
 				break
@@ -3177,7 +3185,8 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk {
 	disk := map[string]shared.ContainerStateDisk{}
 
-	for name, d := range c.expandedDevices {
+	for _, name := range c.expandedDevices.DeviceNames() {
+		d := c.expandedDevices[name]
 		if d["type"] != "disk" {
 			continue
 		}
@@ -3809,7 +3818,8 @@ func (c *containerLXC) fillNetworkDevice(name string, m shared.Device) (shared.D
 		devNames := []string{}
 
 		// Include all static interface names
-		for _, v := range c.expandedDevices {
+		for _, k := range c.expandedDevices.DeviceNames() {
+			v := c.expandedDevices[k]
 			if v["name"] != "" && !shared.StringInSlice(v["name"], devNames) {
 				devNames = append(devNames, v["name"])
 			}
@@ -4207,7 +4217,8 @@ func (c *containerLXC) getDiskLimits() (map[string]deviceBlockLimit, error) {
 
 	// Process all the limits
 	blockLimits := map[string][]deviceBlockLimit{}
-	for _, m := range c.expandedDevices {
+	for _, k := range c.expandedDevices.DeviceNames() {
+		m := c.expandedDevices[k]
 		if m["type"] != "disk" {
 			continue
 		}
@@ -4381,7 +4392,8 @@ func (c *containerLXC) getHostInterface(name string) string {
 		}
 	}
 
-	for k, dev := range c.expandedDevices {
+	for _, k := range c.expandedDevices.DeviceNames() {
+		dev := c.expandedDevices[k]
 		if dev["type"] != "nic" {
 			continue
 		}
diff --git a/lxd/networks.go b/lxd/networks.go
index f649b4e3c..bc209e4e4 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -54,7 +54,10 @@ type network struct {
 }
 
 func isOnBridge(c container, bridge string) bool {
-	for _, device := range c.ExpandedDevices() {
+	devices := c.ExpandedDevices()
+	for _, name := range devices.DeviceNames() {
+		device := devices[name]
+
 		if device["type"] != "nic" {
 			continue
 		}
diff --git a/shared/devices.go b/shared/devices.go
index 93eb29443..fb2938ec0 100644
--- a/shared/devices.go
+++ b/shared/devices.go
@@ -1,5 +1,9 @@
 package shared
 
+import (
+	"sort"
+)
+
 type Device map[string]string
 type Devices map[string]Device
 
@@ -102,3 +106,15 @@ func (newBaseDevices Devices) ExtendFromProfile(currentFullDevices Devices, newD
 
 	return nil
 }
+
+/* DeviceNames returns the device names for this Devices in sorted order */
+func (devices Devices) DeviceNames() []string {
+	sorted := sort.StringSlice([]string{})
+	for k, _ := range devices {
+		sorted = append(sorted, k)
+	}
+
+	sort.Sort(sorted)
+
+	return sorted
+}

From 69f2bc6aef43c5417fc2a3d2d4d5228fa79dea1c Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 5 Aug 2016 00:55:49 +0000
Subject: [PATCH 0187/1193] sort disk devices by their path before their names

We need to do this so that we mount things in the right order: something
mounted at /foo should be mounted before something at /foo/bar, so the
former doesn't cover up the latter in the final container.

Closes #2249

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/devices.go      | 58 ++++++++++++++++++++++++++++++++++++++++++++------
 shared/devices_test.go | 22 +++++++++++++++++++
 2 files changed, 73 insertions(+), 7 deletions(-)
 create mode 100644 shared/devices_test.go

diff --git a/shared/devices.go b/shared/devices.go
index fb2938ec0..e2b22db5e 100644
--- a/shared/devices.go
+++ b/shared/devices.go
@@ -107,14 +107,58 @@ func (newBaseDevices Devices) ExtendFromProfile(currentFullDevices Devices, newD
 	return nil
 }
 
-/* DeviceNames returns the device names for this Devices in sorted order */
-func (devices Devices) DeviceNames() []string {
-	sorted := sort.StringSlice([]string{})
-	for k, _ := range devices {
-		sorted = append(sorted, k)
+type namedDevice struct {
+	name   string
+	device Device
+}
+type sortableDevices []namedDevice
+
+func (devices Devices) toSortable() sortableDevices {
+	named := []namedDevice{}
+	for k, d := range devices {
+		named = append(named, namedDevice{k, d})
+	}
+
+	return named
+}
+
+func (devices sortableDevices) Len() int {
+	return len(devices)
+}
+
+func (devices sortableDevices) Less(i, j int) bool {
+	a := devices[i]
+	b := devices[j]
+
+	if a.device["type"] == "disk" && b.device["type"] == "disk" {
+		if a.device["path"] == b.device["path"] {
+			return a.name < b.name
+		}
+
+		return a.device["path"] < b.device["path"]
 	}
 
-	sort.Sort(sorted)
+	return a.name < b.name
+}
+
+func (devices sortableDevices) Swap(i, j int) {
+	tmp := devices[i]
+	devices[i] = devices[j]
+	devices[j] = tmp
+}
 
-	return sorted
+func (devices sortableDevices) Names() []string {
+	result := []string{}
+	for _, d := range devices {
+		result = append(result, d.name)
+	}
+
+	return result
+}
+
+/* DeviceNames returns the device names for this Devices in sorted order */
+func (devices Devices) DeviceNames() []string {
+	sortable := devices.toSortable()
+	sort.Sort(sortable)
+	return sortable.Names()
 }
diff --git a/shared/devices_test.go b/shared/devices_test.go
new file mode 100644
index 000000000..07aaed6c5
--- /dev/null
+++ b/shared/devices_test.go
@@ -0,0 +1,22 @@
+package shared
+
+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{"1", "2", "4", "3"}
+
+	result := devices.DeviceNames()
+	if !reflect.DeepEqual(result, expected) {
+		t.Error("devices sorted incorrectly")
+	}
+}

From b7a2f47058883ddab1e8520789cf3ec6b0d843dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 11 Aug 2016 09:21:10 -0600
Subject: [PATCH 0188/1193] Prevent using invalid profile names
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Profile names with slashes or called "." or ".." will get mangled/eaten
by the http server, so creating them works, using them works but they
can't be modified or deleted.

This prevents creating such profiles now and also removes any existing
one that already exists (they couldn't have been configured so it should
be safe).

Closes #2274

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/patches.go  | 26 +++++++++++++++++++++++++-
 lxd/profiles.go | 17 +++++++++++++++++
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/lxd/patches.go b/lxd/patches.go
index 11030b4be..72360d90e 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -1,7 +1,11 @@
 package main
 
 import (
+	"strings"
+
 	"github.com/lxc/lxd/shared"
+
+	log "gopkg.in/inconshreveable/log15.v2"
 )
 
 /* Patches are one-time actions that are sometimes needed to update
@@ -21,7 +25,9 @@ import (
    Only append to the patches list, never remove entries and never re-order them.
 */
 
-var patches = []patch{}
+var patches = []patch{
+	patch{name: "invalid_profile_names", run: patchInvalidProfileNames},
+}
 
 type patch struct {
 	name string
@@ -65,3 +71,21 @@ func patchesApplyAll(d *Daemon) error {
 }
 
 // Patches begin here
+func patchInvalidProfileNames(name string, d *Daemon) error {
+	profiles, err := dbProfiles(d.db)
+	if err != nil {
+		return err
+	}
+
+	for _, profile := range profiles {
+		if strings.Contains(profile, "/") || shared.StringInSlice(profile, []string{".", ".."}) {
+			shared.Log.Info("Removing unreachable profile (invalid name)", log.Ctx{"name": profile})
+			err := dbProfileDelete(d.db, profile)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 746bd3251..9e49da47d 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net/http"
 	"reflect"
+	"strings"
 
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
@@ -66,6 +67,14 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
+	if strings.Contains(req.Name, "/") {
+		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
+	}
+
+	if shared.StringInSlice(req.Name, []string{".", ".."}) {
+		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
+	}
+
 	err := containerValidConfig(req.Config, true, false)
 	if err != nil {
 		return BadRequest(err)
@@ -248,6 +257,14 @@ func profilePost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
+	if strings.Contains(req.Name, "/") {
+		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
+	}
+
+	if shared.StringInSlice(req.Name, []string{".", ".."}) {
+		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
+	}
+
 	err := dbProfileUpdate(d.db, name, req.Name)
 	if err != nil {
 		return InternalError(err)

From d11c41ca1220c2ef937c031cdd4f184041889ae5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 11 Aug 2016 14:30:53 -0600
Subject: [PATCH 0189/1193] Error when trying to remove a non-existent device
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2277

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/client.go b/client.go
index 4192088ce..142336464 100644
--- a/client.go
+++ b/client.go
@@ -2168,9 +2168,14 @@ func (c *Client) ContainerDeviceDelete(container, devname string) (*Response, er
 		return nil, err
 	}
 
-	delete(st.Devices, devname)
+	for n, _ := range st.Devices {
+		if n == devname {
+			delete(st.Devices, n)
+			return c.put(fmt.Sprintf("containers/%s", container), st, Async)
+		}
+	}
 
-	return c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	return nil, fmt.Errorf("Device doesn't exist.")
 }
 
 func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []string) (*Response, error) {
@@ -2237,10 +2242,11 @@ func (c *Client) ProfileDeviceDelete(profile, devname string) (*Response, error)
 	for n, _ := range st.Devices {
 		if n == devname {
 			delete(st.Devices, n)
+			return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
 		}
 	}
 
-	return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
+	return nil, fmt.Errorf("Device doesn't exist.")
 }
 
 func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []string) (*Response, error) {

From 52b6fb2d3dff8df23e57cc21a01c2d1055620f64 Mon Sep 17 00:00:00 2001
From: Sean Christopherson <sean.j.christopherson at intel.com>
Date: Thu, 11 Aug 2016 08:16:47 -0700
Subject: [PATCH 0190/1193] Always use fingerprint for image export filename

Independently detect the compression algorithm for the metadata and
rootfs files as they may use different compression algorithms, e.g.
XZ for the metadata and squashfs for the rootfs.  Using the original
file name of the metadata as the base file name for all output files
can result an incorrect file extension for the rootfs.

Signed-off-by: Sean Christopherson <sean.j.christopherson at intel.com>
---
 lxd/images.go        | 20 +++++++++++++-------
 test/suites/basic.sh | 19 +++++++------------
 2 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index a5d6b9438..25bb6294f 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1231,16 +1231,14 @@ func imageExport(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	filename := imgInfo.Filename
 	imagePath := shared.VarPath("images", imgInfo.Fingerprint)
 	rootfsPath := imagePath + ".rootfs"
-	if filename == "" {
-		_, ext, err := detectCompression(imagePath)
-		if err != nil {
-			ext = ""
-		}
-		filename = fmt.Sprintf("%s%s", fingerprint, ext)
+
+	_, ext, err := detectCompression(imagePath)
+	if err != nil {
+		ext = ""
 	}
+	filename := fmt.Sprintf("%s%s", fingerprint, ext)
 
 	if shared.PathExists(rootfsPath) {
 		files := make([]fileResponseEntry, 2)
@@ -1249,6 +1247,14 @@ func imageExport(d *Daemon, r *http.Request) Response {
 		files[0].path = imagePath
 		files[0].filename = "meta-" + filename
 
+		// Recompute the extension for the root filesystem, it may use a different
+		// compression algorithm than the metadata.
+		_, ext, err = detectCompression(rootfsPath)
+		if err != nil {
+			ext = ""
+		}
+		filename = fmt.Sprintf("%s%s", fingerprint, ext)
+
 		files[1].identifier = "rootfs"
 		files[1].path = rootfsPath
 		files[1].filename = filename
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index cbfb547c6..2b4edfc4d 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -18,12 +18,7 @@ test_basic_usage() {
   # Test image export
   sum=$(lxc image info testimage | grep ^Fingerprint | cut -d' ' -f2)
   lxc image export testimage "${LXD_DIR}/"
-  if [ -e "${LXD_TEST_IMAGE:-}" ]; then
-    name=$(basename "${LXD_TEST_IMAGE}")
-  else
-    name=${sum}.tar.xz
-  fi
-  [ "${sum}" = "$(sha256sum "${LXD_DIR}/${name}" | cut -d' ' -f1)" ]
+  [ "${sum}" = "$(sha256sum "${LXD_DIR}/${sum}.tar.xz" | cut -d' ' -f1)" ]
   
   # Test an alias with slashes
   lxc image show "${sum}"
@@ -49,14 +44,14 @@ test_basic_usage() {
   my_curl -f -X GET "https://${LXD_ADDR}/1.0/containers"
 
   # Re-import the image
-  mv "${LXD_DIR}/${name}" "${LXD_DIR}/testimage.tar.xz"
+  mv "${LXD_DIR}/${sum}.tar.xz" "${LXD_DIR}/testimage.tar.xz"
   lxc image import "${LXD_DIR}/testimage.tar.xz" --alias testimage
   rm "${LXD_DIR}/testimage.tar.xz"
 
   # Test filename for image export
   lxc image export testimage "${LXD_DIR}/"
-  [ "${sum}" = "$(sha256sum "${LXD_DIR}/testimage.tar.xz" | cut -d' ' -f1)" ]
-  rm "${LXD_DIR}/testimage.tar.xz"
+  [ "${sum}" = "$(sha256sum "${LXD_DIR}/${sum}.tar.xz" | cut -d' ' -f1)" ]
+  rm "${LXD_DIR}/${sum}.tar.xz"
 
   # Test custom filename for image export
   lxc image export testimage "${LXD_DIR}/foo"
@@ -83,11 +78,11 @@ test_basic_usage() {
   deps/import-busybox --split --filename --alias splitimage
 
   lxc image export splitimage "${LXD_DIR}"
-  [ "${sum}" = "$(cat "${LXD_DIR}/meta-busybox.tar.xz" "${LXD_DIR}/busybox.tar.xz" | sha256sum | cut -d' ' -f1)" ]
+  [ "${sum}" = "$(cat "${LXD_DIR}/meta-${sum}.tar.xz" "${LXD_DIR}/${sum}.tar.xz" | sha256sum | cut -d' ' -f1)" ]
   
   # Delete the split image and exported files
-  rm "${LXD_DIR}/busybox.tar.xz"
-  rm "${LXD_DIR}/meta-busybox.tar.xz"
+  rm "${LXD_DIR}/${sum}.tar.xz"
+  rm "${LXD_DIR}/meta-${sum}.tar.xz"
   lxc image delete splitimage
 
 

From b210a99803841a0754d394cfe1a29523ca6dad8c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 11 Aug 2016 23:08:18 -0600
Subject: [PATCH 0191/1193] Fix API info reporting in "lxc info"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/server.go | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/shared/server.go b/shared/server.go
index b16f1c295..96044daa7 100644
--- a/shared/server.go
+++ b/shared/server.go
@@ -18,11 +18,13 @@ type ServerStateEnvironment struct {
 }
 
 type ServerState struct {
-	APICompat   int                    `json:"api_compat"`
-	Auth        string                 `json:"auth"`
-	Environment ServerStateEnvironment `json:"environment"`
-	Config      map[string]interface{} `json:"config"`
-	Public      bool                   `json:"public"`
+	APIExtensions []string               `json:"api_extensions"`
+	APIStatus     string                 `json:"api_status"`
+	APIVersion    string                 `json:"api_version"`
+	Auth          string                 `json:"auth"`
+	Environment   ServerStateEnvironment `json:"environment"`
+	Config        map[string]interface{} `json:"config"`
+	Public        bool                   `json:"public"`
 }
 
 type BriefServerState struct {

From c01e7b7caecaef112af525817c9fab23c9e20901 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 15 Aug 2016 15:10:35 -0400
Subject: [PATCH 0192/1193] Report download progress on image import
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go    | 39 ++++++++++++++++++++++++++++++++++++++-
 lxc/image.go |  6 +++++-
 po/lxd.pot   | 37 +++++++++++++++++++++----------------
 3 files changed, 64 insertions(+), 18 deletions(-)

diff --git a/client.go b/client.go
index 142336464..18d06e20d 100644
--- a/client.go
+++ b/client.go
@@ -873,7 +873,7 @@ func (c *Client) ExportImage(image string, target string) (string, error) {
 	return destpath, nil
 }
 
-func (c *Client) PostImageURL(imageFile string, public bool, aliases []string) (string, error) {
+func (c *Client) PostImageURL(imageFile string, public bool, aliases []string, progressHandler func(progress string)) (string, error) {
 	if c.Remote.Public {
 		return "", fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -884,11 +884,48 @@ func (c *Client) PostImageURL(imageFile string, public bool, aliases []string) (
 		"url":  imageFile}
 	body := shared.Jmap{"public": public, "source": source}
 
+	operation := ""
+	handler := func(msg interface{}) {
+		if msg == nil {
+			return
+		}
+
+		event := msg.(map[string]interface{})
+		if event["type"].(string) != "operation" {
+			return
+		}
+
+		if event["metadata"] == nil {
+			return
+		}
+
+		md := event["metadata"].(map[string]interface{})
+		if !strings.HasSuffix(operation, md["id"].(string)) {
+			return
+		}
+
+		if md["metadata"] == nil {
+			return
+		}
+
+		opMd := md["metadata"].(map[string]interface{})
+		_, ok := opMd["download_progress"]
+		if ok {
+			progressHandler(opMd["download_progress"].(string))
+		}
+	}
+
+	if progressHandler != nil {
+		go c.Monitor([]string{"operation"}, handler)
+	}
+
 	resp, err := c.post("images", body, Async)
 	if err != nil {
 		return "", err
 	}
 
+	operation = resp.Operation
+
 	op, err := c.WaitFor(resp.Operation)
 	if err != nil {
 		return "", err
diff --git a/lxc/image.go b/lxc/image.go
index dcd397d7d..5117e71f7 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -415,7 +415,11 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		}
 
 		if strings.HasPrefix(imageFile, "https://") {
-			fingerprint, err = d.PostImageURL(imageFile, c.publicImage, c.addAliases)
+			progressHandler := func(progress string) {
+				fmt.Printf(i18n.G("Importing the image: %s")+"\r", progress)
+			}
+
+			fingerprint, err = d.PostImageURL(imageFile, c.publicImage, c.addAliases, progressHandler)
 		} else if strings.HasPrefix(imageFile, "http://") {
 			return fmt.Errorf(i18n.G("Only https:// is supported for remote image import."))
 		} else {
diff --git a/po/lxd.pot b/po/lxd.pot
index 582b3ed4f..a11172bd1 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-08-11 11:46-0600\n"
+        "POT-Creation-Date: 2016-08-15 19:25-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -77,7 +77,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:592
+#: lxc/image.go:596
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -90,11 +90,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:613 lxc/image.go:642
+#: lxc/image.go:617 lxc/image.go:646
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:617
+#: lxc/image.go:621
 msgid   "ARCH"
 msgstr  ""
 
@@ -187,7 +187,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:696 lxc/profile.go:191
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:700 lxc/profile.go:191
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -259,7 +259,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:616 lxc/image.go:644
+#: lxc/image.go:620 lxc/image.go:648
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -326,7 +326,7 @@ msgstr  ""
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:273 lxc/image.go:614 lxc/image.go:643
+#: lxc/config.go:273 lxc/image.go:618 lxc/image.go:647
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -393,11 +393,16 @@ msgstr  ""
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:428
+#: lxc/image.go:432
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
+#: lxc/image.go:419
+#, c-format
+msgid   "Importing the image: %s"
+msgstr  ""
+
 #: lxc/init.go:73
 msgid   "Initialize a container from a particular image.\n"
         "\n"
@@ -752,7 +757,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:420
+#: lxc/image.go:424
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -760,7 +765,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:515
+#: lxc/image.go:519
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -785,7 +790,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:615 lxc/remote.go:356
+#: lxc/image.go:619 lxc/remote.go:356
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -824,7 +829,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:697
+#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:701
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -916,7 +921,7 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:618
+#: lxc/image.go:622
 msgid   "SIZE"
 msgstr  ""
 
@@ -1083,7 +1088,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:619
+#: lxc/image.go:623
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1179,7 +1184,7 @@ msgstr  ""
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:327 lxc/image.go:595
+#: lxc/image.go:327 lxc/image.go:599
 msgid   "no"
 msgstr  ""
 
@@ -1237,7 +1242,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:599
+#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:603
 msgid   "yes"
 msgstr  ""
 

From c8f25238000325f0be0b6d90a669c4e936402d86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 15 Aug 2016 15:20:24 -0400
Subject: [PATCH 0193/1193] Fix image import from URL
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Support URLs containing an equals signs
 - Actually respect the provided properties

Closes #2272

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go    | 14 ++++++++++++--
 lxc/image.go |  5 +++--
 2 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/client.go b/client.go
index 18d06e20d..c118ab8c1 100644
--- a/client.go
+++ b/client.go
@@ -873,16 +873,26 @@ func (c *Client) ExportImage(image string, target string) (string, error) {
 	return destpath, nil
 }
 
-func (c *Client) PostImageURL(imageFile string, public bool, aliases []string, progressHandler func(progress string)) (string, error) {
+func (c *Client) PostImageURL(imageFile string, properties []string, public bool, aliases []string, progressHandler func(progress string)) (string, error) {
 	if c.Remote.Public {
 		return "", fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
+	imgProperties := map[string]string{}
+	for _, entry := range properties {
+		fields := strings.SplitN(entry, "=", 2)
+		if len(fields) != 2 {
+			return "", fmt.Errorf("Invalid image property: %s", entry)
+		}
+
+		imgProperties[fields[0]] = fields[1]
+	}
+
 	source := shared.Jmap{
 		"type": "url",
 		"mode": "pull",
 		"url":  imageFile}
-	body := shared.Jmap{"public": public, "source": source}
+	body := shared.Jmap{"public": public, "properties": imgProperties, "source": source}
 
 	operation := ""
 	handler := func(msg interface{}) {
diff --git a/lxc/image.go b/lxc/image.go
index 5117e71f7..07cb28e4f 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -399,7 +399,8 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		}
 
 		if imageFile == "" {
-			return errArgs
+			imageFile = args[1]
+			properties = properties[1:]
 		}
 
 		d, err := lxd.NewClient(config, remote)
@@ -419,7 +420,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 				fmt.Printf(i18n.G("Importing the image: %s")+"\r", progress)
 			}
 
-			fingerprint, err = d.PostImageURL(imageFile, c.publicImage, c.addAliases, progressHandler)
+			fingerprint, err = d.PostImageURL(imageFile, properties, c.publicImage, c.addAliases, progressHandler)
 		} else if strings.HasPrefix(imageFile, "http://") {
 			return fmt.Errorf(i18n.G("Only https:// is supported for remote image import."))
 		} else {

From 1fc07c7a7b2ec0bd474f90febb0590305f506258 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 15 Aug 2016 17:33:18 -0400
Subject: [PATCH 0194/1193] Fix unix-char/unix-block in nested containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This comes with a number of limitations due to us not being able to
mknod the device, but it's better than what we have currently.

Closes #2279

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 90 ++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 63 insertions(+), 27 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 4278edcb6..50b719aab 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1089,15 +1089,17 @@ func (c *containerLXC) startCommon() (string, error) {
 				return "", err
 			}
 
-			// Add the new device cgroup rule
-			dType, dMajor, dMinor, err := deviceGetAttributes(devPath)
-			if err != nil {
-				return "", err
-			}
+			if c.IsPrivileged() && !runningInUserns && cgDevicesController {
+				// 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")
+				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"] == "disk" {
 			// Disk device
@@ -3520,6 +3522,15 @@ func (c *containerLXC) createUnixDevice(m shared.Device) (string, error) {
 	devName := fmt.Sprintf("unix.%s", strings.Replace(tgtPath, "/", "-", -1))
 	devPath := filepath.Join(c.DevicesPath(), devName)
 
+	// Extra checks for nesting
+	if runningInUserns {
+		for key, value := range m {
+			if shared.StringInSlice(key, []string{"major", "minor", "mode", "uid", "gid"}) && value != "" {
+				return "", fmt.Errorf("The \"%s\" property may not be set when adding a device to a nested container", key)
+			}
+		}
+	}
+
 	// 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
@@ -3585,6 +3596,10 @@ func (c *containerLXC) createUnixDevice(m shared.Device) (string, error) {
 
 	// Clean any existing entry
 	if shared.PathExists(devPath) {
+		if runningInUserns {
+			syscall.Unmount(devPath, syscall.MNT_DETACH)
+		}
+
 		err = os.Remove(devPath)
 		if err != nil {
 			return "", fmt.Errorf("Failed to remove existing entry: %s", err)
@@ -3592,23 +3607,36 @@ func (c *containerLXC) createUnixDevice(m shared.Device) (string, error) {
 	}
 
 	// Create the new entry
-	if err := syscall.Mknod(devPath, uint32(mode), minor|(major<<8)); err != nil {
-		return "", fmt.Errorf("Failed to create device %s for %s: %s", devPath, m["path"], err)
-	}
+	if !runningInUserns {
+		if err := syscall.Mknod(devPath, uint32(mode), minor|(major<<8)); err != nil {
+			return "", fmt.Errorf("Failed to create device %s for %s: %s", devPath, m["path"], err)
+		}
 
-	if err := os.Chown(devPath, uid, gid); err != nil {
-		return "", fmt.Errorf("Failed to chown device %s: %s", devPath, err)
-	}
+		if err := os.Chown(devPath, uid, gid); err != nil {
+			return "", fmt.Errorf("Failed to chown device %s: %s", devPath, err)
+		}
 
-	// Needed as mknod respects the umask
-	if err := os.Chmod(devPath, mode); err != nil {
-		return "", fmt.Errorf("Failed to chmod device %s: %s", devPath, err)
-	}
+		// Needed as mknod respects the umask
+		if err := os.Chmod(devPath, mode); err != nil {
+			return "", fmt.Errorf("Failed to chmod device %s: %s", devPath, err)
+		}
 
-	if c.idmapset != nil {
-		if err := c.idmapset.ShiftFile(devPath); err != nil {
-			// uidshift failing is weird, but not a big problem.  Log and proceed
-			shared.Debugf("Failed to uidshift device %s: %s\n", m["path"], err)
+		if c.idmapset != nil {
+			if err := c.idmapset.ShiftFile(devPath); err != nil {
+				// uidshift failing is weird, but not a big problem.  Log and proceed
+				shared.Debugf("Failed to uidshift device %s: %s\n", m["path"], err)
+			}
+		}
+	} else {
+		f, err := os.Create(devPath)
+		if err != nil {
+			return "", err
+		}
+		f.Close()
+
+		err = deviceMountDisk(srcPath, devPath, false, false)
+		if err != nil {
+			return "", err
 		}
 	}
 
@@ -3640,8 +3668,10 @@ func (c *containerLXC) insertUnixDevice(name string, m shared.Device) error {
 		return fmt.Errorf("Failed to get device attributes: %s", err)
 	}
 
-	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")
+	if c.IsPrivileged() && !runningInUserns && cgDevicesController {
+		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
@@ -3666,9 +3696,11 @@ func (c *containerLXC) removeUnixDevice(m shared.Device) error {
 		return err
 	}
 
-	err = c.CGroupSet("devices.deny", fmt.Sprintf("%s %d:%d rwm", dType, dMajor, dMinor))
-	if err != nil {
-		return err
+	if c.IsPrivileged() && !runningInUserns && cgDevicesController {
+		err = c.CGroupSet("devices.deny", fmt.Sprintf("%s %d:%d rwm", dType, dMajor, dMinor))
+		if err != nil {
+			return err
+		}
 	}
 
 	// Remove the bind-mount from the container
@@ -3687,6 +3719,10 @@ func (c *containerLXC) removeUnixDevice(m shared.Device) error {
 	}
 
 	// Remove the host side
+	if runningInUserns {
+		syscall.Unmount(devPath, syscall.MNT_DETACH)
+	}
+
 	err = os.Remove(devPath)
 	if err != nil {
 		return err

From c51427acaaa15c2bc64dcd3f5b3f9f93abc863e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 15 Aug 2016 17:47:39 -0400
Subject: [PATCH 0195/1193] Fix error handling and race in "lxc list"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - If a go routine was to fail, "lxc list" would just hang
 - On machines with a large number of containers, "lxc list" could get
   into a deadlock situation between the go routines already processing
   requests and the dispatcher.

Closes #1753

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index 5a165fe8f..fb4ab362b 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -211,6 +211,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 		go func() {
 			d, err := lxd.NewClient(&d.Config, d.Name)
 			if err != nil {
+				cStatesWg.Done()
 				return
 			}
 
@@ -236,6 +237,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 		go func() {
 			d, err := lxd.NewClient(&d.Config, d.Name)
 			if err != nil {
+				cSnapshotsWg.Done()
 				return
 			}
 
@@ -259,33 +261,37 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 	}
 
 	for _, cInfo := range cinfos {
-		cStatesLock.Lock()
-		cSnapshotsLock.Lock()
 		for _, column := range columns {
 			if column.NeedsState && cInfo.IsActive() {
+				cStatesLock.Lock()
 				_, ok := cStates[cInfo.Name]
+				cStatesLock.Unlock()
 				if ok {
 					continue
 				}
 
+				cStatesLock.Lock()
 				cStates[cInfo.Name] = nil
+				cStatesLock.Unlock()
 
 				cStatesQueue <- cInfo.Name
 			}
 
 			if column.NeedsSnapshots {
+				cSnapshotsLock.Lock()
 				_, ok := cSnapshots[cInfo.Name]
+				cSnapshotsLock.Unlock()
 				if ok {
 					continue
 				}
 
+				cSnapshotsLock.Lock()
 				cSnapshots[cInfo.Name] = nil
+				cSnapshotsLock.Unlock()
 
 				cSnapshotsQueue <- cInfo.Name
 			}
 		}
-		cStatesLock.Unlock()
-		cSnapshotsLock.Unlock()
 	}
 
 	close(cStatesQueue)

From 1a6adb0332e8f1da761cf899a92f9b8d8af53268 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 15 Aug 2016 22:45:18 -0400
Subject: [PATCH 0196/1193] Release LXD 2.0.4
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/flex.go b/shared/flex.go
index 50a681ee8..e61c8d027 100644
--- a/shared/flex.go
+++ b/shared/flex.go
@@ -3,7 +3,7 @@
  */
 package shared
 
-var Version = "2.0.3"
+var Version = "2.0.4"
 var UserAgent = "LXD " + Version
 
 /*

From af086e4faee1c936c02be441c37fc545c4d0e6dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 16 Aug 2016 13:18:57 -0400
Subject: [PATCH 0197/1193] Fix for newer shellcheck
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd-bridge/lxd-bridge          |  3 ++-
 test/backends/btrfs.sh         |  6 ++++++
 test/backends/dir.sh           |  6 ++++++
 test/backends/lvm.sh           |  6 ++++++
 test/backends/zfs.sh           |  6 ++++++
 test/main.sh                   | 15 +++++++++++----
 test/suites/basic.sh           |  3 ++-
 test/suites/devlxd.sh          |  2 ++
 test/suites/remote.sh          |  2 +-
 test/suites/static_analysis.sh |  2 +-
 10 files changed, 43 insertions(+), 8 deletions(-)

diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
index 67a7829c8..15390a3d4 100755
--- a/lxd-bridge/lxd-bridge
+++ b/lxd-bridge/lxd-bridge
@@ -27,6 +27,7 @@ LXD_IPV6_NETWORK=""
 LXD_IPV6_NAT="false"
 LXD_IPV6_PROXY="true"
 
+# shellcheck disable=SC1090
 [ ! -f "${config}" ] || . "${config}"
 
 use_iptables_lock="-w"
@@ -39,7 +40,7 @@ HAS_IPV6=false
 _netmask2cidr ()
 {
     # Assumes there's no "255." after a non-255 byte in the mask
-    local x=${1##*255.}
+    x=${1##*255.}
     set -- "0^^^128^192^224^240^248^252^254^" "$(( (${#1} - ${#x})*2 ))" "${x%%.*}"
     x=${1%%${3}*}
     echo $(( ${2} + (${#x}/4) ))
diff --git a/test/backends/btrfs.sh b/test/backends/btrfs.sh
index 56399284a..efbcc460d 100644
--- a/test/backends/btrfs.sh
+++ b/test/backends/btrfs.sh
@@ -1,7 +1,9 @@
 #!/bin/sh
 
 btrfs_setup() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Setting up btrfs backend in ${LXD_DIR}"
@@ -17,14 +19,18 @@ btrfs_setup() {
 }
 
 btrfs_configure() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Configuring btrfs backend in ${LXD_DIR}"
 }
 
 btrfs_teardown() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Tearing down btrfs backend in ${LXD_DIR}"
diff --git a/test/backends/dir.sh b/test/backends/dir.sh
index beeaefc85..7a14be57e 100644
--- a/test/backends/dir.sh
+++ b/test/backends/dir.sh
@@ -5,7 +5,9 @@
 
 # Any necessary backend-specific setup
 dir_setup() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Setting up directory backend in ${LXD_DIR}"
@@ -13,14 +15,18 @@ dir_setup() {
 
 # Do the API voodoo necessary to configure LXD to use this backend
 dir_configure() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Configuring directory backend in ${LXD_DIR}"
 }
 
 dir_teardown() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Tearing down directory backend in ${LXD_DIR}"
diff --git a/test/backends/lvm.sh b/test/backends/lvm.sh
index 0996e0a20..862e3301f 100644
--- a/test/backends/lvm.sh
+++ b/test/backends/lvm.sh
@@ -1,7 +1,9 @@
 #!/bin/sh
 
 lvm_setup() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Setting up lvm backend in ${LXD_DIR}"
@@ -23,7 +25,9 @@ lvm_setup() {
 }
 
 lvm_configure() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Configuring lvm backend in ${LXD_DIR}"
@@ -33,7 +37,9 @@ lvm_configure() {
 }
 
 lvm_teardown() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Tearing down lvm backend in ${LXD_DIR}"
diff --git a/test/backends/zfs.sh b/test/backends/zfs.sh
index 0a7b515d3..41013fee1 100644
--- a/test/backends/zfs.sh
+++ b/test/backends/zfs.sh
@@ -1,7 +1,9 @@
 #!/bin/sh
 
 zfs_setup() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Setting up ZFS backend in ${LXD_DIR}"
@@ -17,7 +19,9 @@ zfs_setup() {
 }
 
 zfs_configure() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Configuring ZFS backend in ${LXD_DIR}"
@@ -26,7 +30,9 @@ zfs_configure() {
 }
 
 zfs_teardown() {
+  # shellcheck disable=2039
   local LXD_DIR
+
   LXD_DIR=$1
 
   echo "==> Tearing down ZFS backend in ${LXD_DIR}"
diff --git a/test/main.sh b/test/main.sh
index fa22aa3d4..e41bbb0ec 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -39,6 +39,7 @@ local_tcp_port() {
 
 # import all the backends
 for backend in backends/*.sh; do
+  # shellcheck disable=SC1090
   . "${backend}"
 done
 
@@ -50,6 +51,8 @@ spawn_lxd() {
   set +x
   # LXD_DIR is local here because since $(lxc) is actually a function, it
   # overwrites the environment and we would lose LXD_DIR's value otherwise.
+
+  # shellcheck disable=2039
   local LXD_DIR
 
   lxddir=${1}
@@ -176,7 +179,10 @@ check_empty_table() {
 kill_lxd() {
   # LXD_DIR is local here because since $(lxc) is actually a function, it
   # overwrites the environment and we would lose LXD_DIR's value otherwise.
+
+  # shellcheck disable=2039
   local LXD_DIR
+
   daemon_dir=${1}
   LXD_DIR=${daemon_dir}
   daemon_pid=$(cat "${daemon_dir}/lxd.pid")
@@ -263,13 +269,13 @@ cleanup() {
     echo "Tests Completed (${TEST_RESULT}): hit enter to continue"
 
     # shellcheck disable=SC2034
-    read nothing
+    read -r nothing
   fi
 
   echo "==> Cleaning up"
 
   # Kill all the LXD instances
-  while read daemon_dir; do
+  while read -r daemon_dir; do
     kill_lxd "${daemon_dir}"
   done < "${TEST_DIR}/daemons"
 
@@ -293,12 +299,12 @@ wipe() {
   fi
 
   # shellcheck disable=SC2009
-  ps aux | grep lxc-monitord | grep "${1}" | awk '{print $2}' | while read pid; do
+  ps aux | grep lxc-monitord | grep "${1}" | awk '{print $2}' | while read -r pid; do
     kill -9 "${pid}"
   done
 
   if [ -f "${TEST_DIR}/loops" ]; then
-    while read line; do
+    while read -r line; do
       losetup -d "${line}" || true
     done < "${TEST_DIR}/loops"
   fi
@@ -317,6 +323,7 @@ trap cleanup EXIT HUP INT TERM
 
 # Import all the testsuites
 for suite in suites/*.sh; do
+  # shellcheck disable=SC1090
  . "${suite}"
 done
 
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 2b4edfc4d..5b66560df 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -19,7 +19,7 @@ test_basic_usage() {
   sum=$(lxc image info testimage | grep ^Fingerprint | cut -d' ' -f2)
   lxc image export testimage "${LXD_DIR}/"
   [ "${sum}" = "$(sha256sum "${LXD_DIR}/${sum}.tar.xz" | cut -d' ' -f1)" ]
-  
+
   # Test an alias with slashes
   lxc image show "${sum}"
   lxc image alias create a/b/ "${sum}"
@@ -183,6 +183,7 @@ test_basic_usage() {
   # Test "nonetype" container creation with an LXC config
   wait_for "${LXD_ADDR}" my_curl -X POST "https://${LXD_ADDR}/1.0/containers" \
         -d "{\"name\":\"configtest\",\"config\":{\"raw.lxc\":\"lxc.hook.clone=/bin/true\"},\"source\":{\"type\":\"none\"}}"
+  # shellcheck disable=SC2102
   [ "$(my_curl "https://${LXD_ADDR}/1.0/containers/configtest" | jq -r .metadata.config[\"raw.lxc\"])" = "lxc.hook.clone=/bin/true" ]
   lxc delete configtest
 
diff --git a/test/suites/devlxd.sh b/test/suites/devlxd.sh
index 4d57547a3..3e4f13851 100644
--- a/test/suites/devlxd.sh
+++ b/test/suites/devlxd.sh
@@ -3,8 +3,10 @@
 test_devlxd() {
   ensure_import_testimage
 
+  # shellcheck disable=SC2164
   cd "${TEST_DIR}"
   go build -tags netgo -a -installsuffix devlxd ../deps/devlxd-client.go
+  # shellcheck disable=SC2164
   cd -
 
   lxc launch testimage devlxd
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index f731d2ec8..327ef91bf 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -15,7 +15,7 @@ test_remote_url() {
   for url in "${LXD_ADDR}" "https://${LXD_ADDR}"; do
     lxc_remote remote add test "${url}" --accept-certificate --password foo
     lxc_remote finger test:
-    lxc_remote config trust list | grep @ | awk '{print $2}' | while read line ; do
+    lxc_remote config trust list | grep @ | awk '{print $2}' | while read -r line ; do
       lxc_remote config trust remove "\"${line}\""
     done
     lxc_remote remote remove test
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 301e3ef29..174f5fd29 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 safe_pot_hash() {
-  sed -e "/Project-Id-Version/,/Content-Transfer-Encoding/d" -e "/^#/d" "po/lxd.pot" | tee /tmp/foo | md5sum | cut -f1 -d" "
+  sed -e "/Project-Id-Version/,/Content-Transfer-Encoding/d" -e "/^#/d" "po/lxd.pot" | md5sum | cut -f1 -d" "
 }
 
 test_static_analysis() {

From caa6243c68bf393f5bd599fb909275b21a187b94 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 16 Aug 2016 13:34:20 -0400
Subject: [PATCH 0198/1193] lxd-bridge: Fail on dnsmasq failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd-bridge/lxd-bridge | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
index 15390a3d4..14d8f4d0b 100755
--- a/lxd-bridge/lxd-bridge
+++ b/lxd-bridge/lxd-bridge
@@ -165,7 +165,7 @@ start() {
 
     if [ -n "${LXD_IPV4_ADDR}" ] || [ -n "${LXD_IPV6_ADDR}" ]; then
         # shellcheck disable=SC2086
-        dnsmasq ${LXD_CONFILE_ARG} ${LXD_DOMAIN_ARG} -u "${DNSMASQ_USER}" --strict-order --bind-interfaces --pid-file="${varrun}/dnsmasq.pid" --dhcp-no-override --except-interface=lo --interface="${LXD_BRIDGE}" --dhcp-leasefile="${varlib}/dnsmasq.${LXD_BRIDGE}.leases" --dhcp-authoritative ${LXD_IPV4_ARG} ${LXD_IPV6_ARG} || cleanup
+        dnsmasq ${LXD_CONFILE_ARG} ${LXD_DOMAIN_ARG} -u "${DNSMASQ_USER}" --strict-order --bind-interfaces --pid-file="${varrun}/dnsmasq.pid" --dhcp-no-override --except-interface=lo --interface="${LXD_BRIDGE}" --dhcp-leasefile="${varlib}/dnsmasq.${LXD_BRIDGE}.leases" --dhcp-authoritative ${LXD_IPV4_ARG} ${LXD_IPV6_ARG}
     fi
 
     if [ "${HAS_IPV6}" = "true" ] && [ "${LXD_IPV6_PROXY}" = "true" ]; then

From a311b36ac79ae9915710dcf45c022d4e5174553c Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 17 Aug 2016 10:03:41 -0600
Subject: [PATCH 0199/1193] make TestReaderToChannel transfer smaller

We get intermittent failures like the one below. It looks like the problem is
that the test runners don't have enough randomness to fill up a 64 MB buffer,
so they just fail. Since the point of this test really is to test anything >
the default buffer size which is 128k, 1MB should suffice, and will hopefully
cut down on eating all the entropy from the test runners.

=== RUN   TestReaderToChannel
SIGQUIT: quit
PC=0x45fbe1 m=0

goroutine 0 [idle]:
runtime.futex(0x9f3a90, 0x0, 0x0, 0x0, 0x0, 0x9f3360, 0x0, 0x0, 0x7ffe2e658080, 0x40ff52, ...)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/sys_linux_amd64.s:387 +0x21
runtime.futexsleep(0x9f3a90, 0x0, 0xffffffffffffffff)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/os_linux.go:45 +0x62
runtime.notesleep(0x9f3a90)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/lock_futex.go:145 +0x82
runtime.stopm()
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/proc.go:1594 +0xad
runtime.findrunnable(0xc420022000, 0x0)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/proc.go:2021 +0x228
runtime.schedule()
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/proc.go:2120 +0x14c
runtime.park_m(0xc420095040)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/proc.go:2183 +0x123
runtime.mcall(0x7ffe2e658220)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/asm_amd64.s:240 +0x5b

goroutine 1 [chan receive, 9 minutes]:
testing.(*T).Run(0xc42009a0c0, 0x6bedb9, 0x13, 0x6dedf0, 0xc42005bd01)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/testing/testing.go:647 +0x316
testing.RunTests.func1(0xc42009a0c0)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/testing/testing.go:793 +0x6d
testing.tRunner(0xc42009a0c0, 0xc42004be30)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/testing/testing.go:610 +0x81
testing.RunTests(0x6dee70, 0x9d7e20, 0x6, 0x6, 0x9e0d00)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/testing/testing.go:799 +0x2f5
testing.(*M).Run(0xc42004bef8, 0xc4200d9360)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/testing/testing.go:743 +0x85
main.main()
	github.com/lxc/lxd/shared/_test/_testmain.go:64 +0xc6

goroutine 17 [syscall, 9 minutes, locked to thread]:
runtime.goexit()
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/runtime/asm_amd64.s:2086 +0x1

goroutine 23 [syscall, 6 minutes]:
syscall.Syscall(0x13e, 0xc4224abfff, 0x2000001, 0x0, 0xc420048c38, 0x499389, 0xa0f538)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/syscall/asm_linux_amd64.s:18 +0x5
internal/syscall/unix.GetRandom(0xc4224abfff, 0x2000001, 0x2000001, 0x0, 0xc420048c90, 0x455b20, 0xc4200162c0)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/internal/syscall/unix/getrandom_linux.go:41 +0xdb
crypto/rand.getRandomLinux(0xc4224abfff, 0x2000001, 0x2000001, 0xc)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/crypto/rand/rand_linux.go:37 +0x73
crypto/rand.(*devReader).Read(0xc4200124b0, 0xc4224abfff, 0x2000001, 0x2000001, 0x0, 0x0, 0x0)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/crypto/rand/rand_unix.go:48 +0x423
io.ReadAtLeast(0x9dbc80, 0xc4200124b0, 0xc4204ac000, 0x4000000, 0x4000000, 0x4000000, 0xc420358e20, 0x1, 0xc420358e68)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/io/io.go:307 +0xa4
io.ReadFull(0x9dbc80, 0xc4200124b0, 0xc4204ac000, 0x4000000, 0x4000000, 0xc4200109a0, 0xc42046e018, 0x2000107)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/io/io.go:325 +0x58
crypto/rand.Read(0xc4204ac000, 0x4000000, 0x4000000, 0xc4204ac000, 0x4000000, 0x4000000)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/crypto/rand/rand.go:23 +0x57
github.com/lxc/lxd/shared.TestReaderToChannel(0xc42009a540)
	/lxd/build/tmp.qQKnZ6Eutl/go/src/github.com/lxc/lxd/shared/util_test.go:108 +0x85
testing.tRunner(0xc42009a540, 0x6dedf0)
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/testing/testing.go:610 +0x81
created by testing.(*T).Run
	/lxd/build/tmp.qQKnZ6Eutl/go/golang/src/testing/testing.go:646 +0x2ec

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/util_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/util_test.go b/shared/util_test.go
index 19d07e9b9..22e3d6f8a 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -104,7 +104,7 @@ func TestReadLastNLines(t *testing.T) {
 }
 
 func TestReaderToChannel(t *testing.T) {
-	buf := make([]byte, 64*1024*1024)
+	buf := make([]byte, 1*1024*1024)
 	rand.Read(buf)
 
 	offset := 0

From c1ff46e85afcf10b9a7ea958a1d3aada75406906 Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Thu, 18 Aug 2016 08:34:10 +0300
Subject: [PATCH 0200/1193] Use os.LookupEnv from go 1.5 to find environment
 vars

https://github.com/golang/go/issues/9676#issuecomment-99163260

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 lxc/exec.go | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/lxc/exec.go b/lxc/exec.go
index 87427eda2..70a8f1af9 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -96,11 +96,8 @@ func (c *execCmd) run(config *lxd.Config, args []string) error {
 	}
 
 	env := map[string]string{"HOME": "/root", "USER": "root"}
-	myEnv := os.Environ()
-	for _, ent := range myEnv {
-		if strings.HasPrefix(ent, "TERM=") {
-			env["TERM"] = ent[len("TERM="):]
-		}
+	if myTerm, ok := os.LookupEnv("TERM"); ok {
+		env["TERM"] = myTerm
 	}
 
 	for _, arg := range c.envArgs {

From 9e585b7e29cb6f033e9d57bf6e7954e280a0cca0 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 19 Aug 2016 09:51:33 -0600
Subject: [PATCH 0201/1193] fix spacing alignment in config.go's exmaples

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/config.go |  2 +-
 po/lxd.pot    | 64 +++++++++++++++++++++++++++++------------------------------
 2 files changed, 33 insertions(+), 33 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index 3923cf6f7..3c8a8bf08 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -81,7 +81,7 @@ lxc config trust remove [remote] [hostname|fingerprint]                     Remo
 
 Examples:
 To mount host's /share/c1 onto /opt in the container:
-   lxc config device add [remote:]container1 <device-name> disk source=/share/c1 path=opt
+    lxc config device add [remote:]container1 <device-name> disk source=/share/c1 path=opt
 
 To set an lxc config value:
     lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete = 1'
diff --git a/po/lxd.pot b/po/lxd.pot
index a11172bd1..e1d19c3bf 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-08-15 19:25-0400\n"
+        "POT-Creation-Date: 2016-09-14 01:36-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -77,7 +77,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:596
+#: lxc/image.go:597
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -90,15 +90,15 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:617 lxc/image.go:646
+#: lxc/image.go:618 lxc/image.go:647
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:621
+#: lxc/image.go:622
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:388
+#: lxc/list.go:394
 msgid   "ARCHITECTURE"
 msgstr  ""
 
@@ -145,7 +145,7 @@ msgstr  ""
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:389
+#: lxc/list.go:395
 msgid   "CREATED AT"
 msgstr  ""
 
@@ -187,7 +187,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:700 lxc/profile.go:191
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:701 lxc/profile.go:191
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -259,7 +259,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:620 lxc/image.go:648
+#: lxc/image.go:621 lxc/image.go:649
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -281,7 +281,7 @@ msgstr  ""
 msgid   "Device %s removed from %s"
 msgstr  ""
 
-#: lxc/list.go:472
+#: lxc/list.go:478
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -326,7 +326,7 @@ msgstr  ""
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:273 lxc/image.go:618 lxc/image.go:647
+#: lxc/config.go:273 lxc/image.go:619 lxc/image.go:648
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -365,11 +365,11 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:386
+#: lxc/list.go:392
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:387
+#: lxc/list.go:393
 msgid   "IPV6"
 msgstr  ""
 
@@ -393,12 +393,12 @@ msgstr  ""
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:432
+#: lxc/image.go:433
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:419
+#: lxc/image.go:420
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
@@ -581,7 +581,7 @@ msgid   "Manage configuration.\n"
         "\n"
         "Examples:\n"
         "To mount host's /share/c1 onto /opt in the container:\n"
-        "   lxc config device add [remote:]container1 <device-name> disk source=/share/c1 path=opt\n"
+        "    lxc config device add [remote:]container1 <device-name> disk source=/share/c1 path=opt\n"
         "\n"
         "To set an lxc config value:\n"
         "    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete = 1'\n"
@@ -728,7 +728,7 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:390 lxc/remote.go:353
+#: lxc/list.go:396 lxc/remote.go:353
 msgid   "NAME"
 msgstr  ""
 
@@ -757,7 +757,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:424
+#: lxc/image.go:425
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -765,7 +765,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:519
+#: lxc/image.go:520
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -774,15 +774,15 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:474
+#: lxc/list.go:480
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:391
+#: lxc/list.go:397
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:392
+#: lxc/list.go:398
 msgid   "PROFILES"
 msgstr  ""
 
@@ -790,7 +790,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:619 lxc/remote.go:356
+#: lxc/image.go:620 lxc/remote.go:356
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -829,7 +829,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:701
+#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:702
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -921,15 +921,15 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:622
+#: lxc/image.go:623
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:393
+#: lxc/list.go:399
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:394
+#: lxc/list.go:400
 msgid   "STATE"
 msgstr  ""
 
@@ -1029,7 +1029,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:395
+#: lxc/list.go:401
 msgid   "TYPE"
 msgstr  ""
 
@@ -1070,7 +1070,7 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:411
+#: lxc/image.go:412
 #, c-format
 msgid   "Transferring image: %d%%"
 msgstr  ""
@@ -1088,7 +1088,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:623
+#: lxc/image.go:624
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1184,7 +1184,7 @@ msgstr  ""
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:327 lxc/image.go:599
+#: lxc/image.go:327 lxc/image.go:600
 msgid   "no"
 msgstr  ""
 
@@ -1234,7 +1234,7 @@ msgstr  ""
 msgid   "taken at %s"
 msgstr  ""
 
-#: lxc/exec.go:166
+#: lxc/exec.go:163
 msgid   "unreachable return reached"
 msgstr  ""
 
@@ -1242,7 +1242,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:603
+#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:604
 msgid   "yes"
 msgstr  ""
 

From 6dfa25a67271b3c4754dbb13d746fca42e5a0b11 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 23 Aug 2016 09:56:33 -0400
Subject: [PATCH 0202/1193] retry generating petnames

newer versions of petname have a smaller name list, which increases the
liklehood of conflicts. let's retry a few times to avoid them.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/containers_post.go | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 795fdbd07..faa9f1070 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -394,7 +394,23 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	}
 
 	if req.Name == "" {
-		req.Name = strings.ToLower(petname.Generate(2, "-"))
+		cs, err := dbContainersList(d.db, cTypeRegular)
+		if err != nil {
+			return InternalError(err)
+		}
+
+		i := 0
+		for {
+			i++
+			req.Name = strings.ToLower(petname.Generate(2, "-"))
+			if !shared.StringInSlice(req.Name, cs) {
+				break
+			}
+
+			if i > 100 {
+				return InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries"))
+			}
+		}
 		shared.Debugf("No name provided, creating %s", req.Name)
 	}
 

From 817923bb4fcbbea8f314241688d0a7f1f64fba58 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 24 Aug 2016 12:15:02 -0400
Subject: [PATCH 0203/1193] Return an error on "restart" without force of a
 paused container
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2311

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_state.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/container_state.go b/lxd/container_state.go
index fb0f8152c..2b73320da 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -115,11 +115,16 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 					return err
 				}
 			} else {
+				if c.IsFrozen() {
+					return fmt.Errorf("container is not running")
+				}
+
 				err = c.Shutdown(time.Duration(raw.Timeout) * time.Second)
 				if err != nil {
 					return err
 				}
 			}
+
 			err = c.Start(false)
 			if err != nil {
 				return err

From 982c88973c414bb0ec3bf6defdfeca0b1d3766a8 Mon Sep 17 00:00:00 2001
From: Carlos <cneirabustos at gmail.com>
Date: Thu, 25 Aug 2016 16:04:35 -0300
Subject: [PATCH 0204/1193] Change lxc help to to go to stdout

Closes #2301
Closes #2317

Signed-off-by: Carlos <cneirabustos at gmail.com>
---
 lxc/help.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/help.go b/lxc/help.go
index 678e218ee..b01732a75 100644
--- a/lxc/help.go
+++ b/lxc/help.go
@@ -39,7 +39,7 @@ func (c *helpCmd) run(_ *lxd.Config, args []string) error {
 			if !ok {
 				fmt.Fprintf(os.Stderr, i18n.G("error: unknown command: %s")+"\n", name)
 			} else {
-				fmt.Fprintf(os.Stderr, cmd.usage()+"\n")
+				fmt.Fprintf(os.Stdout, cmd.usage()+"\n")
 			}
 		}
 		return nil

From df23df5b31c419c36bc14051066a82375fe956ef Mon Sep 17 00:00:00 2001
From: Igor Vuk <parcijala at gmail.com>
Date: Tue, 30 Aug 2016 19:08:20 +0200
Subject: [PATCH 0205/1193] Remove trailing spaces in production-setup.md

Signed-off-by: Igor Vuk <parcijala at gmail.com>
---
 doc/production-setup.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/doc/production-setup.md b/doc/production-setup.md
index 1ddd94ab1..c49ece159 100644
--- a/doc/production-setup.md
+++ b/doc/production-setup.md
@@ -1,11 +1,11 @@
 # Introduction
-So you've made it past trying out [LXD live online](https://linuxcontainers.org/lxd/try-it/), 
-or on a server scavanged from random parts. You like what you see, 
+So you've made it past trying out [LXD live online](https://linuxcontainers.org/lxd/try-it/),
+or on a server scavanged from random parts. You like what you see,
 and now you want to try doing some serious work with LXD.
 
-With the vanilla installation of Ubuntu Server 16.04, there will 
-need to be some modifications to the server configuration to avoid 
-common pitfalls when using containers that require tens of thousands 
+With the vanilla installation of Ubuntu Server 16.04, there will
+need to be some modifications to the server configuration to avoid
+common pitfalls when using containers that require tens of thousands
 of file operations.
 
 

From 8f5aeb02af6ec05b78d9361587ff45957c2591e4 Mon Sep 17 00:00:00 2001
From: Igor Vuk <parcijala at gmail.com>
Date: Tue, 30 Aug 2016 19:09:49 +0200
Subject: [PATCH 0206/1193] Fix typos in production-setup.md

Signed-off-by: Igor Vuk <parcijala at gmail.com>
---
 doc/production-setup.md | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/doc/production-setup.md b/doc/production-setup.md
index c49ece159..b68b2bd9a 100644
--- a/doc/production-setup.md
+++ b/doc/production-setup.md
@@ -1,12 +1,11 @@
 # Introduction
 So you've made it past trying out [LXD live online](https://linuxcontainers.org/lxd/try-it/),
-or on a server scavanged from random parts. You like what you see,
+or on a server scavenged from random parts. You like what you see,
 and now you want to try doing some serious work with LXD.
 
-With the vanilla installation of Ubuntu Server 16.04, there will
-need to be some modifications to the server configuration to avoid
-common pitfalls when using containers that require tens of thousands
-of file operations.
+With the vanilla installation of Ubuntu Server 16.04, some modifications
+to the server configuration will be needed, to avoid common pitfalls when
+using containers that require tens of thousands of file operations.
 
 
 ## Common errors that may be encountered

From 2de01432fe7b5331231ba95f77d848d78eada060 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 31 Aug 2016 21:35:01 -0400
Subject: [PATCH 0207/1193] Allow unsetting any config key
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Prevents a DB failure when trying to unset one of the volatile network
keys (name or hwaddr).

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 50b719aab..21d5f6a00 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2120,11 +2120,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
-	err = c.initLXC()
-	if err != nil {
-		return err
-	}
-
 	// Diff the configurations
 	changedConfig := []string{}
 	for key, _ := range oldExpandedConfig {
@@ -2538,6 +2533,14 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
+	// Invalidate the go-lxc cache
+	c.c = nil
+
+	err = c.initLXC()
+	if err != nil {
+		return err
+	}
+
 	// Success, update the closure to mark that the changes should be kept.
 	undoChanges = false
 

From dd9d40f6dc609fd5eaff9b3f7a0e8f65b431ae48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 2 Sep 2016 17:01:00 -0400
Subject: [PATCH 0208/1193] Do our own socket activation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

go-systemd is great but brings a bunch of dependencies we don't care so
much about when all we want is the tiny bit of code parsing the systemd
environment.

This includes a basic re-implementation of this code done from the
systemd documentation. It basically grabs the two env variables, does a
quick sanity check and opens the fds and turns them into network
listeners.

It also then clears the two env variables so we don't confuse anything else.

Closes #2333

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 46 +++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 39 insertions(+), 7 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 0c81e3744..cc4523144 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -24,7 +24,6 @@ import (
 
 	"golang.org/x/crypto/scrypt"
 
-	"github.com/coreos/go-systemd/activation"
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
 	"github.com/syndtr/gocapability/capability"
@@ -840,11 +839,7 @@ func (d *Daemon) Init() error {
 		NotFound.Render(w)
 	})
 
-	listeners, err := activation.Listeners(false)
-	if err != nil {
-		return err
-	}
-
+	listeners := d.GetListeners()
 	if len(listeners) > 0 {
 		shared.Log.Info("LXD is socket activated")
 
@@ -921,7 +916,7 @@ func (d *Daemon) Init() error {
 			shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err})
 		} else {
 			if d.TCPSocket != nil {
-				shared.Log.Info("Replacing systemd TCP socket by configure one")
+				shared.Log.Info("Replacing inherited TCP socket with configured one")
 				d.TCPSocket.Socket.Close()
 			}
 			d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true}
@@ -1204,6 +1199,43 @@ func (d *Daemon) ExpireLogs() error {
 	return nil
 }
 
+func (d *Daemon) GetListeners() []net.Listener {
+	defer func() {
+		os.Unsetenv("LISTEN_PID")
+		os.Unsetenv("LISTEN_FDS")
+	}()
+
+	pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
+	if err != nil {
+		return nil
+	}
+
+	if pid != os.Getpid() {
+		return nil
+	}
+
+	fds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
+	if err != nil {
+		return nil
+	}
+
+	listeners := []net.Listener{}
+
+	for i := 3; i < 3+fds; i++ {
+		syscall.CloseOnExec(i)
+
+		file := os.NewFile(uintptr(i), fmt.Sprintf("inherited-fd%d", i))
+		listener, err := net.FileListener(file)
+		if err != nil {
+			continue
+		}
+
+		listeners = append(listeners, listener)
+	}
+
+	return listeners
+}
+
 type lxdHttpServer struct {
 	r *mux.Router
 	d *Daemon

From a94c4a306568a7de666556ea565e2a4e54a0b6e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 3 Sep 2016 15:28:34 -0400
Subject: [PATCH 0209/1193] doc: Clarify that user_subvol_rm_allowed is needed
 for btrfs nesting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2338

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/storage-backends.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/storage-backends.md b/doc/storage-backends.md
index 7c33328d8..c3db7802d 100644
--- a/doc/storage-backends.md
+++ b/doc/storage-backends.md
@@ -38,6 +38,7 @@ rsync is used to transfer the container content across.
 
  - The btrfs backend is automatically used if /var/lib/lxd is on a btrfs filesystem.
  - Uses a subvolume per container, image and snapshot, creating btrfs snapshots when creating a new object.
+ - When using for nesting, the host btrfs filesystem must be mounted with the "user\_subvol\_rm\_allowed" mount option.
 
 ### LVM
 

From 1260d5c63e8c6989c5874718481d3be2fded879a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 3 Sep 2016 16:03:42 -0400
Subject: [PATCH 0210/1193] Fix listed default value for ZFS pool
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2339

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index 982b65555..f4e9c6f2e 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -818,7 +818,7 @@ func cmdInit() error {
 					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
 					storageMode = "device"
 				} else {
-					storageLoopSize = askInt("Size in GB of the new loop device (1GB minimum) [default=10GB]: ", 1, -1, "10")
+					storageLoopSize = askInt("Size in GB of the new loop device (1GB minimum) [default=10]: ", 1, -1, "10")
 					storageMode = "loop"
 				}
 			} else {

From 375edbddd7d5ce7da5d9056dcaea65400c09daa2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 3 Sep 2016 20:07:57 -0400
Subject: [PATCH 0211/1193] init: Default to "dir" when "zfs" isn't available
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2340

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index f4e9c6f2e..bc9879a31 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -795,7 +795,12 @@ func cmdInit() error {
 			return fmt.Errorf("Init configuration is only valid with --auto")
 		}
 
-		storageBackend = askChoice("Name of the storage backend to use (dir or zfs) [default=zfs]: ", backendsSupported, "zfs")
+		defaultStorage := "dir"
+		if shared.StringInSlice("zfs", backendsAvailable) {
+			defaultStorage = "zfs"
+		}
+
+		storageBackend = askChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), backendsSupported, defaultStorage)
 
 		if !shared.StringInSlice(storageBackend, backendsSupported) {
 			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)

From 7fe5b8ebe52037ea8005cc5699202ce8be1ddf43 Mon Sep 17 00:00:00 2001
From: Anonymous <admin at hda.me>
Date: Mon, 5 Sep 2016 18:09:11 +0000
Subject: [PATCH 0212/1193] Add txqueuelen tweak.

Signed-off-by: Eugene Gusev <admin at hda.me>
---
 doc/production-setup.md | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/doc/production-setup.md b/doc/production-setup.md
index b68b2bd9a..eb71a2ae6 100644
--- a/doc/production-setup.md
+++ b/doc/production-setup.md
@@ -44,3 +44,43 @@ Then, reboot the server.
 
 
 [1]: http://man7.org/linux/man-pages/man7/inotify.7.html
+
+## Network Bandwidth Tweaking 
+If you have at least 1GbE NIC on your lxd host with a lot of local activity (container - container connections, or host - container connections), or you have 1GbE or better internet connection on your lxd host it worth play with txqueuelen. These settings work even better with 10GbE NIC.
+
+### Server Changes
+
+#### txqueuelen 
+
+You need to change `txqueuelen` of your real NIC to 10000 (not sure about the best possible value for you), and change and change lxdbr0 interface `txqueuelen` to 10000.  
+In Debian-based distros you can change `txqueuelen` permanently in `/etc/network/interfaces`  
+You can add for ex.: `up ip link set eth0 txqueuelen 10000` to your interface configuration to set txqueuelen value on boot.  
+For permanent lxdbr0 txqueuelen value change I prefer edit `/usr/lib/lxd/lxd-bridge`. You can add `ifconfig lxdbr0 txqueuelen 10000` in start section, just after iptables rules. For ex.:
+```bash
+iptables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
+iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
+ifconfig lxdbr0 txqueuelen 10000
+```
+If you use lxd master in production or find this inappropriate you can set in `rc.local` or in another way you like.
+You could set it txqueuelen temporary (for test purpose) with `ifconfig interfacename# txqueuelen 10000`
+
+#### /etc/sysctl.conf
+
+You also need to increase `net.core.netdev_max_backlog` value.  
+You can add `net.core.netdev_max_backlog = 182757` to `/etc/sysctl.conf` to set it permanently (after reboot)
+You set `netdev_max_backlog` temporary (for test purpose) with `echo 182757 > /proc/sys/net/core/netdev_max_backlog`
+Note: You can find this value too high, most people prefer set `netdev_max_backlog` = `net.ipv4.tcp_mem` min. value.
+For example I use this values `net.ipv4.tcp_mem = 182757 243679 365514`
+
+### Containers changes
+
+You also need to change txqueuelen value for all you ethernet interfaces in containers.  
+In Debian-based distros you can change txqueuelen permanently in `/etc/network/interfaces`  
+You can add for ex.: `up ip link set eth0 txqueuelen 10000` to your interface configuration to set txqueuelen value on boot.
+
+### Notes regarding this change
+
+10000 txqueuelen value commonly used with 10GbE NICs. Basically small txqueuelen values used with slow devices with a high latency, and higher with devices with low latency. I personally have like 3-5% improvement with these settings for local (host with container, container vs container) and internet connections. Good thing about txqueuelen value tweak, the more containers you use, the more you can be can benefit from this tweak. And you can always temporary set this values and check this tweak in your environment without lxd host reboot.
+
+
+

From 6804da6097c240bb0f027a4fdbd0662bc85f53ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 5 Sep 2016 18:43:26 -0400
Subject: [PATCH 0213/1193] Rework container operation locking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This should fix a number of race conditions around start, stop and shutdown.

Closes #2297

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 215 +++++++++++++++++++++++++++++++++------------------
 1 file changed, 138 insertions(+), 77 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 21d5f6a00..f1d287795 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -28,9 +28,55 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-// Global variables
-var lxcStoppingContainersLock sync.Mutex
-var lxcStoppingContainers map[int]*sync.WaitGroup = make(map[int]*sync.WaitGroup)
+// Operation locking
+type lxcContainerOperation struct {
+	action   string
+	chanDone chan error
+	err      error
+	id       int
+	timeout  int
+}
+
+func (op *lxcContainerOperation) Create(id int, action string, timeout int) *lxcContainerOperation {
+	op.id = id
+	op.action = action
+	op.timeout = timeout
+	op.chanDone = make(chan error, 0)
+
+	if timeout > 1 {
+		go func(op *lxcContainerOperation) {
+			time.Sleep(time.Second * time.Duration(op.timeout))
+			op.Done(fmt.Errorf("Container %s operation timed out after %d seconds", op.action, op.timeout))
+		}(op)
+	}
+
+	return op
+}
+
+func (op *lxcContainerOperation) Wait() error {
+	<-op.chanDone
+
+	return op.err
+}
+
+func (op *lxcContainerOperation) Done(err error) {
+	lxcContainerOperationsLock.Lock()
+	defer lxcContainerOperationsLock.Unlock()
+
+	// Check if already done
+	runningOp, ok := lxcContainerOperations[op.id]
+	if !ok || runningOp != op {
+		return
+	}
+
+	op.err = err
+	close(op.chanDone)
+
+	delete(lxcContainerOperations, op.id)
+}
+
+var lxcContainerOperationsLock sync.Mutex
+var lxcContainerOperations map[int]*lxcContainerOperation = make(map[int]*lxcContainerOperation)
 
 // Helper functions
 func lxcSetConfigItem(c *lxc.Container, key string, value string) error {
@@ -245,6 +291,51 @@ type containerLXC struct {
 	storage  storage
 }
 
+func (c *containerLXC) createOperation(action string, timeout int) (*lxcContainerOperation, error) {
+	op, _ := c.getOperation("")
+	if op != nil {
+		return nil, fmt.Errorf("Container is already running a %s operation", op.action)
+	}
+
+	lxcContainerOperationsLock.Lock()
+	defer lxcContainerOperationsLock.Unlock()
+
+	op = &lxcContainerOperation{}
+	op.Create(c.id, action, timeout)
+	lxcContainerOperations[c.id] = op
+
+	return lxcContainerOperations[c.id], nil
+}
+
+func (c *containerLXC) getOperation(action string) (*lxcContainerOperation, error) {
+	lxcContainerOperationsLock.Lock()
+	defer lxcContainerOperationsLock.Unlock()
+
+	op := lxcContainerOperations[c.id]
+
+	if op == nil {
+		return nil, fmt.Errorf("No running %s container operation", action)
+	}
+
+	if action != "" && op.action != action {
+		return nil, fmt.Errorf("Container is running a %s operation, not a %s operation", op.action, action)
+	}
+
+	return op, nil
+}
+
+func (c *containerLXC) waitOperation() error {
+	op, _ := c.getOperation("")
+	if op != nil {
+		err := op.Wait()
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 func (c *containerLXC) init() error {
 	// Compute the expanded config and device list
 	err := c.expandConfig()
@@ -1195,15 +1286,15 @@ func (c *containerLXC) startCommon() (string, error) {
 }
 
 func (c *containerLXC) Start(stateful bool) error {
-	// Wait for container tear down to finish
-	lxcStoppingContainersLock.Lock()
-	wgStopping, stopping := lxcStoppingContainers[c.id]
-	lxcStoppingContainersLock.Unlock()
-	if stopping {
-		wgStopping.Wait()
+	// Setup a new operation
+	op, err := c.createOperation("start", 30)
+	if err != nil {
+		return err
 	}
+	defer op.Done(nil)
 
-	if err := setupSharedMounts(); err != nil {
+	err = setupSharedMounts()
+	if err != nil {
 		return fmt.Errorf("Daemon failed to setup shared mounts base: %s.\nDoes security.nesting need to be turned on?", err)
 	}
 
@@ -1374,35 +1465,14 @@ func (c *containerLXC) OnStart() error {
 	return nil
 }
 
-// Container shutdown locking
-func (c *containerLXC) setupStopping() *sync.WaitGroup {
-	// Handle locking
-	lxcStoppingContainersLock.Lock()
-	defer lxcStoppingContainersLock.Unlock()
-
-	// Existing entry
-	wg, stopping := lxcStoppingContainers[c.id]
-	if stopping {
-		return wg
-	}
-
-	// Setup new entry
-	lxcStoppingContainers[c.id] = &sync.WaitGroup{}
-
-	go func(wg *sync.WaitGroup, id int) {
-		wg.Wait()
-
-		lxcStoppingContainersLock.Lock()
-		defer lxcStoppingContainersLock.Unlock()
-
-		delete(lxcStoppingContainers, id)
-	}(lxcStoppingContainers[c.id], c.id)
-
-	return lxcStoppingContainers[c.id]
-}
-
 // Stop functions
 func (c *containerLXC) Stop(stateful bool) error {
+	// Setup a new operation
+	op, err := c.createOperation("stop", 30)
+	if err != nil {
+		return err
+	}
+
 	// Handle stateful stop
 	if stateful {
 		// Cleanup any existing state
@@ -1411,85 +1481,73 @@ func (c *containerLXC) Stop(stateful bool) error {
 
 		err := os.MkdirAll(stateDir, 0700)
 		if err != nil {
+			op.Done(err)
 			return err
 		}
 
 		// Checkpoint
 		err = c.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", true)
 		if err != nil {
+			op.Done(err)
 			return err
 		}
 
 		c.stateful = true
 		err = dbContainerSetStateful(c.daemon.db, c.id, true)
 		if err != nil {
+			op.Done(err)
 			return err
 		}
 
+		op.Done(nil)
 		return nil
 	}
 
 	// Load the go-lxc struct
-	err := c.initLXC()
+	err = c.initLXC()
 	if err != nil {
+		op.Done(err)
 		return err
 	}
 
 	// Attempt to freeze the container first, helps massively with fork bombs
 	c.Freeze()
 
-	// Handle locking
-	wg := c.setupStopping()
-
-	// Stop the container
-	wg.Add(1)
 	if err := c.c.Stop(); err != nil {
-		wg.Done()
+		op.Done(err)
 		return err
 	}
 
-	// Mark ourselves as done
-	wg.Done()
-
-	// Wait for any other teardown routines to finish
-	wg.Wait()
-
-	return nil
+	return op.Wait()
 }
 
 func (c *containerLXC) Shutdown(timeout time.Duration) error {
-	// Load the go-lxc struct
-	err := c.initLXC()
+	// Setup a new operation
+	op, err := c.createOperation("shutdown", 30)
 	if err != nil {
 		return err
 	}
 
-	// Handle locking
-	wg := c.setupStopping()
+	// Load the go-lxc struct
+	err = c.initLXC()
+	if err != nil {
+		op.Done(err)
+		return err
+	}
 
-	// Shutdown the container
-	wg.Add(1)
 	if err := c.c.Shutdown(timeout); err != nil {
-		wg.Done()
+		op.Done(err)
 		return err
 	}
 
-	// Mark ourselves as done
-	wg.Done()
-
-	// Wait for any other teardown routines to finish
-	wg.Wait()
-
-	return nil
+	return op.Wait()
 }
 
 func (c *containerLXC) OnStop(target string) error {
-	// Get locking
-	lxcStoppingContainersLock.Lock()
-	wg, stopping := lxcStoppingContainers[c.id]
-	lxcStoppingContainersLock.Unlock()
-	if wg != nil {
-		wg.Add(1)
+	// Get operation
+	op, _ := c.getOperation("")
+	if op != nil && !shared.StringInSlice(op.action, []string{"stop", "shutdown"}) {
+		return fmt.Errorf("Container is already running a %s operation", op.action)
 	}
 
 	// Make sure we can't call go-lxc functions by mistake
@@ -1498,7 +1556,10 @@ func (c *containerLXC) OnStop(target string) error {
 	// Stop the storage for this container
 	err := c.StorageStop()
 	if err != nil {
-		wg.Done()
+		if op != nil {
+			op.Done(err)
+		}
+
 		return err
 	}
 
@@ -1506,15 +1567,15 @@ func (c *containerLXC) OnStop(target string) error {
 	AAUnloadProfile(c)
 
 	// FIXME: The go routine can go away once we can rely on LXC_TARGET
-	go func(c *containerLXC, target string, wg *sync.WaitGroup) {
+	go func(c *containerLXC, target string, op *lxcContainerOperation) {
 		c.fromHook = false
 
 		// Unlock on return
-		if wg != nil {
-			defer wg.Done()
+		if op != nil {
+			defer op.Done(nil)
 		}
 
-		if target == "unknown" && stopping {
+		if target == "unknown" && op != nil {
 			target = "stop"
 		}
 
@@ -1577,7 +1638,7 @@ func (c *containerLXC) OnStop(target string) error {
 		if c.ephemeral {
 			c.Delete()
 		}
-	}(c, target, wg)
+	}(c, target, op)
 
 	return nil
 }

From ab28d6f34f7c6b0a2c3d7a1c2c00666b0f8e235e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Sep 2016 12:02:16 -0400
Subject: [PATCH 0214/1193] init: Change default host to all (::)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

So we bind all IPv6 addresses too

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index bc9879a31..ba678bb65 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -859,7 +859,11 @@ they otherwise would.
 				return ""
 			}
 
-			networkAddress = askString("Address to bind LXD to (not including port) [default=0.0.0.0]: ", "0.0.0.0", isIPAddress)
+			networkAddress = askString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
+			if networkAddress == "all" {
+				networkAddress = "::"
+			}
+
 			if net.ParseIP(networkAddress).To4() == nil {
 				networkAddress = fmt.Sprintf("[%s]", networkAddress)
 			}

From 24d86c2fac5a012dda2842ff70bb91967dcb42b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Sep 2016 19:05:40 -0400
Subject: [PATCH 0215/1193] Cleaner error on existing profile name
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/profiles.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/profiles.go b/lxd/profiles.go
index 9e49da47d..a6250b2d6 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -67,6 +67,11 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
+	_, profile, _ := dbProfileGet(d.db, req.Name)
+	if profile != nil {
+		return BadRequest(fmt.Errorf("The profile already exists"))
+	}
+
 	if strings.Contains(req.Name, "/") {
 		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
 	}

From 60b4593caa254e8f3cb43daacaba896f727f65ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 01:00:17 -0400
Subject: [PATCH 0216/1193] Properly cleanup on profile removal
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2347

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_profiles.go | 20 +++++++++++++++++---
 lxd/patches.go     | 16 ++++++++++++++++
 lxd/profiles.go    |  5 +++++
 test/main.sh       | 11 +++++++++++
 4 files changed, 49 insertions(+), 3 deletions(-)

diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index c3a5ff192..6e440ab99 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -187,19 +187,33 @@ func dbProfileConfig(db *sql.DB, name string) (map[string]string, error) {
 }
 
 func dbProfileDelete(db *sql.DB, name string) error {
+	id, _, err := dbProfileGet(db, name)
+	if err != nil {
+		return err
+	}
+
 	tx, err := dbBegin(db)
 	if err != nil {
 		return err
 	}
-	_, err = tx.Exec("DELETE FROM profiles WHERE name=?", name)
+
+	_, err = tx.Exec("DELETE FROM profiles WHERE id=?", id)
 	if err != nil {
 		tx.Rollback()
 		return err
 	}
 
-	err = txCommit(tx)
+	err = dbProfileConfigClear(tx, id)
+	if err != nil {
+		return err
+	}
 
-	return err
+	_, err = tx.Exec("DELETE FROM containers_profiles WHERE profile_id=?", id)
+	if err != nil {
+		return err
+	}
+
+	return txCommit(tx)
 }
 
 func dbProfileUpdate(db *sql.DB, name string, newName string) error {
diff --git a/lxd/patches.go b/lxd/patches.go
index 72360d90e..c1a1779bd 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -27,6 +27,7 @@ import (
 
 var patches = []patch{
 	patch{name: "invalid_profile_names", run: patchInvalidProfileNames},
+	patch{name: "leftover_profile_config", run: patchLeftoverProfileConfig},
 }
 
 type patch struct {
@@ -71,6 +72,21 @@ func patchesApplyAll(d *Daemon) error {
 }
 
 // Patches begin here
+func patchLeftoverProfileConfig(name string, d *Daemon) error {
+	stmt := `
+DELETE FROM profiles_config WHERE profile_id NOT IN (SELECT id FROM profiles);
+DELETE FROM profiles_devices WHERE profile_id NOT IN (SELECT id FROM profiles);
+DELETE FROM profiles_devices_config WHERE profile_device_id NOT IN (SELECT id FROM profiles_devices);
+`
+
+	_, err := d.db.Exec(stmt)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func patchInvalidProfileNames(name string, d *Daemon) error {
 	profiles, err := dbProfiles(d.db)
 	if err != nil {
diff --git a/lxd/profiles.go b/lxd/profiles.go
index a6250b2d6..c61965dfc 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -287,6 +287,11 @@ func profileDelete(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
+	clist := getRunningContainersWithProfile(d, name)
+	if len(clist) != 0 {
+		return BadRequest(fmt.Errorf("Profile is currently in use"))
+	}
+
 	err = dbProfileDelete(d.db, name)
 	if err != nil {
 		return SmartError(err)
diff --git a/test/main.sh b/test/main.sh
index e41bbb0ec..f02321590 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -201,6 +201,12 @@ kill_lxd() {
       lxc image delete "${image}" --force-local || true
     done
 
+    # Delete all profiles
+    echo "==> Deleting all profiles"
+    for profile in $(lxc profile list --force-local); do
+      lxc profile delete "${profile}" --force-local || true
+    done
+
     echo "==> Checking for locked DB tables"
     for table in $(echo .tables | sqlite3 "${daemon_dir}/lxd.db"); do
       echo "SELECT * FROM ${table};" | sqlite3 "${daemon_dir}/lxd.db" >/dev/null
@@ -243,6 +249,11 @@ kill_lxd() {
   check_empty_table "${daemon_dir}/lxd.db" "images"
   check_empty_table "${daemon_dir}/lxd.db" "images_aliases"
   check_empty_table "${daemon_dir}/lxd.db" "images_properties"
+  check_empty_table "${daemon_dir}/lxd.db" "images_source"
+  check_empty_table "${daemon_dir}/lxd.db" "profiles"
+  check_empty_table "${daemon_dir}/lxd.db" "profiles_config"
+  check_empty_table "${daemon_dir}/lxd.db" "profiles_devices"
+  check_empty_table "${daemon_dir}/lxd.db" "profiles_devices_config"
 
   # teardown storage
   "$LXD_BACKEND"_teardown "${daemon_dir}"

From ecae385454e02f5fa2929a212947ab3eddf54ade Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 01:27:44 -0400
Subject: [PATCH 0217/1193] test: Only check leftovers on active LXD
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We can't clean the objects on a dead LXD so there's no point in checking
the filesystem for its post-cleanup state.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh             | 61 ++++++++++++++++++++++++++----------------------
 test/suites/profiling.sh |  4 ++++
 2 files changed, 37 insertions(+), 28 deletions(-)

diff --git a/test/main.sh b/test/main.sh
index f02321590..888c610f2 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -186,6 +186,7 @@ kill_lxd() {
   daemon_dir=${1}
   LXD_DIR=${daemon_dir}
   daemon_pid=$(cat "${daemon_dir}/lxd.pid")
+  check_leftovers="false"
   echo "==> Killing LXD at ${daemon_dir}"
 
   if [ -e "${daemon_dir}/unix.socket" ]; then
@@ -217,6 +218,8 @@ kill_lxd() {
 
     # Cleanup shmounts (needed due to the forceful kill)
     find "${daemon_dir}" -name shmounts -exec "umount" "-l" "{}" \; >/dev/null 2>&1 || true
+
+    check_leftovers="true"
   fi
 
   if [ -n "${LXD_LOGS:-}" ]; then
@@ -226,34 +229,36 @@ kill_lxd() {
     cp "${daemon_dir}/lxd.log" "${LXD_LOGS}/${daemon_pid}/"
   fi
 
-  echo "==> Checking for leftover files"
-  rm -f "${daemon_dir}/containers/lxc-monitord.log"
-  rm -f "${daemon_dir}/security/apparmor/cache/.features"
-  check_empty "${daemon_dir}/containers/"
-  check_empty "${daemon_dir}/devices/"
-  check_empty "${daemon_dir}/images/"
-  # FIXME: Once container logging rework is done, uncomment
-  # check_empty "${daemon_dir}/logs/"
-  check_empty "${daemon_dir}/security/apparmor/cache/"
-  check_empty "${daemon_dir}/security/apparmor/profiles/"
-  check_empty "${daemon_dir}/security/seccomp/"
-  check_empty "${daemon_dir}/shmounts/"
-  check_empty "${daemon_dir}/snapshots/"
-
-  echo "==> Checking for leftover DB entries"
-  check_empty_table "${daemon_dir}/lxd.db" "containers"
-  check_empty_table "${daemon_dir}/lxd.db" "containers_config"
-  check_empty_table "${daemon_dir}/lxd.db" "containers_devices"
-  check_empty_table "${daemon_dir}/lxd.db" "containers_devices_config"
-  check_empty_table "${daemon_dir}/lxd.db" "containers_profiles"
-  check_empty_table "${daemon_dir}/lxd.db" "images"
-  check_empty_table "${daemon_dir}/lxd.db" "images_aliases"
-  check_empty_table "${daemon_dir}/lxd.db" "images_properties"
-  check_empty_table "${daemon_dir}/lxd.db" "images_source"
-  check_empty_table "${daemon_dir}/lxd.db" "profiles"
-  check_empty_table "${daemon_dir}/lxd.db" "profiles_config"
-  check_empty_table "${daemon_dir}/lxd.db" "profiles_devices"
-  check_empty_table "${daemon_dir}/lxd.db" "profiles_devices_config"
+  if [ "${check_leftovers}" = "true" ]; then
+    echo "==> Checking for leftover files"
+    rm -f "${daemon_dir}/containers/lxc-monitord.log"
+    rm -f "${daemon_dir}/security/apparmor/cache/.features"
+    check_empty "${daemon_dir}/containers/"
+    check_empty "${daemon_dir}/devices/"
+    check_empty "${daemon_dir}/images/"
+    # FIXME: Once container logging rework is done, uncomment
+    # check_empty "${daemon_dir}/logs/"
+    check_empty "${daemon_dir}/security/apparmor/cache/"
+    check_empty "${daemon_dir}/security/apparmor/profiles/"
+    check_empty "${daemon_dir}/security/seccomp/"
+    check_empty "${daemon_dir}/shmounts/"
+    check_empty "${daemon_dir}/snapshots/"
+
+    echo "==> Checking for leftover DB entries"
+    check_empty_table "${daemon_dir}/lxd.db" "containers"
+    check_empty_table "${daemon_dir}/lxd.db" "containers_config"
+    check_empty_table "${daemon_dir}/lxd.db" "containers_devices"
+    check_empty_table "${daemon_dir}/lxd.db" "containers_devices_config"
+    check_empty_table "${daemon_dir}/lxd.db" "containers_profiles"
+    check_empty_table "${daemon_dir}/lxd.db" "images"
+    check_empty_table "${daemon_dir}/lxd.db" "images_aliases"
+    check_empty_table "${daemon_dir}/lxd.db" "images_properties"
+    check_empty_table "${daemon_dir}/lxd.db" "images_source"
+    check_empty_table "${daemon_dir}/lxd.db" "profiles"
+    check_empty_table "${daemon_dir}/lxd.db" "profiles_config"
+    check_empty_table "${daemon_dir}/lxd.db" "profiles_devices"
+    check_empty_table "${daemon_dir}/lxd.db" "profiles_devices_config"
+  fi
 
   # teardown storage
   "$LXD_BACKEND"_teardown "${daemon_dir}"
diff --git a/test/suites/profiling.sh b/test/suites/profiling.sh
index 519d5fe2c..193a22fa1 100644
--- a/test/suites/profiling.sh
+++ b/test/suites/profiling.sh
@@ -11,6 +11,10 @@ test_cpu_profiling() {
   echo top5 | go tool pprof "$(which lxd)" "${LXD3_DIR}/cpu.out"
   echo ""
 
+  # Cleanup following manual kill
+  rm -f "${LXD3_DIR}/unix.socket"
+  find "${LXD3_DIR}" -name shmounts -exec "umount" "-l" "{}" \; >/dev/null 2>&1 || true
+
   kill_lxd "${LXD3_DIR}"
 }
 

From 4dcb845f552138a866aa9b74b2f45ec829615d3a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 17:20:08 -0400
Subject: [PATCH 0218/1193] Consistently handle name conflicts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

All of those were documented as returning a 409 and clearly weren't :)

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_post.go     |  6 ++++++
 lxd/container_snapshot.go | 14 +++++++++++---
 lxd/images.go             |  6 ++++++
 lxd/profiles.go           |  6 ++++++
 4 files changed, 29 insertions(+), 3 deletions(-)

diff --git a/lxd/container_post.go b/lxd/container_post.go
index 58ebf07ba..4139dd756 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -47,6 +47,12 @@ func containerPost(d *Daemon, r *http.Request) Response {
 		return OperationResponse(op)
 	}
 
+	// Check that the name isn't already in use
+	id, _ := dbContainerId(d.db, body.Name)
+	if id > 0 {
+		return Conflict
+	}
+
 	run := func(*operation) error {
 		return c.Rename(body.Name)
 	}
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index 803ac11ec..ff5eca84f 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -174,7 +174,7 @@ func snapshotHandler(d *Daemon, r *http.Request) Response {
 	case "GET":
 		return snapshotGet(sc, snapshotName)
 	case "POST":
-		return snapshotPost(r, sc, containerName)
+		return snapshotPost(d, r, sc, containerName)
 	case "DELETE":
 		return snapshotDelete(sc, snapshotName)
 	default:
@@ -191,7 +191,7 @@ func snapshotGet(sc container, name string) Response {
 	return SyncResponse(true, render.(*shared.SnapshotInfo))
 }
 
-func snapshotPost(r *http.Request, sc container, containerName string) Response {
+func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) Response {
 	raw := shared.Jmap{}
 	if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
 		return BadRequest(err)
@@ -220,8 +220,16 @@ func snapshotPost(r *http.Request, sc container, containerName string) Response
 		return BadRequest(err)
 	}
 
+	fullName := containerName + shared.SnapshotDelimiter + newName
+
+	// Check that the name isn't already in use
+	id, _ := dbContainerId(d.db, fullName)
+	if id > 0 {
+		return Conflict
+	}
+
 	rename := func(op *operation) error {
-		return sc.Rename(containerName + shared.SnapshotDelimiter + newName)
+		return sc.Rename(fullName)
 	}
 
 	resources := map[string][]string{}
diff --git a/lxd/images.go b/lxd/images.go
index 25bb6294f..8de27ea2f 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1203,6 +1203,12 @@ func aliasPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
+	// Check that the name isn't already in use
+	id, _, _ := dbImageAliasGet(d.db, req.Name, true)
+	if id > 0 {
+		return Conflict
+	}
+
 	id, _, err := dbImageAliasGet(d.db, name, true)
 	if err != nil {
 		return SmartError(err)
diff --git a/lxd/profiles.go b/lxd/profiles.go
index c61965dfc..a1b70f1d9 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -262,6 +262,12 @@ func profilePost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
+	// Check that the name isn't already in use
+	id, _, _ := dbProfileGet(d.db, req.Name)
+	if id > 0 {
+		return Conflict
+	}
+
 	if strings.Contains(req.Name, "/") {
 		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
 	}

From cc48e183df0b2ab882539c5ca16bac341398bffa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 9 Sep 2016 12:59:44 -0400
Subject: [PATCH 0219/1193] Fix crash in lxd-bridge-proxy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd-bridge/lxd-bridge-proxy/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd-bridge/lxd-bridge-proxy/main.go b/lxd-bridge/lxd-bridge-proxy/main.go
index a73b74e31..d68043905 100644
--- a/lxd-bridge/lxd-bridge-proxy/main.go
+++ b/lxd-bridge/lxd-bridge-proxy/main.go
@@ -11,5 +11,5 @@ func main() {
 	addr := flag.String("addr", "[fe80::1%lxdbr0]:13128", "proxy listen address")
 	flag.Parse()
 
-	log.Fatal(http.ListenAndServe(*addr, &httputil.ReverseProxy{}))
+	log.Fatal(http.ListenAndServe(*addr, &httputil.ReverseProxy{Director: func(req *http.Request) {}}))
 }

From ca57370764208eaf18d61e0e29ae4b4076ca575a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 9 Sep 2016 15:25:56 +0200
Subject: [PATCH 0220/1193] container_lxc: handle xattrs

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go |  7 +++-
 shared/util_linux.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 105 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f1d287795..370b558a6 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3447,7 +3447,12 @@ func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *t
 		}
 	}
 
-	// TODO: handle xattrs
+	// Handle xattrs.
+	hdr.Xattrs, err = shared.GetAllXattr(path)
+	if err != nil {
+		return err
+	}
+
 	if err := tw.WriteHeader(hdr); err != nil {
 		return fmt.Errorf("error writing header: %s", err)
 	}
diff --git a/shared/util_linux.go b/shared/util_linux.go
index d9e7bc16e..35daf13ca 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -6,8 +6,10 @@ package shared
 import (
 	"errors"
 	"fmt"
+	"golang.org/x/sys/unix"
 	"os"
 	"os/exec"
+	"strings"
 	"syscall"
 	"unsafe"
 )
@@ -384,3 +386,100 @@ func SetSize(fd int, width int, height int) (err error) {
 	}
 	return nil
 }
+
+// This uses ssize_t llistxattr(const char *path, char *list, size_t size); to
+// handle symbolic links (should it in the future be possible to set extended
+// attributed on symlinks): If path is a symbolic link the extended attributes
+// associated with the link itself are retrieved.
+func llistxattr(path string, list []byte) (sz int, err error) {
+	var _p0 *byte
+	_p0, err = unix.BytePtrFromString(path)
+	if err != nil {
+		return
+	}
+	var _p1 unsafe.Pointer
+	if len(list) > 0 {
+		_p1 = unsafe.Pointer(&list[0])
+	} else {
+		_p1 = unsafe.Pointer(nil)
+	}
+	r0, _, e1 := unix.Syscall(unix.SYS_LLISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(list)))
+	sz = int(r0)
+	if e1 != 0 {
+		err = e1
+	}
+	return
+}
+
+// GetAllXattr retrieves all extended attributes associated with a file,
+// directory or symbolic link.
+func GetAllXattr(path string) (xattrs map[string]string, err error) {
+	e1 := fmt.Errorf("Extended attributes changed during retrieval.")
+
+	// Call llistxattr() twice: First, to determine the size of the buffer
+	// we need to allocate to store the extended attributes, second, to
+	// actually store the extended attributes in the buffer. Also, check if
+	// the size/number of extended attributes hasn't changed between the two
+	// calls.
+	pre, err := llistxattr(path, nil)
+	if err != nil || pre < 0 {
+		return nil, err
+	}
+	if pre == 0 {
+		return nil, nil
+	}
+
+	dest := make([]byte, pre)
+
+	post, err := llistxattr(path, dest)
+	if err != nil || post < 0 {
+		return nil, err
+	}
+	if post != pre {
+		return nil, e1
+	}
+
+	split := strings.Split(string(dest), "\x00")
+	if split == nil {
+		return nil, fmt.Errorf("No valid extended attribute key found.")
+	}
+	// *listxattr functions return a list of  names  as  an unordered array
+	// of null-terminated character strings (attribute names are separated
+	// by null bytes ('\0')), like this: user.name1\0system.name1\0user.name2\0
+	// Since we split at the '\0'-byte the last element of the slice will be
+	// the empty string. We remove it:
+	if split[len(split)-1] == "" {
+		split = split[:len(split)-1]
+	}
+
+	xattrs = make(map[string]string, len(split))
+
+	for _, x := range split {
+		xattr := string(x)
+		// Call Getxattr() twice: First, to determine the size of the
+		// buffer we need to allocate to store the extended attributes,
+		// second, to actually store the extended attributes in the
+		// buffer. Also, check if the size of the extended attribute
+		// hasn't changed between the two calls.
+		pre, err = unix.Getxattr(path, xattr, nil)
+		if err != nil || pre < 0 {
+			return nil, err
+		}
+		if pre == 0 {
+			return nil, fmt.Errorf("No valid extended attribute value found.")
+		}
+
+		dest = make([]byte, pre)
+		post, err = unix.Getxattr(path, xattr, dest)
+		if err != nil || post < 0 {
+			return nil, err
+		}
+		if post != pre {
+			return nil, e1
+		}
+
+		xattrs[xattr] = string(dest)
+	}
+
+	return xattrs, nil
+}

From f8b9dd92f31aeffe061f1bc249b7458c34282fca Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Mon, 12 Sep 2016 13:14:31 +0200
Subject: [PATCH 0221/1193] tests: add test for GetAllXattr()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/util_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

diff --git a/shared/util_test.go b/shared/util_test.go
index 22e3d6f8a..8ca27acfc 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -4,12 +4,88 @@ import (
 	"bytes"
 	"crypto/rand"
 	"fmt"
+	"golang.org/x/sys/unix"
 	"io/ioutil"
 	"os"
 	"strings"
 	"testing"
 )
 
+func TestGetAllXattr(t *testing.T) {
+	var (
+		err       error
+		testxattr = map[string]string{
+			"user.checksum": "asdfsf13434qwf1324",
+			"user.random":   "This is a test",
+		}
+	)
+	xattrFile, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	defer os.Remove(xattrFile.Name())
+	xattrFile.Close()
+
+	xattrDir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	defer os.Remove(xattrDir)
+
+	for k, v := range testxattr {
+		if err = unix.Setxattr(xattrFile.Name(), k, []byte(v), 0); err != nil {
+			t.Error(err)
+			return
+		}
+		if err = unix.Setxattr(xattrDir, k, []byte(v), 0); err != nil {
+			t.Error(err)
+			return
+		}
+	}
+
+	// Test retrieval of extended attributes for regular files.
+	h, err := GetAllXattr(xattrFile.Name())
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if h == nil {
+		t.Errorf("Expected to find extended attributes but did not find any.")
+		return
+	}
+
+	for k, v := range h {
+		found, ok := h[k]
+		if !ok || found != testxattr[k] {
+			t.Errorf("Expected to find extended attribute %s with a value of %s on regular file but did not find it.", k, v)
+			return
+		}
+	}
+
+	// Test retrieval of extended attributes for directories.
+	h, err = GetAllXattr(xattrDir)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if h == nil {
+		t.Errorf("Expected to find extended attributes but did not find any.")
+		return
+	}
+
+	for k, v := range h {
+		found, ok := h[k]
+		if !ok || found != testxattr[k] {
+			t.Errorf("Expected to find extended attribute %s with a value of %s on directory but did not find it.", k, v)
+			return
+		}
+	}
+}
+
 func TestFileCopy(t *testing.T) {
 	helloWorld := []byte("hello world\n")
 	source, err := ioutil.TempFile("", "")

From 9b0bad7a22fa55316b888cd7642a59f27f154e69 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Mon, 12 Sep 2016 14:59:42 +0200
Subject: [PATCH 0222/1193] tests: skip tests when xatts are not supported

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/util_test.go | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/shared/util_test.go b/shared/util_test.go
index 8ca27acfc..386a7a34e 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -35,11 +35,21 @@ func TestGetAllXattr(t *testing.T) {
 	defer os.Remove(xattrDir)
 
 	for k, v := range testxattr {
-		if err = unix.Setxattr(xattrFile.Name(), k, []byte(v), 0); err != nil {
+		err = unix.Setxattr(xattrFile.Name(), k, []byte(v), 0)
+		if err == unix.ENOTSUP {
+			t.Log(err)
+			return
+		}
+		if err != nil {
 			t.Error(err)
 			return
 		}
-		if err = unix.Setxattr(xattrDir, k, []byte(v), 0); err != nil {
+		err = unix.Setxattr(xattrDir, k, []byte(v), 0)
+		if err == unix.ENOTSUP {
+			t.Log(err)
+			return
+		}
+		if err != nil {
 			t.Error(err)
 			return
 		}

From 5f8cbae9ee281cac417190cb7743504919b02e4a Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 12 Sep 2016 10:37:51 -0600
Subject: [PATCH 0223/1193] init: use more intelligent logic for partition
 sizing

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/main.go | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index ba678bb65..de1802844 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -823,7 +823,23 @@ func cmdInit() error {
 					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
 					storageMode = "device"
 				} else {
-					storageLoopSize = askInt("Size in GB of the new loop device (1GB minimum) [default=10]: ", 1, -1, "10")
+					st := syscall.Statfs_t{}
+					err := syscall.Statfs(shared.VarPath(), &st)
+					if err != nil {
+						return fmt.Errorf("couldn't statfs %s: %s", shared.VarPath(), err)
+					}
+
+					/* choose 15 GB < x < 100GB, where x is 20% of the disk size */
+					def := uint64(st.Frsize) * st.Blocks / (1024 * 1024 * 1024) / 5
+					if def > 100 {
+						def = 100
+					}
+					if def < 15 {
+						def = 15
+					}
+
+					q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%d]: ", def)
+					storageLoopSize = askInt(q, 1, -1, fmt.Sprintf("%d", def))
 					storageMode = "loop"
 				}
 			} else {

From a9980bd51586b0044e6d77d27441b1755ecee0b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 13 Sep 2016 00:11:46 -0400
Subject: [PATCH 0224/1193] Fix support for lzma alone file format
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2360

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lxd/images.go b/lxd/images.go
index 8de27ea2f..6213296e1 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -64,12 +64,14 @@ func detectCompression(fname string) ([]string, string, error) {
 		return []string{"-Jxf"}, ".tar.xz", nil
 	case (bytes.Equal(header[1:5], []byte{'7', 'z', 'X', 'Z'}) && header[0] != 0xFD):
 		return []string{"--lzma", "-xf"}, ".tar.lzma", nil
+	case bytes.Equal(header[0:3], []byte{0x5d, 0x00, 0x00}):
+		return []string{"--lzma", "-xf"}, ".tar.lzma", nil
 	case bytes.Equal(header[257:262], []byte{'u', 's', 't', 'a', 'r'}):
 		return []string{"-xf"}, ".tar", nil
 	case bytes.Equal(header[0:4], []byte{'h', 's', 'q', 's'}):
 		return []string{""}, ".squashfs", nil
 	default:
-		return []string{""}, "", fmt.Errorf("Unsupported compression.")
+		return []string{""}, "", fmt.Errorf("Unsupported compression")
 	}
 
 }

From b7c728bcd661148c7b1fca75a4bf61896f51ff9b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 13 Sep 2016 00:31:01 -0400
Subject: [PATCH 0225/1193] init: Don't fail when passed "all" as an IP
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index de1802844..ce1461f69 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -869,7 +869,7 @@ they otherwise would.
 
 		if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
 			isIPAddress := func(s string) string {
-				if net.ParseIP(s) == nil {
+				if s != "all" && net.ParseIP(s) == nil {
 					return fmt.Sprintf("'%s' is not an IP address", s)
 				}
 				return ""

From 58c78d4fabac6aeafc6e6ef1084401ae4c8bfeba Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Tue, 13 Sep 2016 09:50:32 +0200
Subject: [PATCH 0226/1193] xattrs: use syscall library

golang/x/sys/unix library is not yet packaged. So let's use the deprecated
syscall library for now.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/util_linux.go |  9 ++++-----
 shared/util_test.go  | 10 +++++-----
 2 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 35daf13ca..51e0e438b 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -6,7 +6,6 @@ package shared
 import (
 	"errors"
 	"fmt"
-	"golang.org/x/sys/unix"
 	"os"
 	"os/exec"
 	"strings"
@@ -393,7 +392,7 @@ func SetSize(fd int, width int, height int) (err error) {
 // associated with the link itself are retrieved.
 func llistxattr(path string, list []byte) (sz int, err error) {
 	var _p0 *byte
-	_p0, err = unix.BytePtrFromString(path)
+	_p0, err = syscall.BytePtrFromString(path)
 	if err != nil {
 		return
 	}
@@ -403,7 +402,7 @@ func llistxattr(path string, list []byte) (sz int, err error) {
 	} else {
 		_p1 = unsafe.Pointer(nil)
 	}
-	r0, _, e1 := unix.Syscall(unix.SYS_LLISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(list)))
+	r0, _, e1 := syscall.Syscall(syscall.SYS_LLISTXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(_p1), uintptr(len(list)))
 	sz = int(r0)
 	if e1 != 0 {
 		err = e1
@@ -461,7 +460,7 @@ func GetAllXattr(path string) (xattrs map[string]string, err error) {
 		// second, to actually store the extended attributes in the
 		// buffer. Also, check if the size of the extended attribute
 		// hasn't changed between the two calls.
-		pre, err = unix.Getxattr(path, xattr, nil)
+		pre, err = syscall.Getxattr(path, xattr, nil)
 		if err != nil || pre < 0 {
 			return nil, err
 		}
@@ -470,7 +469,7 @@ func GetAllXattr(path string) (xattrs map[string]string, err error) {
 		}
 
 		dest = make([]byte, pre)
-		post, err = unix.Getxattr(path, xattr, dest)
+		post, err = syscall.Getxattr(path, xattr, dest)
 		if err != nil || post < 0 {
 			return nil, err
 		}
diff --git a/shared/util_test.go b/shared/util_test.go
index 386a7a34e..c469846c1 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -4,10 +4,10 @@ import (
 	"bytes"
 	"crypto/rand"
 	"fmt"
-	"golang.org/x/sys/unix"
 	"io/ioutil"
 	"os"
 	"strings"
+	"syscall"
 	"testing"
 )
 
@@ -35,8 +35,8 @@ func TestGetAllXattr(t *testing.T) {
 	defer os.Remove(xattrDir)
 
 	for k, v := range testxattr {
-		err = unix.Setxattr(xattrFile.Name(), k, []byte(v), 0)
-		if err == unix.ENOTSUP {
+		err = syscall.Setxattr(xattrFile.Name(), k, []byte(v), 0)
+		if err == syscall.ENOTSUP {
 			t.Log(err)
 			return
 		}
@@ -44,8 +44,8 @@ func TestGetAllXattr(t *testing.T) {
 			t.Error(err)
 			return
 		}
-		err = unix.Setxattr(xattrDir, k, []byte(v), 0)
-		if err == unix.ENOTSUP {
+		err = syscall.Setxattr(xattrDir, k, []byte(v), 0)
+		if err == syscall.ENOTSUP {
 			t.Log(err)
 			return
 		}

From eef29d66e07a763fed4891fa26b38c3490409b54 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 13 Sep 2016 16:06:57 -0400
Subject: [PATCH 0227/1193] dir: Copy everything on container copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Instead of just copying the rootfs.

Closes #2371

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_dir.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index ef2f158b8..3cb1339c4 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -102,8 +102,8 @@ func (s *storageDir) ContainerDelete(container container) error {
 func (s *storageDir) ContainerCopy(
 	container container, sourceContainer container) error {
 
-	oldPath := sourceContainer.RootfsPath()
-	newPath := container.RootfsPath()
+	oldPath := sourceContainer.Path()
+	newPath := container.Path()
 
 	/*
 	 * Copy by using rsync

From 6aa6df7c7bdfd788fda4e5659f6473878812c798 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Sep 2016 17:15:48 -0400
Subject: [PATCH 0228/1193] doc: Spacing cleanup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/rest-api.md | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 6c020989f..21337c14d 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -135,7 +135,6 @@ Code  | Meaning
 400   | Failure
 401   | Cancelled
 
-
 # Recursion
 To optimize queries of large lists, recursion is implemented for collections.
 A "recursion" argument can be passed to a GET query against a collection.
@@ -446,7 +445,6 @@ Input (using a public remote image):
                    "alias": "ubuntu/devel"},                                # Name of the alias
     }
 
-
 Input (using a private remote image after having obtained a secret for that image):
 
     {
@@ -505,7 +503,6 @@ Input (using a local container):
                    "source": "my-old-container"}                                        # Name of the source container
     }
 
-
 ## /1.0/containers/\<name\>
 ### GET
  * Description: Container information
@@ -584,7 +581,6 @@ Input (update container configuration):
         ]
     }
 
-
 Takes the same structure as that returned by GET but doesn't allow name
 changes (see POST below) or changes to the status sub-dict (since that's
 read-only).
@@ -788,7 +784,6 @@ Output:
         }
     }
 
-
 ### PUT
  * Description: change the container state
  * Authentication: trusted
@@ -1010,7 +1005,6 @@ Return (with wait-for-websocket=true and interactive=true):
         }
     }
 
-
 When the exec command finishes, its exit status is available from the
 operation's metadata:
 
@@ -1088,7 +1082,6 @@ This never returns. Each notification is sent as a separate JSON dict:
         }
     }
 
-
 ## /1.0/images
 ### GET
  * Description: list of images (public or private)
@@ -1172,7 +1165,6 @@ In the remote image URL case, the following dict must be used:
         }
     }
 
-
 After the input is received by LXD, a background operation is started
 which will add the image to the store and possibly do some backend
 filesystem-specific optimizations.
@@ -1265,7 +1257,6 @@ client will POST to /1.0/images/\<fingerprint\>/export to get a secret
 token which it'll then pass to the target LXD. That target LXD will then
 GET the image as a guest, passing the secret token.
 
-
 ## /1.0/images/\<fingerprint\>/secret
 ### POST
  * Description: Generate a random token and tell LXD to expect it be used by a guest
@@ -1491,7 +1482,6 @@ Return:
         "/1.0/profiles/default"
     ]
 
-
 ### POST
  * Description: define a new profile
  * Authentication: trusted
@@ -1574,13 +1564,11 @@ Input (rename a profile):
         "name": "new-name"
     }
 
-
 HTTP return value must be 204 (No content) and Location must point to
 the renamed resource.
 
 Renaming to an existing name must return the 409 (Conflict) HTTP code.
 
-
 ### DELETE
  * Description: remove a profile
  * Authentication: trusted

From aed9f7ee75a20e49befee193cebab9f55e5461c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Sep 2016 16:17:04 -0400
Subject: [PATCH 0229/1193] init: Enable compression on new zfs pools
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index ce1461f69..077b618cc 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -935,7 +935,7 @@ they otherwise would.
 			output, err := exec.Command(
 				"zpool",
 				"create", storagePool, storageDevice,
-				"-f", "-m", "none").CombinedOutput()
+				"-f", "-m", "none", "-O", "compression=on").CombinedOutput()
 			if err != nil {
 				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
 			}

From 3f31803e8c7897b8e5644018f16b46819e389585 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Sep 2016 18:06:01 -0400
Subject: [PATCH 0230/1193] lxc: Drop unused httpAddr property
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config.go  | 1 -
 lxc/move.go    | 1 -
 lxc/profile.go | 1 -
 lxc/remote.go  | 1 -
 4 files changed, 4 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index 3c8a8bf08..fff621e92 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -21,7 +21,6 @@ import (
 )
 
 type configCmd struct {
-	httpAddr string
 	expanded bool
 }
 
diff --git a/lxc/move.go b/lxc/move.go
index 42f85f133..c99779080 100644
--- a/lxc/move.go
+++ b/lxc/move.go
@@ -6,7 +6,6 @@ import (
 )
 
 type moveCmd struct {
-	httpAddr string
 }
 
 func (c *moveCmd) showByDefault() bool {
diff --git a/lxc/profile.go b/lxc/profile.go
index a1e7aa5e9..1c877e42a 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -16,7 +16,6 @@ import (
 )
 
 type profileCmd struct {
-	httpAddr string
 }
 
 func (c *profileCmd) showByDefault() bool {
diff --git a/lxc/remote.go b/lxc/remote.go
index 8bd435c28..ffa79eb29 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -24,7 +24,6 @@ import (
 )
 
 type remoteCmd struct {
-	httpAddr   string
 	acceptCert bool
 	password   string
 	public     bool

From 83c76cea1535c588b1729402bc2434793a850bc4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Sep 2016 22:36:04 -0400
Subject: [PATCH 0231/1193] network: Detect openvswitch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index bc209e4e4..ec164d22e 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"net"
 	"net/http"
+	"os/exec"
 	"strconv"
 
 	"github.com/gorilla/mux"
@@ -125,7 +126,12 @@ func doNetworkGet(d *Daemon, name string) (network, error) {
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/device", n.Name)) {
 		n.Type = "physical"
 	} else {
-		n.Type = "unknown"
+		_, err := exec.Command("ovs-vsctl", "br-exists", n.Name).CombinedOutput()
+		if err == nil {
+			n.Type = "bridge"
+		} else {
+			n.Type = "unknown"
+		}
 	}
 
 	return n, nil

From 6f430b74cd8296374e2bad2de4f9787134db3c26 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Sep 2016 23:00:37 -0400
Subject: [PATCH 0232/1193] network: Detect bonds
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/networks.go b/lxd/networks.go
index ec164d22e..f705f6e5d 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -125,6 +125,8 @@ func doNetworkGet(d *Daemon, name string) (network, error) {
 		n.Type = "bridge"
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/device", n.Name)) {
 		n.Type = "physical"
+	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bonding", n.Name)) {
+		n.Type = "bond"
 	} else {
 		_, err := exec.Command("ovs-vsctl", "br-exists", n.Name).CombinedOutput()
 		if err == nil {

From 4bf305d7fd795f360f665f8223f64de80db40088 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 11:53:01 +0200
Subject: [PATCH 0233/1193] fuidshift: expand symlinks to last path component

So far doUidshiftIntoContainer() expanded all symlinks in the path it got
passed. This meant, that when the user created symlinks referring to
non-existing files or referring to paths on the host fuidshift would either fail
in the first case or change files on the host. With this commit, we start to
only resolve the path that gets passed to fuidshift up to the last path
component. This should be safe since shiftowner() in shared/util_linux.go will
a) perform another safety check and b) will only change ownership of the symlink
itself.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/idmapset_linux.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index b02a40d9b..b3d81d6fc 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -223,11 +223,13 @@ func GetOwner(path string) (int, int, error) {
 }
 
 func (set *IdmapSet) doUidshiftIntoContainer(dir string, testmode bool, how string) error {
-	// Expand any symlink in dir and cleanup resulting path
-	dir, err := filepath.EvalSymlinks(dir)
+	// Expand any symlink before the final path component
+	tmp := filepath.Dir(dir)
+	tmp, err := filepath.EvalSymlinks(tmp)
 	if err != nil {
 		return err
 	}
+	dir = filepath.Join(tmp, filepath.Base(dir))
 	dir = strings.TrimRight(dir, "/")
 
 	convert := func(path string, fi os.FileInfo, err error) (e error) {

From 2a4f72630c49b8ea12ed22cfe3f07e3dfc6735aa Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Wed, 14 Sep 2016 14:14:49 +0200
Subject: [PATCH 0234/1193] log: add wrappers for log functions

All log functions follow the layout of this example:

	LogDebug(msg string, ctx map[string]interface{})

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/log.go | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/shared/log.go b/shared/log.go
index 9bb601ddd..203f8bdcf 100644
--- a/shared/log.go
+++ b/shared/log.go
@@ -2,6 +2,7 @@ package shared
 
 import (
 	"fmt"
+	log "gopkg.in/inconshreveable/log15.v2"
 	"runtime"
 )
 
@@ -27,6 +28,37 @@ func init() {
 	Log = nullLogger{}
 }
 
+// Wrapper function for functions in the Logger interface.
+func LogDebug(msg string, ctx map[string]interface{}) {
+	if Log != nil {
+		Log.Warn(msg, log.Ctx(ctx))
+	}
+}
+
+func LogInfo(msg string, ctx map[string]interface{}) {
+	if Log != nil {
+		Log.Info(msg, log.Ctx(ctx))
+	}
+}
+
+func LogWarn(msg string, ctx map[string]interface{}) {
+	if Log != nil {
+		Log.Warn(msg, log.Ctx(ctx))
+	}
+}
+
+func LogError(msg string, ctx map[string]interface{}) {
+	if Log != nil {
+		Log.Error(msg, log.Ctx(ctx))
+	}
+}
+
+func LogCrit(msg string, ctx map[string]interface{}) {
+	if Log != nil {
+		Log.Crit(msg, log.Ctx(ctx))
+	}
+}
+
 // Logf sends to the logger registered via SetLogger the string resulting
 // from running format and args through Sprintf.
 func Logf(format string, args ...interface{}) {

From cfae7b3c3b520888c9b24be9f8974e368158c238 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:35:01 +0200
Subject: [PATCH 0235/1193] log: add format wrappers for log functions

All log functions follow the layout of this example:

	LogDebugf(msg string, arg ...interface{})

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/log.go | 33 ++++++++++++++++++++++++---------
 1 file changed, 24 insertions(+), 9 deletions(-)

diff --git a/shared/log.go b/shared/log.go
index 203f8bdcf..430080363 100644
--- a/shared/log.go
+++ b/shared/log.go
@@ -28,7 +28,7 @@ func init() {
 	Log = nullLogger{}
 }
 
-// Wrapper function for functions in the Logger interface.
+// General wrappers around Logger interface functions.
 func LogDebug(msg string, ctx map[string]interface{}) {
 	if Log != nil {
 		Log.Warn(msg, log.Ctx(ctx))
@@ -59,25 +59,40 @@ func LogCrit(msg string, ctx map[string]interface{}) {
 	}
 }
 
-// Logf sends to the logger registered via SetLogger the string resulting
-// from running format and args through Sprintf.
-func Logf(format string, args ...interface{}) {
+// Wrappers around Logger interface functions that send a string to the Logger
+// by running it through fmt.Sprintf().
+func LogInfof(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Info(fmt.Sprintf(format, args...))
 	}
 }
 
-// Debugf sends to the logger registered via SetLogger the string resulting
-// from running format and args through Sprintf, but only if debugging was
-// enabled via SetDebug.
-func Debugf(format string, args ...interface{}) {
+func LogDebugf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Debug(fmt.Sprintf(format, args...))
 	}
 }
 
+func LogWarnf(format string, args ...interface{}) {
+	if Log != nil {
+		Log.Warn(fmt.Sprintf(format, args...))
+	}
+}
+
+func LogErrorf(format string, args ...interface{}) {
+	if Log != nil {
+		Log.Error(fmt.Sprintf(format, args...))
+	}
+}
+
+func LogCritf(format string, args ...interface{}) {
+	if Log != nil {
+		Log.Crit(fmt.Sprintf(format, args...))
+	}
+}
+
 func PrintStack() {
 	buf := make([]byte, 1<<16)
 	runtime.Stack(buf, true)
-	Debugf("%s", buf)
+	LogDebugf("%s", buf)
 }

From 5cde0f6566969ce718722ae55c5ba3eb3756c95f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:40:00 +0200
Subject: [PATCH 0236/1193] shared/json: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/json.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/shared/json.go b/shared/json.go
index 920407b22..644d0f783 100644
--- a/shared/json.go
+++ b/shared/json.go
@@ -51,11 +51,11 @@ func (m Jmap) GetBool(key string) (bool, error) {
 func DebugJson(r *bytes.Buffer) {
 	pretty := &bytes.Buffer{}
 	if err := json.Indent(pretty, r.Bytes(), "\t", "\t"); err != nil {
-		Debugf("error indenting json: %s", err)
+		LogDebugf("error indenting json: %s", err)
 		return
 	}
 
 	// Print the JSON without the last "\n"
 	str := pretty.String()
-	Debugf("\n\t%s", str[0:len(str)-1])
+	LogDebugf("\n\t%s", str[0:len(str)-1])
 }

From b9460ae62cb3a1bfb982bc20b737ff2d6d08065e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:42:05 +0200
Subject: [PATCH 0237/1193] shared/network: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/network.go | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/shared/network.go b/shared/network.go
index d6c8d4f44..39a7f589e 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -139,14 +139,14 @@ func WebsocketSendStream(conn *websocket.Conn, r io.Reader, bufferSize int) chan
 
 			w, err := conn.NextWriter(websocket.BinaryMessage)
 			if err != nil {
-				Debugf("Got error getting next writer %s", err)
+				LogDebugf("Got error getting next writer %s", err)
 				break
 			}
 
 			_, err = w.Write(buf)
 			w.Close()
 			if err != nil {
-				Debugf("Got err writing %s", err)
+				LogDebugf("Got err writing %s", err)
 				break
 			}
 		}
@@ -164,23 +164,23 @@ func WebsocketRecvStream(w io.Writer, conn *websocket.Conn) chan bool {
 		for {
 			mt, r, err := conn.NextReader()
 			if mt == websocket.CloseMessage {
-				Debugf("Got close message for reader")
+				LogDebugf("Got close message for reader")
 				break
 			}
 
 			if mt == websocket.TextMessage {
-				Debugf("got message barrier")
+				LogDebugf("got message barrier")
 				break
 			}
 
 			if err != nil {
-				Debugf("Got error getting next reader %s, %s", err, w)
+				LogDebugf("Got error getting next reader %s, %s", err, w)
 				break
 			}
 
 			buf, err := ioutil.ReadAll(r)
 			if err != nil {
-				Debugf("Got error writing to writer %s", err)
+				LogDebugf("Got error writing to writer %s", err)
 				break
 			}
 
@@ -190,11 +190,11 @@ func WebsocketRecvStream(w io.Writer, conn *websocket.Conn) chan bool {
 
 			i, err := w.Write(buf)
 			if i != len(buf) {
-				Debugf("Didn't write all of buf")
+				LogDebugf("Didn't write all of buf")
 				break
 			}
 			if err != nil {
-				Debugf("Error writing buf %s", err)
+				LogDebugf("Error writing buf %s", err)
 				break
 			}
 		}
@@ -216,32 +216,32 @@ func WebsocketMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser) (c
 		for {
 			mt, r, err := conn.NextReader()
 			if err != nil {
-				Debugf("Got error getting next reader %s, %s", err, w)
+				LogDebugf("Got error getting next reader %s, %s", err, w)
 				break
 			}
 
 			if mt == websocket.CloseMessage {
-				Debugf("Got close message for reader")
+				LogDebugf("Got close message for reader")
 				break
 			}
 
 			if mt == websocket.TextMessage {
-				Debugf("Got message barrier, resetting stream")
+				LogDebugf("Got message barrier, resetting stream")
 				break
 			}
 
 			buf, err := ioutil.ReadAll(r)
 			if err != nil {
-				Debugf("Got error writing to writer %s", err)
+				LogDebugf("Got error writing to writer %s", err)
 				break
 			}
 			i, err := w.Write(buf)
 			if i != len(buf) {
-				Debugf("Didn't write all of buf")
+				LogDebugf("Didn't write all of buf")
 				break
 			}
 			if err != nil {
-				Debugf("Error writing buf %s", err)
+				LogDebugf("Error writing buf %s", err)
 				break
 			}
 		}
@@ -259,21 +259,21 @@ func WebsocketMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser) (c
 			buf, ok := <-in
 			if !ok {
 				r.Close()
-				Debugf("sending write barrier")
+				LogDebugf("sending write barrier")
 				conn.WriteMessage(websocket.TextMessage, []byte{})
 				readDone <- true
 				return
 			}
 			w, err := conn.NextWriter(websocket.BinaryMessage)
 			if err != nil {
-				Debugf("Got error getting next writer %s", err)
+				LogDebugf("Got error getting next writer %s", err)
 				break
 			}
 
 			_, err = w.Write(buf)
 			w.Close()
 			if err != nil {
-				Debugf("Got err writing %s", err)
+				LogDebugf("Got err writing %s", err)
 				break
 			}
 		}

From 2c7dee196c97db7d71f7baf313035b7d98835aca Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:43:04 +0200
Subject: [PATCH 0238/1193] client: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 client.go | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/client.go b/client.go
index c118ab8c1..3b6a4e20d 100644
--- a/client.go
+++ b/client.go
@@ -112,7 +112,7 @@ func ParseResponse(r *http.Response) (*Response, error) {
 	if err != nil {
 		return nil, err
 	}
-	shared.Debugf("Raw response: %s", string(s))
+	shared.LogDebugf("Raw response: %s", string(s))
 
 	if err := json.Unmarshal(s, &ret); err != nil {
 		return nil, err
@@ -376,7 +376,7 @@ func (c *Client) put(base string, args interface{}, rtype ResponseType) (*Respon
 		return nil, err
 	}
 
-	shared.Debugf("Putting %s to %s", buf.String(), uri)
+	shared.LogDebugf("Putting %s to %s", buf.String(), uri)
 
 	req, err := http.NewRequest("PUT", uri, &buf)
 	if err != nil {
@@ -402,7 +402,7 @@ func (c *Client) post(base string, args interface{}, rtype ResponseType) (*Respo
 		return nil, err
 	}
 
-	shared.Debugf("Posting %s to %s", buf.String(), uri)
+	shared.LogDebugf("Posting %s to %s", buf.String(), uri)
 
 	req, err := http.NewRequest("POST", uri, &buf)
 	if err != nil {
@@ -452,7 +452,7 @@ func (c *Client) delete(base string, args interface{}, rtype ResponseType) (*Res
 		return nil, err
 	}
 
-	shared.Debugf("Deleting %s to %s", buf.String(), uri)
+	shared.LogDebugf("Deleting %s to %s", buf.String(), uri)
 
 	req, err := http.NewRequest("DELETE", uri, &buf)
 	if err != nil {
@@ -561,7 +561,7 @@ func (c *Client) AmTrusted() bool {
 		return false
 	}
 
-	shared.Debugf("%s", resp)
+	shared.LogDebugf("%s", resp)
 
 	jmap, err := resp.MetadataAsMap()
 	if err != nil {
@@ -582,7 +582,7 @@ func (c *Client) IsPublic() bool {
 		return false
 	}
 
-	shared.Debugf("%s", resp)
+	shared.LogDebugf("%s", resp)
 
 	jmap, err := resp.MetadataAsMap()
 	if err != nil {
@@ -1869,7 +1869,7 @@ func (c *Client) WaitFor(waitURL string) (*shared.Operation, error) {
 	 * "/<version>/operations/" in it; we chop off the leading / and pass
 	 * it to url directly.
 	 */
-	shared.Debugf(path.Join(waitURL[1:], "wait"))
+	shared.LogDebugf(path.Join(waitURL[1:], "wait"))
 	resp, err := c.baseGet(c.url(waitURL, "wait"))
 	if err != nil {
 		return nil, err
@@ -2122,7 +2122,7 @@ func (c *Client) SetProfileConfigItem(profile, key, value string) error {
 
 	st, err := c.ProfileConfig(profile)
 	if err != nil {
-		shared.Debugf("Error getting profile %s to update", profile)
+		shared.LogDebugf("Error getting profile %s to update", profile)
 		return err
 	}
 

From 66954d8f946c208f3c807353bbbcb0508c45e19b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:43:41 +0200
Subject: [PATCH 0239/1193] lxc/exec: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxc/exec.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/exec.go b/lxc/exec.go
index 70a8f1af9..ba3e5e03b 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -61,7 +61,7 @@ func (c *execCmd) sendTermSize(control *websocket.Conn) error {
 		return err
 	}
 
-	shared.Debugf("Window size is now: %dx%d", width, height)
+	shared.LogDebugf("Window size is now: %dx%d", width, height)
 
 	w, err := control.NextWriter(websocket.TextMessage)
 	if err != nil {

From 4789368a09fd65361d2d7b3f6bf8dcdaf9cca623 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:44:12 +0200
Subject: [PATCH 0240/1193] lxc/exec_unix: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxc/exec_unix.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/exec_unix.go b/lxc/exec_unix.go
index 9b46add2c..d22653ed1 100644
--- a/lxc/exec_unix.go
+++ b/lxc/exec_unix.go
@@ -25,11 +25,11 @@ func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) {
 	for {
 		sig := <-ch
 
-		shared.Debugf("Received '%s signal', updating window geometry.", sig)
+		shared.LogDebugf("Received '%s signal', updating window geometry.", sig)
 
 		err := c.sendTermSize(control)
 		if err != nil {
-			shared.Debugf("error setting term size %s", err)
+			shared.LogDebugf("error setting term size %s", err)
 			break
 		}
 	}

From 7f025a55d0637f1f3c8c98c53e82da3b3309175c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:45:14 +0200
Subject: [PATCH 0241/1193] lxd/container_exec: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_exec.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 57b310bb7..960d16ba6 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -153,39 +153,39 @@ func (s *execWs) Do(op *operation) error {
 				}
 
 				if err != nil {
-					shared.Debugf("Got error getting next reader %s", err)
+					shared.LogDebugf("Got error getting next reader %s", err)
 					break
 				}
 
 				buf, err := ioutil.ReadAll(r)
 				if err != nil {
-					shared.Debugf("Failed to read message %s", err)
+					shared.LogDebugf("Failed to read message %s", err)
 					break
 				}
 
 				command := shared.ContainerExecControl{}
 
 				if err := json.Unmarshal(buf, &command); err != nil {
-					shared.Debugf("Failed to unmarshal control socket command: %s", err)
+					shared.LogDebugf("Failed to unmarshal control socket command: %s", err)
 					continue
 				}
 
 				if command.Command == "window-resize" {
 					winchWidth, err := strconv.Atoi(command.Args["width"])
 					if err != nil {
-						shared.Debugf("Unable to extract window width: %s", err)
+						shared.LogDebugf("Unable to extract window width: %s", err)
 						continue
 					}
 
 					winchHeight, err := strconv.Atoi(command.Args["height"])
 					if err != nil {
-						shared.Debugf("Unable to extract window height: %s", err)
+						shared.LogDebugf("Unable to extract window height: %s", err)
 						continue
 					}
 
 					err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight)
 					if err != nil {
-						shared.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
+						shared.LogDebugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
 						continue
 					}
 				}

From b6a6895a13a3de14eede9306406c51d314bf9a56 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:48:59 +0200
Subject: [PATCH 0242/1193] lxd/containers_get: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/containers_get.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 750782d8e..8b36e1372 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -15,7 +15,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 			return SyncResponse(true, result)
 		}
 		if !isDbLockedError(err) {
-			shared.Debugf("DBERR: containersGet: error %q", err)
+			shared.LogDebugf("DBERR: containersGet: error %q", err)
 			return InternalError(err)
 		}
 		// 1 s may seem drastic, but we really don't want to thrash
@@ -23,7 +23,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 		time.Sleep(100 * time.Millisecond)
 	}
 
-	shared.Debugf("DBERR: containersGet, db is locked")
+	shared.LogDebugf("DBERR: containersGet, db is locked")
 	shared.PrintStack()
 	return InternalError(fmt.Errorf("DB is locked"))
 }

From f6f0a9d0ec5c505cc1f4c7055f6b04f4023977a4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:50:07 +0200
Subject: [PATCH 0243/1193] lxd/containers_post: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/containers_post.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index faa9f1070..9081a0bff 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -386,7 +386,7 @@ func createFromCopy(d *Daemon, req *containerPostReq) Response {
 }
 
 func containersPost(d *Daemon, r *http.Request) Response {
-	shared.Debugf("Responding to container create")
+	shared.LogDebugf("Responding to container create")
 
 	req := containerPostReq{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -411,7 +411,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 				return InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries"))
 			}
 		}
-		shared.Debugf("No name provided, creating %s", req.Name)
+		shared.LogDebugf("No name provided, creating %s", req.Name)
 	}
 
 	if req.Devices == nil {

From f1a365d78bbe37acfd456ee2a6e904e09d812d99 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:50:53 +0200
Subject: [PATCH 0244/1193] lxd/daemon: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index cc4523144..4373935fa 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -758,14 +758,14 @@ func (d *Daemon) Init() error {
 	go func() {
 		t := time.NewTicker(24 * time.Hour)
 		for {
-			shared.Debugf("Expiring log files")
+			shared.LogDebugf("Expiring log files")
 
 			err := d.ExpireLogs()
 			if err != nil {
 				shared.Log.Error("Failed to expire logs", log.Ctx{"err": err})
 			}
 
-			shared.Debugf("Done expiring log files")
+			shared.LogDebugf("Done expiring log files")
 			<-t.C
 		}
 	}()
@@ -1069,7 +1069,7 @@ func (d *Daemon) Stop() error {
 
 		syscall.Unmount(shared.VarPath("shmounts"), syscall.MNT_DETACH)
 	} else {
-		shared.Debugf("Not unmounting shmounts (containers are still running)")
+		shared.LogDebugf("Not unmounting shmounts (containers are still running)")
 	}
 
 	shared.Log.Debug("Closing the database")

From abf2f3935f162b8ba18ed378bef4a22837137d47 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:51:42 +0200
Subject: [PATCH 0245/1193] lxd/daemon_images: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon_images.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index c05ea2180..ed1c76355 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -50,7 +50,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			entry = &imageStreamCacheEntry{ss: ss, expiry: time.Now().Add(time.Hour)}
 			imageStreamCache[server] = entry
 		} else {
-			shared.Debugf("Using SimpleStreams cache entry for %s, expires at %s", server, entry.expiry)
+			shared.LogDebugf("Using SimpleStreams cache entry for %s, expires at %s", server, entry.expiry)
 			ss = entry.ss
 		}
 		imageStreamCacheLock.Unlock()

From b68630d65834653052a07542573d355be9eb2ec9 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:52:17 +0200
Subject: [PATCH 0246/1193] lxd/db: Debugf() -> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/db.go | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/lxd/db.go b/lxd/db.go
index 17fc1379a..548185c2e 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -284,13 +284,13 @@ func dbBegin(db *sql.DB) (*sql.Tx, error) {
 			return tx, nil
 		}
 		if !isDbLockedError(err) {
-			shared.Debugf("DbBegin: error %q", err)
+			shared.LogDebugf("DbBegin: error %q", err)
 			return nil, err
 		}
 		time.Sleep(100 * time.Millisecond)
 	}
 
-	shared.Debugf("DbBegin: DB still locked")
+	shared.LogDebugf("DbBegin: DB still locked")
 	shared.PrintStack()
 	return nil, fmt.Errorf("DB is locked")
 }
@@ -302,13 +302,13 @@ func txCommit(tx *sql.Tx) error {
 			return nil
 		}
 		if !isDbLockedError(err) {
-			shared.Debugf("Txcommit: error %q", err)
+			shared.LogDebugf("Txcommit: error %q", err)
 			return err
 		}
 		time.Sleep(100 * time.Millisecond)
 	}
 
-	shared.Debugf("Txcommit: db still locked")
+	shared.LogDebugf("Txcommit: db still locked")
 	shared.PrintStack()
 	return fmt.Errorf("DB is locked")
 }
@@ -328,7 +328,7 @@ func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interfac
 		time.Sleep(100 * time.Millisecond)
 	}
 
-	shared.Debugf("DbQueryRowScan: query %q args %q, DB still locked", q, args)
+	shared.LogDebugf("DbQueryRowScan: query %q args %q, DB still locked", q, args)
 	shared.PrintStack()
 	return fmt.Errorf("DB is locked")
 }
@@ -340,13 +340,13 @@ func dbQuery(db *sql.DB, q string, args ...interface{}) (*sql.Rows, error) {
 			return result, nil
 		}
 		if !isDbLockedError(err) {
-			shared.Debugf("DbQuery: query %q error %q", q, err)
+			shared.LogDebugf("DbQuery: query %q error %q", q, err)
 			return nil, err
 		}
 		time.Sleep(100 * time.Millisecond)
 	}
 
-	shared.Debugf("DbQuery: query %q args %q, DB still locked", q, args)
+	shared.LogDebugf("DbQuery: query %q args %q, DB still locked", q, args)
 	shared.PrintStack()
 	return nil, fmt.Errorf("DB is locked")
 }
@@ -415,13 +415,13 @@ func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{
 			return result, nil
 		}
 		if !isDbLockedError(err) {
-			shared.Debugf("DbQuery: query %q error %q", q, err)
+			shared.LogDebugf("DbQuery: query %q error %q", q, err)
 			return nil, err
 		}
 		time.Sleep(100 * time.Millisecond)
 	}
 
-	shared.Debugf("DbQueryscan: query %q inargs %q, DB still locked", q, inargs)
+	shared.LogDebugf("DbQueryscan: query %q inargs %q, DB still locked", q, inargs)
 	shared.PrintStack()
 	return nil, fmt.Errorf("DB is locked")
 }
@@ -433,13 +433,13 @@ func dbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) {
 			return result, nil
 		}
 		if !isDbLockedError(err) {
-			shared.Debugf("DbExec: query %q error %q", q, err)
+			shared.LogDebugf("DbExec: query %q error %q", q, err)
 			return nil, err
 		}
 		time.Sleep(100 * time.Millisecond)
 	}
 
-	shared.Debugf("DbExec: query %q args %q, DB still locked", q, args)
+	shared.LogDebugf("DbExec: query %q args %q, DB still locked", q, args)
 	shared.PrintStack()
 	return nil, fmt.Errorf("DB is locked")
 }

From 1f38fca00cb63eb032e53082b24e82621ed43bfa Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:57:49 +0200
Subject: [PATCH 0247/1193] lxd/db_containers: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/db_containers.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index 28ce6f074..81efb1c0f 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -206,7 +206,7 @@ func dbContainerConfigInsert(tx *sql.Tx, id int, config map[string]string) error
 	for k, v := range config {
 		_, err := stmt.Exec(id, k, v)
 		if err != nil {
-			shared.Debugf("Error adding configuration item %s = %s to container %d",
+			shared.LogDebugf("Error adding configuration item %s = %s to container %d",
 				k, v, id)
 			return err
 		}
@@ -242,7 +242,7 @@ func dbContainerProfilesInsert(tx *sql.Tx, id int, profiles []string) error {
 	for _, p := range profiles {
 		_, err = stmt.Exec(id, p, applyOrder)
 		if err != nil {
-			shared.Debugf("Error adding profile %s to container: %s",
+			shared.LogDebugf("Error adding profile %s to container: %s",
 				p, err)
 			return err
 		}

From 5009589a41d1f3e7e148b8f4bbb403edd29ae43d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:58:57 +0200
Subject: [PATCH 0248/1193] lxd/debug: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/debug.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/debug.go b/lxd/debug.go
index 1ba9e719a..f1a0fd7c4 100644
--- a/lxd/debug.go
+++ b/lxd/debug.go
@@ -12,7 +12,7 @@ import (
 func doMemDump(memProfile string) {
 	f, err := os.Create(memProfile)
 	if err != nil {
-		shared.Debugf("Error opening memory profile file '%s': %s", err)
+		shared.LogDebugf("Error opening memory profile file '%s': %s", err)
 		return
 	}
 	pprof.WriteHeapProfile(f)
@@ -24,7 +24,7 @@ func memProfiler(memProfile string) {
 	signal.Notify(ch, syscall.SIGUSR1)
 	for {
 		sig := <-ch
-		shared.Debugf("Received '%s signal', dumping memory.", sig)
+		shared.LogDebugf("Received '%s signal', dumping memory.", sig)
 		doMemDump(memProfile)
 	}
 }

From 78baea632bd7edd300ed4efc874dc9157cae8295 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:59:22 +0200
Subject: [PATCH 0249/1193] lxd/devices: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/devices.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index ddbcc63e7..b10704704 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -377,7 +377,7 @@ func deviceEventListener(d *Daemon) {
 				continue
 			}
 
-			shared.Debugf("Scheduler: cpu: %s is now %s: re-balancing", e[0], e[1])
+			shared.LogDebugf("Scheduler: cpu: %s is now %s: re-balancing", e[0], e[1])
 			deviceTaskBalance(d)
 		case e := <-chNetlinkNetwork:
 			if len(e) != 2 {
@@ -389,7 +389,7 @@ func deviceEventListener(d *Daemon) {
 				continue
 			}
 
-			shared.Debugf("Scheduler: network: %s has been added: updating network priorities", e[0])
+			shared.LogDebugf("Scheduler: network: %s has been added: updating network priorities", e[0])
 			deviceNetworkPriority(d, e[0])
 		case e := <-deviceSchedRebalance:
 			if len(e) != 3 {
@@ -401,7 +401,7 @@ func deviceEventListener(d *Daemon) {
 				continue
 			}
 
-			shared.Debugf("Scheduler: %s %s %s: re-balancing", e[0], e[1], e[2])
+			shared.LogDebugf("Scheduler: %s %s %s: re-balancing", e[0], e[1], e[2])
 			deviceTaskBalance(d)
 		}
 	}

From 0fe85e6cfd61f1bce50a4bf160352d646bccdd7c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:59:59 +0200
Subject: [PATCH 0250/1193] lxd/devlxd: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/devlxd.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 8991a67d5..af590006e 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -217,7 +217,7 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) {
 	case http.StateNew:
 		cred, err := getCred(unixConn)
 		if err != nil {
-			shared.Debugf("Error getting ucred for conn %s", err)
+			shared.LogDebugf("Error getting ucred for conn %s", err)
 		} else {
 			m.m[unixConn] = cred
 		}
@@ -238,7 +238,7 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) {
 	case http.StateClosed:
 		delete(m.m, unixConn)
 	default:
-		shared.Debugf("Unknown state for connection %s", state)
+		shared.LogDebugf("Unknown state for connection %s", state)
 	}
 }
 

From 98d3d8ee11d0516c03d25f569e91d87b70e27da7 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:00:27 +0200
Subject: [PATCH 0251/1193] lxd/events: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/events.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/events.go b/lxd/events.go
index 6b10374d3..46b7dc6c0 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -87,7 +87,7 @@ func eventsSocket(r *http.Request, w http.ResponseWriter) error {
 	eventListeners[listener.id] = &listener
 	eventsLock.Unlock()
 
-	shared.Debugf("New events listener: %s", listener.id)
+	shared.LogDebugf("New events listener: %s", listener.id)
 
 	<-listener.active
 
@@ -96,7 +96,7 @@ func eventsSocket(r *http.Request, w http.ResponseWriter) error {
 	eventsLock.Unlock()
 
 	listener.connection.Close()
-	shared.Debugf("Disconnected events listener: %s", listener.id)
+	shared.LogDebugf("Disconnected events listener: %s", listener.id)
 
 	return nil
 }

From 40e042f693d84411f1bf74eb718fd8d52373738c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:00:55 +0200
Subject: [PATCH 0252/1193] lxd/images: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/images.go | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 6213296e1..22d244a21 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -107,8 +107,8 @@ func unpack(file string, path string) error {
 	output, err := exec.Command(command, args...).CombinedOutput()
 	if err != nil {
 		co := string(output)
-		shared.Debugf("Unpacking failed")
-		shared.Debugf(co)
+		shared.LogDebugf("Unpacking failed")
+		shared.LogDebugf(co)
 
 		// Truncate the output to a single line for inclusion in the error
 		// message.  The first line isn't guaranteed to pinpoint the issue,
@@ -654,7 +654,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		}
 
 		if err := os.RemoveAll(path); err != nil {
-			shared.Debugf("Error deleting temporary directory \"%s\": %s", path, err)
+			shared.LogDebugf("Error deleting temporary directory \"%s\": %s", path, err)
 		}
 	}
 
@@ -836,7 +836,7 @@ func imagesGet(d *Daemon, r *http.Request) Response {
 var imagesCmd = Command{name: "images", post: imagesPost, untrustedGet: true, get: imagesGet}
 
 func autoUpdateImages(d *Daemon) {
-	shared.Debugf("Updating images")
+	shared.LogDebugf("Updating images")
 
 	images, err := dbImagesGet(d.db, false)
 	if err != nil {
@@ -895,11 +895,11 @@ func autoUpdateImages(d *Daemon) {
 		}
 	}
 
-	shared.Debugf("Done updating images")
+	shared.LogDebugf("Done updating images")
 }
 
 func pruneExpiredImages(d *Daemon) {
-	shared.Debugf("Pruning expired images")
+	shared.LogDebugf("Pruning expired images")
 
 	// Get the list of expires images
 	expiry := daemonConfig["images.remote_cache_expiry"].GetInt64()
@@ -916,7 +916,7 @@ func pruneExpiredImages(d *Daemon) {
 		}
 	}
 
-	shared.Debugf("Done pruning expired images")
+	shared.LogDebugf("Done pruning expired images")
 }
 
 func doDeleteImage(d *Daemon, fingerprint string) error {
@@ -942,7 +942,7 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	if shared.PathExists(fname) {
 		err = os.Remove(fname)
 		if err != nil {
-			shared.Debugf("Error deleting image file %s: %s", fname, err)
+			shared.LogDebugf("Error deleting image file %s: %s", fname, err)
 		}
 	}
 
@@ -951,7 +951,7 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	if shared.PathExists(fname) {
 		err = os.Remove(fname)
 		if err != nil {
-			shared.Debugf("Error deleting image file %s: %s", fname, err)
+			shared.LogDebugf("Error deleting image file %s: %s", fname, err)
 		}
 	}
 

From 88b1527cf7721ccefd94cef5d3726917af7fb004 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:01:21 +0200
Subject: [PATCH 0253/1193] lxd/main: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/main.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/main.go b/lxd/main.go
index 077b618cc..3467e2768 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -492,7 +492,7 @@ func cmdActivateIfNeeded() error {
 	// Look for network socket
 	value := daemonConfig["core.https_address"].Get()
 	if value != "" {
-		shared.Debugf("Daemon has core.https_address set, activating...")
+		shared.LogDebugf("Daemon has core.https_address set, activating...")
 		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
 		return err
 	}
@@ -519,19 +519,19 @@ func cmdActivateIfNeeded() error {
 		autoStart := config["boot.autostart"]
 
 		if c.IsRunning() {
-			shared.Debugf("Daemon has running containers, activating...")
+			shared.LogDebugf("Daemon has running containers, activating...")
 			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
 			return err
 		}
 
 		if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) {
-			shared.Debugf("Daemon has auto-started containers, activating...")
+			shared.LogDebugf("Daemon has auto-started containers, activating...")
 			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
 			return err
 		}
 	}
 
-	shared.Debugf("No need to start the daemon now.")
+	shared.LogDebugf("No need to start the daemon now.")
 	return nil
 }
 

From 3c25f02531acc072802300ca3c887c8c1e390e70 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:02:12 +0200
Subject: [PATCH 0254/1193] lxd/operations: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/operations.go | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/lxd/operations.go b/lxd/operations.go
index b50d3c790..8e5426300 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -116,7 +116,7 @@ func (op *operation) Run() (chan error, error) {
 				op.done()
 				chanRun <- err
 
-				shared.Debugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err)
+				shared.LogDebugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err)
 
 				_, md, _ := op.Render()
 				eventSend("operation", md)
@@ -130,7 +130,7 @@ func (op *operation) Run() (chan error, error) {
 			chanRun <- nil
 
 			op.lock.Lock()
-			shared.Debugf("Success for %s operation: %s", op.class.String(), op.id)
+			shared.LogDebugf("Success for %s operation: %s", op.class.String(), op.id)
 			_, md, _ := op.Render()
 			eventSend("operation", md)
 			op.lock.Unlock()
@@ -138,7 +138,7 @@ func (op *operation) Run() (chan error, error) {
 	}
 	op.lock.Unlock()
 
-	shared.Debugf("Started %s operation: %s", op.class.String(), op.id)
+	shared.LogDebugf("Started %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
@@ -170,7 +170,7 @@ func (op *operation) Cancel() (chan error, error) {
 				op.lock.Unlock()
 				chanCancel <- err
 
-				shared.Debugf("Failed to cancel %s operation: %s: %s", op.class.String(), op.id, err)
+				shared.LogDebugf("Failed to cancel %s operation: %s: %s", op.class.String(), op.id, err)
 				_, md, _ := op.Render()
 				eventSend("operation", md)
 				return
@@ -182,13 +182,13 @@ func (op *operation) Cancel() (chan error, error) {
 			op.done()
 			chanCancel <- nil
 
-			shared.Debugf("Cancelled %s operation: %s", op.class.String(), op.id)
+			shared.LogDebugf("Cancelled %s operation: %s", op.class.String(), op.id)
 			_, md, _ := op.Render()
 			eventSend("operation", md)
 		}(op, oldStatus, chanCancel)
 	}
 
-	shared.Debugf("Cancelling %s operation: %s", op.class.String(), op.id)
+	shared.LogDebugf("Cancelling %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
@@ -200,7 +200,7 @@ func (op *operation) Cancel() (chan error, error) {
 		chanCancel <- nil
 	}
 
-	shared.Debugf("Cancelled %s operation: %s", op.class.String(), op.id)
+	shared.LogDebugf("Cancelled %s operation: %s", op.class.String(), op.id)
 	_, md, _ = op.Render()
 	eventSend("operation", md)
 
@@ -225,17 +225,17 @@ func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error
 		if err != nil {
 			chanConnect <- err
 
-			shared.Debugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err)
+			shared.LogDebugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err)
 			return
 		}
 
 		chanConnect <- nil
 
-		shared.Debugf("Handled %s operation: %s", op.class.String(), op.id)
+		shared.LogDebugf("Handled %s operation: %s", op.class.String(), op.id)
 	}(op, chanConnect)
 	op.lock.Unlock()
 
-	shared.Debugf("Connected %s operation: %s", op.class.String(), op.id)
+	shared.LogDebugf("Connected %s operation: %s", op.class.String(), op.id)
 
 	return chanConnect, nil
 }
@@ -320,7 +320,7 @@ func (op *operation) UpdateResources(opResources map[string][]string) error {
 	op.resources = opResources
 	op.lock.Unlock()
 
-	shared.Debugf("Updated resources for %s operation: %s", op.class.String(), op.id)
+	shared.LogDebugf("Updated resources for %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
@@ -346,7 +346,7 @@ func (op *operation) UpdateMetadata(opMetadata interface{}) error {
 	op.metadata = newMetadata
 	op.lock.Unlock()
 
-	shared.Debugf("Updated metadata for %s operation: %s", op.class.String(), op.id)
+	shared.LogDebugf("Updated metadata for %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
@@ -401,7 +401,7 @@ func operationCreate(opClass operationClass, opResources map[string][]string, op
 	operations[op.id] = &op
 	operationsLock.Unlock()
 
-	shared.Debugf("New %s operation: %s", op.class.String(), op.id)
+	shared.LogDebugf("New %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 

From 0307bf41f8a3633087ddeac0bbd3368ab037b0ed Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:02:34 +0200
Subject: [PATCH 0255/1193] lxd/rsync: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/rsync.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/rsync.go b/lxd/rsync.go
index 8f4931331..22f21439e 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -37,13 +37,13 @@ func rsyncWebsocket(path string, cmd *exec.Cmd, conn *websocket.Conn) error {
 	readDone, writeDone := shared.WebsocketMirror(conn, stdin, stdout)
 	data, err2 := ioutil.ReadAll(stderr)
 	if err2 != nil {
-		shared.Debugf("error reading rsync stderr: %s", err2)
+		shared.LogDebugf("error reading rsync stderr: %s", err2)
 		return err2
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.Debugf("rsync recv error for path %s: %s: %s", path, err, string(data))
+		shared.LogDebugf("rsync recv error for path %s: %s: %s", path, err, string(data))
 	}
 
 	<-readDone
@@ -139,12 +139,12 @@ func RsyncSend(path string, conn *websocket.Conn) error {
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
-		shared.Debugf("problem reading rsync stderr %s", err)
+		shared.LogDebugf("problem reading rsync stderr %s", err)
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.Debugf("problem with rsync send of %s: %s: %s", path, err, string(output))
+		shared.LogDebugf("problem with rsync send of %s: %s: %s", path, err, string(output))
 	}
 
 	<-readDone

From 5bce8f132b443fd479206da181a0d2df40062bf4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:03:18 +0200
Subject: [PATCH 0256/1193] lxd/patches: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/patches.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/patches.go b/lxd/patches.go
index c1a1779bd..4d7f0f081 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -36,7 +36,7 @@ type patch struct {
 }
 
 func (p *patch) apply(d *Daemon) error {
-	shared.Debugf("Applying patch: %s", p.name)
+	shared.LogDebugf("Applying patch: %s", p.name)
 
 	err := p.run(p.name, d)
 	if err != nil {

From 33be26cf87201d6e5ffcf108a30d141c78bd68c7 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:03:47 +0200
Subject: [PATCH 0257/1193] lxd/storage: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/storage.go b/lxd/storage.go
index a17b2053d..1659f04bd 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -53,7 +53,7 @@ func filesystemDetect(path string) (string, error) {
 	case filesystemSuperMagicNfs:
 		return "nfs", nil
 	default:
-		shared.Debugf("Unknown backing filesystem type: 0x%x", fs.Type)
+		shared.LogDebugf("Unknown backing filesystem type: 0x%x", fs.Type)
 		return string(fs.Type), nil
 	}
 }
@@ -316,7 +316,7 @@ func (ss *storageShared) shiftRootfs(c container) error {
 
 	err := idmapset.ShiftRootfs(rpath)
 	if err != nil {
-		shared.Debugf("Shift of rootfs %s failed: %s", rpath, err)
+		shared.LogDebugf("Shift of rootfs %s failed: %s", rpath, err)
 		return err
 	}
 

From 2212963241aa7b117565b15dc8b01997d3a4c306 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:04:08 +0200
Subject: [PATCH 0258/1193] lxd/storage_btrfs: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage_btrfs.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 08de5621f..8bfd4284d 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -1020,7 +1020,7 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 
 		output, err := ioutil.ReadAll(stderr)
 		if err != nil {
-			shared.Debugf("problem reading btrfs receive stderr %s", err)
+			shared.LogDebugf("problem reading btrfs receive stderr %s", err)
 		}
 
 		err = cmd.Wait()

From f845d0c4fc30f62e4cd7fcb181f89bad10559cee Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:04:32 +0200
Subject: [PATCH 0259/1193] lxd/storage_zfs: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage_zfs.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 31e3f936e..9bb443e5c 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -1361,7 +1361,7 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []c
 
 		output, err := ioutil.ReadAll(stderr)
 		if err != nil {
-			shared.Debugf("problem reading zfs recv stderr %s", "err", err)
+			shared.LogDebugf("problem reading zfs recv stderr %s", "err", err)
 		}
 
 		err = cmd.Wait()

From 1d48a3e42e5cbe13ecca13a66f0c9b4e841acae2 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:05:01 +0200
Subject: [PATCH 0260/1193] lxc/remote: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxc/remote.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index ffa79eb29..5a098fe26 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -271,7 +271,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 
 func (c *remoteCmd) removeCertificate(config *lxd.Config, remote string) {
 	certf := config.ServerCertPath(remote)
-	shared.Debugf("Trying to remove %s", certf)
+	shared.LogDebugf("Trying to remove %s", certf)
 
 	os.Remove(certf)
 }

From 9e19928c92df2a6ecec9acb2a03a04f04efa98cf Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:07:03 +0200
Subject: [PATCH 0261/1193] lxc/exec_windows: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxc/exec_windows.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/exec_windows.go b/lxc/exec_windows.go
index 5b51c7824..586ffb033 100644
--- a/lxc/exec_windows.go
+++ b/lxc/exec_windows.go
@@ -34,6 +34,6 @@ func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) {
 	// won't work quite correctly.
 	err := c.sendTermSize(control)
 	if err != nil {
-		shared.Debugf("error setting term size %s", err)
+		shared.LogDebugf("error setting term size %s", err)
 	}
 }

From 1feeae4d31819818f3dca59f5b5c423f62fd5823 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:08:14 +0200
Subject: [PATCH 0262/1193] lxd/certificates: Logf() --> LogInfof()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/certificates.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/certificates.go b/lxd/certificates.go
index 021b48f9a..b70f6b117 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -63,20 +63,20 @@ func readSavedClientCAList(d *Daemon) {
 
 	dbCerts, err := dbCertsGet(d.db)
 	if err != nil {
-		shared.Logf("Error reading certificates from database: %s", err)
+		shared.LogInfof("Error reading certificates from database: %s", err)
 		return
 	}
 
 	for _, dbCert := range dbCerts {
 		certBlock, _ := pem.Decode([]byte(dbCert.Certificate))
 		if certBlock == nil {
-			shared.Logf("Error decoding certificate for %s: %s", dbCert.Name, err)
+			shared.LogInfof("Error decoding certificate for %s: %s", dbCert.Name, err)
 			continue
 		}
 
 		cert, err := x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
-			shared.Logf("Error reading certificate for %s: %s", dbCert.Name, err)
+			shared.LogInfof("Error reading certificate for %s: %s", dbCert.Name, err)
 			continue
 		}
 		d.clientCerts = append(d.clientCerts, *cert)

From 68d8a8c46d9825e17f40f3b043eaf1f74cbd2ea4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:08:39 +0200
Subject: [PATCH 0263/1193] lxd/daemon: Logf() --> LogInfof()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 4373935fa..dd8e641d6 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -358,21 +358,21 @@ func (d *Daemon) SetupStorageDriver() error {
 	if lvmVgName != "" {
 		d.Storage, err = newStorage(d, storageTypeLvm)
 		if err != nil {
-			shared.Logf("Could not initialize storage type LVM: %s - falling back to dir", err)
+			shared.LogInfof("Could not initialize storage type LVM: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
 	} else if zfsPoolName != "" {
 		d.Storage, err = newStorage(d, storageTypeZfs)
 		if err != nil {
-			shared.Logf("Could not initialize storage type ZFS: %s - falling back to dir", err)
+			shared.LogInfof("Could not initialize storage type ZFS: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
 	} else if d.BackingFs == "btrfs" {
 		d.Storage, err = newStorage(d, storageTypeBtrfs)
 		if err != nil {
-			shared.Logf("Could not initialize storage type btrfs: %s - falling back to dir", err)
+			shared.LogInfof("Could not initialize storage type btrfs: %s - falling back to dir", err)
 		} else {
 			return nil
 		}

From 4b2de1643e2a8322425f36d324a6cf6a667d0afd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:09:12 +0200
Subject: [PATCH 0264/1193] lxd/storage_lvm: Logf() --> LogInfof()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage_lvm.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 057235e22..12be97473 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -732,7 +732,7 @@ func (s *storageLvm) ImageCreate(fingerprint string) error {
 	fstype := daemonConfig["storage.lvm_fstype"].Get()
 	err = tryMount(lvpath, tempLVMountPoint, fstype, 0, "discard")
 	if err != nil {
-		shared.Logf("Error mounting image LV for unpacking: %v", err)
+		shared.LogInfof("Error mounting image LV for unpacking: %v", err)
 		return fmt.Errorf("Error mounting image LV: %v", err)
 	}
 

From a9f5402ff9981e79dd53c2c60c32829b37f6180e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Sep 2016 16:01:57 -0400
Subject: [PATCH 0265/1193] Tweak squashfs for low-memory systems
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

On low-memory systems (below 2GB of RAM), limit unsquashfs to a single
CPU and to 10% of the RAM for its fragment size.

Closes #2382

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/images.go b/lxd/images.go
index 22d244a21..ad5ce72d2 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -99,6 +99,13 @@ func unpack(file string, path string) error {
 	} else if strings.HasPrefix(extension, ".squashfs") {
 		command = "unsquashfs"
 		args = append(args, "-f", "-d", path, "-n")
+
+		mem, err := deviceTotalMemory()
+		mem = mem / 1024 / 1024 / 10
+		if err == nil && mem < 256 {
+			args = append(args, "-da", fmt.Sprintf("%d", mem), "-fr", fmt.Sprintf("%d", mem), "-p", "1")
+		}
+
 		args = append(args, file)
 	} else {
 		return fmt.Errorf("Unsupported image format: %s", extension)

From dc7960f1a0447d048866562274a3e725ecae4999 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 15:01:48 +0200
Subject: [PATCH 0266/1193] lxd/migrate: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/migrate.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index 48a14dd84..6445ad385 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -134,7 +134,7 @@ func (c *migrationFields) controlChannel() <-chan MigrationControl {
 		msg := MigrationControl{}
 		err := c.recv(&msg)
 		if err != nil {
-			shared.Debugf("Got error reading migration control socket %s", err)
+			shared.LogDebugf("Got error reading migration control socket %s", err)
 			close(ch)
 			return
 		}
@@ -595,7 +595,7 @@ func (c *migrationSink) do() error {
 				// The source can only tell us it failed (e.g. if
 				// checkpointing failed). We have to tell the source
 				// whether or not the restore was successful.
-				shared.Debugf("Unknown message %v from source", msg)
+				shared.LogDebugf("Unknown message %v from source", msg)
 			}
 		}
 	}

From 219340aeb4370a0fe6db67aa492839f907ae08e9 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:58:22 +0200
Subject: [PATCH 0267/1193] lxd/db_update: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/db_update.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index ed376ee87..9396cd806 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -234,7 +234,7 @@ func dbUpdateFromV18(db *sql.DB) error {
 		// Deal with completely broken values
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
-			shared.Debugf("Invalid container memory limit, id=%d value=%s, removing.", id, value)
+			shared.LogDebugf("Invalid container memory limit, id=%d value=%s, removing.", id, value)
 			_, err = db.Exec("DELETE FROM containers_config WHERE id=?;", id)
 			if err != nil {
 				return err
@@ -271,7 +271,7 @@ func dbUpdateFromV18(db *sql.DB) error {
 		// Deal with completely broken values
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
-			shared.Debugf("Invalid profile memory limit, id=%d value=%s, removing.", id, value)
+			shared.LogDebugf("Invalid profile memory limit, id=%d value=%s, removing.", id, value)
 			_, err = db.Exec("DELETE FROM profiles_config WHERE id=?;", id)
 			if err != nil {
 				return err
@@ -507,7 +507,7 @@ func dbUpdateFromV10(d *Daemon) error {
 			return err
 		}
 
-		shared.Debugf("Restarting all the containers following directory rename")
+		shared.LogDebugf("Restarting all the containers following directory rename")
 		containersShutdown(d)
 		containersRestart(d)
 	}

From bbdb90c9271d5541f6a7ad76643d5927c9bcc5fd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 15 Sep 2016 14:45:46 +0200
Subject: [PATCH 0268/1193] lxd/container_lxc: Debugf() --> LogDebugf()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 370b558a6..6aa5eecda 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1104,7 +1104,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	}
 
 	if !reflect.DeepEqual(idmap, lastIdmap) {
-		shared.Debugf("Container idmap changed, remapping")
+		shared.LogDebugf("Container idmap changed, remapping")
 
 		err := c.StorageStart()
 		if err != nil {
@@ -1343,7 +1343,7 @@ func (c *containerLXC) Start(stateful bool) error {
 	// Capture debug output
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.Debugf("forkstart: %s", line)
+			shared.LogDebugf("forkstart: %s", line)
 		}
 	}
 
@@ -2646,7 +2646,7 @@ func (c *containerLXC) Export(w io.Writer) error {
 
 	writeToTar := func(path string, fi os.FileInfo, err error) error {
 		if err := c.tarStoreFile(linkmap, offset, tw, path, fi); err != nil {
-			shared.Debugf("Error tarring up %s: %s", path, err)
+			shared.LogDebugf("Error tarring up %s: %s", path, err)
 			return err
 		}
 		return nil
@@ -2708,7 +2708,7 @@ func (c *containerLXC) Export(w io.Writer) error {
 
 		tmpOffset := len(path.Dir(f.Name())) + 1
 		if err := c.tarStoreFile(linkmap, tmpOffset, tw, f.Name(), fi); err != nil {
-			shared.Debugf("Error writing to tarfile: %s", err)
+			shared.LogDebugf("Error writing to tarfile: %s", err)
 			tw.Close()
 			return err
 		}
@@ -2718,13 +2718,13 @@ func (c *containerLXC) Export(w io.Writer) error {
 		// Include metadata.yaml in the tarball
 		fi, err := os.Lstat(fnam)
 		if err != nil {
-			shared.Debugf("Error statting %s during export", fnam)
+			shared.LogDebugf("Error statting %s during export", fnam)
 			tw.Close()
 			return err
 		}
 
 		if err := c.tarStoreFile(linkmap, offset, tw, fnam, fi); err != nil {
-			shared.Debugf("Error writing to tarfile: %s", err)
+			shared.LogDebugf("Error writing to tarfile: %s", err)
 			tw.Close()
 			return err
 		}
@@ -2834,7 +2834,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 
 		if string(out) != "" {
 			for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-				shared.Debugf("forkmigrate: %s", line)
+				shared.LogDebugf("forkmigrate: %s", line)
 			}
 		}
 
@@ -3110,7 +3110,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 			continue
 		}
 
-		shared.Debugf("forkgetfile: %s", line)
+		shared.LogDebugf("forkgetfile: %s", line)
 	}
 
 	if err != nil {
@@ -3189,7 +3189,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 		}
 
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.Debugf("forkgetfile: %s", line)
+			shared.LogDebugf("forkgetfile: %s", line)
 		}
 	}
 
@@ -3535,7 +3535,7 @@ func (c *containerLXC) insertMount(source, target, fstype string, flags int) err
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.Debugf("forkmount: %s", line)
+			shared.LogDebugf("forkmount: %s", line)
 		}
 	}
 
@@ -3565,7 +3565,7 @@ func (c *containerLXC) removeMount(mount string) error {
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.Debugf("forkumount: %s", line)
+			shared.LogDebugf("forkumount: %s", line)
 		}
 	}
 
@@ -3693,7 +3693,7 @@ func (c *containerLXC) createUnixDevice(m shared.Device) (string, error) {
 		if c.idmapset != nil {
 			if err := c.idmapset.ShiftFile(devPath); err != nil {
 				// uidshift failing is weird, but not a big problem.  Log and proceed
-				shared.Debugf("Failed to uidshift device %s: %s\n", m["path"], err)
+				shared.LogDebugf("Failed to uidshift device %s: %s\n", m["path"], err)
 			}
 		}
 	} else {

From 35f52d17b33b56520787f4128ffa40cd29ce0241 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Sep 2016 17:52:27 -0400
Subject: [PATCH 0269/1193] Clarify unsquashfs limit logic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/images.go b/lxd/images.go
index ad5ce72d2..22e9b3392 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -100,6 +100,8 @@ func unpack(file string, path string) error {
 		command = "unsquashfs"
 		args = append(args, "-f", "-d", path, "-n")
 
+		// Limit unsquashfs chunk size to 10% of memory and up to 256MB (default)
+		// When running on a low memory system, also disable multi-processing
 		mem, err := deviceTotalMemory()
 		mem = mem / 1024 / 1024 / 10
 		if err == nil && mem < 256 {

From 9cfb109fa990605613c165dfb8fc106d016779c6 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 09:52:38 +0200
Subject: [PATCH 0270/1193] lxd/container_put: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_put.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container_put.go b/lxd/container_put.go
index 747a234ce..530b74268 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -86,7 +86,7 @@ func containerSnapRestore(d *Daemon, name string, snap string) error {
 		snap = name + shared.SnapshotDelimiter + snap
 	}
 
-	shared.Log.Info(
+	shared.LogInfo(
 		"RESTORE => Restoring snapshot",
 		log.Ctx{
 			"snapshot":  snap,
@@ -94,7 +94,7 @@ func containerSnapRestore(d *Daemon, name string, snap string) error {
 
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		shared.Log.Error(
+		shared.LogError(
 			"RESTORE => loadcontainerLXD() failed",
 			log.Ctx{
 				"container": name,

From c4c2425da423ea89df61e1c0cfb35c14344a2e68 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 10:43:41 +0200
Subject: [PATCH 0271/1193] lxd/containers: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/containers.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/containers.go b/lxd/containers.go
index 16707efcb..f5b8d17b1 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -167,7 +167,7 @@ func containersShutdown(d *Daemon) error {
 }
 
 func containerDeleteSnapshots(d *Daemon, cname string) error {
-	shared.Log.Debug("containerDeleteSnapshots",
+	shared.LogDebug("containerDeleteSnapshots",
 		log.Ctx{"container": cname})
 
 	results, err := dbContainerGetSnapshots(d.db, cname)
@@ -178,7 +178,7 @@ func containerDeleteSnapshots(d *Daemon, cname string) error {
 	for _, sname := range results {
 		sc, err := containerLoadByName(d, sname)
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"containerDeleteSnapshots: Failed to load the snapshotcontainer",
 				log.Ctx{"container": cname, "snapshot": sname})
 
@@ -186,7 +186,7 @@ func containerDeleteSnapshots(d *Daemon, cname string) error {
 		}
 
 		if err := sc.Delete(); err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"containerDeleteSnapshots: Failed to delete a snapshotcontainer",
 				log.Ctx{"container": cname, "snapshot": sname, "err": err})
 		}

From d9e3b58a2ce9bed9dcf766dd9c53160f485e6313 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 10:49:31 +0200
Subject: [PATCH 0272/1193] lxd/daemon_config: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon_config.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 98bee3c0d..719d139c9 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -195,7 +195,7 @@ func daemonConfigInit(db *sql.DB) error {
 	for k, v := range dbValues {
 		_, ok := daemonConfig[k]
 		if !ok {
-			shared.Log.Error("Found invalid configuration key in database", log.Ctx{"key": k})
+			shared.LogError("Found invalid configuration key in database", log.Ctx{"key": k})
 		}
 
 		daemonConfig[k].currentValue = v

From 973862c942ea2cf62e1511121068303183962df8 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 10:51:29 +0200
Subject: [PATCH 0273/1193] lxd/daemon_images: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon_images.go | 42 +++++++++++++++++++++---------------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index ed1c76355..887450ccd 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -77,12 +77,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	}
 
 	if _, _, err := dbImageGet(d.db, fp, false, false); err == nil {
-		shared.Log.Debug("Image already exists in the db", log.Ctx{"image": fp})
+		shared.LogDebug("Image already exists in the db", log.Ctx{"image": fp})
 		// already have it
 		return fp, nil
 	}
 
-	shared.Log.Info(
+	shared.LogInfo(
 		"Image not in the db, downloading it",
 		log.Ctx{"image": fp, "server": server})
 
@@ -92,24 +92,24 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		// We already download the image
 		d.imagesDownloadingLock.RUnlock()
 
-		shared.Log.Info(
+		shared.LogInfo(
 			"Already downloading the image, waiting for it to succeed",
 			log.Ctx{"image": fp})
 
 		// Wait until the download finishes (channel closes)
 		if _, ok := <-waitChannel; ok {
-			shared.Log.Warn("Value transmitted over image lock semaphore?")
+			shared.LogWarnf("Value transmitted over image lock semaphore?")
 		}
 
 		if _, _, err := dbImageGet(d.db, fp, false, true); err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Previous download didn't succeed",
 				log.Ctx{"image": fp})
 
 			return "", fmt.Errorf("Previous download didn't succeed")
 		}
 
-		shared.Log.Info(
+		shared.LogInfo(
 			"Previous download succeeded",
 			log.Ctx{"image": fp})
 
@@ -118,7 +118,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	d.imagesDownloadingLock.RUnlock()
 
-	shared.Log.Info(
+	shared.LogInfo(
 		"Downloading the image",
 		log.Ctx{"image": fp})
 
@@ -179,7 +179,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 		resp, err := d.httpGetSync(url, certificate)
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Failed to download image metadata",
 				log.Ctx{"image": fp, "err": err})
 
@@ -246,7 +246,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	raw, err := d.httpGetFile(exporturl, certificate)
 	if err != nil {
-		shared.Log.Error(
+		shared.LogError(
 			"Failed to download image",
 			log.Ctx{"image": fp, "err": err})
 		return "", err
@@ -267,7 +267,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		// Get the metadata tarball
 		part, err := mr.NextPart()
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Invalid multipart image",
 				log.Ctx{"image": fp, "err": err})
 
@@ -275,7 +275,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 
 		if part.FormName() != "metadata" {
-			shared.Log.Error(
+			shared.LogError(
 				"Invalid multipart image",
 				log.Ctx{"image": fp, "err": err})
 
@@ -285,7 +285,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		destName = filepath.Join(destDir, info.Fingerprint)
 		f, err := os.Create(destName)
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Failed to save image",
 				log.Ctx{"image": fp, "err": err})
 
@@ -296,7 +296,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		f.Close()
 
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Failed to save image",
 				log.Ctx{"image": fp, "err": err})
 
@@ -306,7 +306,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		// Get the rootfs tarball
 		part, err = mr.NextPart()
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Invalid multipart image",
 				log.Ctx{"image": fp, "err": err})
 
@@ -314,7 +314,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 
 		if part.FormName() != "rootfs" {
-			shared.Log.Error(
+			shared.LogError(
 				"Invalid multipart image",
 				log.Ctx{"image": fp})
 			return "", fmt.Errorf("Invalid multipart image")
@@ -323,7 +323,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		destName = filepath.Join(destDir, info.Fingerprint+".rootfs")
 		f, err = os.Create(destName)
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Failed to save image",
 				log.Ctx{"image": fp, "err": err})
 			return "", err
@@ -333,7 +333,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		f.Close()
 
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Failed to save image",
 				log.Ctx{"image": fp, "err": err})
 			return "", err
@@ -343,7 +343,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 		f, err := os.Create(destName)
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Failed to save image",
 				log.Ctx{"image": fp, "err": err})
 
@@ -354,7 +354,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		f.Close()
 
 		if err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Failed to save image",
 				log.Ctx{"image": fp, "err": err})
 			return "", err
@@ -382,7 +382,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	_, err = imageBuildFromInfo(d, info)
 	if err != nil {
-		shared.Log.Error(
+		shared.LogError(
 			"Failed to create image",
 			log.Ctx{"image": fp, "err": err})
 
@@ -401,7 +401,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 	}
 
-	shared.Log.Info(
+	shared.LogInfo(
 		"Download succeeded",
 		log.Ctx{"image": fp})
 

From e7cc049afbedfdfe97acf90b15844a8e07b7295f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 10:53:04 +0200
Subject: [PATCH 0274/1193] lxd/db_containers: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/db_containers.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index 81efb1c0f..7b0a175f9 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -337,7 +337,7 @@ func dbContainerRename(db *sql.DB, oldName string, newName string) error {
 	}
 	defer stmt.Close()
 
-	shared.Log.Debug(
+	shared.LogDebug(
 		"Calling SQL Query",
 		log.Ctx{
 			"query":   "UPDATE containers SET name = ? WHERE name = ?",

From d2bdffbc50be95c751e98ff2234fcc31dbd41aa4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 11:25:58 +0200
Subject: [PATCH 0275/1193] lxd/main: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/main.go | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/lxd/main.go b/lxd/main.go
index 3467e2768..3c71b3d21 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -370,8 +370,7 @@ func cmdDaemon() error {
 		signal.Notify(ch, syscall.SIGPWR)
 		sig := <-ch
 
-		shared.Log.Info(
-			fmt.Sprintf("Received '%s signal', shutting down containers.", sig))
+		shared.LogInfof("Received '%s signal', shutting down containers.", sig)
 
 		containersShutdown(d)
 
@@ -382,8 +381,7 @@ func cmdDaemon() error {
 	go func() {
 		<-d.shutdownChan
 
-		shared.Log.Info(
-			fmt.Sprintf("Asked to shutdown by API, shutting down containers."))
+		shared.LogInfof("Asked to shutdown by API, shutting down containers.")
 
 		containersShutdown(d)
 
@@ -398,7 +396,7 @@ func cmdDaemon() error {
 		signal.Notify(ch, syscall.SIGTERM)
 		sig := <-ch
 
-		shared.Log.Info(fmt.Sprintf("Received '%s signal', exiting.", sig))
+		shared.LogInfof("Received '%s signal', exiting.", sig)
 		ret = d.Stop()
 		wg.Done()
 	}()

From 72cc2acab7fdaaed03117ba2f8a8694cdceca542 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 11:27:54 +0200
Subject: [PATCH 0276/1193] lxd/patches: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/patches.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/patches.go b/lxd/patches.go
index 4d7f0f081..3d303dde3 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -95,7 +95,7 @@ func patchInvalidProfileNames(name string, d *Daemon) error {
 
 	for _, profile := range profiles {
 		if strings.Contains(profile, "/") || shared.StringInSlice(profile, []string{".", ".."}) {
-			shared.Log.Info("Removing unreachable profile (invalid name)", log.Ctx{"name": profile})
+			shared.LogInfo("Removing unreachable profile (invalid name)", log.Ctx{"name": profile})
 			err := dbProfileDelete(d.db, profile)
 			if err != nil {
 				return err

From d9add487ebaf0008d7e4fe3c40d264d1caa59fbd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 11:29:51 +0200
Subject: [PATCH 0277/1193] lxd/profiles: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/profiles.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/profiles.go b/lxd/profiles.go
index a1b70f1d9..2d774acf1 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -41,7 +41,7 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 		} else {
 			profile, err := doProfileGet(d, name)
 			if err != nil {
-				shared.Log.Error("Failed to get profile", log.Ctx{"profile": name})
+				shared.LogError("Failed to get profile", log.Ctx{"profile": name})
 				continue
 			}
 			resultMap[i] = profile
@@ -132,7 +132,7 @@ func getRunningContainersWithProfile(d *Daemon, profile string) []container {
 	for _, name := range output {
 		c, err := containerLoadByName(d, name)
 		if err != nil {
-			shared.Log.Error("Failed opening container", log.Ctx{"container": name})
+			shared.LogError("Failed opening container", log.Ctx{"container": name})
 			continue
 		}
 		results = append(results, c)

From 08e132c4e474319f063224a860fe839f1bda77a6 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 11:35:48 +0200
Subject: [PATCH 0278/1193] lxd/storage: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage.go b/lxd/storage.go
index 1659f04bd..a17c8438d 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -305,7 +305,7 @@ func (ss *storageShared) shiftRootfs(c container) error {
 	dpath := c.Path()
 	rpath := c.RootfsPath()
 
-	shared.Log.Debug("Shifting root filesystem",
+	shared.LogDebug("Shifting root filesystem",
 		log.Ctx{"container": c.Name(), "rootfs": rpath})
 
 	idmapset := c.IdmapSet()

From f50d7771aec6dd4126391d49de840eb0adfbb921 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 11:37:59 +0200
Subject: [PATCH 0279/1193] lxd/storage_btrfs: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage_btrfs.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 8bfd4284d..fe29c34a2 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -1025,7 +1025,7 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 
 		err = cmd.Wait()
 		if err != nil {
-			shared.Log.Error("problem with btrfs receive", log.Ctx{"output": string(output)})
+			shared.LogError("problem with btrfs receive", log.Ctx{"output": string(output)})
 			return err
 		}
 
@@ -1034,13 +1034,13 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 
 			err := s.subvolSnapshot(cPath, targetPath, false)
 			if err != nil {
-				shared.Log.Error("problem with btrfs snapshot", log.Ctx{"err": err})
+				shared.LogError("problem with btrfs snapshot", log.Ctx{"err": err})
 				return err
 			}
 
 			err = s.subvolsDelete(cPath)
 			if err != nil {
-				shared.Log.Error("problem with btrfs delete", log.Ctx{"err": err})
+				shared.LogError("problem with btrfs delete", log.Ctx{"err": err})
 				return err
 			}
 		}

From e4c0df09e8caea565daded502d5df836c39745f1 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 11:44:28 +0200
Subject: [PATCH 0280/1193] lxd/storage_lvm: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage_lvm.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 12be97473..c93935c37 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -20,7 +20,7 @@ import (
 func storageLVMCheckVolumeGroup(vgName string) error {
 	output, err := exec.Command("vgdisplay", "-s", vgName).CombinedOutput()
 	if err != nil {
-		shared.Log.Debug("vgdisplay failed to find vg", log.Ctx{"output": string(output)})
+		shared.LogDebug("vgdisplay failed to find vg", log.Ctx{"output": string(output)})
 		return fmt.Errorf("LVM volume group '%s' not found", vgName)
 	}
 
@@ -557,7 +557,7 @@ func (s *storageLvm) createSnapshotContainer(
 
 	srcName := containerNameToLVName(sourceContainer.Name())
 	destName := containerNameToLVName(snapshotContainer.Name())
-	shared.Log.Debug(
+	shared.LogDebug(
 		"Creating snapshot",
 		log.Ctx{"srcName": srcName, "destName": destName})
 
@@ -649,7 +649,7 @@ func (s *storageLvm) ContainerSnapshotStart(container container) error {
 	srcName := containerNameToLVName(container.Name())
 	destName := containerNameToLVName(container.Name() + "/rw")
 
-	shared.Log.Debug(
+	shared.LogDebug(
 		"Creating snapshot",
 		log.Ctx{"srcName": srcName, "destName": destName})
 

From 3d70da3b3b55f93202e5d72727bc342c11ea0f10 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 11:47:19 +0200
Subject: [PATCH 0281/1193] lxd/storage_zfs: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage_zfs.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 9bb443e5c..004dcac12 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -1218,12 +1218,12 @@ func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zf
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
-		shared.Log.Error("problem reading zfs send stderr", "err", err)
+		shared.LogError("problem reading zfs send stderr", log.Ctx{"err": err})
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.Log.Error("problem with zfs send", "output", string(output))
+		shared.LogError("problem with zfs send", log.Ctx{"output": string(output)})
 	}
 
 	return err
@@ -1361,12 +1361,12 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []c
 
 		output, err := ioutil.ReadAll(stderr)
 		if err != nil {
-			shared.LogDebugf("problem reading zfs recv stderr %s", "err", err)
+			shared.LogDebug("problem reading zfs recv stderr %s", log.Ctx{"err": err})
 		}
 
 		err = cmd.Wait()
 		if err != nil {
-			shared.Log.Error("problem with zfs recv", "output", string(output))
+			shared.LogError("problem with zfs recv", log.Ctx{"output": string(output)})
 		}
 		return err
 	}
@@ -1405,7 +1405,7 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []c
 		/* clean up our migration-send snapshots that we got from recv. */
 		zfsSnapshots, err := s.zfsListSnapshots(fmt.Sprintf("containers/%s", container.Name()))
 		if err != nil {
-			shared.Log.Error("failed listing snapshots post migration", "err", err)
+			shared.LogError("failed listing snapshots post migration", log.Ctx{"err": err})
 			return
 		}
 

From a3d683398d1f10f9dd2762a191b9ab0089e21bfe Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 09:51:07 +0200
Subject: [PATCH 0282/1193] lxd/container_lxc: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 6aa5eecda..5b41aaf98 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1437,7 +1437,7 @@ func (c *containerLXC) OnStart() error {
 			c.fromHook = false
 			err := c.setNetworkPriority()
 			if err != nil {
-				shared.Log.Error("Failed to apply network priority", log.Ctx{"container": c.name, "err": err})
+				shared.LogError("Failed to apply network priority", log.Ctx{"container": c.name, "err": err})
 			}
 		}(c)
 	}
@@ -1457,7 +1457,7 @@ func (c *containerLXC) OnStart() error {
 			c.fromHook = false
 			err = c.setNetworkLimits(name, m)
 			if err != nil {
-				shared.Log.Error("Failed to apply network limits", log.Ctx{"container": c.name, "err": err})
+				shared.LogError("Failed to apply network limits", log.Ctx{"container": c.name, "err": err})
 			}
 		}(c, name, m)
 	}
@@ -1599,13 +1599,13 @@ func (c *containerLXC) OnStop(target string) error {
 		// Clean all the unix devices
 		err = c.removeUnixDevices()
 		if err != nil {
-			shared.Log.Error("Unable to remove unix devices", log.Ctx{"err": err})
+			shared.LogError("Unable to remove unix devices", log.Ctx{"err": err})
 		}
 
 		// Clean all the disk devices
 		err = c.removeDiskDevices()
 		if err != nil {
-			shared.Log.Error("Unable to remove disk devices", log.Ctx{"err": err})
+			shared.LogError("Unable to remove disk devices", log.Ctx{"err": err})
 		}
 
 		// Reboot the container
@@ -1806,7 +1806,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 	if c.IsRunning() {
 		wasRunning = true
 		if err := c.Stop(false); err != nil {
-			shared.Log.Error(
+			shared.LogError(
 				"Could not stop container",
 				log.Ctx{
 					"container": c.Name(),
@@ -1818,7 +1818,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 	// Restore the rootfs
 	err = c.storage.ContainerRestore(c, sourceContainer)
 	if err != nil {
-		shared.Log.Error("Restoring the filesystem failed",
+		shared.LogError("Restoring the filesystem failed",
 			log.Ctx{
 				"source":      sourceContainer.Name(),
 				"destination": c.Name()})
@@ -1836,7 +1836,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 
 	err = c.Update(args, false)
 	if err != nil {
-		shared.Log.Error("Restoring the configuration failed",
+		shared.LogError("Restoring the configuration failed",
 			log.Ctx{
 				"source":      sourceContainer.Name(),
 				"destination": c.Name()})
@@ -1855,7 +1855,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 		// this in snapshots.
 		err2 := os.RemoveAll(c.StatePath())
 		if err2 != nil {
-			shared.Log.Error("failed to delete snapshot state", "path", c.StatePath(), "err", err2)
+			shared.LogError("failed to delete snapshot state", log.Ctx{"path": c.StatePath(), "err": err2})
 		}
 
 		if err != nil {
@@ -1893,12 +1893,12 @@ func (c *containerLXC) Delete() error {
 	if c.IsSnapshot() {
 		// Remove the snapshot
 		if err := c.storage.ContainerSnapshotDelete(c); err != nil {
-			shared.Log.Warn("failed to delete snapshot", "name", c.Name(), "err", err)
+			shared.LogWarn("failed to delete snapshot", log.Ctx{"name": c.Name(), "err": err})
 		}
 	} else {
 		// Remove all snapshot
 		if err := containerDeleteSnapshots(c.daemon, c.Name()); err != nil {
-			shared.Log.Warn("failed to delete snapshots", "name", c.Name(), "err", err)
+			shared.LogWarn("failed to delete snapshots", log.Ctx{"name": c.Name(), "err": err})
 		}
 
 		// Clean things up
@@ -2793,7 +2793,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 		prettyCmd = "restore"
 	default:
 		prettyCmd = "unknown"
-		shared.Log.Warn("unknown migrate call", log.Ctx{"cmd": cmd})
+		shared.LogWarn("unknown migrate call", log.Ctx{"cmd": cmd})
 	}
 
 	var migrateErr error
@@ -2866,7 +2866,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 
 	collectErr := collectCRIULogFile(c, stateDir, function, prettyCmd)
 	if collectErr != nil {
-		shared.Log.Error("Error collecting checkpoint log file", log.Ctx{"err": collectErr})
+		shared.LogError("Error collecting checkpoint log file", log.Ctx{"err": collectErr})
 	}
 
 	if migrateErr != nil {
@@ -3335,7 +3335,7 @@ func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
 
 	// Process forkgetnet response
 	if err != nil {
-		shared.Log.Error("Error calling 'lxd forkgetnet", log.Ctx{"container": c.name, "output": string(out), "pid": pid})
+		shared.LogError("Error calling 'lxd forkgetnet", log.Ctx{"container": c.name, "output": string(out), "pid": pid})
 		return result
 	}
 
@@ -3343,7 +3343,7 @@ func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
 
 	err = json.Unmarshal(out, &networks)
 	if err != nil {
-		shared.Log.Error("Failure to read forkgetnet json", log.Ctx{"container": c.name, "err": err})
+		shared.LogError("Failure to read forkgetnet json", log.Ctx{"container": c.name, "err": err})
 		return result
 	}
 
@@ -3823,7 +3823,7 @@ func (c *containerLXC) removeUnixDevices() error {
 		devicePath := filepath.Join(c.DevicesPath(), f.Name())
 		err := os.Remove(devicePath)
 		if err != nil {
-			shared.Log.Error("failed removing unix device", log.Ctx{"err": err, "path": devicePath})
+			shared.LogError("failed removing unix device", log.Ctx{"err": err, "path": devicePath})
 		}
 	}
 
@@ -4283,7 +4283,7 @@ func (c *containerLXC) removeDiskDevices() error {
 		diskPath := filepath.Join(c.DevicesPath(), f.Name())
 		err := os.Remove(diskPath)
 		if err != nil {
-			shared.Log.Error("Failed to remove disk device path", log.Ctx{"err": err, "path": diskPath})
+			shared.LogError("Failed to remove disk device path", log.Ctx{"err": err, "path": diskPath})
 		}
 	}
 

From 494456e28f87174fbaba3d700f624959d13a99d9 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 10:49:03 +0200
Subject: [PATCH 0283/1193] lxd/daemon: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon.go | 91 +++++++++++++++++++++++++++++------------------------------
 1 file changed, 45 insertions(+), 46 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index dd8e641d6..d64b2ff04 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -216,8 +216,7 @@ func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, er
 func readMyCert() (string, string, error) {
 	certf := shared.VarPath("server.crt")
 	keyf := shared.VarPath("server.key")
-	shared.Log.Info("Looking for existing certificates", log.Ctx{"cert": certf, "key": keyf})
-
+	shared.LogInfo("Looking for existing certificates", log.Ctx{"cert": certf, "key": keyf})
 	err := shared.FindOrGenCert(certf, keyf)
 
 	return certf, keyf, err
@@ -272,19 +271,19 @@ func (d *Daemon) createCmd(version string, c Command) {
 		w.Header().Set("Content-Type", "application/json")
 
 		if d.isTrustedClient(r) {
-			shared.Log.Info(
+			shared.LogInfo(
 				"handling",
 				log.Ctx{"method": r.Method, "url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else if r.Method == "GET" && c.untrustedGet {
-			shared.Log.Info(
+			shared.LogInfo(
 				"allowing untrusted GET",
 				log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else if r.Method == "POST" && c.untrustedPost {
-			shared.Log.Info(
+			shared.LogInfo(
 				"allowing untrusted POST",
 				log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else {
-			shared.Log.Warn(
+			shared.LogWarn(
 				"rejecting request from untrusted client",
 				log.Ctx{"ip": r.RemoteAddr})
 			Forbidden.Render(w)
@@ -331,7 +330,7 @@ func (d *Daemon) createCmd(version string, c Command) {
 		if err := resp.Render(w); err != nil {
 			err := InternalError(err).Render(w)
 			if err != nil {
-				shared.Log.Error("Failed writing error for error, giving up")
+				shared.LogErrorf("Failed writing error for error, giving up")
 			}
 		}
 
@@ -564,13 +563,13 @@ func (d *Daemon) Init() error {
 
 	/* Print welcome message */
 	if d.MockMode {
-		shared.Log.Info("LXD is starting in mock mode",
+		shared.LogInfo("LXD is starting in mock mode",
 			log.Ctx{"path": shared.VarPath("")})
 	} else if d.SetupMode {
-		shared.Log.Info("LXD is starting in setup mode",
+		shared.LogInfo("LXD is starting in setup mode",
 			log.Ctx{"path": shared.VarPath("")})
 	} else {
-		shared.Log.Info("LXD is starting in normal mode",
+		shared.LogInfo("LXD is starting in normal mode",
 			log.Ctx{"path": shared.VarPath("")})
 	}
 
@@ -581,31 +580,31 @@ func (d *Daemon) Init() error {
 	if aaAvailable && os.Getenv("LXD_SECURITY_APPARMOR") == "false" {
 		aaAvailable = false
 		aaAdmin = false
-		shared.Log.Warn("AppArmor support has been manually disabled")
+		shared.LogWarnf("AppArmor support has been manually disabled")
 	}
 
 	if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") {
 		aaAvailable = false
 		aaAdmin = false
-		shared.Log.Warn("AppArmor support has been disabled because of lack of kernel support")
+		shared.LogWarnf("AppArmor support has been disabled because of lack of kernel support")
 	}
 
 	_, err = exec.LookPath("apparmor_parser")
 	if aaAvailable && err != nil {
 		aaAvailable = false
 		aaAdmin = false
-		shared.Log.Warn("AppArmor support has been disabled because 'apparmor_parser' couldn't be found")
+		shared.LogWarnf("AppArmor support has been disabled because 'apparmor_parser' couldn't be found")
 	}
 
 	/* Detect AppArmor admin support */
 	if aaAdmin && !haveMacAdmin() {
 		aaAdmin = false
-		shared.Log.Warn("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.")
+		shared.LogWarnf("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.")
 	}
 
 	if aaAdmin && runningInUserns {
 		aaAdmin = false
-		shared.Log.Warn("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.")
+		shared.LogWarnf("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.")
 	}
 
 	/* Detect AppArmor confinment */
@@ -613,49 +612,49 @@ func (d *Daemon) Init() error {
 		profile := aaProfile()
 		if profile != "unconfined" && profile != "" {
 			aaConfined = true
-			shared.Log.Warn("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.")
+			shared.LogWarnf("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.")
 		}
 	}
 
 	/* Detect CGroup support */
 	cgBlkioController = shared.PathExists("/sys/fs/cgroup/blkio/")
 	if !cgBlkioController {
-		shared.Log.Warn("Couldn't find the CGroup blkio controller, I/O limits will be ignored.")
+		shared.LogWarnf("Couldn't find the CGroup blkio controller, I/O limits will be ignored.")
 	}
 
 	cgCpuController = shared.PathExists("/sys/fs/cgroup/cpu/")
 	if !cgCpuController {
-		shared.Log.Warn("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.")
+		shared.LogWarnf("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.")
 	}
 
 	cgCpusetController = shared.PathExists("/sys/fs/cgroup/cpuset/")
 	if !cgCpusetController {
-		shared.Log.Warn("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.")
+		shared.LogWarnf("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.")
 	}
 
 	cgDevicesController = shared.PathExists("/sys/fs/cgroup/devices/")
 	if !cgDevicesController {
-		shared.Log.Warn("Couldn't find the CGroup devices controller, device access control won't work.")
+		shared.LogWarnf("Couldn't find the CGroup devices controller, device access control won't work.")
 	}
 
 	cgMemoryController = shared.PathExists("/sys/fs/cgroup/memory/")
 	if !cgMemoryController {
-		shared.Log.Warn("Couldn't find the CGroup memory controller, memory limits will be ignored.")
+		shared.LogWarnf("Couldn't find the CGroup memory controller, memory limits will be ignored.")
 	}
 
 	cgNetPrioController = shared.PathExists("/sys/fs/cgroup/net_prio/")
 	if !cgNetPrioController {
-		shared.Log.Warn("Couldn't find the CGroup network class controller, network limits will be ignored.")
+		shared.LogWarnf("Couldn't find the CGroup network class controller, network limits will be ignored.")
 	}
 
 	cgPidsController = shared.PathExists("/sys/fs/cgroup/pids/")
 	if !cgPidsController {
-		shared.Log.Warn("Couldn't find the CGroup pids controller, process limits will be ignored.")
+		shared.LogWarnf("Couldn't find the CGroup pids controller, process limits will be ignored.")
 	}
 
 	cgSwapAccounting = shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes")
 	if !cgSwapAccounting {
-		shared.Log.Warn("CGroup memory swap accounting is disabled, swap limits will be ignored.")
+		shared.LogWarnf("CGroup memory swap accounting is disabled, swap limits will be ignored.")
 	}
 
 	/* Get the list of supported architectures */
@@ -713,18 +712,18 @@ func (d *Daemon) Init() error {
 	/* Detect the filesystem */
 	d.BackingFs, err = filesystemDetect(d.lxcpath)
 	if err != nil {
-		shared.Log.Error("Error detecting backing fs", log.Ctx{"err": err})
+		shared.LogError("Error detecting backing fs", log.Ctx{"err": err})
 	}
 
 	/* Read the uid/gid allocation */
 	d.IdmapSet, err = shared.DefaultIdmapSet()
 	if err != nil {
-		shared.Log.Warn("Error reading idmap", log.Ctx{"err": err.Error()})
-		shared.Log.Warn("Only privileged containers will be able to run")
+		shared.LogWarn("Error reading idmap", log.Ctx{"err": err.Error()})
+		shared.LogWarnf("Only privileged containers will be able to run")
 	} else {
-		shared.Log.Info("Default uid/gid map:")
+		shared.LogInfof("Default uid/gid map:")
 		for _, lxcmap := range d.IdmapSet.ToLxcString() {
-			shared.Log.Info(strings.TrimRight(" - "+lxcmap, "\n"))
+			shared.LogInfof(strings.TrimRight(" - "+lxcmap, "\n"))
 		}
 	}
 
@@ -762,7 +761,7 @@ func (d *Daemon) Init() error {
 
 			err := d.ExpireLogs()
 			if err != nil {
-				shared.Log.Error("Failed to expire logs", log.Ctx{"err": err})
+				shared.LogError("Failed to expire logs", log.Ctx{"err": err})
 			}
 
 			shared.LogDebugf("Done expiring log files")
@@ -834,14 +833,14 @@ func (d *Daemon) Init() error {
 	}
 
 	d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		shared.Log.Debug("Sending top level 404", log.Ctx{"url": r.URL})
+		shared.LogDebug("Sending top level 404", log.Ctx{"url": r.URL})
 		w.Header().Set("Content-Type", "application/json")
 		NotFound.Render(w)
 	})
 
 	listeners := d.GetListeners()
 	if len(listeners) > 0 {
-		shared.Log.Info("LXD is socket activated")
+		shared.LogInfof("LXD is socket activated")
 
 		for _, listener := range listeners {
 			if shared.PathExists(listener.Addr().String()) {
@@ -852,7 +851,7 @@ func (d *Daemon) Init() error {
 			}
 		}
 	} else {
-		shared.Log.Info("LXD isn't socket activated")
+		shared.LogInfof("LXD isn't socket activated")
 
 		localSocketPath := shared.VarPath("unix.socket")
 
@@ -861,7 +860,7 @@ func (d *Daemon) Init() error {
 		if shared.PathExists(localSocketPath) {
 			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
 			if err != nil {
-				shared.Log.Debug("Detected stale unix socket, deleting")
+				shared.LogDebugf("Detected stale unix socket, deleting")
 				// Connecting failed, so let's delete the socket and
 				// listen on it ourselves.
 				err = os.Remove(localSocketPath)
@@ -913,10 +912,10 @@ func (d *Daemon) Init() error {
 
 		tcpl, err := tls.Listen("tcp", listenAddr, d.tlsConfig)
 		if err != nil {
-			shared.Log.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err})
+			shared.LogError("cannot listen on https socket, skipping...", log.Ctx{"err": err})
 		} else {
 			if d.TCPSocket != nil {
-				shared.Log.Info("Replacing inherited TCP socket with configured one")
+				shared.LogInfof("Replacing inherited TCP socket with configured one")
 				d.TCPSocket.Socket.Close()
 			}
 			d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true}
@@ -924,14 +923,14 @@ func (d *Daemon) Init() error {
 	}
 
 	d.tomb.Go(func() error {
-		shared.Log.Info("REST API daemon:")
+		shared.LogInfof("REST API daemon:")
 		if d.UnixSocket != nil {
-			shared.Log.Info(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
+			shared.LogInfo(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
 			d.tomb.Go(func() error { return http.Serve(d.UnixSocket.Socket, &lxdHttpServer{d.mux, d}) })
 		}
 
 		if d.TCPSocket != nil {
-			shared.Log.Info(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
+			shared.LogInfo(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
 			d.tomb.Go(func() error { return http.Serve(d.TCPSocket.Socket, &lxdHttpServer{d.mux, d}) })
 		}
 
@@ -1013,7 +1012,7 @@ func (d *Daemon) Ready() error {
 func (d *Daemon) CheckTrustState(cert x509.Certificate) bool {
 	for k, v := range d.clientCerts {
 		if bytes.Compare(cert.Raw, v.Raw) == 0 {
-			shared.Log.Debug("Found cert", log.Ctx{"k": k})
+			shared.LogDebug("Found cert", log.Ctx{"k": k})
 			return true
 		}
 		shared.Log.Debug("Client cert != key", log.Ctx{"k": k})
@@ -1049,33 +1048,33 @@ func (d *Daemon) Stop() error {
 	forceStop := false
 
 	d.tomb.Kill(errStop)
-	shared.Log.Info("Stopping REST API handler:")
+	shared.LogInfof("Stopping REST API handler:")
 	for _, socket := range []*Socket{d.TCPSocket, d.UnixSocket} {
 		if socket == nil {
 			continue
 		}
 
 		if socket.CloseOnExit {
-			shared.Log.Info(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()})
+			shared.LogInfo(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()})
 			socket.Socket.Close()
 		} else {
-			shared.Log.Info(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()})
+			shared.LogInfo(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()})
 			forceStop = true
 		}
 	}
 
 	if n, err := d.numRunningContainers(); err != nil || n == 0 {
-		shared.Log.Debug("Unmounting shmounts")
+		shared.LogDebugf("Unmounting shmounts")
 
 		syscall.Unmount(shared.VarPath("shmounts"), syscall.MNT_DETACH)
 	} else {
 		shared.LogDebugf("Not unmounting shmounts (containers are still running)")
 	}
 
-	shared.Log.Debug("Closing the database")
+	shared.LogDebugf("Closing the database")
 	d.db.Close()
 
-	shared.Log.Debug("Stopping /dev/lxd handler")
+	shared.LogDebugf("Stopping /dev/lxd handler")
 	d.devlxd.Close()
 
 	if d.MockMode || forceStop {

From 1c1118109064c83d3fb79bd6e36f09ebe0037abd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 10:58:46 +0200
Subject: [PATCH 0284/1193] lxd/devices: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/devices.go | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index b10704704..2a517e94d 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -187,24 +187,24 @@ func deviceTaskBalance(d *Daemon) {
 		// Older kernel - use cpuset.cpus
 		effectiveCpus, err = cGroupGet("cpuset", "/", "cpuset.cpus")
 		if err != nil {
-			shared.Log.Error("Error reading host's cpuset.cpus")
+			shared.LogErrorf("Error reading host's cpuset.cpus")
 			return
 		}
 	}
 	err = cGroupSet("cpuset", "/lxc", "cpuset.cpus", effectiveCpus)
 	if err != nil && shared.PathExists("/sys/fs/cgroup/cpuset/lxc") {
-		shared.Log.Warn("Error setting lxd's cpuset.cpus", log.Ctx{"err": err})
+		shared.LogWarn("Error setting lxd's cpuset.cpus", log.Ctx{"err": err})
 	}
 	cpus, err := parseCpuset(effectiveCpus)
 	if err != nil {
-		shared.Log.Error("Error parsing host's cpu set", log.Ctx{"cpuset": effectiveCpus, "err": err})
+		shared.LogError("Error parsing host's cpu set", log.Ctx{"cpuset": effectiveCpus, "err": err})
 		return
 	}
 
 	// Iterate through the containers
 	containers, err := dbContainersList(d.db, cTypeRegular)
 	if err != nil {
-		shared.Log.Error("problem loading containers list", log.Ctx{"err": err})
+		shared.LogError("problem loading containers list", log.Ctx{"err": err})
 		return
 	}
 	fixedContainers := map[int][]container{}
@@ -268,7 +268,7 @@ func deviceTaskBalance(d *Daemon) {
 	for cpu, ctns := range fixedContainers {
 		c, ok := usage[cpu]
 		if !ok {
-			shared.Log.Error("Internal error: container using unavailable cpu")
+			shared.LogErrorf("Internal error: container using unavailable cpu")
 			continue
 		}
 		id := c.strId
@@ -317,7 +317,7 @@ func deviceTaskBalance(d *Daemon) {
 		sort.Strings(set)
 		err := ctn.CGroupSet("cpuset.cpus", strings.Join(set, ","))
 		if err != nil {
-			shared.Log.Error("balance: Unable to set cpuset", log.Ctx{"name": ctn.Name(), "err": err, "value": strings.Join(set, ",")})
+			shared.LogError("balance: Unable to set cpuset", log.Ctx{"name": ctn.Name(), "err": err, "value": strings.Join(set, ",")})
 		}
 	}
 }
@@ -361,7 +361,7 @@ func deviceNetworkPriority(d *Daemon, netif string) {
 func deviceEventListener(d *Daemon) {
 	chNetlinkCPU, chNetlinkNetwork, err := deviceNetlinkListener()
 	if err != nil {
-		shared.Log.Error("scheduler: couldn't setup netlink listener")
+		shared.LogErrorf("scheduler: couldn't setup netlink listener")
 		return
 	}
 
@@ -369,7 +369,7 @@ func deviceEventListener(d *Daemon) {
 		select {
 		case e := <-chNetlinkCPU:
 			if len(e) != 2 {
-				shared.Log.Error("Scheduler: received an invalid cpu hotplug event")
+				shared.LogErrorf("Scheduler: received an invalid cpu hotplug event")
 				continue
 			}
 
@@ -381,7 +381,7 @@ func deviceEventListener(d *Daemon) {
 			deviceTaskBalance(d)
 		case e := <-chNetlinkNetwork:
 			if len(e) != 2 {
-				shared.Log.Error("Scheduler: received an invalid network hotplug event")
+				shared.LogErrorf("Scheduler: received an invalid network hotplug event")
 				continue
 			}
 
@@ -393,7 +393,7 @@ func deviceEventListener(d *Daemon) {
 			deviceNetworkPriority(d, e[0])
 		case e := <-deviceSchedRebalance:
 			if len(e) != 3 {
-				shared.Log.Error("Scheduler: received an invalid rebalance event")
+				shared.LogErrorf("Scheduler: received an invalid rebalance event")
 				continue
 			}
 

From 7b946b419014f02d29d651ec4ce99c3c848a7158 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 10 Jun 2016 17:14:57 -0400
Subject: [PATCH 0285/1193] db: Rework DB schema updates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Make it look more like the new patches mechanism.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go        |  19 +-
 lxd/db_test.go   |   8 +-
 lxd/db_update.go | 562 +++++++++++++++++++------------------------------------
 3 files changed, 209 insertions(+), 380 deletions(-)

diff --git a/lxd/db.go b/lxd/db.go
index 548185c2e..397ed1af8 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -34,8 +34,6 @@ type Profile struct {
 // Profiles will contain a list of all Profiles.
 type Profiles []Profile
 
-const DB_CURRENT_VERSION int = 32
-
 // CURRENT_SCHEMA contains the current SQLite SQL Schema.
 const CURRENT_SCHEMA string = `
 CREATE TABLE IF NOT EXISTS certificates (
@@ -193,7 +191,7 @@ func createDb(db *sql.DB) (err error) {
 
 	// There isn't an entry for schema version, let's put it in.
 	insertStmt := `INSERT INTO schema (version, updated_at) values (?, strftime("%s"));`
-	_, err = db.Exec(insertStmt, DB_CURRENT_VERSION)
+	_, err = db.Exec(insertStmt, dbGetLatestSchema())
 	if err != nil {
 		return err
 	}
@@ -217,6 +215,10 @@ func dbGetSchema(db *sql.DB) (v int) {
 	return v
 }
 
+func dbGetLatestSchema() int {
+	return dbUpdates[len(dbUpdates)-1].version
+}
+
 // Create a database connection object and return it.
 func initializeDbObject(d *Daemon, path string) (err error) {
 	var openPath string
@@ -242,13 +244,10 @@ func initializeDbObject(d *Daemon, path string) (err error) {
 	// Run PRAGMA statements now since they are *per-connection*.
 	d.db.Exec("PRAGMA foreign_keys=ON;") // This allows us to use ON DELETE CASCADE
 
-	v := dbGetSchema(d.db)
-
-	if v != DB_CURRENT_VERSION {
-		err = dbUpdate(d, v)
-		if err != nil {
-			return err
-		}
+	// Apply any update
+	err = dbUpdatesApplyAll(d)
+	if err != nil {
+		return err
 	}
 
 	return nil
diff --git a/lxd/db_test.go b/lxd/db_test.go
index d68f5996a..eece15e2c 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -262,7 +262,7 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	}
 
 	// Run the upgrade from V6 code
-	err = dbUpdateFromV6(d.db)
+	err = dbUpdateFromV6(5, 6, d)
 
 	// Make sure the inserted data is still there.
 	statements = `SELECT count(*) FROM containers_config;`
@@ -376,15 +376,15 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	d.db = db
 	daemonConfigInit(db)
 
-	err = dbUpdate(d, 1)
+	err = dbUpdatesApplyAll(d)
 	if err != nil {
 		t.Error("Error upgrading database schema!")
 		t.Fatal(err)
 	}
 
 	result := dbGetSchema(db)
-	if result != DB_CURRENT_VERSION {
-		t.Fatal(fmt.Sprintf("The schema is not at the latest version after update! Found: %d, should be: %d", result, DB_CURRENT_VERSION))
+	if result != dbGetLatestSchema() {
+		t.Fatal(fmt.Sprintf("The schema is not at the latest version after update! Found: %d, should be: %d", result, dbGetLatestSchema()))
 	}
 
 	// Make sure there are 0 containers_config entries left.
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 9396cd806..4c631feb9 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"database/sql"
 	"encoding/hex"
 	"fmt"
 	"io/ioutil"
@@ -17,24 +16,116 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-func dbUpdateFromV31(db *sql.DB) error {
+/* Database updates are one-time actions that are needed to move an
+   existing database from one version of the schema to the next.
+
+   Those updates are applied at startup time before anything else in LXD
+   is initialized. This means that they should be entirely
+   self-contained and not touch anything but the database.
+
+   Calling LXD functions isn't allowed as such functions may themselves
+   depend on a newer DB schema and so would fail when upgrading a very old
+   version of LXD.
+
+   DO NOT USE this mechanism for one-time actions which do not involve
+   changes to the database schema. Use patches instead.
+
+   Only append to the updates list, never remove entries and never re-order them.
+*/
+
+var dbUpdates = []dbUpdate{
+	dbUpdate{version: 1, run: dbUpdateFromV0},
+	dbUpdate{version: 2, run: dbUpdateFromV1},
+	dbUpdate{version: 3, run: dbUpdateFromV2},
+	dbUpdate{version: 4, run: dbUpdateFromV3},
+	dbUpdate{version: 5, run: dbUpdateFromV4},
+	dbUpdate{version: 6, run: dbUpdateFromV5},
+	dbUpdate{version: 7, run: dbUpdateFromV6},
+	dbUpdate{version: 8, run: dbUpdateFromV7},
+	dbUpdate{version: 9, run: dbUpdateFromV8},
+	dbUpdate{version: 10, run: dbUpdateFromV9},
+	dbUpdate{version: 11, run: dbUpdateFromV10},
+	dbUpdate{version: 12, run: dbUpdateFromV11},
+	dbUpdate{version: 13, run: dbUpdateFromV12},
+	dbUpdate{version: 14, run: dbUpdateFromV13},
+	dbUpdate{version: 15, run: dbUpdateFromV14},
+	dbUpdate{version: 16, run: dbUpdateFromV15},
+	dbUpdate{version: 17, run: dbUpdateFromV16},
+	dbUpdate{version: 18, run: dbUpdateFromV17},
+	dbUpdate{version: 19, run: dbUpdateFromV18},
+	dbUpdate{version: 20, run: dbUpdateFromV19},
+	dbUpdate{version: 21, run: dbUpdateFromV20},
+	dbUpdate{version: 22, run: dbUpdateFromV21},
+	dbUpdate{version: 23, run: dbUpdateFromV22},
+	dbUpdate{version: 24, run: dbUpdateFromV23},
+	dbUpdate{version: 25, run: dbUpdateFromV24},
+	dbUpdate{version: 26, run: dbUpdateFromV25},
+	dbUpdate{version: 27, run: dbUpdateFromV26},
+	dbUpdate{version: 28, run: dbUpdateFromV27},
+	dbUpdate{version: 29, run: dbUpdateFromV28},
+	dbUpdate{version: 30, run: dbUpdateFromV29},
+	dbUpdate{version: 31, run: dbUpdateFromV30},
+	dbUpdate{version: 32, run: dbUpdateFromV31},
+}
+
+type dbUpdate struct {
+	version int
+	run     func(previousVersion int, version int, d *Daemon) error
+}
+
+func (u *dbUpdate) apply(currentVersion int, d *Daemon) error {
+	// Get the current schema version
+
+	shared.LogDebugf("Updating DB schema from %d to %d", currentVersion, u.version)
+
+	err := u.run(currentVersion, u.version, d)
+	if err != nil {
+		return err
+	}
+
+	_, err = d.db.Exec("INSERT INTO schema (version, updated_at) VALUES (?, strftime(\"%s\"));", u.version)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func dbUpdatesApplyAll(d *Daemon) error {
+	currentVersion := dbGetSchema(d.db)
+
+	for _, update := range dbUpdates {
+		if update.version <= currentVersion {
+			continue
+		}
+
+		err := update.apply(currentVersion, d)
+		if err != nil {
+			return err
+		}
+
+		currentVersion = update.version
+	}
+
+	return nil
+}
+
+// Schema updates begin here
+func dbUpdateFromV31(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS patches (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
     applied_at DATETIME NOT NULL,
     UNIQUE (name)
-);
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 32)
+);`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV30(db *sql.DB, mockMode bool) error {
-	if mockMode {
-		stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-		_, err := db.Exec(stmt, 31)
-		return err
+func dbUpdateFromV30(currentVersion int, version int, d *Daemon) error {
+	if d.MockMode {
+		return nil
 	}
 
 	entries, err := ioutil.ReadDir(shared.VarPath("containers"))
@@ -72,16 +163,12 @@ func dbUpdateFromV30(db *sql.DB, mockMode bool) error {
 		}
 	}
 
-	stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err = db.Exec(stmt, 31)
-	return err
+	return nil
 }
 
-func dbUpdateFromV29(db *sql.DB, mockMode bool) error {
-	if mockMode {
-		stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-		_, err := db.Exec(stmt, 30)
-		return err
+func dbUpdateFromV29(currentVersion int, version int, d *Daemon) error {
+	if d.MockMode {
+		return nil
 	}
 
 	if shared.PathExists(shared.VarPath("zfs.img")) {
@@ -91,32 +178,25 @@ func dbUpdateFromV29(db *sql.DB, mockMode bool) error {
 		}
 	}
 
-	stmt := `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 30)
-	return err
+	return nil
 }
 
-func dbUpdateFromV28(db *sql.DB) error {
+func dbUpdateFromV28(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "aadisable", 2 FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "source", "/dev/null" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker" AND profiles_devices.name = "aadisable";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "path", "/sys/module/apparmor/parameters/enabled" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker" AND profiles_devices.name = "aadisable";`
-	db.Exec(stmt)
+	d.db.Exec(stmt)
 
-	stmt = `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 29)
-	return err
+	return nil
 }
 
-func dbUpdateFromV27(db *sql.DB) error {
-	stmt := `
-UPDATE profiles_devices SET type=3 WHERE type='unix-char';
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 28)
+func dbUpdateFromV27(currentVersion int, version int, d *Daemon) error {
+	_, err := d.db.Exec("UPDATE profiles_devices SET type=3 WHERE type='unix-char';")
 	return err
 }
 
-func dbUpdateFromV26(db *sql.DB) error {
+func dbUpdateFromV26(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 ALTER TABLE images ADD COLUMN auto_update INTEGER NOT NULL DEFAULT 0;
 CREATE TABLE IF NOT EXISTS images_source (
@@ -127,92 +207,76 @@ CREATE TABLE IF NOT EXISTS images_source (
     certificate TEXT NOT NULL,
     alias VARCHAR(255) NOT NULL,
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
-);
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 27)
+);`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV25(db *sql.DB) error {
+func dbUpdateFromV25(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 INSERT INTO profiles (name, description) VALUES ("docker", "Profile supporting docker in containers");
 INSERT INTO profiles_config (profile_id, key, value) SELECT id, "security.nesting", "true" FROM profiles WHERE name="docker";
 INSERT INTO profiles_config (profile_id, key, value) SELECT id, "linux.kernel_modules", "overlay, nf_nat" FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "fuse", "unix-char" FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "path", "/dev/fuse" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker";`
-	db.Exec(stmt)
+	d.db.Exec(stmt)
 
-	stmt = `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 26)
-	return err
+	return nil
 }
 
-func dbUpdateFromV24(db *sql.DB) error {
-	stmt := `
-ALTER TABLE containers ADD COLUMN stateful INTEGER NOT NULL DEFAULT 0;
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 25)
+func dbUpdateFromV24(currentVersion int, version int, d *Daemon) error {
+	_, err := d.db.Exec("ALTER TABLE containers ADD COLUMN stateful INTEGER NOT NULL DEFAULT 0;")
 	return err
 }
 
-func dbUpdateFromV23(db *sql.DB) error {
-	stmt := `
-ALTER TABLE profiles ADD COLUMN description TEXT;
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 24)
+func dbUpdateFromV23(currentVersion int, version int, d *Daemon) error {
+	_, err := d.db.Exec("ALTER TABLE profiles ADD COLUMN description TEXT;")
 	return err
 }
 
-func dbUpdateFromV22(db *sql.DB) error {
+func dbUpdateFromV22(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 DELETE FROM containers_devices_config WHERE key='type';
-DELETE FROM profiles_devices_config WHERE key='type';
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 23)
+DELETE FROM profiles_devices_config WHERE key='type';`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV21(db *sql.DB) error {
-	stmt := `
-ALTER TABLE containers ADD COLUMN creation_date DATETIME NOT NULL DEFAULT 0;
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 22)
+func dbUpdateFromV21(currentVersion int, version int, d *Daemon) error {
+	_, err := d.db.Exec("ALTER TABLE containers ADD COLUMN creation_date DATETIME NOT NULL DEFAULT 0;")
 	return err
 }
 
-func dbUpdateFromV20(db *sql.DB) error {
+func dbUpdateFromV20(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 UPDATE containers_devices SET name='__lxd_upgrade_root' WHERE name='root';
 UPDATE profiles_devices SET name='__lxd_upgrade_root' WHERE name='root';
 
 INSERT INTO containers_devices (container_id, name, type) SELECT id, "root", 2 FROM containers;
-INSERT INTO containers_devices_config (container_device_id, key, value) SELECT id, "path", "/" FROM containers_devices WHERE name='root';
-
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 21)
+INSERT INTO containers_devices_config (container_device_id, key, value) SELECT id, "path", "/" FROM containers_devices WHERE name='root';`
+	_, err := d.db.Exec(stmt)
 
 	return err
 }
 
-func dbUpdateFromV19(db *sql.DB) error {
+func dbUpdateFromV19(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 DELETE FROM containers_config WHERE container_id NOT IN (SELECT id FROM containers);
 DELETE FROM containers_devices_config WHERE container_device_id NOT IN (SELECT id FROM containers_devices WHERE container_id IN (SELECT id FROM containers));
 DELETE FROM containers_devices WHERE container_id NOT IN (SELECT id FROM containers);
 DELETE FROM containers_profiles WHERE container_id NOT IN (SELECT id FROM containers);
 DELETE FROM images_aliases WHERE image_id NOT IN (SELECT id FROM images);
-DELETE FROM images_properties WHERE image_id NOT IN (SELECT id FROM images);
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 20)
+DELETE FROM images_properties WHERE image_id NOT IN (SELECT id FROM images);`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV18(db *sql.DB) error {
+func dbUpdateFromV18(currentVersion int, version int, d *Daemon) error {
 	var id int
 	var value string
 
 	// Update container config
-	rows, err := dbQueryScan(db, "SELECT id, value FROM containers_config WHERE key='limits.memory'", nil, []interface{}{id, value})
+	rows, err := dbQueryScan(d.db, "SELECT id, value FROM containers_config WHERE key='limits.memory'", nil, []interface{}{id, value})
 	if err != nil {
 		return err
 	}
@@ -235,21 +299,21 @@ func dbUpdateFromV18(db *sql.DB) error {
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
 			shared.LogDebugf("Invalid container memory limit, id=%d value=%s, removing.", id, value)
-			_, err = db.Exec("DELETE FROM containers_config WHERE id=?;", id)
+			_, err = d.db.Exec("DELETE FROM containers_config WHERE id=?;", id)
 			if err != nil {
 				return err
 			}
 		}
 
 		// Set the new value
-		_, err = db.Exec("UPDATE containers_config SET value=? WHERE id=?", value, id)
+		_, err = d.db.Exec("UPDATE containers_config SET value=? WHERE id=?", value, id)
 		if err != nil {
 			return err
 		}
 	}
 
 	// Update profiles config
-	rows, err = dbQueryScan(db, "SELECT id, value FROM profiles_config WHERE key='limits.memory'", nil, []interface{}{id, value})
+	rows, err = dbQueryScan(d.db, "SELECT id, value FROM profiles_config WHERE key='limits.memory'", nil, []interface{}{id, value})
 	if err != nil {
 		return err
 	}
@@ -272,43 +336,40 @@ func dbUpdateFromV18(db *sql.DB) error {
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
 			shared.LogDebugf("Invalid profile memory limit, id=%d value=%s, removing.", id, value)
-			_, err = db.Exec("DELETE FROM profiles_config WHERE id=?;", id)
+			_, err = d.db.Exec("DELETE FROM profiles_config WHERE id=?;", id)
 			if err != nil {
 				return err
 			}
 		}
 
 		// Set the new value
-		_, err = db.Exec("UPDATE profiles_config SET value=? WHERE id=?", value, id)
+		_, err = d.db.Exec("UPDATE profiles_config SET value=? WHERE id=?", value, id)
 		if err != nil {
 			return err
 		}
 	}
 
-	_, err = db.Exec("INSERT INTO schema (version, updated_at) VALUES (?, strftime(\"%s\"));", 19)
-	return err
+	return nil
 }
 
-func dbUpdateFromV17(db *sql.DB) error {
+func dbUpdateFromV17(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 DELETE FROM profiles_config WHERE key LIKE 'volatile.%';
 UPDATE containers_config SET key='limits.cpu' WHERE key='limits.cpus';
-UPDATE profiles_config SET key='limits.cpu' WHERE key='limits.cpus';
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 18)
+UPDATE profiles_config SET key='limits.cpu' WHERE key='limits.cpus';`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV16(db *sql.DB) error {
+func dbUpdateFromV16(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 UPDATE config SET key='storage.lvm_vg_name' WHERE key = 'core.lvm_vg_name';
-UPDATE config SET key='storage.lvm_thinpool_name' WHERE key = 'core.lvm_thinpool_name';
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 17)
+UPDATE config SET key='storage.lvm_thinpool_name' WHERE key = 'core.lvm_thinpool_name';`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV15(d *Daemon) error {
+func dbUpdateFromV15(currentVersion int, version int, d *Daemon) error {
 	// munge all LVM-backed containers' LV names to match what is
 	// required for snapshot support
 
@@ -359,13 +420,11 @@ func dbUpdateFromV15(d *Daemon) error {
 			return fmt.Errorf("Couldn't recreate symlink '%s'->'%s'", lvLinkPath, newLinkDest)
 		}
 	}
-	stmt := `
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err = d.db.Exec(stmt, 16)
-	return err
+
+	return nil
 }
 
-func dbUpdateFromV14(db *sql.DB) error {
+func dbUpdateFromV14(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 PRAGMA foreign_keys=OFF; -- So that integrity doesn't get in the way for now
 
@@ -393,30 +452,27 @@ INSERT INTO tmp SELECT id, name, architecture, type, ephemeral FROM containers;
 DROP TABLE containers;
 ALTER TABLE tmp RENAME TO containers;
 
-PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 15)
+PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV13(db *sql.DB) error {
+func dbUpdateFromV13(currentVersion int, version int, d *Daemon) error {
 	stmt := `
-UPDATE containers_config SET key='volatile.base_image' WHERE key = 'volatile.baseImage';
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 14)
+UPDATE containers_config SET key='volatile.base_image' WHERE key = 'volatile.baseImage';`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV12(db *sql.DB) error {
+func dbUpdateFromV12(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 ALTER TABLE images ADD COLUMN cached INTEGER NOT NULL DEFAULT 0;
-ALTER TABLE images ADD COLUMN last_use_date DATETIME;
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 13)
+ALTER TABLE images ADD COLUMN last_use_date DATETIME;`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV11(d *Daemon) error {
+func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 	if d.MockMode {
 		// No need to move snapshots no mock runs,
 		// dbUpdateFromV12 will then set the db version to 13
@@ -487,14 +543,10 @@ func dbUpdateFromV11(d *Daemon) error {
 		return fmt.Errorf("Got errors while moving snapshots, see the log output.")
 	}
 
-	stmt := `
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err = d.db.Exec(stmt, 12)
-
-	return err
+	return nil
 }
 
-func dbUpdateFromV10(d *Daemon) error {
+func dbUpdateFromV10(currentVersion int, version int, d *Daemon) error {
 	if d.MockMode {
 		// No need to move lxc to containers in mock runs,
 		// dbUpdateFromV12 will then set the db version to 13
@@ -512,13 +564,10 @@ func dbUpdateFromV10(d *Daemon) error {
 		containersRestart(d)
 	}
 
-	stmt := `
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := d.db.Exec(stmt, 11)
-	return err
+	return nil
 }
 
-func dbUpdateFromV9(db *sql.DB) error {
+func dbUpdateFromV9(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 CREATE TABLE tmp (
     id INTEGER primary key AUTOINCREMENT NOT NULL,
@@ -556,30 +605,27 @@ UPDATE profiles_devices SET type=2 WHERE id IN (SELECT id FROM tmp WHERE type="d
 UPDATE profiles_devices SET type=3 WHERE id IN (SELECT id FROM tmp WHERE type="unix-char");
 UPDATE profiles_devices SET type=4 WHERE id IN (SELECT id FROM tmp WHERE type="unix-block");
 
-DROP TABLE tmp;
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 10)
+DROP TABLE tmp;`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV8(db *sql.DB) error {
+func dbUpdateFromV8(currentVersion int, version int, d *Daemon) error {
 	stmt := `
-UPDATE certificates SET fingerprint = replace(fingerprint, " ", "");
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 9)
+UPDATE certificates SET fingerprint = replace(fingerprint, " ", "");`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV7(db *sql.DB) error {
+func dbUpdateFromV7(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 UPDATE config SET key='core.trust_password' WHERE key IN ('password', 'trust_password', 'trust-password', 'core.trust-password');
-DELETE FROM config WHERE key != 'core.trust_password';
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 8)
+DELETE FROM config WHERE key != 'core.trust_password';`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV6(db *sql.DB) error {
+func dbUpdateFromV6(currentVersion int, version int, d *Daemon) error {
 	// This update recreates the schemas that need an ON DELETE CASCADE foreign
 	// key.
 	stmt := `
@@ -703,15 +749,14 @@ CREATE TABLE IF NOT EXISTS profiles_devices_config (
 INSERT INTO profiles_devices_config SELECT * FROM tmp;
 DROP TABLE tmp;
 
-PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 7)
+PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
+	_, err := d.db.Exec(stmt)
 	if err != nil {
 		return err
 	}
 
 	// Get the rows with broken foreign keys an nuke them
-	rows, err := db.Query("PRAGMA foreign_key_check;")
+	rows, err := d.db.Query("PRAGMA foreign_key_check;")
 	if err != nil {
 		return err
 	}
@@ -733,7 +778,7 @@ INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
 	rows.Close()
 
 	for i := range tablestodelete {
-		_, err = db.Exec(fmt.Sprintf("DELETE FROM %s WHERE rowid = %d;", tablestodelete[i], rowidtodelete[i]))
+		_, err = d.db.Exec(fmt.Sprintf("DELETE FROM %s WHERE rowid = %d;", tablestodelete[i], rowidtodelete[i]))
 		if err != nil {
 			return err
 		}
@@ -742,25 +787,24 @@ INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
 	return err
 }
 
-func dbUpdateFromV5(db *sql.DB) error {
+func dbUpdateFromV5(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 ALTER TABLE containers ADD COLUMN power_state INTEGER NOT NULL DEFAULT 0;
-ALTER TABLE containers ADD COLUMN ephemeral INTEGER NOT NULL DEFAULT 0;
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 6)
+ALTER TABLE containers ADD COLUMN ephemeral INTEGER NOT NULL DEFAULT 0;`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV4(db *sql.DB) error {
+func dbUpdateFromV4(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS config (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     key VARCHAR(255) NOT NULL,
     value TEXT,
     UNIQUE (key)
-);
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 5)
+);`
+
+	_, err := d.db.Exec(stmt)
 	if err != nil {
 		return err
 	}
@@ -779,7 +823,7 @@ INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
 		oldPassword = hex.EncodeToString(buff)
 		stmt := `INSERT INTO config (key, value) VALUES ("core.trust_password", ?);`
 
-		_, err := db.Exec(stmt, oldPassword)
+		_, err := d.db.Exec(stmt, oldPassword)
 		if err != nil {
 			return err
 		}
@@ -790,21 +834,19 @@ INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
 	return nil
 }
 
-func dbUpdateFromV3(db *sql.DB) error {
+func dbUpdateFromV3(currentVersion int, version int, d *Daemon) error {
 	// Attempt to create a default profile (but don't fail if already there)
 	stmt := `INSERT INTO profiles (name) VALUES ("default");
 INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "eth0", "nic" FROM profiles WHERE profiles.name="default";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "nictype", "bridged" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, 'name', "eth0" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "parent", "lxdbr0" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";`
-	db.Exec(stmt)
+	d.db.Exec(stmt)
 
-	stmt = `INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 4)
-	return err
+	return nil
 }
 
-func dbUpdateFromV2(db *sql.DB) error {
+func dbUpdateFromV2(currentVersion int, version int, d *Daemon) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS containers_devices (
     id INTEGER primary key AUTOINCREMENT NOT NULL,
@@ -859,14 +901,12 @@ CREATE TABLE IF NOT EXISTS profiles_devices_config (
     value TEXT,
     UNIQUE (profile_device_id, key),
     FOREIGN KEY (profile_device_id) REFERENCES profiles_devices (id)
-);
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 3)
+);`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-/* Yeah we can do htis in a more clever way */
-func dbUpdateFromV1(db *sql.DB) error {
+func dbUpdateFromV1(currentVersion int, version int, d *Daemon) error {
 	// v1..v2 adds images aliases
 	stmt := `
 CREATE TABLE IF NOT EXISTS images_aliases (
@@ -876,13 +916,12 @@ CREATE TABLE IF NOT EXISTS images_aliases (
     description VARCHAR(255),
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
     UNIQUE (name)
-);
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 2)
+);`
+	_, err := d.db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV0(db *sql.DB) error {
+func dbUpdateFromV0(currentVersion int, version int, d *Daemon) error {
 	// v0..v1 adds schema table
 	stmt := `
 CREATE TABLE IF NOT EXISTS schema (
@@ -890,216 +929,7 @@ CREATE TABLE IF NOT EXISTS schema (
     version INTEGER NOT NULL,
     updated_at DATETIME NOT NULL,
     UNIQUE (version)
-);
-INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
-	_, err := db.Exec(stmt, 1)
+);`
+	_, err := d.db.Exec(stmt)
 	return err
 }
-
-func dbUpdate(d *Daemon, prevVersion int) error {
-	db := d.db
-
-	if prevVersion == DB_CURRENT_VERSION {
-		return nil
-	}
-
-	if prevVersion < 0 || prevVersion > DB_CURRENT_VERSION {
-		return fmt.Errorf("Bad database version: %d", prevVersion)
-	}
-
-	var err error
-	if prevVersion < 1 {
-		err = dbUpdateFromV0(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 2 {
-		err = dbUpdateFromV1(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 3 {
-		err = dbUpdateFromV2(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 4 {
-		err = dbUpdateFromV3(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 5 {
-		err = dbUpdateFromV4(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 6 {
-		err = dbUpdateFromV5(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 7 {
-		err = dbUpdateFromV6(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 8 {
-		err = dbUpdateFromV7(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 9 {
-		err = dbUpdateFromV8(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 10 {
-		err = dbUpdateFromV9(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 11 {
-		err = dbUpdateFromV10(d)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 12 {
-		err = dbUpdateFromV11(d)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 13 {
-		err = dbUpdateFromV12(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 14 {
-		err = dbUpdateFromV13(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 15 {
-		err = dbUpdateFromV14(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 16 {
-		err = dbUpdateFromV15(d)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 17 {
-		err = dbUpdateFromV16(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 18 {
-		err = dbUpdateFromV17(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 19 {
-		err = dbUpdateFromV18(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 20 {
-		err = dbUpdateFromV19(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 21 {
-		err = dbUpdateFromV20(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 22 {
-		err = dbUpdateFromV21(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 23 {
-		err = dbUpdateFromV22(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 24 {
-		err = dbUpdateFromV23(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 25 {
-		err = dbUpdateFromV24(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 26 {
-		err = dbUpdateFromV25(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 27 {
-		err = dbUpdateFromV26(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 28 {
-		err = dbUpdateFromV27(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 29 {
-		err = dbUpdateFromV28(db)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 30 {
-		err = dbUpdateFromV29(db, d.MockMode)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 31 {
-		err = dbUpdateFromV30(db, d.MockMode)
-		if err != nil {
-			return err
-		}
-	}
-	if prevVersion < 32 {
-		err = dbUpdateFromV31(db)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}

From 1dcf55d1c3de3b0e17a96ef33135769e42e1ff90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 Aug 2016 16:21:57 -0400
Subject: [PATCH 0286/1193] Make a database backup on schema updates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2299

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_update.go | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index 4c631feb9..c9a2ad731 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -94,11 +94,22 @@ func (u *dbUpdate) apply(currentVersion int, d *Daemon) error {
 func dbUpdatesApplyAll(d *Daemon) error {
 	currentVersion := dbGetSchema(d.db)
 
+	backup := false
 	for _, update := range dbUpdates {
 		if update.version <= currentVersion {
 			continue
 		}
 
+		if !backup {
+			shared.Log.Info("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
+			err := shared.FileCopy(shared.VarPath("lxd.db"), shared.VarPath("lxd.db.bak"))
+			if err != nil {
+				return err
+			}
+
+			backup = true
+		}
+
 		err := update.apply(currentVersion, d)
 		if err != nil {
 			return err

From 187ce92edbb385196e772859ca6c831e788b760a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 10:55:59 +0200
Subject: [PATCH 0287/1193] lxd/db_update: switch to new logging functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/db_update.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index c9a2ad731..38a066009 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -101,7 +101,7 @@ func dbUpdatesApplyAll(d *Daemon) error {
 		}
 
 		if !backup {
-			shared.Log.Info("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
+			shared.LogInfof("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
 			err := shared.FileCopy(shared.VarPath("lxd.db"), shared.VarPath("lxd.db.bak"))
 			if err != nil {
 				return err

From a1a361a8d9011df224ce4905aca58da8302d444e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 16:17:52 +0200
Subject: [PATCH 0288/1193] shared/log: fix LogDebug()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/log.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/log.go b/shared/log.go
index 430080363..bfaa8f01d 100644
--- a/shared/log.go
+++ b/shared/log.go
@@ -31,7 +31,7 @@ func init() {
 // General wrappers around Logger interface functions.
 func LogDebug(msg string, ctx map[string]interface{}) {
 	if Log != nil {
-		Log.Warn(msg, log.Ctx(ctx))
+		Log.Debug(msg, log.Ctx(ctx))
 	}
 }
 

From b79ffc982906c41c3fe890c6ba86117ff510a79b Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 15 Sep 2016 22:16:53 +0000
Subject: [PATCH 0289/1193] don't try to backup the database when running tests

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/db_update.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index 38a066009..102601b4d 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -100,8 +100,8 @@ func dbUpdatesApplyAll(d *Daemon) error {
 			continue
 		}
 
-		if !backup {
-			shared.LogInfof("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
+		if !d.MockMode && !backup {
+			shared.Log.Info("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
 			err := shared.FileCopy(shared.VarPath("lxd.db"), shared.VarPath("lxd.db.bak"))
 			if err != nil {
 				return err

From 86c7eb6dbdd4800a02c1f411e2b9112c2055e10d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Sep 2016 00:13:03 -0400
Subject: [PATCH 0290/1193] init: Change validation functions for consistency
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/lxd/main.go b/lxd/main.go
index 3c71b3d21..36c450bcc 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -667,7 +667,7 @@ func cmdInit() error {
 		}
 	}
 
-	askString := func(question string, default_ string, validate func(string) string) string {
+	askString := func(question string, default_ string, validate func(string) error) string {
 		for {
 			fmt.Printf(question)
 			input, _ := reader.ReadString('\n')
@@ -677,7 +677,7 @@ func cmdInit() error {
 			}
 			if validate != nil {
 				result := validate(input)
-				if result != "" {
+				if result != nil {
 					fmt.Printf("Invalid input: %s\n\n", result)
 					continue
 				}
@@ -812,11 +812,11 @@ func cmdInit() error {
 			if askBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
 				storagePool = askString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
 				if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
-					deviceExists := func(path string) string {
+					deviceExists := func(path string) error {
 						if !shared.IsBlockdevPath(path) {
-							return fmt.Sprintf("'%s' is not a block device", path)
+							return fmt.Errorf("'%s' is not a block device", path)
 						}
-						return ""
+						return nil
 					}
 					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
 					storageMode = "device"
@@ -866,11 +866,11 @@ they otherwise would.
 		}
 
 		if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
-			isIPAddress := func(s string) string {
+			isIPAddress := func(s string) error {
 				if s != "all" && net.ParseIP(s) == nil {
-					return fmt.Sprintf("'%s' is not an IP address", s)
+					return fmt.Errorf("'%s' is not an IP address", s)
 				}
-				return ""
+				return nil
 			}
 
 			networkAddress = askString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)

From 9ccb1e9ddcc8b269fd07d84c5b4940e9e1c82fcf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Sep 2016 23:16:50 -0400
Subject: [PATCH 0291/1193] network: Move and rename isOnBridge
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 54 ++++++++++++++++++++++++++++--------------------------
 1 file changed, 28 insertions(+), 26 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index f705f6e5d..cfd1e722d 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -12,6 +12,33 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
+// Helper functions
+func networkIsInUse(c container, name string) bool {
+	devices := c.ExpandedDevices()
+	for _, name := range devices.DeviceNames() {
+		device := devices[name]
+
+		if device["type"] != "nic" {
+			continue
+		}
+
+		if !shared.StringInSlice(device["nictype"], []string{"bridged", "macvlan"}) {
+			continue
+		}
+
+		if device["parent"] == "" {
+			continue
+		}
+
+		if device["parent"] == name {
+			return true
+		}
+	}
+
+	return false
+}
+
+// API endpoints
 func networksGet(d *Daemon, r *http.Request) Response {
 	recursionStr := r.FormValue("recursion")
 	recursion, err := strconv.Atoi(recursionStr)
@@ -54,31 +81,6 @@ type network struct {
 	UsedBy []string `json:"used_by"`
 }
 
-func isOnBridge(c container, bridge string) bool {
-	devices := c.ExpandedDevices()
-	for _, name := range devices.DeviceNames() {
-		device := devices[name]
-
-		if device["type"] != "nic" {
-			continue
-		}
-
-		if !shared.StringInSlice(device["nictype"], []string{"bridged", "macvlan"}) {
-			continue
-		}
-
-		if device["parent"] == "" {
-			continue
-		}
-
-		if device["parent"] == bridge {
-			return true
-		}
-	}
-
-	return false
-}
-
 func networkGet(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
@@ -113,7 +115,7 @@ func doNetworkGet(d *Daemon, name string) (network, error) {
 			return network{}, err
 		}
 
-		if isOnBridge(c, n.Name) {
+		if networkIsInUse(c, n.Name) {
 			n.UsedBy = append(n.UsedBy, fmt.Sprintf("/%s/containers/%s", shared.APIVersion, ct))
 		}
 	}

From 186c47332b1587d80991324a834aa7cf4c5638c6 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 15:17:37 +0200
Subject: [PATCH 0292/1193] lxd/containers_post: switch to new log functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/containers_post.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 9081a0bff..0556afbc8 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -291,7 +291,7 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 		err = sink()
 		if err != nil {
 			c.StorageStop()
-			shared.Log.Error("Error during migration sink", "err", err)
+			shared.LogError("Error during migration sink", log.Ctx{"err": err})
 			c.Delete()
 			return fmt.Errorf("Error transferring container data: %s", err)
 		}
@@ -336,7 +336,7 @@ func createFromCopy(d *Daemon, req *containerPostReq) Response {
 
 	for key, value := range sourceConfig {
 		if len(key) > 8 && key[0:8] == "volatile" && key[9:] != "base_image" {
-			shared.Log.Debug("Skipping volatile key from copy source",
+			shared.LogDebug("Skipping volatile key from copy source",
 				log.Ctx{"key": key})
 			continue
 		}

From 2aca1e152e4e1d25d823193c934bea12fe8c218c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 15:22:59 +0200
Subject: [PATCH 0293/1193] lxd/container_exec: switch to new log functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_exec.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 960d16ba6..c42ba965c 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -335,7 +335,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		metadata := shared.Jmap{"return": cmdResult}
 		err = op.UpdateMetadata(metadata)
 		if err != nil {
-			shared.Log.Error("error updating metadata for cmd", log.Ctx{"err": err, "cmd": post.Command})
+			shared.LogError("error updating metadata for cmd", log.Ctx{"err": err, "cmd": post.Command})
 		}
 		return cmdErr
 	}

From dc11d6f96c7b352b8e703c82812dbc77134d9361 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 15:23:41 +0200
Subject: [PATCH 0294/1193] lxd/apparmor: switch to new log functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/apparmor.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index 22e5f5ec7..b4d73f3e5 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -325,7 +325,7 @@ func runApparmor(command string, c container) error {
 
 	output, err := cmd.CombinedOutput()
 	if err != nil {
-		shared.Log.Error("Running apparmor",
+		shared.LogError("Running apparmor",
 			log.Ctx{"action": command, "output": string(output), "err": err})
 	}
 

From ed0b37db4255d40a9921b66a2f62ed80aa978d44 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 15:24:34 +0200
Subject: [PATCH 0295/1193] lxd/storage_btrfs: switch to new log functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage_btrfs.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index fe29c34a2..f1f160186 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -854,12 +854,12 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
-		shared.Log.Error("problem reading btrfs send stderr", "err", err)
+		shared.LogError("problem reading btrfs send stderr", log.Ctx{"err": err})
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.Log.Error("problem with btrfs send", "output", string(output))
+		shared.LogError("problem with btrfs send", log.Ctx{"output": string(output)})
 	}
 	return err
 }

From 4a09fe823369bee3307e2841223fb1906d4d99cc Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 15:25:22 +0200
Subject: [PATCH 0296/1193] lxd/db_update: switch to new log functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/db_update.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index 102601b4d..d0fb0357d 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -412,11 +412,11 @@ func dbUpdateFromV15(currentVersion int, version int, d *Daemon) error {
 		newLVName = strings.Replace(newLVName, shared.SnapshotDelimiter, "-", -1)
 
 		if cName == newLVName {
-			shared.Log.Debug("No need to rename, skipping", log.Ctx{"cName": cName, "newLVName": newLVName})
+			shared.LogDebug("No need to rename, skipping", log.Ctx{"cName": cName, "newLVName": newLVName})
 			continue
 		}
 
-		shared.Log.Debug("About to rename cName in lv upgrade", log.Ctx{"lvLinkPath": lvLinkPath, "cName": cName, "newLVName": newLVName})
+		shared.LogDebug("About to rename cName in lv upgrade", log.Ctx{"lvLinkPath": lvLinkPath, "cName": cName, "newLVName": newLVName})
 
 		output, err := exec.Command("lvrename", vgName, cName, newLVName).CombinedOutput()
 		if err != nil {
@@ -502,7 +502,7 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 		oldPath := shared.VarPath("containers", snappieces[0], "snapshots", snappieces[1])
 		newPath := shared.VarPath("snapshots", snappieces[0], snappieces[1])
 		if shared.PathExists(oldPath) && !shared.PathExists(newPath) {
-			shared.Log.Info(
+			shared.LogInfo(
 				"Moving snapshot",
 				log.Ctx{
 					"snapshot": cName,
@@ -515,7 +515,7 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 			// snapshots/<container>/<snap0>
 			output, err := storageRsyncCopy(oldPath, newPath)
 			if err != nil {
-				shared.Log.Error(
+				shared.LogError(
 					"Failed rsync snapshot",
 					log.Ctx{
 						"snapshot": cName,
@@ -527,7 +527,7 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 
 			// Remove containers/<container>/snapshots/<snap0>
 			if err := os.RemoveAll(oldPath); err != nil {
-				shared.Log.Error(
+				shared.LogError(
 					"Failed to remove the old snapshot path",
 					log.Ctx{
 						"snapshot": cName,

From f3dfd08ee25ad3e8a0be1c8ad8166ea50d6d86b4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 15:26:16 +0200
Subject: [PATCH 0297/1193] lxd/images: switch to new log functions

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/images.go | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 22e9b3392..eb59932b3 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -849,14 +849,14 @@ func autoUpdateImages(d *Daemon) {
 
 	images, err := dbImagesGet(d.db, false)
 	if err != nil {
-		shared.Log.Error("Unable to retrieve the list of images", log.Ctx{"err": err})
+		shared.LogError("Unable to retrieve the list of images", log.Ctx{"err": err})
 		return
 	}
 
 	for _, fp := range images {
 		id, info, err := dbImageGet(d.db, fp, false, true)
 		if err != nil {
-			shared.Log.Error("Error loading image", log.Ctx{"err": err, "fp": fp})
+			shared.LogError("Error loading image", log.Ctx{"err": err, "fp": fp})
 			continue
 		}
 
@@ -869,38 +869,38 @@ func autoUpdateImages(d *Daemon) {
 			continue
 		}
 
-		shared.Log.Debug("Processing image", log.Ctx{"fp": fp, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias})
+		shared.LogDebug("Processing image", log.Ctx{"fp": fp, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias})
 
 		hash, err := d.ImageDownload(nil, source.Server, source.Protocol, "", "", source.Alias, false, true)
 		if hash == fp {
-			shared.Log.Debug("Already up to date", log.Ctx{"fp": fp})
+			shared.LogDebug("Already up to date", log.Ctx{"fp": fp})
 			continue
 		} else if err != nil {
-			shared.Log.Error("Failed to update the image", log.Ctx{"err": err, "fp": fp})
+			shared.LogError("Failed to update the image", log.Ctx{"err": err, "fp": fp})
 			continue
 		}
 
 		newId, _, err := dbImageGet(d.db, hash, false, true)
 		if err != nil {
-			shared.Log.Error("Error loading image", log.Ctx{"err": err, "fp": hash})
+			shared.LogError("Error loading image", log.Ctx{"err": err, "fp": hash})
 			continue
 		}
 
 		err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedDate)
 		if err != nil {
-			shared.Log.Error("Error setting last use date", log.Ctx{"err": err, "fp": hash})
+			shared.LogError("Error setting last use date", log.Ctx{"err": err, "fp": hash})
 			continue
 		}
 
 		err = dbImageAliasesMove(d.db, id, newId)
 		if err != nil {
-			shared.Log.Error("Error moving aliases", log.Ctx{"err": err, "fp": hash})
+			shared.LogError("Error moving aliases", log.Ctx{"err": err, "fp": hash})
 			continue
 		}
 
 		err = doDeleteImage(d, fp)
 		if err != nil {
-			shared.Log.Error("Error deleting image", log.Ctx{"err": err, "fp": fp})
+			shared.LogError("Error deleting image", log.Ctx{"err": err, "fp": fp})
 		}
 	}
 
@@ -914,14 +914,14 @@ func pruneExpiredImages(d *Daemon) {
 	expiry := daemonConfig["images.remote_cache_expiry"].GetInt64()
 	images, err := dbImagesGetExpired(d.db, expiry)
 	if err != nil {
-		shared.Log.Error("Unable to retrieve the list of expired images", log.Ctx{"err": err})
+		shared.LogError("Unable to retrieve the list of expired images", log.Ctx{"err": err})
 		return
 	}
 
 	// Delete them
 	for _, fp := range images {
 		if err := doDeleteImage(d, fp); err != nil {
-			shared.Log.Error("Error deleting image", log.Ctx{"err": err, "fp": fp})
+			shared.LogError("Error deleting image", log.Ctx{"err": err, "fp": fp})
 		}
 	}
 
@@ -938,11 +938,11 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	// look at the path
 	s, err := storageForImage(d, imgInfo)
 	if err != nil {
-		shared.Log.Error("error detecting image storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
+		shared.LogError("error detecting image storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
 	} else {
 		// Remove the image from storage backend
 		if err = s.ImageDelete(imgInfo.Fingerprint); err != nil {
-			shared.Log.Error("error deleting the image from storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
+			shared.LogError("error deleting the image from storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
 		}
 	}
 

From 02dc0f47dcb9d462c626934f39615e56be5b64c4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 16:20:57 +0200
Subject: [PATCH 0298/1193] lxd/daemon: adjust log levels

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon.go | 42 +++++++++++++++++++++---------------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index d64b2ff04..137f709aa 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -216,7 +216,7 @@ func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, er
 func readMyCert() (string, string, error) {
 	certf := shared.VarPath("server.crt")
 	keyf := shared.VarPath("server.key")
-	shared.LogInfo("Looking for existing certificates", log.Ctx{"cert": certf, "key": keyf})
+	shared.LogDebug("Looking for existing certificates", log.Ctx{"cert": certf, "key": keyf})
 	err := shared.FindOrGenCert(certf, keyf)
 
 	return certf, keyf, err
@@ -271,15 +271,15 @@ func (d *Daemon) createCmd(version string, c Command) {
 		w.Header().Set("Content-Type", "application/json")
 
 		if d.isTrustedClient(r) {
-			shared.LogInfo(
+			shared.LogDebug(
 				"handling",
 				log.Ctx{"method": r.Method, "url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else if r.Method == "GET" && c.untrustedGet {
-			shared.LogInfo(
+			shared.LogDebug(
 				"allowing untrusted GET",
 				log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else if r.Method == "POST" && c.untrustedPost {
-			shared.LogInfo(
+			shared.LogDebug(
 				"allowing untrusted POST",
 				log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else {
@@ -357,21 +357,21 @@ func (d *Daemon) SetupStorageDriver() error {
 	if lvmVgName != "" {
 		d.Storage, err = newStorage(d, storageTypeLvm)
 		if err != nil {
-			shared.LogInfof("Could not initialize storage type LVM: %s - falling back to dir", err)
+			shared.LogDebugf("Could not initialize storage type LVM: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
 	} else if zfsPoolName != "" {
 		d.Storage, err = newStorage(d, storageTypeZfs)
 		if err != nil {
-			shared.LogInfof("Could not initialize storage type ZFS: %s - falling back to dir", err)
+			shared.LogDebugf("Could not initialize storage type ZFS: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
 	} else if d.BackingFs == "btrfs" {
 		d.Storage, err = newStorage(d, storageTypeBtrfs)
 		if err != nil {
-			shared.LogInfof("Could not initialize storage type btrfs: %s - falling back to dir", err)
+			shared.LogDebugf("Could not initialize storage type btrfs: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
@@ -563,13 +563,13 @@ func (d *Daemon) Init() error {
 
 	/* Print welcome message */
 	if d.MockMode {
-		shared.LogInfo("LXD is starting in mock mode",
+		shared.LogDebug("LXD is starting in mock mode",
 			log.Ctx{"path": shared.VarPath("")})
 	} else if d.SetupMode {
-		shared.LogInfo("LXD is starting in setup mode",
+		shared.LogDebug("LXD is starting in setup mode",
 			log.Ctx{"path": shared.VarPath("")})
 	} else {
-		shared.LogInfo("LXD is starting in normal mode",
+		shared.LogDebug("LXD is starting in normal mode",
 			log.Ctx{"path": shared.VarPath("")})
 	}
 
@@ -721,9 +721,9 @@ func (d *Daemon) Init() error {
 		shared.LogWarn("Error reading idmap", log.Ctx{"err": err.Error()})
 		shared.LogWarnf("Only privileged containers will be able to run")
 	} else {
-		shared.LogInfof("Default uid/gid map:")
+		shared.LogDebugf("Default uid/gid map:")
 		for _, lxcmap := range d.IdmapSet.ToLxcString() {
-			shared.LogInfof(strings.TrimRight(" - "+lxcmap, "\n"))
+			shared.LogDebugf(strings.TrimRight(" - "+lxcmap, "\n"))
 		}
 	}
 
@@ -840,7 +840,7 @@ func (d *Daemon) Init() error {
 
 	listeners := d.GetListeners()
 	if len(listeners) > 0 {
-		shared.LogInfof("LXD is socket activated")
+		shared.LogDebugf("LXD is socket activated")
 
 		for _, listener := range listeners {
 			if shared.PathExists(listener.Addr().String()) {
@@ -851,7 +851,7 @@ func (d *Daemon) Init() error {
 			}
 		}
 	} else {
-		shared.LogInfof("LXD isn't socket activated")
+		shared.LogDebugf("LXD isn't socket activated")
 
 		localSocketPath := shared.VarPath("unix.socket")
 
@@ -915,7 +915,7 @@ func (d *Daemon) Init() error {
 			shared.LogError("cannot listen on https socket, skipping...", log.Ctx{"err": err})
 		} else {
 			if d.TCPSocket != nil {
-				shared.LogInfof("Replacing inherited TCP socket with configured one")
+				shared.LogDebugf("Replacing inherited TCP socket with configured one")
 				d.TCPSocket.Socket.Close()
 			}
 			d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true}
@@ -923,14 +923,14 @@ func (d *Daemon) Init() error {
 	}
 
 	d.tomb.Go(func() error {
-		shared.LogInfof("REST API daemon:")
+		shared.LogDebugf("REST API daemon:")
 		if d.UnixSocket != nil {
-			shared.LogInfo(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
+			shared.LogDebug(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
 			d.tomb.Go(func() error { return http.Serve(d.UnixSocket.Socket, &lxdHttpServer{d.mux, d}) })
 		}
 
 		if d.TCPSocket != nil {
-			shared.LogInfo(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
+			shared.LogDebug(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
 			d.tomb.Go(func() error { return http.Serve(d.TCPSocket.Socket, &lxdHttpServer{d.mux, d}) })
 		}
 
@@ -1048,17 +1048,17 @@ func (d *Daemon) Stop() error {
 	forceStop := false
 
 	d.tomb.Kill(errStop)
-	shared.LogInfof("Stopping REST API handler:")
+	shared.LogDebugf("Stopping REST API handler:")
 	for _, socket := range []*Socket{d.TCPSocket, d.UnixSocket} {
 		if socket == nil {
 			continue
 		}
 
 		if socket.CloseOnExit {
-			shared.LogInfo(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()})
+			shared.LogDebug(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()})
 			socket.Socket.Close()
 		} else {
-			shared.LogInfo(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()})
+			shared.LogDebug(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()})
 			forceStop = true
 		}
 	}

From 7ebb7b06a4bb486317c5a2cea84362df7a47d88a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 16 Sep 2016 16:21:27 +0200
Subject: [PATCH 0299/1193] lxd/storage: adjust log levels

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/storage.go b/lxd/storage.go
index a17c8438d..f8ab30550 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -368,7 +368,7 @@ func (lw *storageLogWrapper) Init(config map[string]interface{}) (storage, error
 		log.Ctx{"driver": fmt.Sprintf("storage/%s", lw.w.GetStorageTypeName())},
 	)
 
-	lw.log.Info("Init")
+	lw.log.Debug("Init")
 	return lw, err
 }
 

From 08911fde01053737766716275988d55b14bc88bd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 11:54:34 +0200
Subject: [PATCH 0300/1193] lxd/daemon_images: adapt log levels

LogInfo() --> LogDebug()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon_images.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 887450ccd..88a4657b8 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -92,7 +92,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		// We already download the image
 		d.imagesDownloadingLock.RUnlock()
 
-		shared.LogInfo(
+		shared.LogDebug(
 			"Already downloading the image, waiting for it to succeed",
 			log.Ctx{"image": fp})
 
@@ -109,7 +109,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			return "", fmt.Errorf("Previous download didn't succeed")
 		}
 
-		shared.LogInfo(
+		shared.LogDebug(
 			"Previous download succeeded",
 			log.Ctx{"image": fp})
 
@@ -401,7 +401,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 	}
 
-	shared.LogInfo(
+	shared.LogDebug(
 		"Download succeeded",
 		log.Ctx{"image": fp})
 

From 26fc77677d492993b3c5a9b56e3a3e61c84bd924 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 11:51:47 +0200
Subject: [PATCH 0301/1193] lxd/daemon_images: improve log for image downloads

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon_images.go | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 88a4657b8..c589f7a1c 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -82,10 +82,6 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		return fp, nil
 	}
 
-	shared.LogInfo(
-		"Image not in the db, downloading it",
-		log.Ctx{"image": fp, "server": server})
-
 	// Now check if we already downloading the image
 	d.imagesDownloadingLock.RLock()
 	if waitChannel, ok := d.imagesDownloading[fp]; ok {
@@ -119,8 +115,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	d.imagesDownloadingLock.RUnlock()
 
 	shared.LogInfo(
-		"Downloading the image",
-		log.Ctx{"image": fp})
+		"Downloading image",
+		log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server})
 
 	// Add the download to the queue
 	d.imagesDownloadingLock.Lock()
@@ -237,6 +233,10 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			}
 		}
 
+		shared.LogInfo(
+			"Image downloaded",
+			log.Ctx{"image": fp, "fingerprint": fp, "operation": op.id, "alias": alias, "server": server})
+
 		if forContainer {
 			return fp, dbImageLastAccessInit(d.db, fp)
 		}
@@ -401,9 +401,9 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 	}
 
-	shared.LogDebug(
-		"Download succeeded",
-		log.Ctx{"image": fp})
+	shared.LogInfo(
+		"Image downloaded",
+		log.Ctx{"image": fp, "operation": op.id, "alias": alias, "server": server})
 
 	if forContainer {
 		return fp, dbImageLastAccessInit(d.db, fp)

From ae38c31c4518707215081fbcc41fbf3c2270e8ee Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 22:11:32 +0200
Subject: [PATCH 0302/1193] lxd/daemon: convert storage errors to LogError*()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 137f709aa..9d0c3be14 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -357,21 +357,21 @@ func (d *Daemon) SetupStorageDriver() error {
 	if lvmVgName != "" {
 		d.Storage, err = newStorage(d, storageTypeLvm)
 		if err != nil {
-			shared.LogDebugf("Could not initialize storage type LVM: %s - falling back to dir", err)
+			shared.LogErrorf("Could not initialize storage type LVM: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
 	} else if zfsPoolName != "" {
 		d.Storage, err = newStorage(d, storageTypeZfs)
 		if err != nil {
-			shared.LogDebugf("Could not initialize storage type ZFS: %s - falling back to dir", err)
+			shared.LogErrorf("Could not initialize storage type ZFS: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
 	} else if d.BackingFs == "btrfs" {
 		d.Storage, err = newStorage(d, storageTypeBtrfs)
 		if err != nil {
-			shared.LogDebugf("Could not initialize storage type btrfs: %s - falling back to dir", err)
+			shared.LogErrorf("Could not initialize storage type btrfs: %s - falling back to dir", err)
 		} else {
 			return nil
 		}

From d928ac0151a38203d5a21d6f8159a2e1d872d45a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 22:16:28 +0200
Subject: [PATCH 0303/1193] lxd/daemon: use more LogInfo*()s in Init()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon.go | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 9d0c3be14..db630b2a7 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -563,13 +563,13 @@ func (d *Daemon) Init() error {
 
 	/* Print welcome message */
 	if d.MockMode {
-		shared.LogDebug("LXD is starting in mock mode",
+		shared.LogInfo("LXD is starting in mock mode",
 			log.Ctx{"path": shared.VarPath("")})
 	} else if d.SetupMode {
-		shared.LogDebug("LXD is starting in setup mode",
+		shared.LogInfo("LXD is starting in setup mode",
 			log.Ctx{"path": shared.VarPath("")})
 	} else {
-		shared.LogDebug("LXD is starting in normal mode",
+		shared.LogInfo("LXD is starting in normal mode",
 			log.Ctx{"path": shared.VarPath("")})
 	}
 
@@ -721,9 +721,9 @@ func (d *Daemon) Init() error {
 		shared.LogWarn("Error reading idmap", log.Ctx{"err": err.Error()})
 		shared.LogWarnf("Only privileged containers will be able to run")
 	} else {
-		shared.LogDebugf("Default uid/gid map:")
+		shared.LogInfof("Default uid/gid map:")
 		for _, lxcmap := range d.IdmapSet.ToLxcString() {
-			shared.LogDebugf(strings.TrimRight(" - "+lxcmap, "\n"))
+			shared.LogInfof(strings.TrimRight(" - "+lxcmap, "\n"))
 		}
 	}
 
@@ -764,7 +764,7 @@ func (d *Daemon) Init() error {
 				shared.LogError("Failed to expire logs", log.Ctx{"err": err})
 			}
 
-			shared.LogDebugf("Done expiring log files")
+			shared.LogInfof("Done expiring log files")
 			<-t.C
 		}
 	}()
@@ -833,14 +833,14 @@ func (d *Daemon) Init() error {
 	}
 
 	d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		shared.LogDebug("Sending top level 404", log.Ctx{"url": r.URL})
+		shared.LogInfo("Sending top level 404", log.Ctx{"url": r.URL})
 		w.Header().Set("Content-Type", "application/json")
 		NotFound.Render(w)
 	})
 
 	listeners := d.GetListeners()
 	if len(listeners) > 0 {
-		shared.LogDebugf("LXD is socket activated")
+		shared.LogInfof("LXD is socket activated")
 
 		for _, listener := range listeners {
 			if shared.PathExists(listener.Addr().String()) {
@@ -851,7 +851,7 @@ func (d *Daemon) Init() error {
 			}
 		}
 	} else {
-		shared.LogDebugf("LXD isn't socket activated")
+		shared.LogInfof("LXD isn't socket activated")
 
 		localSocketPath := shared.VarPath("unix.socket")
 
@@ -915,7 +915,7 @@ func (d *Daemon) Init() error {
 			shared.LogError("cannot listen on https socket, skipping...", log.Ctx{"err": err})
 		} else {
 			if d.TCPSocket != nil {
-				shared.LogDebugf("Replacing inherited TCP socket with configured one")
+				shared.LogInfof("Replacing inherited TCP socket with configured one")
 				d.TCPSocket.Socket.Close()
 			}
 			d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true}
@@ -923,14 +923,14 @@ func (d *Daemon) Init() error {
 	}
 
 	d.tomb.Go(func() error {
-		shared.LogDebugf("REST API daemon:")
+		shared.LogInfof("REST API daemon:")
 		if d.UnixSocket != nil {
-			shared.LogDebug(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
+			shared.LogInfo(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
 			d.tomb.Go(func() error { return http.Serve(d.UnixSocket.Socket, &lxdHttpServer{d.mux, d}) })
 		}
 
 		if d.TCPSocket != nil {
-			shared.LogDebug(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
+			shared.LogInfo(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
 			d.tomb.Go(func() error { return http.Serve(d.TCPSocket.Socket, &lxdHttpServer{d.mux, d}) })
 		}
 

From 351f3595f380d13a52c8a96bb799b5d68d2667b5 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sat, 17 Sep 2016 22:27:04 +0200
Subject: [PATCH 0304/1193] lxd/daemon: use more LogInfo*() in Stop()

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index db630b2a7..ef4112626 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1048,17 +1048,17 @@ func (d *Daemon) Stop() error {
 	forceStop := false
 
 	d.tomb.Kill(errStop)
-	shared.LogDebugf("Stopping REST API handler:")
+	shared.LogInfof("Stopping REST API handler:")
 	for _, socket := range []*Socket{d.TCPSocket, d.UnixSocket} {
 		if socket == nil {
 			continue
 		}
 
 		if socket.CloseOnExit {
-			shared.LogDebug(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()})
+			shared.LogInfo(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()})
 			socket.Socket.Close()
 		} else {
-			shared.LogDebug(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()})
+			shared.LogInfo(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()})
 			forceStop = true
 		}
 	}

From 890fe1134b7df6720c9acbbfea7cb5cdf0d4712e Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 1 Jun 2016 10:43:59 -0600
Subject: [PATCH 0305/1193] use liblxc's new preserves_inodes feature

We know that some filesystems (BTRFS, ZFS) preserve inodes across sends and
receives, so we can use this to optimize the inotify handling when these
filesystems are used.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 15 ++++++++++++---
 lxd/storage.go       |  8 ++++++++
 lxd/storage_btrfs.go |  8 ++++++++
 lxd/storage_dir.go   |  4 ++++
 lxd/storage_lvm.go   |  4 ++++
 lxd/storage_zfs.go   |  4 ++++
 6 files changed, 40 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5b41aaf98..ca41dae27 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2796,6 +2796,14 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 		shared.LogWarn("unknown migrate call", log.Ctx{"cmd": cmd})
 	}
 
+	preservesInodes := c.storage.PreservesInodes()
+	/* This feature was only added in 2.0.1, let's not ask for it
+	 * before then or migrations will fail.
+	 */
+	if !lxc.VersionAtLeast(2, 0, 1) {
+		preservesInodes = false
+	}
+
 	var migrateErr error
 
 	/* For restore, we need an extra fork so that we daemonize monitor
@@ -2856,9 +2864,10 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 		}
 
 		opts := lxc.MigrateOptions{
-			Stop:      stop,
-			Directory: stateDir,
-			Verbose:   true,
+			Stop:            stop,
+			Directory:       stateDir,
+			Verbose:         true,
+			PreservesInodes: preservesInodes,
 		}
 
 		migrateErr = c.c.Migrate(cmd, opts)
diff --git a/lxd/storage.go b/lxd/storage.go
index f8ab30550..e41f5ad81 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -169,6 +169,10 @@ type storage interface {
 	ImageDelete(fingerprint string) error
 
 	MigrationType() MigrationFSType
+	/* does this storage backend preserve inodes when it is moved across
+	 * LXD hosts?
+	 */
+	PreservesInodes() bool
 
 	// Get the pieces required to migrate the source. This contains a list
 	// of the "object" (i.e. container or snapshot, depending on whether or
@@ -543,6 +547,10 @@ func (lw *storageLogWrapper) MigrationType() MigrationFSType {
 	return lw.w.MigrationType()
 }
 
+func (lw *storageLogWrapper) PreservesInodes() bool {
+	return lw.w.PreservesInodes()
+}
+
 func (lw *storageLogWrapper) MigrationSource(container container) (MigrationStorageSourceDriver, error) {
 	lw.log.Debug("MigrationSource", log.Ctx{"container": container.Name()})
 	return lw.w.MigrationSource(container)
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index f1f160186..685f61775 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -948,6 +948,14 @@ func (s *storageBtrfs) MigrationType() MigrationFSType {
 	}
 }
 
+func (s *storageBtrfs) PreservesInodes() bool {
+	if runningInUserns {
+		return false
+	} else {
+		return true
+	}
+}
+
 func (s *storageBtrfs) MigrationSource(c container) (MigrationStorageSourceDriver, error) {
 	if runningInUserns {
 		return rsyncMigrationSource(c)
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 3cb1339c4..7cfe0a8ec 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -274,6 +274,10 @@ func (s *storageDir) MigrationType() MigrationFSType {
 	return MigrationFSType_RSYNC
 }
 
+func (s *storageDir) PreservesInodes() bool {
+	return false
+}
+
 func (s *storageDir) MigrationSource(container container) (MigrationStorageSourceDriver, error) {
 	return rsyncMigrationSource(container)
 }
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index c93935c37..3f8fc5dbe 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -964,6 +964,10 @@ func (s *storageLvm) MigrationType() MigrationFSType {
 	return MigrationFSType_RSYNC
 }
 
+func (s *storageLvm) PreservesInodes() bool {
+	return false
+}
+
 func (s *storageLvm) MigrationSource(container container) (MigrationStorageSourceDriver, error) {
 	return rsyncMigrationSource(container)
 }
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 004dcac12..6d49ae7f5 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -1290,6 +1290,10 @@ func (s *storageZfs) MigrationType() MigrationFSType {
 	return MigrationFSType_ZFS
 }
 
+func (s *storageZfs) PreservesInodes() bool {
+	return true
+}
+
 func (s *storageZfs) MigrationSource(ct container) (MigrationStorageSourceDriver, error) {
 	/* If the container is a snapshot, let's just send that; we don't need
 	 * to send anything else, because that's all the user asked for.

From 6bd1bb7121eda3ec0e3e95d7e2e092fe350b0f64 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 1 Jun 2016 12:09:04 -0600
Subject: [PATCH 0306/1193] PreservesInodes needs to be implemented for
 storageMock too

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/storage_test.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/storage_test.go b/lxd/storage_test.go
index eec78cc93..7f0929b79 100644
--- a/lxd/storage_test.go
+++ b/lxd/storage_test.go
@@ -131,6 +131,10 @@ func (s *storageMock) MigrationType() MigrationFSType {
 	return MigrationFSType_RSYNC
 }
 
+func (s *storageMock) PreservesInodes() bool {
+	return false
+}
+
 func (s *storageMock) MigrationSource(container container) (MigrationStorageSourceDriver, error) {
 	return nil, fmt.Errorf("not implemented")
 }

From 124f6e771aa6e1e5d104ceb5576fee184e101f9c Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 14 Jun 2016 18:44:02 +0000
Subject: [PATCH 0307/1193] pass preservesInodes on migrate restore too

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go |  3 ++-
 lxd/migrate.go       | 13 ++++++++-----
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ca41dae27..a9b086088 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2838,7 +2838,8 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 			c.name,
 			c.daemon.lxcpath,
 			configPath,
-			stateDir).CombinedOutput()
+			stateDir,
+			fmt.Sprintf("%v", preservesInodes)).CombinedOutput()
 
 		if string(out) != "" {
 			for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 6445ad385..43602ff29 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -11,6 +11,7 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -604,7 +605,7 @@ func (c *migrationSink) do() error {
 /*
  * Similar to forkstart, this is called when lxd is invoked as:
  *
- *    lxd forkmigrate <container> <lxcpath> <path_to_config> <path_to_criu_images>
+ *    lxd forkmigrate <container> <lxcpath> <path_to_config> <path_to_criu_images> <preserves_inodes>
  *
  * liblxc's restore() sets up the processes in such a way that the monitor ends
  * up being a child of the process that calls it, in our case lxd. However, we
@@ -613,7 +614,7 @@ func (c *migrationSink) do() error {
  * footprint when we fork tasks that will never free golang's memory, etc.)
  */
 func MigrateContainer(args []string) error {
-	if len(args) != 5 {
+	if len(args) != 6 {
 		return fmt.Errorf("Bad arguments %q", args)
 	}
 
@@ -621,6 +622,7 @@ func MigrateContainer(args []string) error {
 	lxcpath := args[2]
 	configPath := args[3]
 	imagesDir := args[4]
+	preservesInodes, err := strconv.ParseBool(args[5])
 
 	c, err := lxc.NewContainer(name, lxcpath)
 	if err != nil {
@@ -636,8 +638,9 @@ func MigrateContainer(args []string) error {
 	os.Stdout.Close()
 	os.Stderr.Close()
 
-	return c.Restore(lxc.RestoreOptions{
-		Directory: imagesDir,
-		Verbose:   true,
+	return c.Migrate(lxc.MIGRATE_RESTORE, lxc.MigrateOptions{
+		Directory:       imagesDir,
+		Verbose:         true,
+		PreservesInodes: preservesInodes,
 	})
 }

From 20da1fc407e0657577d65d1fbd855b4503e774c4 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 7 Jul 2016 02:35:44 +0000
Subject: [PATCH 0308/1193] resume dumped container on failed restore

This commit implements "freezing" the dumped container until we are sure
that the restore was successful. Because of various issues (namely, TCP
socket repair mode and windowing, but also various other problems), we
can't simply implement some kind of --leave-frozen option in CRIU.

Instead, we have CRIU do a callback when the dump is done but the container
is still frozen, so that we can then try the restore and see if is succeds.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container.go     |   7 +++-
 lxd/container_lxc.go |  14 +++++--
 lxd/main.go          |  23 +++++++++++
 lxd/migrate.go       | 115 ++++++++++++++++++++++++++++++++++++++++++++++++---
 4 files changed, 147 insertions(+), 12 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index ad5747f31..45ab54519 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -347,7 +347,10 @@ type container interface {
 
 	// Snapshots & migration
 	Restore(sourceContainer container) error
-	Migrate(cmd uint, stateDir string, function string, stop bool) error
+	/* actionScript here is a script called action.sh in the stateDir, to
+	 * be passed to CRIU as --action-script
+	 */
+	Migrate(cmd uint, stateDir string, function string, stop bool, actionScript bool) error
 	Snapshots() ([]container, error)
 
 	// Config handling
@@ -532,7 +535,7 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 		 * after snapshotting will fail.
 		 */
 
-		err = sourceContainer.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", false)
+		err = sourceContainer.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", false, false)
 		if err != nil {
 			os.RemoveAll(sourceContainer.StatePath())
 			return nil, err
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index a9b086088..09d38327c 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1310,7 +1310,7 @@ func (c *containerLXC) Start(stateful bool) error {
 			return fmt.Errorf("Container has no existing state to restore.")
 		}
 
-		err := c.Migrate(lxc.MIGRATE_RESTORE, c.StatePath(), "snapshot", false)
+		err := c.Migrate(lxc.MIGRATE_RESTORE, c.StatePath(), "snapshot", false, false)
 		if err != nil && !c.IsRunning() {
 			return err
 		}
@@ -1486,7 +1486,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		}
 
 		// Checkpoint
-		err = c.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", true)
+		err = c.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", true, false)
 		if err != nil {
 			op.Done(err)
 			return err
@@ -1847,7 +1847,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 	// If the container wasn't running but was stateful, should we restore
 	// it as running?
 	if shared.PathExists(c.StatePath()) {
-		if err := c.Migrate(lxc.MIGRATE_RESTORE, c.StatePath(), "snapshot", false); err != nil {
+		if err := c.Migrate(lxc.MIGRATE_RESTORE, c.StatePath(), "snapshot", false, false); err != nil {
 			return err
 		}
 
@@ -2778,7 +2778,7 @@ func findCriu(host string) error {
 	return nil
 }
 
-func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop bool) error {
+func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop bool, actionScript bool) error {
 	if err := findCriu(function); err != nil {
 		return err
 	}
@@ -2864,11 +2864,17 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 			return err
 		}
 
+		script := ""
+		if actionScript {
+			script = filepath.Join(stateDir, "action.sh")
+		}
+
 		opts := lxc.MigrateOptions{
 			Stop:            stop,
 			Directory:       stateDir,
 			Verbose:         true,
 			PreservesInodes: preservesInodes,
+			ActionScript:    script,
 		}
 
 		migrateErr = c.c.Migrate(cmd, opts)
diff --git a/lxd/main.go b/lxd/main.go
index 36c450bcc..2769c3df2 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -158,6 +158,8 @@ func run() error {
 		fmt.Printf("        Start a container\n")
 		fmt.Printf("    callhook\n")
 		fmt.Printf("        Call a container hook\n")
+		fmt.Printf("    migratedumpsuccess\n")
+		fmt.Printf("        Indicate that a migration dump was successful\n")
 		fmt.Printf("    netcat\n")
 		fmt.Printf("        Mirror a unix socket to stdin/stdout\n")
 	}
@@ -237,6 +239,8 @@ func run() error {
 			os.Exit(ret)
 		case "netcat":
 			return Netcat(os.Args[1:])
+		case "migratedumpsuccess":
+			return cmdMigrateDumpSuccess(os.Args[1:])
 		}
 	}
 
@@ -1108,3 +1112,22 @@ func printnet() error {
 
 	return nil
 }
+
+func cmdMigrateDumpSuccess(args []string) error {
+	if len(args) != 3 {
+		return fmt.Errorf("bad migrate dump success args %s", args)
+	}
+
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	conn, err := c.Websocket(args[1], args[2])
+	if err != nil {
+		return err
+	}
+	conn.Close()
+
+	return c.WaitForSuccess(args[1])
+}
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 43602ff29..031e77ebf 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -11,6 +11,7 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"sync"
@@ -228,7 +229,28 @@ func (s *migrationSourceWs) Connect(op *operation, r *http.Request, w http.Respo
 	return nil
 }
 
-func (s *migrationSourceWs) Do(op *operation) error {
+func writeActionScript(directory string, operation string, secret string) error {
+	script := fmt.Sprintf(`#!/bin/sh -e
+if [ "$CRTOOLS_SCRIPT_ACTION" = "post-dump" ]; then
+	%s migratedumpsuccess %s %s
+fi
+`, execPath, operation, secret)
+
+	f, err := os.Create(filepath.Join(directory, "action.sh"))
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	if err := f.Chmod(0500); err != nil {
+		return err
+	}
+
+	_, err = f.WriteString(script)
+	return err
+}
+
+func (s *migrationSourceWs) Do(migrateOp *operation) error {
 	<-s.allConnected
 
 	criuType := CRIUType_CRIU_RSYNC.Enum()
@@ -326,15 +348,98 @@ func (s *migrationSourceWs) Do(op *operation) error {
 			return abort(fmt.Errorf("Formats other than criu rsync not understood"))
 		}
 
+		/* What happens below is slightly convoluted. Due to various
+		 * complications with networking, there's no easy way for criu
+		 * to exit and leave the container in a frozen state for us to
+		 * somehow resume later.
+		 *
+		 * Instead, we use what criu calls an "action-script", which is
+		 * basically a callback that lets us know when the dump is
+		 * done. (Unfortunately, we can't pass arguments, just an
+		 * executable path, so we write a custom action script with the
+		 * real command we want to run.)
+		 *
+		 * This script then hangs until the migration operation either
+		 * finishes successfully or fails, and exits 1 or 0, which
+		 * causes criu to either leave the container running or kill it
+		 * as we asked.
+		 */
+		dumpDone := make(chan bool, 1)
+		actionScriptOpSecret, err := shared.RandomCryptoString()
+		if err != nil {
+			return abort(err)
+		}
+
+		actionScriptOp, err := operationCreate(
+			operationClassWebsocket,
+			nil,
+			nil,
+			func(op *operation) error {
+				_, err := migrateOp.WaitFinal(-1)
+				if err != nil {
+					return err
+				}
+
+				if migrateOp.status != shared.Success {
+					return fmt.Errorf("restore failed: %s", op.status.String())
+				}
+				return nil
+			},
+			nil,
+			func(op *operation, r *http.Request, w http.ResponseWriter) error {
+				secret := r.FormValue("secret")
+				if secret == "" {
+					return fmt.Errorf("missing secret")
+				}
+
+				if secret != actionScriptOpSecret {
+					return os.ErrPermission
+				}
+
+				c, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
+				if err != nil {
+					return err
+				}
+
+				dumpDone <- true
+
+				closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
+				return c.WriteMessage(websocket.CloseMessage, closeMsg)
+			},
+		)
+		if err != nil {
+			return abort(err)
+		}
+
 		checkpointDir, err := ioutil.TempDir("", "lxd_checkpoint_")
 		if err != nil {
 			return abort(err)
 		}
-		defer os.RemoveAll(checkpointDir)
 
-		err = s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true)
+		if err := writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret); err != nil {
+			os.RemoveAll(checkpointDir)
+			return abort(err)
+		}
+
+		_, err = actionScriptOp.Run()
 		if err != nil {
+			os.RemoveAll(checkpointDir)
+			return abort(err)
+		}
+
+		migrateDone := make(chan error, 1)
+		go func() {
+			defer os.RemoveAll(checkpointDir)
+			migrateDone <- s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, true)
+		}()
+
+		select {
+		/* the checkpoint failed, let's just abort */
+		case err = <-migrateDone:
 			return abort(err)
+		/* the dump finished, let's continue on to the restore */
+		case <-dumpDone:
+			shared.LogDebugf("Dump finished, continuing with restore...")
 		}
 
 		/*
@@ -361,8 +466,6 @@ func (s *migrationSourceWs) Do(op *operation) error {
 		return err
 	}
 
-	// TODO: should we add some config here about automatically restarting
-	// the container migrate failure? What about the failures above?
 	if !*msg.Success {
 		return fmt.Errorf(*msg.Message)
 	}
@@ -559,7 +662,7 @@ func (c *migrationSink) do() error {
 		}
 
 		if c.live {
-			err = c.container.Migrate(lxc.MIGRATE_RESTORE, imagesDir, "migration", false)
+			err = c.container.Migrate(lxc.MIGRATE_RESTORE, imagesDir, "migration", false, false)
 			if err != nil {
 				restore <- err
 				return

From e03915ea987531cd2fe69d9a94f4439c3a8cbe0a Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 19 Jul 2016 20:58:00 +0000
Subject: [PATCH 0309/1193] migrate: bump ghost limit

1MB is not very big, let's make that bigger.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 09d38327c..86a9d3840 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2869,12 +2869,21 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 			script = filepath.Join(stateDir, "action.sh")
 		}
 
+		// TODO: make this configurable? Ultimately I think we don't
+		// want to do that; what we really want to do is have "modes"
+		// of criu operation where one is "make this succeed" and the
+		// other is "make this fast". Anyway, for now, let's choose a
+		// really big size so it almost always succeeds, even if it is
+		// slow.
+		ghostLimit := uint64(256 * 1024 * 1024)
+
 		opts := lxc.MigrateOptions{
 			Stop:            stop,
 			Directory:       stateDir,
 			Verbose:         true,
 			PreservesInodes: preservesInodes,
 			ActionScript:    script,
+			GhostLimit:      ghostLimit,
 		}
 
 		migrateErr = c.c.Migrate(cmd, opts)

From 5e18581945910f8fc77252952544a8fb09318551 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 15 Sep 2016 13:13:30 +0000
Subject: [PATCH 0310/1193] actually support copying across different CoW based
 backend types

Closes #2359

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go       |  35 +---------------
 lxd/storage.go       | 112 +++++++++++++++++++++++++++++++++++++++++----------
 lxd/storage_btrfs.go |  27 +++++++++++--
 lxd/storage_dir.go   |   4 +-
 lxd/storage_lvm.go   |   4 +-
 lxd/storage_test.go  |   4 +-
 lxd/storage_zfs.go   |  66 +++++++++++++++++++++++++-----
 7 files changed, 179 insertions(+), 73 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index 031e77ebf..9330a6810 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -582,32 +582,6 @@ func (c *migrationSink) do() error {
 		imagesDir := ""
 		srcIdmap := new(shared.IdmapSet)
 
-		snapshots := []container{}
-		for _, snap := range header.Snapshots {
-			// TODO: we need to propagate snapshot configurations
-			// as well. Right now the container configuration is
-			// done through the initial migration post. Should we
-			// post the snapshots and their configs as well, or do
-			// it some other way?
-			name := c.container.Name() + shared.SnapshotDelimiter + snap
-			args := containerArgs{
-				Ctype:        cTypeSnapshot,
-				Config:       c.container.LocalConfig(),
-				Profiles:     c.container.Profiles(),
-				Ephemeral:    c.container.IsEphemeral(),
-				Architecture: c.container.Architecture(),
-				Devices:      c.container.LocalDevices(),
-				Name:         name,
-			}
-
-			ct, err := containerCreateEmptySnapshot(c.container.Daemon(), args)
-			if err != nil {
-				restore <- err
-				return
-			}
-			snapshots = append(snapshots, ct)
-		}
-
 		for _, idmap := range header.Idmap {
 			e := shared.IdmapEntry{
 				Isuid:    *idmap.Isuid,
@@ -626,7 +600,7 @@ func (c *migrationSink) do() error {
 		 */
 		fsTransfer := make(chan error)
 		go func() {
-			if err := mySink(c.live, c.container, snapshots, c.fsConn); err != nil {
+			if err := mySink(c.live, c.container, header.Snapshots, c.fsConn, srcIdmap); err != nil {
 				fsTransfer <- err
 				return
 			}
@@ -670,13 +644,6 @@ func (c *migrationSink) do() error {
 
 		}
 
-		for _, snap := range snapshots {
-			if err := ShiftIfNecessary(snap, srcIdmap); err != nil {
-				restore <- err
-				return
-			}
-		}
-
 		restore <- nil
 	}(c)
 
diff --git a/lxd/storage.go b/lxd/storage.go
index e41f5ad81..37b80e0f6 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -192,7 +192,7 @@ type storage interface {
 	// already present on the target instance as an exercise for the
 	// enterprising developer.
 	MigrationSource(container container) (MigrationStorageSourceDriver, error)
-	MigrationSink(live bool, container container, objects []container, conn *websocket.Conn) error
+	MigrationSink(live bool, container container, objects []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error
 }
 
 func newStorage(d *Daemon, sType storageType) (storage, error) {
@@ -556,19 +556,15 @@ func (lw *storageLogWrapper) MigrationSource(container container) (MigrationStor
 	return lw.w.MigrationSource(container)
 }
 
-func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []container, conn *websocket.Conn) error {
-	objNames := []string{}
-	for _, obj := range objects {
-		objNames = append(objNames, obj.Name())
-	}
-
+func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	lw.log.Debug("MigrationSink", log.Ctx{
 		"live":      live,
 		"container": container.Name(),
-		"objects":   objNames,
+		"objects":   objects,
+		"srcIdmap":  *srcIdmap,
 	})
 
-	return lw.w.MigrationSink(live, container, objects, conn)
+	return lw.w.MigrationSink(live, container, objects, conn, srcIdmap)
 }
 
 func ShiftIfNecessary(container container, srcIdmap *shared.IdmapSet) error {
@@ -608,9 +604,17 @@ func (s rsyncStorageSourceDriver) Snapshots() []container {
 }
 
 func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn) error {
-	toSend := append([]container{s.container}, s.snapshots...)
+	toSend := []container{}
+	toSend = append(toSend, s.snapshots...)
+	toSend = append(toSend, s.container)
 
 	for _, send := range toSend {
+		if send.IsSnapshot() {
+			if err := send.StorageStart(); err != nil {
+				return err
+			}
+			defer send.StorageStop()
+		}
 		path := send.Path()
 		if err := RsyncSend(shared.AddSlash(path), conn); err != nil {
 			return err
@@ -638,21 +642,83 @@ func rsyncMigrationSource(container container) (MigrationStorageSourceDriver, er
 	return rsyncStorageSourceDriver{container, snapshots}, nil
 }
 
-func rsyncMigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error {
-	/* the first object is the actual container */
-	if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil {
-		return err
-	}
+func rsyncMigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+	isDirBackend := container.Storage().GetStorageType() == storageTypeDir
 
-	if len(snapshots) > 0 {
-		err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", container.Name())), 0700)
-		if err != nil {
+	if isDirBackend {
+		if len(snapshots) > 0 {
+			err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", container.Name())), 0700)
+			if err != nil {
+				return err
+			}
+		}
+		for _, snap := range snapshots {
+			// TODO: we need to propagate snapshot configurations
+			// as well. Right now the container configuration is
+			// done through the initial migration post. Should we
+			// post the snapshots and their configs as well, or do
+			// it some other way?
+			name := container.Name() + shared.SnapshotDelimiter + snap
+			args := containerArgs{
+				Ctype:        cTypeSnapshot,
+				Config:       container.LocalConfig(),
+				Profiles:     container.Profiles(),
+				Ephemeral:    container.IsEphemeral(),
+				Architecture: container.Architecture(),
+				Devices:      container.LocalDevices(),
+				Name:         name,
+			}
+
+			s, err := containerCreateEmptySnapshot(container.Daemon(), args)
+			if err != nil {
+				return err
+			}
+
+			if err := RsyncRecv(shared.AddSlash(s.Path()), conn); err != nil {
+				return err
+			}
+
+			if err := ShiftIfNecessary(container, srcIdmap); err != nil {
+				return err
+			}
+		}
+
+		if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil {
 			return err
 		}
-	}
+	} else {
+		for _, snap := range snapshots {
+			if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil {
+				return err
+			}
 
-	for _, snap := range snapshots {
-		if err := RsyncRecv(shared.AddSlash(snap.Path()), conn); err != nil {
+			if err := ShiftIfNecessary(container, srcIdmap); err != nil {
+				return err
+			}
+
+			// TODO: we need to propagate snapshot configurations
+			// as well. Right now the container configuration is
+			// done through the initial migration post. Should we
+			// post the snapshots and their configs as well, or do
+			// it some other way?
+			name := container.Name() + shared.SnapshotDelimiter + snap
+			args := containerArgs{
+				Ctype:        cTypeSnapshot,
+				Config:       container.LocalConfig(),
+				Profiles:     container.Profiles(),
+				Ephemeral:    container.IsEphemeral(),
+				Architecture: container.Architecture(),
+				Devices:      container.LocalDevices(),
+				Name:         name,
+			}
+
+			_, err := containerCreateAsSnapshot(container.Daemon(), args, container)
+			if err != nil {
+				return err
+			}
+		}
+
+		if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil {
 			return err
 		}
 	}
@@ -664,6 +730,10 @@ func rsyncMigrationSink(live bool, container container, snapshots []container, c
 		}
 	}
 
+	if err := ShiftIfNecessary(container, srcIdmap); err != nil {
+		return err
+	}
+
 	return nil
 }
 
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 685f61775..d41030166 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -985,9 +985,9 @@ func (s *storageBtrfs) MigrationSource(c container) (MigrationStorageSourceDrive
 	return driver, nil
 }
 
-func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error {
+func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	if runningInUserns {
-		return rsyncMigrationSink(live, container, snapshots, conn)
+		return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap)
 	}
 
 	cName := container.Name()
@@ -1057,7 +1057,28 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 	}
 
 	for _, snap := range snapshots {
-		if err := btrfsRecv(containerPath(cName, true), snap.Path(), true); err != nil {
+		// TODO: we need to propagate snapshot configurations
+		// as well. Right now the container configuration is
+		// done through the initial migration post. Should we
+		// post the snapshots and their configs as well, or do
+		// it some other way?
+		name := container.Name() + shared.SnapshotDelimiter + snap
+		args := containerArgs{
+			Ctype:        cTypeSnapshot,
+			Config:       container.LocalConfig(),
+			Profiles:     container.Profiles(),
+			Ephemeral:    container.IsEphemeral(),
+			Architecture: container.Architecture(),
+			Devices:      container.LocalDevices(),
+			Name:         name,
+		}
+
+		s, err := containerCreateEmptySnapshot(container.Daemon(), args)
+		if err != nil {
+			return err
+		}
+
+		if err := btrfsRecv(containerPath(cName, true), s.Path(), true); err != nil {
 			return err
 		}
 	}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 7cfe0a8ec..8c3d33df9 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -282,6 +282,6 @@ func (s *storageDir) MigrationSource(container container) (MigrationStorageSourc
 	return rsyncMigrationSource(container)
 }
 
-func (s *storageDir) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error {
-	return rsyncMigrationSink(live, container, snapshots, conn)
+func (s *storageDir) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap)
 }
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 3f8fc5dbe..ac5a04dde 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -972,6 +972,6 @@ func (s *storageLvm) MigrationSource(container container) (MigrationStorageSourc
 	return rsyncMigrationSource(container)
 }
 
-func (s *storageLvm) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error {
-	return rsyncMigrationSink(live, container, snapshots, conn)
+func (s *storageLvm) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap)
 }
diff --git a/lxd/storage_test.go b/lxd/storage_test.go
index 7f0929b79..a8638947c 100644
--- a/lxd/storage_test.go
+++ b/lxd/storage_test.go
@@ -5,6 +5,8 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/shared"
+
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
@@ -138,6 +140,6 @@ func (s *storageMock) PreservesInodes() bool {
 func (s *storageMock) MigrationSource(container container) (MigrationStorageSourceDriver, error) {
 	return nil, fmt.Errorf("not implemented")
 }
-func (s *storageMock) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error {
+func (s *storageMock) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	return nil
 }
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 6d49ae7f5..832be9c13 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -79,18 +79,44 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 
 // Things we don't need to care about
 func (s *storageZfs) ContainerStart(container container) error {
-	fs := fmt.Sprintf("containers/%s", container.Name())
 
-	// Just in case the container filesystem got unmounted
-	if !shared.IsMountPoint(shared.VarPath(fs)) {
-		s.zfsMount(fs)
+	if !container.IsSnapshot() {
+		fs := fmt.Sprintf("containers/%s", container.Name())
+
+		// Just in case the container filesystem got unmounted
+		if !shared.IsMountPoint(shared.VarPath(fs)) {
+			s.zfsMount(fs)
+		}
+	} else {
+		/* the zfs CLI tool doesn't support mounting snapshots, but we
+		 * can mount them with the syscall directly...
+		 */
+		fields := strings.SplitN(container.Name(), shared.SnapshotDelimiter, 2)
+		if len(fields) != 2 {
+			return fmt.Errorf("invalid snapshot name %s", container.Name())
+		}
+
+		src := fmt.Sprintf("containers/%s@%s", fields[0], fields[1])
+		dest := shared.VarPath("snapshots", fields[0], fields[1])
+
+		return tryMount(src, dest, "zfs", 0, "")
 	}
 
 	return nil
 }
 
 func (s *storageZfs) ContainerStop(container container) error {
-	return nil
+	if !container.IsSnapshot() {
+		return nil
+	}
+
+	fields := strings.SplitN(container.Name(), shared.SnapshotDelimiter, 2)
+	if len(fields) != 2 {
+		return fmt.Errorf("invalid snapshot name %s", container.Name())
+	}
+
+	p := shared.VarPath("snapshots", fields[0], fields[1])
+	return tryUnmount(p, 0)
 }
 
 // Things we do have to care about
@@ -1341,7 +1367,7 @@ func (s *storageZfs) MigrationSource(ct container) (MigrationStorageSourceDriver
 	return &driver, nil
 }
 
-func (s *storageZfs) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error {
+func (s *storageZfs) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	zfsRecv := func(zfsName string) error {
 		zfsFsName := fmt.Sprintf("%s/%s", s.zfsPool, zfsName)
 		args := []string{"receive", "-F", "-u", zfsFsName}
@@ -1388,18 +1414,38 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []c
 	}
 
 	for _, snap := range snapshots {
-		fields := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)
-		name := fmt.Sprintf("containers/%s at snapshot-%s", fields[0], fields[1])
+		// TODO: we need to propagate snapshot configurations
+		// as well. Right now the container configuration is
+		// done through the initial migration post. Should we
+		// post the snapshots and their configs as well, or do
+		// it some other way?
+		snapName := container.Name() + shared.SnapshotDelimiter + snap
+		args := containerArgs{
+			Ctype:        cTypeSnapshot,
+			Config:       container.LocalConfig(),
+			Profiles:     container.Profiles(),
+			Ephemeral:    container.IsEphemeral(),
+			Architecture: container.Architecture(),
+			Devices:      container.LocalDevices(),
+			Name:         snapName,
+		}
+
+		_, err := containerCreateEmptySnapshot(container.Daemon(), args)
+		if err != nil {
+			return err
+		}
+
+		name := fmt.Sprintf("containers/%s at snapshot-%s", container.Name(), snap)
 		if err := zfsRecv(name); err != nil {
 			return err
 		}
 
-		err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", fields[0])), 0700)
+		err = os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", container.Name())), 0700)
 		if err != nil {
 			return err
 		}
 
-		err = os.Symlink("on-zfs", shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", fields[0], fields[1])))
+		err = os.Symlink("on-zfs", shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", container.Name(), snap)))
 		if err != nil {
 			return err
 		}

From f1e04117bb6754ff7e2b3e30238e69263397f7a7 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 15 Sep 2016 20:51:31 +0000
Subject: [PATCH 0311/1193] container copy: preserve snapshot configuration

Previously, we weren't preserving snapshot configuration (as noted in the
TODO). Let's do that.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go           |  51 +++++++++++++++++++++-
 lxd/migrate.pb.go        | 111 ++++++++++++++++++++++++++++++++++++++++++++++-
 lxd/migrate.proto        |  54 +++++++++++++++--------
 lxd/storage.go           |  75 +++++++++++++++++---------------
 lxd/storage_btrfs.go     |  19 +-------
 lxd/storage_dir.go       |   2 +-
 lxd/storage_lvm.go       |   2 +-
 lxd/storage_test.go      |   2 +-
 lxd/storage_zfs.go       |  19 +-------
 test/suites/migration.sh |   4 ++
 10 files changed, 244 insertions(+), 95 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index 9330a6810..cf75c70cd 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -250,6 +250,37 @@ fi
 	return err
 }
 
+func snapshotToProtobuf(c container) *Snapshot {
+	config := []*Config{}
+	for k, v := range c.LocalConfig() {
+		kCopy := string(k)
+		vCopy := string(v)
+		config = append(config, &Config{Key: &kCopy, Value: &vCopy})
+	}
+
+	devices := []*Device{}
+	for name, d := range c.LocalDevices() {
+		props := []*Config{}
+		for k, v := range d {
+			kCopy := string(k)
+			vCopy := string(v)
+			props = append(props, &Config{Key: &kCopy, Value: &vCopy})
+		}
+
+		devices = append(devices, &Device{Name: &name, Config: props})
+	}
+
+	parts := strings.SplitN(c.Name(), shared.SnapshotDelimiter, 2)
+	isEphemeral := c.IsEphemeral()
+	return &Snapshot{
+		Name:      &parts[len(parts)-1],
+		Config:    config,
+		Profiles:  c.Profiles(),
+		Ephemeral: &isEphemeral,
+		Devices:   devices,
+	}
+}
+
 func (s *migrationSourceWs) Do(migrateOp *operation) error {
 	<-s.allConnected
 
@@ -286,11 +317,11 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 	/* the protocol says we have to send a header no matter what, so let's
 	 * do that, but then immediately send an error.
 	 */
-	snapshots := []string{}
+	snapshots := []*Snapshot{}
 	if fsErr == nil {
 		fullSnaps := driver.Snapshots()
 		for _, snap := range fullSnaps {
-			snapshots = append(snapshots, shared.ExtractSnapshotName(snap.Name()))
+			snapshots = append(snapshots, snapshotToProtobuf(snap))
 		}
 	}
 
@@ -600,6 +631,22 @@ func (c *migrationSink) do() error {
 		 */
 		fsTransfer := make(chan error)
 		go func() {
+			snapshots := []*Snapshot{}
+
+			/* Legacy: we only sent the snapshot names, so we just
+			 * copy the container's config over, same as we used to
+			 * do.
+			 */
+			if len(header.SnapshotNames) > 0 {
+				for _, name := range header.SnapshotNames {
+					base := snapshotToProtobuf(c.container)
+					base.Name = &name
+					snapshots = append(snapshots, base)
+				}
+			} else {
+				snapshots = header.Snapshots
+			}
+
 			if err := mySink(c.live, c.container, header.Snapshots, c.fsConn, srcIdmap); err != nil {
 				fsTransfer <- err
 				return
diff --git a/lxd/migrate.pb.go b/lxd/migrate.pb.go
index 751c0db9b..ae119744d 100644
--- a/lxd/migrate.pb.go
+++ b/lxd/migrate.pb.go
@@ -10,6 +10,9 @@ It is generated from these files:
 
 It has these top-level messages:
 	IDMapType
+	Config
+	Device
+	Snapshot
 	MigrationHeader
 	MigrationControl
 */
@@ -139,11 +142,108 @@ func (m *IDMapType) GetMaprange() int32 {
 	return 0
 }
 
+type Config struct {
+	Key              *string `protobuf:"bytes,1,req,name=key" json:"key,omitempty"`
+	Value            *string `protobuf:"bytes,2,req,name=value" json:"value,omitempty"`
+	XXX_unrecognized []byte  `json:"-"`
+}
+
+func (m *Config) Reset()         { *m = Config{} }
+func (m *Config) String() string { return proto.CompactTextString(m) }
+func (*Config) ProtoMessage()    {}
+
+func (m *Config) GetKey() string {
+	if m != nil && m.Key != nil {
+		return *m.Key
+	}
+	return ""
+}
+
+func (m *Config) GetValue() string {
+	if m != nil && m.Value != nil {
+		return *m.Value
+	}
+	return ""
+}
+
+type Device struct {
+	Name             *string   `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+	Config           []*Config `protobuf:"bytes,2,rep,name=config" json:"config,omitempty"`
+	XXX_unrecognized []byte    `json:"-"`
+}
+
+func (m *Device) Reset()         { *m = Device{} }
+func (m *Device) String() string { return proto.CompactTextString(m) }
+func (*Device) ProtoMessage()    {}
+
+func (m *Device) GetName() string {
+	if m != nil && m.Name != nil {
+		return *m.Name
+	}
+	return ""
+}
+
+func (m *Device) GetConfig() []*Config {
+	if m != nil {
+		return m.Config
+	}
+	return nil
+}
+
+type Snapshot struct {
+	Name             *string   `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+	Config           []*Config `protobuf:"bytes,2,rep,name=config" json:"config,omitempty"`
+	Profiles         []string  `protobuf:"bytes,3,rep,name=profiles" json:"profiles,omitempty"`
+	Ephemeral        *bool     `protobuf:"varint,4,req,name=ephemeral" json:"ephemeral,omitempty"`
+	Devices          []*Device `protobuf:"bytes,5,rep,name=devices" json:"devices,omitempty"`
+	XXX_unrecognized []byte    `json:"-"`
+}
+
+func (m *Snapshot) Reset()         { *m = Snapshot{} }
+func (m *Snapshot) String() string { return proto.CompactTextString(m) }
+func (*Snapshot) ProtoMessage()    {}
+
+func (m *Snapshot) GetName() string {
+	if m != nil && m.Name != nil {
+		return *m.Name
+	}
+	return ""
+}
+
+func (m *Snapshot) GetConfig() []*Config {
+	if m != nil {
+		return m.Config
+	}
+	return nil
+}
+
+func (m *Snapshot) GetProfiles() []string {
+	if m != nil {
+		return m.Profiles
+	}
+	return nil
+}
+
+func (m *Snapshot) GetEphemeral() bool {
+	if m != nil && m.Ephemeral != nil {
+		return *m.Ephemeral
+	}
+	return false
+}
+
+func (m *Snapshot) GetDevices() []*Device {
+	if m != nil {
+		return m.Devices
+	}
+	return nil
+}
+
 type MigrationHeader struct {
 	Fs               *MigrationFSType `protobuf:"varint,1,req,name=fs,enum=main.MigrationFSType" json:"fs,omitempty"`
 	Criu             *CRIUType        `protobuf:"varint,2,opt,name=criu,enum=main.CRIUType" json:"criu,omitempty"`
 	Idmap            []*IDMapType     `protobuf:"bytes,3,rep,name=idmap" json:"idmap,omitempty"`
-	Snapshots        []string         `protobuf:"bytes,4,rep,name=snapshots" json:"snapshots,omitempty"`
+	SnapshotNames    []string         `protobuf:"bytes,4,rep,name=snapshotNames" json:"snapshotNames,omitempty"`
+	Snapshots        []*Snapshot      `protobuf:"bytes,5,rep,name=snapshots" json:"snapshots,omitempty"`
 	XXX_unrecognized []byte           `json:"-"`
 }
 
@@ -172,7 +272,14 @@ func (m *MigrationHeader) GetIdmap() []*IDMapType {
 	return nil
 }
 
-func (m *MigrationHeader) GetSnapshots() []string {
+func (m *MigrationHeader) GetSnapshotNames() []string {
+	if m != nil {
+		return m.SnapshotNames
+	}
+	return nil
+}
+
+func (m *MigrationHeader) GetSnapshots() []*Snapshot {
 	if m != nil {
 		return m.Snapshots
 	}
diff --git a/lxd/migrate.proto b/lxd/migrate.proto
index bdf56088a..6140fb4b7 100644
--- a/lxd/migrate.proto
+++ b/lxd/migrate.proto
@@ -1,35 +1,53 @@
 package main;
 
 enum MigrationFSType {
-  RSYNC     = 0;
-  BTRFS     = 1;
-  ZFS       = 2;
+	RSYNC		= 0;
+	BTRFS		= 1;
+	ZFS		= 2;
 }
 
 enum CRIUType {
-  CRIU_RSYNC    = 0;
-  PHAUL         = 1;
+	CRIU_RSYNC	= 0;
+	PHAUL		= 1;
 }
 
 message IDMapType {
-  required bool   isuid       = 1;
-  required bool   isgid       = 2;
-  required int32  hostid      = 3;
-  required int32  nsid        = 4;
-  required int32  maprange    = 5;
+	required bool	isuid			= 1;
+	required bool	isgid			= 2;
+	required int32	hostid			= 3;
+	required int32	nsid			= 4;
+	required int32	maprange		= 5;
 }
 
-message MigrationHeader {
-  required MigrationFSType  fs        = 1;
-  optional CRIUType         criu      = 2;
-  repeated IDMapType        idmap     = 3;
+message Config {
+	required string		key	= 1;
+	required string		value	= 2;
+}
+
+message Device {
+	required string		name	= 1;
+	repeated Config		config	= 2;
+}
 
-  repeated string           snapshots = 4;
+message Snapshot {
+	required string			name		= 1;
+	repeated Config 		config		= 2;
+	repeated string			profiles	= 3;
+	required bool			ephemeral	= 4;
+	repeated Device			devices		= 5;
+}
+
+message MigrationHeader {
+	required MigrationFSType		fs		= 1;
+	optional CRIUType			criu		= 2;
+	repeated IDMapType	 		idmap		= 3;
+	repeated string				snapshotNames	= 4;
+	repeated Snapshot			snapshots	= 5;
 }
 
 message MigrationControl {
-  required bool     success     = 1;
+	required bool		success		= 1;
 
-  /* optional failure message if sending a failure */
-  optional string   message     = 2;
+	/* optional failure message if sending a failure */
+	optional string		message		= 2;
 }
diff --git a/lxd/storage.go b/lxd/storage.go
index 37b80e0f6..ec6948065 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -192,7 +192,7 @@ type storage interface {
 	// already present on the target instance as an exercise for the
 	// enterprising developer.
 	MigrationSource(container container) (MigrationStorageSourceDriver, error)
-	MigrationSink(live bool, container container, objects []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error
+	MigrationSink(live bool, container container, objects []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error
 }
 
 func newStorage(d *Daemon, sType storageType) (storage, error) {
@@ -556,11 +556,16 @@ func (lw *storageLogWrapper) MigrationSource(container container) (MigrationStor
 	return lw.w.MigrationSource(container)
 }
 
-func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+	objNames := []string{}
+	for _, obj := range objects {
+		objNames = append(objNames, obj.GetName())
+	}
+
 	lw.log.Debug("MigrationSink", log.Ctx{
 		"live":      live,
 		"container": container.Name(),
-		"objects":   objects,
+		"objects":   objNames,
 		"srcIdmap":  *srcIdmap,
 	})
 
@@ -642,7 +647,35 @@ func rsyncMigrationSource(container container) (MigrationStorageSourceDriver, er
 	return rsyncStorageSourceDriver{container, snapshots}, nil
 }
 
-func rsyncMigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) containerArgs {
+	config := map[string]string{}
+
+	for _, ent := range snap.Config {
+		config[ent.GetKey()] = ent.GetValue()
+	}
+
+	devices := shared.Devices{}
+	for _, ent := range snap.Devices {
+		props := map[string]string{}
+		for _, prop := range ent.Config {
+			props[prop.GetKey()] = prop.GetValue()
+		}
+
+		devices[ent.GetName()] = props
+	}
+
+	name := containerName + shared.SnapshotDelimiter + snap.GetName()
+	return containerArgs{
+		Name:      name,
+		Ctype:     cTypeSnapshot,
+		Config:    config,
+		Profiles:  snap.Profiles,
+		Ephemeral: snap.GetEphemeral(),
+		Devices:   devices,
+	}
+}
+
+func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	isDirBackend := container.Storage().GetStorageType() == storageTypeDir
 
 	if isDirBackend {
@@ -653,22 +686,7 @@ func rsyncMigrationSink(live bool, container container, snapshots []string, conn
 			}
 		}
 		for _, snap := range snapshots {
-			// TODO: we need to propagate snapshot configurations
-			// as well. Right now the container configuration is
-			// done through the initial migration post. Should we
-			// post the snapshots and their configs as well, or do
-			// it some other way?
-			name := container.Name() + shared.SnapshotDelimiter + snap
-			args := containerArgs{
-				Ctype:        cTypeSnapshot,
-				Config:       container.LocalConfig(),
-				Profiles:     container.Profiles(),
-				Ephemeral:    container.IsEphemeral(),
-				Architecture: container.Architecture(),
-				Devices:      container.LocalDevices(),
-				Name:         name,
-			}
-
+			args := snapshotProtobufToContainerArgs(container.Name(), snap)
 			s, err := containerCreateEmptySnapshot(container.Daemon(), args)
 			if err != nil {
 				return err
@@ -696,22 +714,7 @@ func rsyncMigrationSink(live bool, container container, snapshots []string, conn
 				return err
 			}
 
-			// TODO: we need to propagate snapshot configurations
-			// as well. Right now the container configuration is
-			// done through the initial migration post. Should we
-			// post the snapshots and their configs as well, or do
-			// it some other way?
-			name := container.Name() + shared.SnapshotDelimiter + snap
-			args := containerArgs{
-				Ctype:        cTypeSnapshot,
-				Config:       container.LocalConfig(),
-				Profiles:     container.Profiles(),
-				Ephemeral:    container.IsEphemeral(),
-				Architecture: container.Architecture(),
-				Devices:      container.LocalDevices(),
-				Name:         name,
-			}
-
+			args := snapshotProtobufToContainerArgs(container.Name(), snap)
 			_, err := containerCreateAsSnapshot(container.Daemon(), args, container)
 			if err != nil {
 				return err
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index d41030166..83fa35731 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -985,7 +985,7 @@ func (s *storageBtrfs) MigrationSource(c container) (MigrationStorageSourceDrive
 	return driver, nil
 }
 
-func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	if runningInUserns {
 		return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap)
 	}
@@ -1057,22 +1057,7 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 	}
 
 	for _, snap := range snapshots {
-		// TODO: we need to propagate snapshot configurations
-		// as well. Right now the container configuration is
-		// done through the initial migration post. Should we
-		// post the snapshots and their configs as well, or do
-		// it some other way?
-		name := container.Name() + shared.SnapshotDelimiter + snap
-		args := containerArgs{
-			Ctype:        cTypeSnapshot,
-			Config:       container.LocalConfig(),
-			Profiles:     container.Profiles(),
-			Ephemeral:    container.IsEphemeral(),
-			Architecture: container.Architecture(),
-			Devices:      container.LocalDevices(),
-			Name:         name,
-		}
-
+		args := snapshotProtobufToContainerArgs(container.Name(), snap)
 		s, err := containerCreateEmptySnapshot(container.Daemon(), args)
 		if err != nil {
 			return err
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 8c3d33df9..94eec94cd 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -282,6 +282,6 @@ func (s *storageDir) MigrationSource(container container) (MigrationStorageSourc
 	return rsyncMigrationSource(container)
 }
 
-func (s *storageDir) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+func (s *storageDir) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap)
 }
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index ac5a04dde..6bfefc5ad 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -972,6 +972,6 @@ func (s *storageLvm) MigrationSource(container container) (MigrationStorageSourc
 	return rsyncMigrationSource(container)
 }
 
-func (s *storageLvm) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap)
 }
diff --git a/lxd/storage_test.go b/lxd/storage_test.go
index a8638947c..c31db24b8 100644
--- a/lxd/storage_test.go
+++ b/lxd/storage_test.go
@@ -140,6 +140,6 @@ func (s *storageMock) PreservesInodes() bool {
 func (s *storageMock) MigrationSource(container container) (MigrationStorageSourceDriver, error) {
 	return nil, fmt.Errorf("not implemented")
 }
-func (s *storageMock) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+func (s *storageMock) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	return nil
 }
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 832be9c13..698602375 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -1367,7 +1367,7 @@ func (s *storageZfs) MigrationSource(ct container) (MigrationStorageSourceDriver
 	return &driver, nil
 }
 
-func (s *storageZfs) MigrationSink(live bool, container container, snapshots []string, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
+func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet) error {
 	zfsRecv := func(zfsName string) error {
 		zfsFsName := fmt.Sprintf("%s/%s", s.zfsPool, zfsName)
 		args := []string{"receive", "-F", "-u", zfsFsName}
@@ -1414,22 +1414,7 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []s
 	}
 
 	for _, snap := range snapshots {
-		// TODO: we need to propagate snapshot configurations
-		// as well. Right now the container configuration is
-		// done through the initial migration post. Should we
-		// post the snapshots and their configs as well, or do
-		// it some other way?
-		snapName := container.Name() + shared.SnapshotDelimiter + snap
-		args := containerArgs{
-			Ctype:        cTypeSnapshot,
-			Config:       container.LocalConfig(),
-			Profiles:     container.Profiles(),
-			Ephemeral:    container.IsEphemeral(),
-			Architecture: container.Architecture(),
-			Devices:      container.LocalDevices(),
-			Name:         snapName,
-		}
-
+		args := snapshotProtobufToContainerArgs(container.Name(), snap)
 		_, err := containerCreateEmptySnapshot(container.Daemon(), args)
 		if err != nil {
 			return err
diff --git a/test/suites/migration.sh b/test/suites/migration.sh
index da0c9282a..0c865f6a5 100644
--- a/test/suites/migration.sh
+++ b/test/suites/migration.sh
@@ -12,8 +12,12 @@ test_migration() {
 
   lxc_remote init testimage nonlive
   # test moving snapshots
+  lxc_remote config set l1:nonlive user.tester foo
   lxc_remote snapshot l1:nonlive
+  lxc_remote config unset l1:nonlive user.tester
   lxc_remote move l1:nonlive l2:
+  lxc_remote config show l2:nonlive/snap0 | grep user.tester | grep foo
+
   # FIXME: make this backend agnostic
   if [ "${LXD_BACKEND}" != "lvm" ]; then
     [ -d "${LXD2_DIR}/containers/nonlive/rootfs" ]

From f8e8295d0de5a8f9f5b3d531165289c8d4caeea0 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 15 Sep 2016 22:25:38 +0000
Subject: [PATCH 0312/1193] always send SnapshotNames

This way we can differentiate between an old LXD and a new LXD by comparing
Snapshots and SnapshotNames.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index cf75c70cd..21a0278e9 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -318,19 +318,22 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 	 * do that, but then immediately send an error.
 	 */
 	snapshots := []*Snapshot{}
+	snapshotNames := []string{}
 	if fsErr == nil {
 		fullSnaps := driver.Snapshots()
 		for _, snap := range fullSnaps {
 			snapshots = append(snapshots, snapshotToProtobuf(snap))
+			snapshotNames = append(snapshotNames, shared.ExtractSnapshotName(snap.Name()))
 		}
 	}
 
 	myType := s.container.Storage().MigrationType()
 	header := MigrationHeader{
-		Fs:        &myType,
-		Criu:      criuType,
-		Idmap:     idmaps,
-		Snapshots: snapshots,
+		Fs:            &myType,
+		Criu:          criuType,
+		Idmap:         idmaps,
+		SnapshotNames: snapshotNames,
+		Snapshots:     snapshots,
 	}
 
 	if err := s.send(&header); err != nil {
@@ -637,7 +640,7 @@ func (c *migrationSink) do() error {
 			 * copy the container's config over, same as we used to
 			 * do.
 			 */
-			if len(header.SnapshotNames) > 0 {
+			if len(header.SnapshotNames) != len(header.Snapshots) {
 				for _, name := range header.SnapshotNames {
 					base := snapshotToProtobuf(c.container)
 					base.Name = &name

From d770ac54015939799af374e126cb8f5d3b8faa3a Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 15 Sep 2016 20:23:14 -0600
Subject: [PATCH 0313/1193] functions must not have a blank line at the
 beginning

This is a stupid rule.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/storage_zfs.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 698602375..ee9fc1538 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -79,7 +79,6 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 
 // Things we don't need to care about
 func (s *storageZfs) ContainerStart(container container) error {
-
 	if !container.IsSnapshot() {
 		fs := fmt.Sprintf("containers/%s", container.Name())
 

From d644bdf7b8b643c27693206941a75fa2004557d2 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 16 Sep 2016 03:46:03 +0000
Subject: [PATCH 0314/1193] use GetName() in a few places

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/storage_zfs.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index ee9fc1538..710b65e6d 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -1419,7 +1419,7 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*
 			return err
 		}
 
-		name := fmt.Sprintf("containers/%s at snapshot-%s", container.Name(), snap)
+		name := fmt.Sprintf("containers/%s at snapshot-%s", container.Name(), snap.GetName())
 		if err := zfsRecv(name); err != nil {
 			return err
 		}
@@ -1429,7 +1429,7 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*
 			return err
 		}
 
-		err = os.Symlink("on-zfs", shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", container.Name(), snap)))
+		err = os.Symlink("on-zfs", shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", container.Name(), snap.GetName())))
 		if err != nil {
 			return err
 		}

From 241a89c06049080d38446c67aaf97f8ac91fc0d8 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 16 Sep 2016 05:41:02 +0000
Subject: [PATCH 0315/1193] don't accidentally start snapshot storage twice

The problem here is that if you are copying a snapshot, it gets passed as
the "container" even though it's not. Then, the code here would see that
it's a snapshot and try to start its storage again.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/storage.go | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/lxd/storage.go b/lxd/storage.go
index ec6948065..2c88957e4 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -609,24 +609,19 @@ func (s rsyncStorageSourceDriver) Snapshots() []container {
 }
 
 func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn) error {
-	toSend := []container{}
-	toSend = append(toSend, s.snapshots...)
-	toSend = append(toSend, s.container)
-
-	for _, send := range toSend {
-		if send.IsSnapshot() {
-			if err := send.StorageStart(); err != nil {
-				return err
-			}
-			defer send.StorageStop()
+	for _, send := range s.snapshots {
+		if err := send.StorageStart(); err != nil {
+			return err
 		}
+		defer send.StorageStop()
+
 		path := send.Path()
 		if err := RsyncSend(shared.AddSlash(path), conn); err != nil {
 			return err
 		}
 	}
 
-	return nil
+	return RsyncSend(shared.AddSlash(s.container.Path()), conn)
 }
 
 func (s rsyncStorageSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) error {

From 71446a2817100f58e6069e77f0280cb1d460856c Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 16 Sep 2016 20:33:39 +0000
Subject: [PATCH 0316/1193] Add architecture, stateful, prefix config/devices
 with local

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go    | 14 +++++++++-----
 lxd/migrate.pb.go | 28 ++++++++++++++++++++++------
 lxd/migrate.proto |  6 ++++--
 lxd/storage.go    | 18 ++++++++++--------
 4 files changed, 45 insertions(+), 21 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index 21a0278e9..b9337fa2f 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -272,12 +272,16 @@ func snapshotToProtobuf(c container) *Snapshot {
 
 	parts := strings.SplitN(c.Name(), shared.SnapshotDelimiter, 2)
 	isEphemeral := c.IsEphemeral()
+	arch := int32(c.Architecture())
+	stateful := c.IsStateful()
 	return &Snapshot{
-		Name:      &parts[len(parts)-1],
-		Config:    config,
-		Profiles:  c.Profiles(),
-		Ephemeral: &isEphemeral,
-		Devices:   devices,
+		Name:         &parts[len(parts)-1],
+		LocalConfig:  config,
+		Profiles:     c.Profiles(),
+		Ephemeral:    &isEphemeral,
+		LocalDevices: devices,
+		Architecture: &arch,
+		Stateful:     &stateful,
 	}
 }
 
diff --git a/lxd/migrate.pb.go b/lxd/migrate.pb.go
index ae119744d..31b0ea89a 100644
--- a/lxd/migrate.pb.go
+++ b/lxd/migrate.pb.go
@@ -192,10 +192,12 @@ func (m *Device) GetConfig() []*Config {
 
 type Snapshot struct {
 	Name             *string   `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
-	Config           []*Config `protobuf:"bytes,2,rep,name=config" json:"config,omitempty"`
+	LocalConfig      []*Config `protobuf:"bytes,2,rep,name=localConfig" json:"localConfig,omitempty"`
 	Profiles         []string  `protobuf:"bytes,3,rep,name=profiles" json:"profiles,omitempty"`
 	Ephemeral        *bool     `protobuf:"varint,4,req,name=ephemeral" json:"ephemeral,omitempty"`
-	Devices          []*Device `protobuf:"bytes,5,rep,name=devices" json:"devices,omitempty"`
+	LocalDevices     []*Device `protobuf:"bytes,5,rep,name=localDevices" json:"localDevices,omitempty"`
+	Architecture     *int32    `protobuf:"varint,6,req,name=architecture" json:"architecture,omitempty"`
+	Stateful         *bool     `protobuf:"varint,7,req,name=stateful" json:"stateful,omitempty"`
 	XXX_unrecognized []byte    `json:"-"`
 }
 
@@ -210,9 +212,9 @@ func (m *Snapshot) GetName() string {
 	return ""
 }
 
-func (m *Snapshot) GetConfig() []*Config {
+func (m *Snapshot) GetLocalConfig() []*Config {
 	if m != nil {
-		return m.Config
+		return m.LocalConfig
 	}
 	return nil
 }
@@ -231,13 +233,27 @@ func (m *Snapshot) GetEphemeral() bool {
 	return false
 }
 
-func (m *Snapshot) GetDevices() []*Device {
+func (m *Snapshot) GetLocalDevices() []*Device {
 	if m != nil {
-		return m.Devices
+		return m.LocalDevices
 	}
 	return nil
 }
 
+func (m *Snapshot) GetArchitecture() int32 {
+	if m != nil && m.Architecture != nil {
+		return *m.Architecture
+	}
+	return 0
+}
+
+func (m *Snapshot) GetStateful() bool {
+	if m != nil && m.Stateful != nil {
+		return *m.Stateful
+	}
+	return false
+}
+
 type MigrationHeader struct {
 	Fs               *MigrationFSType `protobuf:"varint,1,req,name=fs,enum=main.MigrationFSType" json:"fs,omitempty"`
 	Criu             *CRIUType        `protobuf:"varint,2,opt,name=criu,enum=main.CRIUType" json:"criu,omitempty"`
diff --git a/lxd/migrate.proto b/lxd/migrate.proto
index 6140fb4b7..1de3d7453 100644
--- a/lxd/migrate.proto
+++ b/lxd/migrate.proto
@@ -31,10 +31,12 @@ message Device {
 
 message Snapshot {
 	required string			name		= 1;
-	repeated Config 		config		= 2;
+	repeated Config 		localConfig	= 2;
 	repeated string			profiles	= 3;
 	required bool			ephemeral	= 4;
-	repeated Device			devices		= 5;
+	repeated Device			localDevices	= 5;
+	required int32			architecture	= 6;
+	required bool			stateful	= 7;
 }
 
 message MigrationHeader {
diff --git a/lxd/storage.go b/lxd/storage.go
index 2c88957e4..7c25bfd3f 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -645,12 +645,12 @@ func rsyncMigrationSource(container container) (MigrationStorageSourceDriver, er
 func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) containerArgs {
 	config := map[string]string{}
 
-	for _, ent := range snap.Config {
+	for _, ent := range snap.LocalConfig {
 		config[ent.GetKey()] = ent.GetValue()
 	}
 
 	devices := shared.Devices{}
-	for _, ent := range snap.Devices {
+	for _, ent := range snap.LocalDevices {
 		props := map[string]string{}
 		for _, prop := range ent.Config {
 			props[prop.GetKey()] = prop.GetValue()
@@ -661,12 +661,14 @@ func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) conta
 
 	name := containerName + shared.SnapshotDelimiter + snap.GetName()
 	return containerArgs{
-		Name:      name,
-		Ctype:     cTypeSnapshot,
-		Config:    config,
-		Profiles:  snap.Profiles,
-		Ephemeral: snap.GetEphemeral(),
-		Devices:   devices,
+		Name:         name,
+		Ctype:        cTypeSnapshot,
+		Config:       config,
+		Profiles:     snap.Profiles,
+		Ephemeral:    snap.GetEphemeral(),
+		Devices:      devices,
+		Architecture: int(snap.GetArchitecture()),
+		Stateful:     snap.GetStateful(),
 	}
 }
 

From efc6b37584012be9a46af2f73352ff135fff1a29 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 16 Sep 2016 20:57:44 +0000
Subject: [PATCH 0317/1193] remove dead code

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/storage_zfs.go | 35 +++++------------------------------
 1 file changed, 5 insertions(+), 30 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 710b65e6d..6700d35c7 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -79,43 +79,18 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 
 // Things we don't need to care about
 func (s *storageZfs) ContainerStart(container container) error {
-	if !container.IsSnapshot() {
-		fs := fmt.Sprintf("containers/%s", container.Name())
-
-		// Just in case the container filesystem got unmounted
-		if !shared.IsMountPoint(shared.VarPath(fs)) {
-			s.zfsMount(fs)
-		}
-	} else {
-		/* the zfs CLI tool doesn't support mounting snapshots, but we
-		 * can mount them with the syscall directly...
-		 */
-		fields := strings.SplitN(container.Name(), shared.SnapshotDelimiter, 2)
-		if len(fields) != 2 {
-			return fmt.Errorf("invalid snapshot name %s", container.Name())
-		}
-
-		src := fmt.Sprintf("containers/%s@%s", fields[0], fields[1])
-		dest := shared.VarPath("snapshots", fields[0], fields[1])
+	fs := fmt.Sprintf("containers/%s", container.Name())
 
-		return tryMount(src, dest, "zfs", 0, "")
+	// Just in case the container filesystem got unmounted
+	if !shared.IsMountPoint(shared.VarPath(fs)) {
+		s.zfsMount(fs)
 	}
 
 	return nil
 }
 
 func (s *storageZfs) ContainerStop(container container) error {
-	if !container.IsSnapshot() {
-		return nil
-	}
-
-	fields := strings.SplitN(container.Name(), shared.SnapshotDelimiter, 2)
-	if len(fields) != 2 {
-		return fmt.Errorf("invalid snapshot name %s", container.Name())
-	}
-
-	p := shared.VarPath("snapshots", fields[0], fields[1])
-	return tryUnmount(p, 0)
+	return nil
 }
 
 // Things we do have to care about

From d229ff883a9f5dabb2c6a306a0e36db797a0c0b6 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 16 Sep 2016 15:32:56 -0600
Subject: [PATCH 0318/1193] fix bad merge

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/db_update.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index d0fb0357d..6c7b6f710 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -101,7 +101,7 @@ func dbUpdatesApplyAll(d *Daemon) error {
 		}
 
 		if !d.MockMode && !backup {
-			shared.Log.Info("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
+			shared.LogInfof("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
 			err := shared.FileCopy(shared.VarPath("lxd.db"), shared.VarPath("lxd.db.bak"))
 			if err != nil {
 				return err

From 0b52020162279424948155dfe6e20b16873518b4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 02:09:12 +0200
Subject: [PATCH 0319/1193] lxd/container_lxc: improve log for container start

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 28 +++++++++++++++++++++++-----
 1 file changed, 23 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 86a9d3840..d79dd1371 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1332,6 +1332,12 @@ func (c *containerLXC) Start(stateful bool) error {
 		}
 	}
 
+	shared.LogInfo("Starting container",
+		log.Ctx{"name": c.name,
+			"action":        op.action,
+			"creation date": c.creationDate,
+			"ephemeral":     c.ephemeral})
+
 	// Start the LXC container
 	out, err := exec.Command(
 		execPath,
@@ -1349,7 +1355,7 @@ func (c *containerLXC) Start(stateful bool) error {
 
 	if err != nil && !c.IsRunning() {
 		// Attempt to extract the LXC errors
-		log := ""
+		lxcLog := ""
 		logPath := filepath.Join(c.LogPath(), "lxc.log")
 		if shared.PathExists(logPath) {
 			logContent, err := ioutil.ReadFile(logPath)
@@ -1366,24 +1372,36 @@ func (c *containerLXC) Start(stateful bool) error {
 					}
 
 					// Prepend the line break
-					if len(log) == 0 {
-						log += "\n"
+					if len(lxcLog) == 0 {
+						lxcLog += "\n"
 					}
 
-					log += fmt.Sprintf("  %s\n", strings.Join(fields[0:], " "))
+					lxcLog += fmt.Sprintf("  %s\n", strings.Join(fields[0:], " "))
 				}
 			}
 		}
 
+		shared.LogError("Failed starting container",
+			log.Ctx{"name": c.name,
+				"action":        op.action,
+				"creation date": c.creationDate,
+				"ephemeral":     c.ephemeral})
+
 		// Return the actual error
 		return fmt.Errorf(
 			"Error calling 'lxd forkstart %s %s %s': err='%v'%s",
 			c.name,
 			c.daemon.lxcpath,
 			filepath.Join(c.LogPath(), "lxc.conf"),
-			err, log)
+			err, lxcLog)
 	}
 
+	shared.LogInfo("Started container",
+		log.Ctx{"name": c.name,
+			"action":        op.action,
+			"creation date": c.creationDate,
+			"ephemeral":     c.ephemeral})
+
 	return nil
 }
 

From 620f44562f9bffa3e078f3e7e3348266d56b17ea Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 02:25:33 +0200
Subject: [PATCH 0320/1193] lxd/daemon_images: avoid null deref

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon_images.go | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index c589f7a1c..b7ea9191a 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -29,6 +29,7 @@ var imageStreamCacheLock sync.Mutex
 func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) {
 	var err error
 	var ss *shared.SimpleStreams
+	var ctxMap log.Ctx
 
 	if protocol == "" {
 		protocol = "lxd"
@@ -114,9 +115,13 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	d.imagesDownloadingLock.RUnlock()
 
-	shared.LogInfo(
-		"Downloading image",
-		log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server})
+	if op == nil {
+		ctxMap = log.Ctx{"alias": alias, "server": server}
+	} else {
+		ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server}
+	}
+
+	shared.LogInfo("Downloading image", ctxMap)
 
 	// Add the download to the queue
 	d.imagesDownloadingLock.Lock()
@@ -233,9 +238,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			}
 		}
 
-		shared.LogInfo(
-			"Image downloaded",
-			log.Ctx{"image": fp, "fingerprint": fp, "operation": op.id, "alias": alias, "server": server})
+		shared.LogInfo("Image downloaded", ctxMap)
 
 		if forContainer {
 			return fp, dbImageLastAccessInit(d.db, fp)
@@ -401,9 +404,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 	}
 
-	shared.LogInfo(
-		"Image downloaded",
-		log.Ctx{"image": fp, "operation": op.id, "alias": alias, "server": server})
+	shared.LogInfo("Image downloaded", ctxMap)
 
 	if forContainer {
 		return fp, dbImageLastAccessInit(d.db, fp)

From e1a0a65aa921389f4b04769a020f98b26428822a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 17:28:43 +0200
Subject: [PATCH 0321/1193] lxd/container_lxc: improve log for container stop

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d79dd1371..f1cd929f0 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1485,12 +1485,21 @@ func (c *containerLXC) OnStart() error {
 
 // Stop functions
 func (c *containerLXC) Stop(stateful bool) error {
+	var ctxMap log.Ctx
 	// Setup a new operation
 	op, err := c.createOperation("stop", 30)
 	if err != nil {
 		return err
 	}
 
+	ctxMap = log.Ctx{"name": c.name,
+		"action":        op.action,
+		"creation date": c.creationDate,
+		"ephemeral":     c.ephemeral,
+		"stateful":      stateful}
+
+	shared.LogInfo("Stopping container", ctxMap)
+
 	// Handle stateful stop
 	if stateful {
 		// Cleanup any existing state
@@ -1500,6 +1509,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		err := os.MkdirAll(stateDir, 0700)
 		if err != nil {
 			op.Done(err)
+			shared.LogError("Failed stopping container", ctxMap)
 			return err
 		}
 
@@ -1507,6 +1517,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		err = c.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", true, false)
 		if err != nil {
 			op.Done(err)
+			shared.LogError("Failed stopping container", ctxMap)
 			return err
 		}
 
@@ -1514,10 +1525,12 @@ func (c *containerLXC) Stop(stateful bool) error {
 		err = dbContainerSetStateful(c.daemon.db, c.id, true)
 		if err != nil {
 			op.Done(err)
+			shared.LogError("Failed stopping container", ctxMap)
 			return err
 		}
 
 		op.Done(nil)
+		shared.LogInfo("Stopped container", ctxMap)
 		return nil
 	}
 
@@ -1525,6 +1538,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 	err = c.initLXC()
 	if err != nil {
 		op.Done(err)
+		shared.LogError("Failed stopping container", ctxMap)
 		return err
 	}
 
@@ -1533,10 +1547,18 @@ func (c *containerLXC) Stop(stateful bool) error {
 
 	if err := c.c.Stop(); err != nil {
 		op.Done(err)
+		shared.LogError("Failed stopping container", ctxMap)
 		return err
 	}
 
-	return op.Wait()
+	err = op.Wait()
+	if err != nil {
+		shared.LogError("Failed stopping container", ctxMap)
+		return err
+	}
+
+	shared.LogInfo("Stopped container", ctxMap)
+	return err
 }
 
 func (c *containerLXC) Shutdown(timeout time.Duration) error {

From 3b021b1799844b0843311043f2271cdeeda9e3e5 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 17:33:32 +0200
Subject: [PATCH 0322/1193] lxd/container_lxc: improve log on shutdown

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f1cd929f0..52be20bdf 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1562,25 +1562,45 @@ func (c *containerLXC) Stop(stateful bool) error {
 }
 
 func (c *containerLXC) Shutdown(timeout time.Duration) error {
+	var ctxMap log.Ctx
+
 	// Setup a new operation
 	op, err := c.createOperation("shutdown", 30)
 	if err != nil {
 		return err
 	}
 
+	ctxMap = log.Ctx{"name": c.name,
+		"action":        op.action,
+		"creation date": c.creationDate,
+		"ephemeral":     c.ephemeral,
+		"timeout":       timeout}
+
+	shared.LogInfo("Shutting down container", ctxMap)
+
 	// Load the go-lxc struct
 	err = c.initLXC()
 	if err != nil {
 		op.Done(err)
+		shared.LogError("Failed shutting down container", ctxMap)
 		return err
 	}
 
 	if err := c.c.Shutdown(timeout); err != nil {
 		op.Done(err)
+		shared.LogError("Failed shutting down container", ctxMap)
 		return err
 	}
 
-	return op.Wait()
+	err = op.Wait()
+	if err != nil {
+		shared.LogError("Failed shutting down container", ctxMap)
+		return err
+	}
+
+	shared.LogInfo("Shut down container", ctxMap)
+
+	return err
 }
 
 func (c *containerLXC) OnStop(target string) error {

From 508225d7865d5f0e0ac8a2942154086b2d41af6e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 17:39:19 +0200
Subject: [PATCH 0323/1193] lxd/container_lxc: improve log on container freeze

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 52be20bdf..d46e52785 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1705,13 +1705,28 @@ func (c *containerLXC) OnStop(target string) error {
 
 // Freezer functions
 func (c *containerLXC) Freeze() error {
+	ctxMap := log.Ctx{"name": c.name,
+		"creation date": c.creationDate,
+		"ephemeral":     c.ephemeral}
+
+	shared.LogInfo("Freezing container", ctxMap)
+
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
+		shared.LogError("Failed freezing container", ctxMap)
 		return err
 	}
 
-	return c.c.Freeze()
+	err = c.c.Freeze()
+	if err != nil {
+		shared.LogError("Failed freezing container", ctxMap)
+		return err
+	}
+
+	shared.LogInfo("Froze container", ctxMap)
+
+	return err
 }
 
 func (c *containerLXC) Unfreeze() error {

From 7acc453ecdbdd3d63d025a4d344165fd8fd6898a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 17:45:50 +0200
Subject: [PATCH 0324/1193] lxd/container_lxc: improve log for unfreeze

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d46e52785..a974d049f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1730,13 +1730,27 @@ func (c *containerLXC) Freeze() error {
 }
 
 func (c *containerLXC) Unfreeze() error {
+	ctxMap := log.Ctx{"name": c.name,
+		"creation date": c.creationDate,
+		"ephemeral":     c.ephemeral}
+
+	shared.LogInfo("Unfreezing container", ctxMap)
+
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
+		shared.LogError("Failed unfreezing container", ctxMap)
 		return err
 	}
 
-	return c.c.Unfreeze()
+	err = c.c.Unfreeze()
+	if err != nil {
+		shared.LogError("Failed unfreezing container", ctxMap)
+	}
+
+	shared.LogInfo("Unfroze container", ctxMap)
+
+	return err
 }
 
 var LxcMonitorStateError = fmt.Errorf("Monitor is hung")

From 351694ea1eceb8cc21e2d5983f0e94b3ab2a1ede Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 17:48:25 +0200
Subject: [PATCH 0325/1193] lxd/container_lxc: rm LogError() from Restore()

With recent changes Stop() will write a LogError*() itself.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index a974d049f..18660e4c9 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1895,11 +1895,6 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 	if c.IsRunning() {
 		wasRunning = true
 		if err := c.Stop(false); err != nil {
-			shared.LogError(
-				"Could not stop container",
-				log.Ctx{
-					"container": c.Name(),
-					"err":       err})
 			return err
 		}
 	}

From afff3daeddbb82b0949b91aade9360a64895a1d5 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 18:01:48 +0200
Subject: [PATCH 0326/1193] lxd/container_lxc: smarter logs on container start

Before, no log entry was created when a stateful container was started. This
commit makes sure that a log entry is created for stateful container starts.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 39 ++++++++++++++++++++++-----------------
 1 file changed, 22 insertions(+), 17 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 18660e4c9..12da4513f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1286,6 +1286,8 @@ func (c *containerLXC) startCommon() (string, error) {
 }
 
 func (c *containerLXC) Start(stateful bool) error {
+	var ctxMap log.Ctx
+
 	// Setup a new operation
 	op, err := c.createOperation("start", 30)
 	if err != nil {
@@ -1304,6 +1306,14 @@ func (c *containerLXC) Start(stateful bool) error {
 		return err
 	}
 
+	ctxMap = log.Ctx{"name": c.name,
+		"action":        op.action,
+		"creation date": c.creationDate,
+		"ephemeral":     c.ephemeral,
+		"stateful":      stateful}
+
+	shared.LogInfo("Starting container", ctxMap)
+
 	// If stateful, restore now
 	if stateful {
 		if !c.stateful {
@@ -1317,7 +1327,16 @@ func (c *containerLXC) Start(stateful bool) error {
 
 		os.RemoveAll(c.StatePath())
 		c.stateful = false
-		return dbContainerSetStateful(c.daemon.db, c.id, false)
+
+		err = dbContainerSetStateful(c.daemon.db, c.id, false)
+		if err != nil {
+			shared.LogError("Failed starting container", ctxMap)
+			return err
+		}
+
+		shared.LogInfo("Started container", ctxMap)
+
+		return err
 	} else if c.stateful {
 		/* stateless start required when we have state, let's delete it */
 		err := os.RemoveAll(c.StatePath())
@@ -1332,12 +1351,6 @@ func (c *containerLXC) Start(stateful bool) error {
 		}
 	}
 
-	shared.LogInfo("Starting container",
-		log.Ctx{"name": c.name,
-			"action":        op.action,
-			"creation date": c.creationDate,
-			"ephemeral":     c.ephemeral})
-
 	// Start the LXC container
 	out, err := exec.Command(
 		execPath,
@@ -1381,11 +1394,7 @@ func (c *containerLXC) Start(stateful bool) error {
 			}
 		}
 
-		shared.LogError("Failed starting container",
-			log.Ctx{"name": c.name,
-				"action":        op.action,
-				"creation date": c.creationDate,
-				"ephemeral":     c.ephemeral})
+		shared.LogError("Failed starting container", ctxMap)
 
 		// Return the actual error
 		return fmt.Errorf(
@@ -1396,11 +1405,7 @@ func (c *containerLXC) Start(stateful bool) error {
 			err, lxcLog)
 	}
 
-	shared.LogInfo("Started container",
-		log.Ctx{"name": c.name,
-			"action":        op.action,
-			"creation date": c.creationDate,
-			"ephemeral":     c.ephemeral})
+	shared.LogInfo("Started container", ctxMap)
 
 	return nil
 }

From 507fb6e02aa8edf8903a739a71725aceaebd7b42 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 18:14:50 +0200
Subject: [PATCH 0327/1193] lxd/container_lxc: improve log for restore

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 12da4513f..2d25a6bb1 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1880,6 +1880,8 @@ func (c *containerLXC) Snapshots() ([]container, error) {
 }
 
 func (c *containerLXC) Restore(sourceContainer container) error {
+	var ctxMap log.Ctx
+
 	// Check if we can restore the container
 	err := c.storage.ContainerCanRestore(c, sourceContainer)
 	if err != nil {
@@ -1904,13 +1906,17 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 		}
 	}
 
+	ctxMap = log.Ctx{"name": c.name,
+		"creation date":    c.creationDate,
+		"ephemeral":        c.ephemeral,
+		"source container": sourceContainer.Name()}
+
+	shared.LogInfo("Restoring container", ctxMap)
+
 	// Restore the rootfs
 	err = c.storage.ContainerRestore(c, sourceContainer)
 	if err != nil {
-		shared.LogError("Restoring the filesystem failed",
-			log.Ctx{
-				"source":      sourceContainer.Name(),
-				"destination": c.Name()})
+		shared.LogError("Failed restoring container filesystem", ctxMap)
 		return err
 	}
 
@@ -1925,11 +1931,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 
 	err = c.Update(args, false)
 	if err != nil {
-		shared.LogError("Restoring the configuration failed",
-			log.Ctx{
-				"source":      sourceContainer.Name(),
-				"destination": c.Name()})
-
+		shared.LogError("Failed restoring container configuration", ctxMap)
 		return err
 	}
 
@@ -1944,21 +1946,26 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 		// this in snapshots.
 		err2 := os.RemoveAll(c.StatePath())
 		if err2 != nil {
-			shared.LogError("failed to delete snapshot state", log.Ctx{"path": c.StatePath(), "err": err2})
+			shared.LogError("Failed to delete snapshot state", log.Ctx{"path": c.StatePath(), "err": err2})
 		}
 
 		if err != nil {
+			shared.LogInfo("Failed restoring container", ctxMap)
 			return err
 		}
 
+		shared.LogInfo("Restored container", ctxMap)
 		return nil
 	}
 
 	// Restart the container
 	if wasRunning {
+		shared.LogInfo("Restored container", ctxMap)
 		return c.Start(false)
 	}
 
+	shared.LogInfo("Restored container", ctxMap)
+
 	return nil
 }
 

From c3889789c67cd84cb0129422aeb0ac7a12c69a6e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 18:23:44 +0200
Subject: [PATCH 0328/1193] lxd/container_lxc: improve log on container delete

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 2d25a6bb1..39065f966 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1986,15 +1986,21 @@ func (c *containerLXC) cleanup() {
 }
 
 func (c *containerLXC) Delete() error {
+	ctxMap := log.Ctx{"name": c.name,
+		"creation date": c.creationDate,
+		"ephemeral":     c.ephemeral}
+
+	shared.LogInfo("Deleting container", ctxMap)
+
 	if c.IsSnapshot() {
 		// Remove the snapshot
 		if err := c.storage.ContainerSnapshotDelete(c); err != nil {
-			shared.LogWarn("failed to delete snapshot", log.Ctx{"name": c.Name(), "err": err})
+			shared.LogWarn("Failed to delete snapshot", log.Ctx{"name": c.Name(), "err": err})
 		}
 	} else {
 		// Remove all snapshot
 		if err := containerDeleteSnapshots(c.daemon, c.Name()); err != nil {
-			shared.LogWarn("failed to delete snapshots", log.Ctx{"name": c.Name(), "err": err})
+			shared.LogWarn("Failed to delete snapshots", log.Ctx{"name": c.Name(), "err": err})
 		}
 
 		// Clean things up
@@ -2003,6 +2009,7 @@ func (c *containerLXC) Delete() error {
 		// Delete the container from disk
 		if shared.PathExists(c.Path()) {
 			if err := c.storage.ContainerDelete(c); err != nil {
+				shared.LogError("Failed deleting container", ctxMap)
 				return err
 			}
 		}
@@ -2010,9 +2017,12 @@ func (c *containerLXC) Delete() error {
 
 	// Remove the database record
 	if err := dbContainerRemove(c.daemon.db, c.Name()); err != nil {
+		shared.LogError("Failed deleting container", ctxMap)
 		return err
 	}
 
+	shared.LogInfo("Deleted container", ctxMap)
+
 	return nil
 }
 

From df0b4d7dbc03992482790c36025edfde2fd34496 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 18 Sep 2016 18:40:11 +0200
Subject: [PATCH 0329/1193] lxd/container_lxc: improve log on container rename

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 39065f966..d0835e540 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2028,6 +2028,12 @@ func (c *containerLXC) Delete() error {
 
 func (c *containerLXC) Rename(newName string) error {
 	oldName := c.Name()
+	ctxMap := log.Ctx{"name": c.name,
+		"creation date": c.creationDate,
+		"ephemeral":     c.ephemeral,
+		"newname":       newName}
+
+	shared.LogInfo("Renaming container", ctxMap)
 
 	// Sanity checks
 	if !c.IsSnapshot() && !shared.ValidHostname(newName) {
@@ -2035,7 +2041,7 @@ func (c *containerLXC) Rename(newName string) error {
 	}
 
 	if c.IsRunning() {
-		return fmt.Errorf("renaming of running container not allowed")
+		return fmt.Errorf("Renaming of running container not allowed")
 	}
 
 	// Clean things up
@@ -2046,6 +2052,7 @@ func (c *containerLXC) Rename(newName string) error {
 	if shared.PathExists(c.LogPath()) {
 		err := os.Rename(c.LogPath(), shared.LogPath(newName))
 		if err != nil {
+			shared.LogError("Failed renaming container", ctxMap)
 			return err
 		}
 	}
@@ -2053,16 +2060,19 @@ func (c *containerLXC) Rename(newName string) error {
 	// Rename the storage entry
 	if c.IsSnapshot() {
 		if err := c.storage.ContainerSnapshotRename(c, newName); err != nil {
+			shared.LogError("Failed renaming container", ctxMap)
 			return err
 		}
 	} else {
 		if err := c.storage.ContainerRename(c, newName); err != nil {
+			shared.LogError("Failed renaming container", ctxMap)
 			return err
 		}
 	}
 
 	// Rename the database entry
 	if err := dbContainerRename(c.daemon.db, oldName, newName); err != nil {
+		shared.LogError("Failed renaming container", ctxMap)
 		return err
 	}
 
@@ -2070,6 +2080,7 @@ func (c *containerLXC) Rename(newName string) error {
 		// Rename all the snapshots
 		results, err := dbContainerGetSnapshots(c.daemon.db, oldName)
 		if err != nil {
+			shared.LogError("Failed renaming container", ctxMap)
 			return err
 		}
 
@@ -2078,6 +2089,7 @@ func (c *containerLXC) Rename(newName string) error {
 			baseSnapName := filepath.Base(sname)
 			newSnapshotName := newName + shared.SnapshotDelimiter + baseSnapName
 			if err := dbContainerRename(c.daemon.db, sname, newSnapshotName); err != nil {
+				shared.LogError("Failed renaming container", ctxMap)
 				return err
 			}
 		}
@@ -2089,6 +2101,8 @@ func (c *containerLXC) Rename(newName string) error {
 	// Invalidate the go-lxc cache
 	c.c = nil
 
+	shared.LogInfo("Renamed container", ctxMap)
+
 	return nil
 }
 

From f8816e49329418392ceddf57eb4f3a98f6f91ca9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 02:18:46 -0400
Subject: [PATCH 0330/1193] network: Fix networkIsInUse
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Variable name re-use was breaking it.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/lxd/networks.go b/lxd/networks.go
index cfd1e722d..a5b29e327 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -14,23 +14,20 @@ import (
 
 // Helper functions
 func networkIsInUse(c container, name string) bool {
-	devices := c.ExpandedDevices()
-	for _, name := range devices.DeviceNames() {
-		device := devices[name]
-
-		if device["type"] != "nic" {
+	for _, d := range c.ExpandedDevices() {
+		if d["type"] != "nic" {
 			continue
 		}
 
-		if !shared.StringInSlice(device["nictype"], []string{"bridged", "macvlan"}) {
+		if !shared.StringInSlice(d["nictype"], []string{"bridged", "macvlan"}) {
 			continue
 		}
 
-		if device["parent"] == "" {
+		if d["parent"] == "" {
 			continue
 		}
 
-		if device["parent"] == name {
+		if d["parent"] == name {
 			return true
 		}
 	}

From b3f8b40fa5bba080693b0217f9cfe80cea3ba1ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 02:23:31 -0400
Subject: [PATCH 0331/1193] shared: New RunCommand wrapper function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/util.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/shared/util.go b/shared/util.go
index ab6d0610f..dd495d966 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -738,3 +738,12 @@ func (pt *TransferProgress) Read(p []byte) (int, error) {
 
 	return n, err
 }
+
+func RunCommand(name string, arg ...string) error {
+	output, err := exec.Command(name, arg...).CombinedOutput()
+	if err != nil {
+		return fmt.Errorf("Failed to run: %s %s: %s", name, strings.Join(arg, " "), strings.TrimSpace(string(output)))
+	}
+
+	return nil
+}

From 11969fbe6c57064a11a78d76c5710ee4a08e8dfd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 17 Sep 2016 18:03:13 -0400
Subject: [PATCH 0332/1193] db: Fix int64 handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/db.go b/lxd/db.go
index 397ed1af8..b1eabf4ef 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -367,6 +367,9 @@ func doDbQueryScan(db *sql.DB, q string, args []interface{}, outargs []interface
 			case int:
 				integer := 0
 				ptrargs[i] = &integer
+			case int64:
+				integer := int64(0)
+				ptrargs[i] = &integer
 			default:
 				return [][]interface{}{}, fmt.Errorf("Bad interface type: %s", t)
 			}
@@ -382,6 +385,8 @@ func doDbQueryScan(db *sql.DB, q string, args []interface{}, outargs []interface
 				newargs[i] = *ptrargs[i].(*string)
 			case int:
 				newargs[i] = *ptrargs[i].(*int)
+			case int64:
+				newargs[i] = *ptrargs[i].(*int64)
 			default:
 				return [][]interface{}{}, fmt.Errorf("Bad interface type: %s", t)
 			}

From a23ff810760fcb3e3f11f169d2a7ecb93ffbaade Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Mon, 19 Sep 2016 12:57:39 +0200
Subject: [PATCH 0333/1193] lxd/container_lxc: improve log for export

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 31 +++++++++++++++++++++++++++----
 1 file changed, 27 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d0835e540..0f752d578 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2729,13 +2729,20 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 }
 
 func (c *containerLXC) Export(w io.Writer) error {
+	ctxMap := log.Ctx{"name": c.name,
+		"created":   c.creationDate,
+		"ephemeral": c.ephemeral}
+
 	if c.IsRunning() {
 		return fmt.Errorf("Cannot export a running container as an image")
 	}
 
+	shared.LogInfo("Exporting container", ctxMap)
+
 	// Start the storage
 	err := c.StorageStart()
 	if err != nil {
+		shared.LogError("Failed exporting container", ctxMap)
 		return err
 	}
 	defer c.StorageStop()
@@ -2743,11 +2750,13 @@ func (c *containerLXC) Export(w io.Writer) error {
 	// Unshift the container
 	idmap, err := c.LastIdmapSet()
 	if err != nil {
+		shared.LogError("Failed exporting container", ctxMap)
 		return err
 	}
 
 	if idmap != nil {
 		if err := idmap.UnshiftRootfs(c.RootfsPath()); err != nil {
+			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
 
@@ -2779,6 +2788,7 @@ func (c *containerLXC) Export(w io.Writer) error {
 		f, err := ioutil.TempFile("", "lxd_lxd_metadata_")
 		if err != nil {
 			tw.Close()
+			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
 		defer os.Remove(f.Name())
@@ -2790,6 +2800,7 @@ func (c *containerLXC) Export(w io.Writer) error {
 			parent, err := containerLoadByName(c.daemon, parentName)
 			if err != nil {
 				tw.Close()
+				shared.LogError("Failed exporting container", ctxMap)
 				return err
 			}
 
@@ -2801,6 +2812,7 @@ func (c *containerLXC) Export(w io.Writer) error {
 		if arch == "" {
 			arch, err = shared.ArchitectureName(c.daemon.architectures[0])
 			if err != nil {
+				shared.LogError("Failed exporting container", ctxMap)
 				return err
 			}
 		}
@@ -2813,6 +2825,7 @@ func (c *containerLXC) Export(w io.Writer) error {
 		data, err := yaml.Marshal(&meta)
 		if err != nil {
 			tw.Close()
+			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
 
@@ -2823,13 +2836,15 @@ func (c *containerLXC) Export(w io.Writer) error {
 		fi, err := os.Lstat(f.Name())
 		if err != nil {
 			tw.Close()
+			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
 
 		tmpOffset := len(path.Dir(f.Name())) + 1
 		if err := c.tarStoreFile(linkmap, tmpOffset, tw, f.Name(), fi); err != nil {
-			shared.LogDebugf("Error writing to tarfile: %s", err)
 			tw.Close()
+			shared.LogDebugf("Error writing to tarfile: %s", err)
+			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
 
@@ -2838,14 +2853,16 @@ func (c *containerLXC) Export(w io.Writer) error {
 		// Include metadata.yaml in the tarball
 		fi, err := os.Lstat(fnam)
 		if err != nil {
-			shared.LogDebugf("Error statting %s during export", fnam)
 			tw.Close()
+			shared.LogDebugf("Error statting %s during export", fnam)
+			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
 
 		if err := c.tarStoreFile(linkmap, offset, tw, fnam, fi); err != nil {
-			shared.LogDebugf("Error writing to tarfile: %s", err)
 			tw.Close()
+			shared.LogDebugf("Error writing to tarfile: %s", err)
+			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
 	}
@@ -2860,7 +2877,13 @@ func (c *containerLXC) Export(w io.Writer) error {
 		filepath.Walk(fnam, writeToTar)
 	}
 
-	return tw.Close()
+	err = tw.Close()
+	if err != nil {
+		shared.LogError("Failed exporting container", ctxMap)
+	}
+
+	shared.LogInfo("Exported container", ctxMap)
+	return err
 }
 
 func collectCRIULogFile(c container, imagesDir string, function string, method string) error {

From 3042a809071c2f2a1ee7ef638377c9cb32e5db09 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Mon, 19 Sep 2016 13:06:51 +0200
Subject: [PATCH 0334/1193] lxd/container_lxc: improve log for migrate

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0f752d578..5dfee6369 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2922,10 +2922,19 @@ func findCriu(host string) error {
 }
 
 func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop bool, actionScript bool) error {
+	ctxMap := log.Ctx{"name": c.name,
+		"created":      c.creationDate,
+		"ephemeral":    c.ephemeral,
+		"statedir":     stateDir,
+		"actionscript": actionScript,
+		"stop":         stop}
+
 	if err := findCriu(function); err != nil {
 		return err
 	}
 
+	shared.LogInfo("Migrating container", ctxMap)
+
 	prettyCmd := ""
 	switch cmd {
 	case lxc.MIGRATE_PRE_DUMP:
@@ -3040,10 +3049,13 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 	if migrateErr != nil {
 		log, err2 := getCRIULogErrors(stateDir, prettyCmd)
 		if err2 == nil {
+			shared.LogInfo("Failed migrating container", ctxMap)
 			migrateErr = fmt.Errorf("%s %s failed\n%s", function, prettyCmd, log)
 		}
 	}
 
+	shared.LogInfo("Migrated container", ctxMap)
+
 	return migrateErr
 }
 

From 1b232938e248c772df0093781c71eee991bdd8e3 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Mon, 19 Sep 2016 13:19:50 +0200
Subject: [PATCH 0335/1193] lxd/container_lxc: improve log for container exec

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5dfee6369..3037c082c 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3412,19 +3412,24 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	cmd.Stdout = stdout
 	cmd.Stderr = stderr
 
+	shared.LogInfo("Executing command", log.Ctx{"environment": envSlice, "args": args})
+
 	err := cmd.Run()
 	if err != nil {
 		exitErr, ok := err.(*exec.ExitError)
 		if ok {
 			status, ok := exitErr.Sys().(syscall.WaitStatus)
 			if ok {
+				shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": args, "exit_status": status.ExitStatus()})
 				return status.ExitStatus(), nil
 			}
 		}
 
+		shared.LogInfo("Failed executing command", log.Ctx{"environment": envSlice, "args": args, "err": err})
 		return -1, err
 	}
 
+	shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": args})
 	return 0, nil
 }
 

From fcf0ce7ae3d4e2b8c529bb0aec1c7882b7e5a059 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Mon, 19 Sep 2016 17:43:58 +0200
Subject: [PATCH 0336/1193] lxc/info: show remote in lxc info output

Name: test
Remote: unix:/var/lib/lxd/unix.socket
Architecture: x86_64
Created: 2016/09/12 12:29 UTC
Status: Stopped
Type: persistent
Profiles: default

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxc/info.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxc/info.go b/lxc/info.go
index 6cb617b33..a7480f82b 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -87,6 +87,9 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 	const layout = "2006/01/02 15:04 UTC"
 
 	fmt.Printf(i18n.G("Name: %s")+"\n", ct.Name)
+	if d.Remote != nil && d.Remote.Addr != "" {
+		fmt.Printf(i18n.G("Remote: %s")+"\n", d.Remote.Addr)
+	}
 	fmt.Printf(i18n.G("Architecture: %s")+"\n", ct.Architecture)
 	if ct.CreationDate.UTC().Unix() != 0 {
 		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreationDate.UTC().Format(layout))

From cfd907dbdb898e7547e91504f4747f255c96ff15 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Mon, 19 Sep 2016 18:01:28 +0200
Subject: [PATCH 0337/1193] lxc/info: make i18n after adding remote to info

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 po/lxd.pot | 167 +++++++++++++++++++++++++++++++------------------------------
 1 file changed, 86 insertions(+), 81 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index e1d19c3bf..44952d513 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-09-14 01:36-0400\n"
+        "POT-Creation-Date: 2016-09-23 19:36-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -16,19 +16,19 @@ msgstr  "Project-Id-Version: lxd\n"
         "Content-Type: text/plain; charset=CHARSET\n"
         "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:140
+#: lxc/info.go:143
 msgid   "  Disk usage:"
 msgstr  ""
 
-#: lxc/info.go:163
+#: lxc/info.go:166
 msgid   "  Memory usage:"
 msgstr  ""
 
-#: lxc/info.go:180
+#: lxc/info.go:183
 msgid   "  Network usage:"
 msgstr  ""
 
-#: lxc/config.go:37
+#: lxc/config.go:36
 msgid   "### This is a yaml representation of the configuration.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -57,7 +57,7 @@ msgid   "### This is a yaml representation of the image properties.\n"
         "###  description: My custom image"
 msgstr  ""
 
-#: lxc/profile.go:27
+#: lxc/profile.go:26
 msgid   "### This is a yaml representation of the profile.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -86,7 +86,7 @@ msgstr  ""
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/profile.go:227
+#: lxc/profile.go:226
 msgid   "(none)"
 msgstr  ""
 
@@ -102,11 +102,11 @@ msgstr  ""
 msgid   "ARCHITECTURE"
 msgstr  ""
 
-#: lxc/remote.go:53
+#: lxc/remote.go:52
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:246
+#: lxc/remote.go:245
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
@@ -119,7 +119,7 @@ msgstr  ""
 msgid   "An environment variable of the form HOME=/home/foo"
 msgstr  ""
 
-#: lxc/image.go:339 lxc/info.go:90
+#: lxc/image.go:339 lxc/info.go:93
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
@@ -133,15 +133,15 @@ msgstr  ""
 msgid   "Available commands:"
 msgstr  ""
 
-#: lxc/info.go:172
+#: lxc/info.go:175
 msgid   "Bytes received"
 msgstr  ""
 
-#: lxc/info.go:173
+#: lxc/info.go:176
 msgid   "Bytes sent"
 msgstr  ""
 
-#: lxc/config.go:274
+#: lxc/config.go:273
 msgid   "COMMON NAME"
 msgstr  ""
 
@@ -149,21 +149,21 @@ msgstr  ""
 msgid   "CREATED AT"
 msgstr  ""
 
-#: lxc/config.go:114
+#: lxc/config.go:113
 #, c-format
 msgid   "Can't read from stdin: %s"
 msgstr  ""
 
-#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182
+#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181
 #, c-format
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:344
+#: lxc/profile.go:343
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:196
+#: lxc/remote.go:195
 #, c-format
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
@@ -175,7 +175,7 @@ msgid   "Changes state of one or more containers to %s.\n"
         "lxc %s <name> [<name>...]%s"
 msgstr  ""
 
-#: lxc/remote.go:269
+#: lxc/remote.go:268
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
@@ -187,7 +187,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:701 lxc/profile.go:191
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:701 lxc/profile.go:190
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -225,7 +225,7 @@ msgstr  ""
 msgid   "Copying the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:211
+#: lxc/remote.go:210
 msgid   "Could not create server cert dir"
 msgstr  ""
 
@@ -245,7 +245,7 @@ msgid   "Create a read-only snapshot of a container.\n"
         "lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:344 lxc/info.go:92
+#: lxc/image.go:344 lxc/info.go:95
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -271,12 +271,12 @@ msgid   "Delete containers or container snapshots.\n"
         "Destroy containers or snapshots with any attached data (configuration, snapshots, ...)."
 msgstr  ""
 
-#: lxc/config.go:648
+#: lxc/config.go:647
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:835
+#: lxc/config.go:834
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
@@ -285,7 +285,7 @@ msgstr  ""
 msgid   "EPHEMERAL"
 msgstr  ""
 
-#: lxc/config.go:276
+#: lxc/config.go:275
 msgid   "EXPIRY DATE"
 msgstr  ""
 
@@ -326,7 +326,7 @@ msgstr  ""
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:273 lxc/image.go:619 lxc/image.go:648
+#: lxc/config.go:272 lxc/image.go:619 lxc/image.go:648
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -373,7 +373,7 @@ msgstr  ""
 msgid   "IPV6"
 msgstr  ""
 
-#: lxc/config.go:275
+#: lxc/config.go:274
 msgid   "ISSUE DATE"
 msgstr  ""
 
@@ -417,12 +417,12 @@ msgid   "Initialize a container from a particular image.\n"
         "lxc init ubuntu u1"
 msgstr  ""
 
-#: lxc/remote.go:122
+#: lxc/remote.go:121
 #, c-format
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
 
-#: lxc/config.go:254
+#: lxc/config.go:253
 msgid   "Invalid certificate"
 msgstr  ""
 
@@ -440,7 +440,7 @@ msgstr  ""
 msgid   "Invalid target %s"
 msgstr  ""
 
-#: lxc/info.go:121
+#: lxc/info.go:124
 msgid   "Ips:"
 msgstr  ""
 
@@ -507,7 +507,7 @@ msgid   "Lists the available resources.\n"
         "Fast column layout: nsacPt"
 msgstr  ""
 
-#: lxc/info.go:225
+#: lxc/info.go:228
 msgid   "Log:"
 msgstr  ""
 
@@ -519,7 +519,7 @@ msgstr  ""
 msgid   "Make the image public"
 msgstr  ""
 
-#: lxc/profile.go:48
+#: lxc/profile.go:47
 msgid   "Manage configuration profiles.\n"
         "\n"
         "lxc profile list [filters]                     List available profiles.\n"
@@ -555,7 +555,7 @@ msgid   "Manage configuration profiles.\n"
         "    using the specified profile."
 msgstr  ""
 
-#: lxc/config.go:58
+#: lxc/config.go:57
 msgid   "Manage configuration.\n"
         "\n"
         "lxc config device add <[remote:]container> <name> <type> [key=value]...     Add a device to a container.\n"
@@ -603,7 +603,7 @@ msgid   "Manage files on a container.\n"
         "<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>"
 msgstr  ""
 
-#: lxc/remote.go:39
+#: lxc/remote.go:38
 msgid   "Manage remote LXD servers.\n"
         "\n"
         "lxc remote add <name> <url> [--accept-certificate] [--password=PASSWORD]\n"
@@ -684,11 +684,11 @@ msgid   "Manipulate container images.\n"
         "    List the aliases. Filters may be part of the image hash or part of the image alias name.\n"
 msgstr  ""
 
-#: lxc/info.go:147
+#: lxc/info.go:150
 msgid   "Memory (current)"
 msgstr  ""
 
-#: lxc/info.go:151
+#: lxc/info.go:154
 msgid   "Memory (peak)"
 msgstr  ""
 
@@ -714,7 +714,7 @@ msgstr  ""
 msgid   "More than one file to download, but target is not a directory"
 msgstr  ""
 
-#: lxc/move.go:17
+#: lxc/move.go:16
 msgid   "Move containers within or in between lxd instances.\n"
         "\n"
         "lxc move [remote:]<source container> [remote:]<destination container>\n"
@@ -728,11 +728,11 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:396 lxc/remote.go:353
+#: lxc/list.go:396 lxc/remote.go:352
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/remote.go:327 lxc/remote.go:332
+#: lxc/remote.go:326 lxc/remote.go:331
 msgid   "NO"
 msgstr  ""
 
@@ -745,15 +745,15 @@ msgstr  ""
 msgid   "New alias to define at target"
 msgstr  ""
 
-#: lxc/config.go:285
+#: lxc/config.go:284
 msgid   "No certificate provided to add"
 msgstr  ""
 
-#: lxc/config.go:308
+#: lxc/config.go:307
 msgid   "No fingerprint specified."
 msgstr  ""
 
-#: lxc/remote.go:107
+#: lxc/remote.go:106
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
@@ -786,19 +786,19 @@ msgstr  ""
 msgid   "PROFILES"
 msgstr  ""
 
-#: lxc/remote.go:355
+#: lxc/remote.go:354
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:620 lxc/remote.go:356
+#: lxc/image.go:620 lxc/remote.go:355
 msgid   "PUBLIC"
 msgstr  ""
 
-#: lxc/info.go:174
+#: lxc/info.go:177
 msgid   "Packets received"
 msgstr  ""
 
-#: lxc/info.go:175
+#: lxc/info.go:178
 msgid   "Packets sent"
 msgstr  ""
 
@@ -814,7 +814,7 @@ msgstr  ""
 msgid   "Permission denied, are you in the lxd group?"
 msgstr  ""
 
-#: lxc/info.go:103
+#: lxc/info.go:106
 #, c-format
 msgid   "Pid: %d"
 msgstr  ""
@@ -825,11 +825,11 @@ msgid   "Presents details on how to use LXD.\n"
         "lxd help [--all]"
 msgstr  ""
 
-#: lxc/profile.go:192
+#: lxc/profile.go:191
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:702
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:702
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -851,22 +851,22 @@ msgid   "Prints the version number of this client tool.\n"
         "lxc version"
 msgstr  ""
 
-#: lxc/info.go:127
+#: lxc/info.go:130
 #, c-format
 msgid   "Processes: %d"
 msgstr  ""
 
-#: lxc/profile.go:229
+#: lxc/profile.go:228
 #, c-format
 msgid   "Profile %s applied to %s"
 msgstr  ""
 
-#: lxc/profile.go:143
+#: lxc/profile.go:142
 #, c-format
 msgid   "Profile %s created"
 msgstr  ""
 
-#: lxc/profile.go:213
+#: lxc/profile.go:212
 #, c-format
 msgid   "Profile %s deleted"
 msgstr  ""
@@ -875,7 +875,7 @@ msgstr  ""
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
-#: lxc/info.go:101
+#: lxc/info.go:104
 #, c-format
 msgid   "Profiles: %s"
 msgstr  ""
@@ -884,7 +884,7 @@ msgstr  ""
 msgid   "Properties:"
 msgstr  ""
 
-#: lxc/remote.go:56
+#: lxc/remote.go:55
 msgid   "Public image server"
 msgstr  ""
 
@@ -899,10 +899,15 @@ msgid   "Publish containers as images.\n"
         "lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-value]..."
 msgstr  ""
 
-#: lxc/remote.go:54
+#: lxc/remote.go:53
 msgid   "Remote admin password"
 msgstr  ""
 
+#: lxc/info.go:91
+#, c-format
+msgid   "Remote: %s"
+msgstr  ""
+
 #: lxc/delete.go:42
 #, c-format
 msgid   "Remove %s (yes/no): "
@@ -912,7 +917,7 @@ msgstr  ""
 msgid   "Require user confirmation."
 msgstr  ""
 
-#: lxc/info.go:124
+#: lxc/info.go:127
 msgid   "Resources:"
 msgstr  ""
 
@@ -933,19 +938,19 @@ msgstr  ""
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:357
+#: lxc/remote.go:356
 msgid   "STATIC"
 msgstr  ""
 
-#: lxc/remote.go:204
+#: lxc/remote.go:203
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:266
+#: lxc/remote.go:265
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
-#: lxc/remote.go:55
+#: lxc/remote.go:54
 msgid   "Server protocol (lxd or simplestreams)"
 msgstr  ""
 
@@ -991,7 +996,7 @@ msgstr  ""
 msgid   "Size: %.2fMB"
 msgstr  ""
 
-#: lxc/info.go:194
+#: lxc/info.go:197
 msgid   "Snapshots:"
 msgstr  ""
 
@@ -1004,7 +1009,7 @@ msgstr  ""
 msgid   "Starting %s"
 msgstr  ""
 
-#: lxc/info.go:95
+#: lxc/info.go:98
 #, c-format
 msgid   "Status: %s"
 msgstr  ""
@@ -1021,11 +1026,11 @@ msgstr  ""
 msgid   "Store the container state (only for stop)."
 msgstr  ""
 
-#: lxc/info.go:155
+#: lxc/info.go:158
 msgid   "Swap (current)"
 msgstr  ""
 
-#: lxc/info.go:159
+#: lxc/info.go:162
 msgid   "Swap (peak)"
 msgstr  ""
 
@@ -1041,7 +1046,7 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/config.go:676 lxc/config.go:688 lxc/config.go:721 lxc/config.go:739 lxc/config.go:777 lxc/config.go:795
+#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738 lxc/config.go:776 lxc/config.go:794
 msgid   "The device doesn't exist"
 msgstr  ""
 
@@ -1080,11 +1085,11 @@ msgstr  ""
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
 
-#: lxc/info.go:97
+#: lxc/info.go:100
 msgid   "Type: ephemeral"
 msgstr  ""
 
-#: lxc/info.go:99
+#: lxc/info.go:102
 msgid   "Type: persistent"
 msgstr  ""
 
@@ -1092,11 +1097,11 @@ msgstr  ""
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:354
+#: lxc/remote.go:353
 msgid   "URL"
 msgstr  ""
 
-#: lxc/remote.go:82
+#: lxc/remote.go:81
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
@@ -1126,11 +1131,11 @@ msgstr  ""
 msgid   "Whether or not to snapshot the container's running state"
 msgstr  ""
 
-#: lxc/config.go:33
+#: lxc/config.go:32
 msgid   "Whether to show the expanded configuration"
 msgstr  ""
 
-#: lxc/remote.go:329 lxc/remote.go:334
+#: lxc/remote.go:328 lxc/remote.go:333
 msgid   "YES"
 msgstr  ""
 
@@ -1150,11 +1155,11 @@ msgstr  ""
 msgid   "can't copy to the same container name"
 msgstr  ""
 
-#: lxc/remote.go:317
+#: lxc/remote.go:316
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:343
+#: lxc/remote.go:342
 msgid   "default"
 msgstr  ""
 
@@ -1192,7 +1197,7 @@ msgstr  ""
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
-#: lxc/remote.go:197
+#: lxc/remote.go:196
 msgid   "ok (y/n)?"
 msgstr  ""
 
@@ -1201,35 +1206,35 @@ msgstr  ""
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:379
+#: lxc/remote.go:378
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:309 lxc/remote.go:371 lxc/remote.go:406 lxc/remote.go:422
+#: lxc/remote.go:308 lxc/remote.go:370 lxc/remote.go:405 lxc/remote.go:421
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:292
+#: lxc/remote.go:291
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
-#: lxc/remote.go:313 lxc/remote.go:375 lxc/remote.go:410
+#: lxc/remote.go:312 lxc/remote.go:374 lxc/remote.go:409
 #, c-format
 msgid   "remote %s is static and cannot be modified"
 msgstr  ""
 
-#: lxc/info.go:205
+#: lxc/info.go:208
 msgid   "stateful"
 msgstr  ""
 
-#: lxc/info.go:207
+#: lxc/info.go:210
 msgid   "stateless"
 msgstr  ""
 
-#: lxc/info.go:201
+#: lxc/info.go:204
 #, c-format
 msgid   "taken at %s"
 msgstr  ""

From 19acc419585cb40474fb7a2cfdcc6e478870e764 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 19 Sep 2016 11:30:50 -0600
Subject: [PATCH 0338/1193] include remote name in pretty printed error message

Closes #1843

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/launch.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxc/launch.go b/lxc/launch.go
index eea49297e..d31ae6c99 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -129,7 +129,12 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 
 	err = d.WaitForSuccess(resp.Operation)
 	if err != nil {
-		return fmt.Errorf("%s\n"+i18n.G("Try `lxc info --show-log %s` for more info"), err, name)
+		prettyName := name
+		if remote != "" {
+			prettyName = fmt.Sprintf("%s:%s", remote, name)
+		}
+
+		return fmt.Errorf("%s\n"+i18n.G("Try `lxc info --show-log %s` for more info"), err, prettyName)
 	}
 
 	return nil

From 93d06cba17e836938648599bcb13de3d6114ac4d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Tue, 20 Sep 2016 09:48:03 +0200
Subject: [PATCH 0339/1193] lxd/daemon: adapt log levels

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/daemon.go | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index ef4112626..7ea3ea12b 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -757,7 +757,7 @@ func (d *Daemon) Init() error {
 	go func() {
 		t := time.NewTicker(24 * time.Hour)
 		for {
-			shared.LogDebugf("Expiring log files")
+			shared.LogInfof("Expiring log files")
 
 			err := d.ExpireLogs()
 			if err != nil {
@@ -777,6 +777,7 @@ func (d *Daemon) Init() error {
 	)
 
 	/* Setup /dev/lxd */
+	shared.LogInfof("Starting /dev/lxd handler")
 	d.devlxd, err = createAndBindDevLxd()
 	if err != nil {
 		return err
@@ -1064,18 +1065,21 @@ func (d *Daemon) Stop() error {
 	}
 
 	if n, err := d.numRunningContainers(); err != nil || n == 0 {
-		shared.LogDebugf("Unmounting shmounts")
+		shared.LogInfof("Unmounting shmounts")
 
 		syscall.Unmount(shared.VarPath("shmounts"), syscall.MNT_DETACH)
+
+		shared.LogInfof("Done unmounting shmounts")
 	} else {
 		shared.LogDebugf("Not unmounting shmounts (containers are still running)")
 	}
 
-	shared.LogDebugf("Closing the database")
+	shared.LogInfof("Closing the database")
 	d.db.Close()
 
-	shared.LogDebugf("Stopping /dev/lxd handler")
+	shared.LogInfof("Stopping /dev/lxd handler")
 	d.devlxd.Close()
+	shared.LogInfof("Stopped /dev/lxd handler")
 
 	if d.MockMode || forceStop {
 		return nil

From b13f1df8def7f232a3330a0b39497ef6d1a4ffe8 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Tue, 20 Sep 2016 09:54:41 +0200
Subject: [PATCH 0340/1193] lxd/images: adapt log levels

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/images.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index eb59932b3..bd15d3a4f 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -845,7 +845,7 @@ func imagesGet(d *Daemon, r *http.Request) Response {
 var imagesCmd = Command{name: "images", post: imagesPost, untrustedGet: true, get: imagesGet}
 
 func autoUpdateImages(d *Daemon) {
-	shared.LogDebugf("Updating images")
+	shared.LogInfof("Updating images")
 
 	images, err := dbImagesGet(d.db, false)
 	if err != nil {
@@ -904,11 +904,11 @@ func autoUpdateImages(d *Daemon) {
 		}
 	}
 
-	shared.LogDebugf("Done updating images")
+	shared.LogInfof("Done updating images")
 }
 
 func pruneExpiredImages(d *Daemon) {
-	shared.LogDebugf("Pruning expired images")
+	shared.LogInfof("Pruning expired images")
 
 	// Get the list of expires images
 	expiry := daemonConfig["images.remote_cache_expiry"].GetInt64()
@@ -925,7 +925,7 @@ func pruneExpiredImages(d *Daemon) {
 		}
 	}
 
-	shared.LogDebugf("Done pruning expired images")
+	shared.LogInfof("Done pruning expired images")
 }
 
 func doDeleteImage(d *Daemon, fingerprint string) error {

From 711d5afefbfffe0a48bee7dceb5bafeac35004af Mon Sep 17 00:00:00 2001
From: Meaglith Ma <genedna at gmail.com>
Date: Wed, 21 Sep 2016 15:24:39 +0000
Subject: [PATCH 0341/1193] Fix the table sytle of environment.md

Closes #2410

Signed-off-by: Meaglith Ma genedna at gmail.com
---
 doc/environment.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/doc/environment.md b/doc/environment.md
index d63befc47..058a7a754 100644
--- a/doc/environment.md
+++ b/doc/environment.md
@@ -3,6 +3,8 @@ The LXD client and daemon respect some environment variables to adapt to
 the user's environment and to turn some advanced features on and off.
 
 # Common
+Name                            | Description
+:---                            | :----
 LXD\_DIR                        | The LXD data directory
 PATH                            | List of paths to look into when resolving binaries
 http\_proxy                     | Proxy server URL for HTTP

From 0670d671b47767e8266a6b2850b725c1ff4eefc1 Mon Sep 17 00:00:00 2001
From: jpic <jamespic at gmail.com>
Date: Sat, 17 Sep 2016 14:48:49 +0200
Subject: [PATCH 0342/1193] Document lxc exec -- args

Signed-off-by: James Pic <jpic at yourlabs.org>
---
 lxc/exec.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/exec.go b/lxc/exec.go
index ba3e5e03b..f68bb0a3f 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -45,7 +45,7 @@ func (c *execCmd) usage() string {
 	return i18n.G(
 		`Execute the specified command in a container.
 
-lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env EDITOR=/usr/bin/vim]... <command>
+lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env EDITOR=/usr/bin/vim]... [--] <command line>
 
 Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored).`)
 }

From 6a7503b0ff55fee8d069d1095079d9e6772dacfe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Sep 2016 01:40:24 -0400
Subject: [PATCH 0343/1193] i18n: Update for previous commit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2392

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/lxd.pot | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index 44952d513..531222519 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-09-23 19:36-0400\n"
+        "POT-Creation-Date: 2016-09-23 19:38-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -312,7 +312,7 @@ msgstr  ""
 #: lxc/exec.go:45
 msgid   "Execute the specified command in a container.\n"
         "\n"
-        "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env EDITOR=/usr/bin/vim]... <command>\n"
+        "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env EDITOR=/usr/bin/vim]... [--] <command line>\n"
         "\n"
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
@@ -1080,7 +1080,7 @@ msgstr  ""
 msgid   "Transferring image: %d%%"
 msgstr  ""
 
-#: lxc/action.go:99 lxc/launch.go:132
+#: lxc/action.go:99 lxc/launch.go:137
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""

From 82b234bf0dff79404363234e1067a28fac6be2ec Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 26 Sep 2016 17:31:18 +0000
Subject: [PATCH 0344/1193] also show warnings on c/r errors

Some of these are relevant and potentially useful in debugging.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 3037c082c..7bf4e225b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2904,7 +2904,7 @@ func getCRIULogErrors(imagesDir string, method string) (string, error) {
 	ret := []string{}
 	for scanner.Scan() {
 		line := scanner.Text()
-		if strings.Contains(line, "Error") {
+		if strings.Contains(line, "Error") || strings.Contains(line, "Warn") {
 			ret = append(ret, scanner.Text())
 		}
 	}

From 5116c4b61cddf0a15480d7be622a3f0f76382690 Mon Sep 17 00:00:00 2001
From: Denis Pynkin <dans at altlinux.org>
Date: Mon, 26 Sep 2016 21:00:00 +0300
Subject: [PATCH 0345/1193] Fixed container convert from LXC to LXD

Added LXDAPI exception during container name check.

Signed-off-by: Denis Pynkin <denis_pynkin at epam.com>
---
 scripts/lxc-to-lxd | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index df8fbf581..aa60011c0 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -6,6 +6,7 @@ import lxc
 import os
 import subprocess
 from pylxd.client import Client
+from pylxd import exceptions
 
 
 # Fetch a config key as a list
@@ -110,7 +111,7 @@ def convert_container(container_name, args):
         lxd.containers.get(container_name)
         print("Container already exists, skipping...")
         return False
-    except NameError:
+    except (NameError, exceptions.LXDAPIException):
         pass
 
     # Validate lxc.utsname

From 33f114a6fbfde9a0222527c02d555a8f949de5a5 Mon Sep 17 00:00:00 2001
From: mis <mistoo at gmail.com>
Date: Mon, 26 Sep 2016 22:38:46 +0200
Subject: [PATCH 0346/1193] Containers state checking for start, stop and exec
 commands

Signed-off-by: Pawel Gajda <mis at pld-linux.org>
---
 config/bash/lxd-client | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/config/bash/lxd-client b/config/bash/lxd-client
index 21bfd4f8e..472967f78 100644
--- a/config/bash/lxd-client
+++ b/config/bash/lxd-client
@@ -3,8 +3,14 @@ _have lxc && {
   {
     _lxd_names()
     {
+      local state=$1
+      local keys=$2
+      
+      local cmd="lxc list --fast"
+      [ -n "$state" ] && cmd="$cmd | grep $state"
+
       COMPREPLY=( $( compgen -W \
-        "$( lxc list --fast | tail -n +4 | awk '{print $2}' | egrep -v '^(\||^$)' ) $1" "$cur" )
+        "$( eval $cmd | grep -Ev '(+--|NAME)' | awk '{print $2}' ) $keys" "$cur" )
       )
     }
 
@@ -87,7 +93,7 @@ _have lxc && {
                 _lxd_names
                 ;;
               "get"|"set"|"unset")
-                _lxd_names "$global_keys"
+                _lxd_names "" "$global_keys"
                 ;;
             esac
             ;;
@@ -115,7 +121,7 @@ _have lxc && {
         _lxd_names
         ;;
       "exec")
-        _lxd_names
+        _lxd_names "RUNNING"
         ;;
       "file")
         COMPREPLY=( $(compgen -W "pull push edit" -- $cur) )
@@ -182,12 +188,10 @@ _have lxc && {
         _lxd_names
         ;;
       "start")
-        # should check if containers are stopped
-        _lxd_names
+        _lxd_names "STOPPED"
         ;;
       "stop")
-        # should check if containers are started
-        _lxd_names
+        _lxd_names "RUNNING"
         ;;
       *)
         ;;

From 19aeb20567a2cf6a8735f32572e451c97e4493af Mon Sep 17 00:00:00 2001
From: mis <mistoo at gmail.com>
Date: Mon, 26 Sep 2016 23:13:29 +0200
Subject: [PATCH 0347/1193] "start" should also work for containers that are in
 state "FROZEN"

Signed-off-by: Pawel Gajda <mis at pld-linux.org>
---
 config/bash/lxd-client | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/config/bash/lxd-client b/config/bash/lxd-client
index 472967f78..d1b028537 100644
--- a/config/bash/lxd-client
+++ b/config/bash/lxd-client
@@ -7,7 +7,7 @@ _have lxc && {
       local keys=$2
       
       local cmd="lxc list --fast"
-      [ -n "$state" ] && cmd="$cmd | grep $state"
+      [ -n "$state" ] && cmd="$cmd | grep -E '$state'"
 
       COMPREPLY=( $( compgen -W \
         "$( eval $cmd | grep -Ev '(+--|NAME)' | awk '{print $2}' ) $keys" "$cur" )
@@ -188,7 +188,7 @@ _have lxc && {
         _lxd_names
         ;;
       "start")
-        _lxd_names "STOPPED"
+        _lxd_names "STOPPED|FROZEN"
         ;;
       "stop")
         _lxd_names "RUNNING"

From 8760440aef9b4338e82d275f9617ca35756746a7 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 17 Jun 2016 17:51:17 +0000
Subject: [PATCH 0348/1193] apparmor: create an apparmor namespace for each
 container

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/apparmor.go      | 101 +++++++++++++++++++++++++++++++++++++++++++++------
 lxd/container_lxc.go |  26 +++++++++++--
 lxd/daemon.go        |  43 ++++++++++++++++++++++
 test/suites/basic.sh |  13 +++++--
 4 files changed, 165 insertions(+), 18 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index b4d73f3e5..1b1d78acb 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -94,7 +94,7 @@ const AA_PROFILE_BASE = `
   mount fstype=sysfs -> /sys/,
   mount options=(rw, nosuid, nodev, noexec, remount) -> /sys/,
   deny /sys/firmware/efi/efivars/** rwklx,
-  deny /sys/kernel/security/** rwklx,
+  # note, /sys/kernel/security/** handled below
   mount options=(move) /sys/fs/cgroup/cgmanager/ -> /sys/fs/cgroup/cgmanager.lower/,
   mount options=(ro, nosuid, nodev, noexec, remount, strictatime) -> /sys/fs/cgroup/,
 
@@ -186,7 +186,7 @@ const AA_PROFILE_BASE = `
   deny /proc/sys/n[^e]*{,/**} wklx,
   deny /proc/sys/ne[^t]*{,/**} wklx,
   deny /proc/sys/net?*{,/**} wklx,
-  deny /sys/[^fdc]*{,/**} wklx,
+  deny /sys/[^fdck]*{,/**} wklx,
   deny /sys/c[^l]*{,/**} wklx,
   deny /sys/cl[^a]*{,/**} wklx,
   deny /sys/cla[^s]*{,/**} wklx,
@@ -250,14 +250,28 @@ const AA_PROFILE_NESTING = `
   mount options=bind /var/lib/lxd/shmounts/** -> /var/lib/lxd/**,
 `
 
-func AAProfileFull(c container) string {
-	lxddir := shared.VarPath("")
-	if len(c.Name())+len(lxddir)+7 >= 253 {
+func mkApparmorName(name string) string {
+	if len(name)+7 >= 253 {
 		hash := sha256.New()
-		io.WriteString(hash, lxddir)
-		lxddir = fmt.Sprintf("%x", hash.Sum(nil))
+		io.WriteString(hash, name)
+		return fmt.Sprintf("%x", hash.Sum(nil))
 	}
 
+	return name
+}
+
+func AANamespace(c container) string {
+	/* / is not allowed in apparmor namespace names; let's also trim the
+	 * leading / so it doesn't look like "-var-lib-lxd"
+	 */
+	lxddir := strings.Replace(strings.Trim(shared.VarPath(""), "/"), "/", "-", -1)
+	lxddir = mkApparmorName(lxddir)
+	return fmt.Sprintf("lxd-%s_<%s>", c.Name(), lxddir)
+}
+
+func AAProfileFull(c container) string {
+	lxddir := shared.VarPath("")
+	lxddir = mkApparmorName(lxddir)
 	return fmt.Sprintf("lxd-%s_<%s>", c.Name(), lxddir)
 }
 
@@ -289,11 +303,50 @@ func getAAProfileContent(c container) string {
 		profile += "  mount fstype=cgroup -> /sys/fs/cgroup/**,\n"
 	}
 
-	// Apply nesting bits
+	if aaStacking {
+		profile += "\n  ### Feature: apparmor stacking\n"
+
+		if c.IsPrivileged() {
+			profile += "\n  ### Configuration: apparmor loading disabled in privileged containers\n"
+			profile += "  deny /sys/k*{,/**} rwklx,\n"
+		} else {
+			profile += `  ### Configuration: apparmor loading in unprivileged containers
+  deny /sys/k[^e]*{,/**} wklx,
+  deny /sys/ke[^r]*{,/**} wklx,
+  deny /sys/ker[^n]*{,/**} wklx,
+  deny /sys/kern[^e]*{,/**} wklx,
+  deny /sys/kerne[^l]*{,/**} wklx,
+  deny /sys/kernel/[^s]*{,/**} wklx,
+  deny /sys/kernel/s[^e]*{,/**} wklx,
+  deny /sys/kernel/se[^c]*{,/**} wklx,
+  deny /sys/kernel/sec[^u]*{,/**} wklx,
+  deny /sys/kernel/secu[^r]*{,/**} wklx,
+  deny /sys/kernel/secur[^i]*{,/**} wklx,
+  deny /sys/kernel/securi[^t]*{,/**} wklx,
+  deny /sys/kernel/securit[^y]*{,/**} wklx,
+  deny /sys/kernel/security/[^a]*{,/**} wklx,
+  deny /sys/kernel/security/a[^p]*{,/**} wklx,
+  deny /sys/kernel/security/ap[^p]*{,/**} wklx,
+  deny /sys/kernel/security/app[^a]*{,/**} wklx,
+  deny /sys/kernel/security/appa[^r]*{,/**} wklx,
+  deny /sys/kernel/security/appar[^m]*{,/**} wklx,
+  deny /sys/kernel/security/apparm[^o]*{,/**} wklx,
+  deny /sys/kernel/security/apparmo[^r]*{,/**} wklx,
+  deny /sys/kernel/security/apparmor?*{,/**} wklx,
+  deny /sys/kernel/security?*{,/**} wklx,
+  deny /sys/kernel?*{,/**} wklx,
+`
+			profile += fmt.Sprintf("  change_profile -> \":%s://*\",\n", AANamespace(c))
+		}
+	}
+
 	if c.IsNesting() {
+		// Apply nesting bits
 		profile += "\n  ### Configuration: nesting\n"
 		profile += strings.TrimLeft(AA_PROFILE_NESTING, "\n")
-		profile += fmt.Sprintf("  change_profile -> \"%s\",\n", AAProfileFull(c))
+		if !aaStacking || c.IsPrivileged() {
+			profile += fmt.Sprintf("  change_profile -> \"%s\",\n", AAProfileFull(c))
+		}
 	}
 
 	// Append raw.apparmor
@@ -332,6 +385,19 @@ func runApparmor(command string, c container) error {
 	return err
 }
 
+func mkApparmorNamespace(namespace string) error {
+	if !aaStacking {
+		return nil
+	}
+
+	p := path.Join("/sys/kernel/security/apparmor/policy/namespaces", namespace)
+	if err := os.Mkdir(p, 0755); !os.IsExist(err) {
+		return err
+	}
+
+	return nil
+}
+
 // Ensure that the container's policy is loaded into the kernel so the
 // container can boot.
 func AALoadProfile(c container) error {
@@ -339,6 +405,10 @@ func AALoadProfile(c container) error {
 		return nil
 	}
 
+	if err := mkApparmorNamespace(AANamespace(c)); err != nil {
+		return err
+	}
+
 	/* In order to avoid forcing a profile parse (potentially slow) on
 	 * every container start, let's use apparmor's binary policy cache,
 	 * which checks mtime of the files to figure out if the policy needs to
@@ -375,13 +445,20 @@ func AALoadProfile(c container) error {
 	return runApparmor(APPARMOR_CMD_LOAD, c)
 }
 
-// Ensure that the container's policy is unloaded to free kernel memory. This
-// does not delete the policy from disk or cache.
-func AAUnloadProfile(c container) error {
+// Ensure that the container's policy namespace is unloaded to free kernel
+// memory. This does not delete the policy from disk or cache.
+func AADestroy(c container) error {
 	if !aaAdmin {
 		return nil
 	}
 
+	if aaStacking {
+		p := path.Join("/sys/kernel/security/apparmor/policy/namespaces", AANamespace(c))
+		if err := os.Remove(p); err != nil {
+			shared.LogError("error removing apparmor namespace", log.Ctx{"err": err, "ns": p})
+		}
+	}
+
 	return runApparmor(APPARMOR_CMD_UNLOAD, c)
 }
 
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7bf4e225b..e34f2218b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -382,7 +382,12 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Base config
-	err = lxcSetConfigItem(cc, "lxc.cap.drop", "mac_admin mac_override sys_time sys_module sys_rawio")
+	toDrop := "sys_time sys_module sys_rawio"
+	if !aaStacking || c.IsPrivileged() {
+		toDrop = toDrop + " mac_admin mac_override"
+	}
+
+	err = lxcSetConfigItem(cc, "lxc.cap.drop", toDrop)
 	if err != nil {
 		return err
 	}
@@ -581,7 +586,20 @@ func (c *containerLXC) initLXC() error {
 			}
 		} else {
 			// If not currently confined, use the container's profile
-			err := lxcSetConfigItem(cc, "lxc.aa_profile", AAProfileFull(c))
+			profile := AAProfileFull(c)
+
+			/* In the nesting case, we want to enable the inside
+			 * LXD to load its profile. Unprivileged containers can
+			 * load profiles, but privileged containers cannot, so
+			 * let's not use a namespace so they can fall back to
+			 * the old way of nesting, i.e. using the parent's
+			 * profile.
+			 */
+			if aaStacking && (!c.IsNesting() || !c.IsPrivileged()) {
+				profile = fmt.Sprintf("%s//&:%s:", profile, AANamespace(c))
+			}
+
+			err := lxcSetConfigItem(cc, "lxc.aa_profile", profile)
 			if err != nil {
 				return err
 			}
@@ -1629,7 +1647,9 @@ func (c *containerLXC) OnStop(target string) error {
 	}
 
 	// Unload the apparmor profile
-	AAUnloadProfile(c)
+	if err := AADestroy(c); err != nil {
+		shared.LogError("failed to destroy apparmor namespace", log.Ctx{"container": c.Name(), "err": err})
+	}
 
 	// FIXME: The go routine can go away once we can rely on LXC_TARGET
 	go func(c *containerLXC, target string, op *lxcContainerOperation) {
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 7ea3ea12b..f26f7b01b 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -40,6 +40,7 @@ import (
 var aaAdmin = true
 var aaAvailable = true
 var aaConfined = false
+var aaStacking = false
 
 // CGroup
 var cgBlkioController = false
@@ -616,6 +617,48 @@ func (d *Daemon) Init() error {
 		}
 	}
 
+	if aaAvailable {
+		canStack := func() bool {
+			contentBytes, err := ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/stack")
+			if err != nil {
+				return false
+			}
+
+			if string(contentBytes) != "yes\n" {
+				return false
+			}
+
+			contentBytes, err = ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/version")
+			if err != nil {
+				return false
+			}
+
+			content := string(contentBytes)
+
+			parts := strings.Split(strings.TrimSpace(content), ".")
+			if len(parts) != 2 {
+				shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
+				return false
+			}
+
+			major, err := strconv.Atoi(parts[0])
+			if err != nil {
+				shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
+				return false
+			}
+
+			minor, err := strconv.Atoi(parts[1])
+			if err != nil {
+				shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
+				return false
+			}
+
+			return major >= 1 && minor >= 1
+		}
+
+		aaStacking = canStack()
+	}
+
 	/* Detect CGroup support */
 	cgBlkioController = shared.PathExists("/sys/fs/cgroup/blkio/")
 	if !cgBlkioController {
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 5b66560df..352153655 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -279,9 +279,16 @@ test_basic_usage() {
   # check that an apparmor profile is created for this container, that it is
   # unloaded on stop, and that it is deleted when the container is deleted
   lxc launch testimage lxd-apparmor-test
-  aa-status | grep "lxd-lxd-apparmor-test_<${LXD_DIR}>"
-  lxc stop lxd-apparmor-test --force
-  ! aa-status | grep -q "lxd-lxd-apparmor-test_<${LXD_DIR}>"
+  if [ -f /sys/kernel/security/apparmor/features/domain/version ]; then
+    aa_namespace="lxd-lxd-apparmor-test_<$(echo "${LXD_DIR}" | sed -e 's/\//-/g' -e 's/^.//')>"
+    aa-status | grep ":${aa_namespace}://unconfined"
+    lxc stop lxd-apparmor-test --force
+    ! aa-status | grep -q ":${aa_namespace}:"
+  else
+    aa-status | grep "lxd-lxd-apparmor-test_<${LXD_DIR}>"
+    lxc stop lxd-apparmor-test --force
+    ! aa-status | grep -q "lxd-lxd-apparmor-test_<${LXD_DIR}>"
+  fi
   lxc delete lxd-apparmor-test
   [ ! -f "${LXD_DIR}/security/apparmor/profiles/lxd-lxd-apparmor-test" ]
 

From 094f133887abc06dec5af95d3ef7ebfe2e4553b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Sep 2016 15:07:48 -0400
Subject: [PATCH 0349/1193] apparmor: Only turn on stacking for >= 1.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index f26f7b01b..a83afcf46 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -636,7 +636,8 @@ func (d *Daemon) Init() error {
 			content := string(contentBytes)
 
 			parts := strings.Split(strings.TrimSpace(content), ".")
-			if len(parts) != 2 {
+
+			if len(parts) == 0 {
 				shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
 				return false
 			}
@@ -647,13 +648,16 @@ func (d *Daemon) Init() error {
 				return false
 			}
 
-			minor, err := strconv.Atoi(parts[1])
-			if err != nil {
-				shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
-				return false
+			minor := 0
+			if len(parts) == 2 {
+				minor, err = strconv.Atoi(parts[1])
+				if err != nil {
+					shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
+					return false
+				}
 			}
 
-			return major >= 1 && minor >= 1
+			return major >= 1 && minor >= 2
 		}
 
 		aaStacking = canStack()

From e2578f59c3c4dfe92a4d056598fa609de7045410 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Sep 2016 16:06:53 -0400
Subject: [PATCH 0350/1193] apparmor: Block /sys/kernel when no stacking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index 1b1d78acb..802a789ca 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -338,6 +338,9 @@ func getAAProfileContent(c container) string {
 `
 			profile += fmt.Sprintf("  change_profile -> \":%s://*\",\n", AANamespace(c))
 		}
+	} else {
+		profile += "\n  ### Feature: apparmor stacking (not present)\n"
+		profile += "  deny /sys/k*{,/**} rwklx,\n"
 	}
 
 	if c.IsNesting() {

From 09e2ff024f17cdcfc18d8519ca9a837245439ddb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Sep 2016 17:34:36 -0400
Subject: [PATCH 0351/1193] snappy: Add /snap/bin to PATH if present
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_exec.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index c42ba965c..ec62d6e99 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -282,6 +282,14 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		}
 	}
 
+	_, ok := env["PATH"]
+	if !ok {
+		env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+		if shared.PathExists(fmt.Sprintf("%s/snap/bin", c.RootfsPath())) {
+			env["PATH"] = fmt.Sprintf("%s:/snap/bin", env["PATH"])
+		}
+	}
+
 	if post.WaitForWS {
 		ws := &execWs{}
 		ws.fds = map[int]string{}

From 08bae1b3e542c6e103c1d4fa56c38005d2bf5e8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Sep 2016 19:37:47 -0400
Subject: [PATCH 0352/1193] apparmor: Be less restrictive when unprivileged
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change allows:
 - Various remount of /
 - pivot_root inside the container
 - All bind mounts

This can be used to bypass a bunch of our apparmor restrictions and so
would be completely unacceptable for privileged containers. But since we
use apparmor mostly as an added safety net for unpriivleged containers,
this should be fine.

Those changes should help running snapd inside a container.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index 802a789ca..cd4ac6c49 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -250,6 +250,22 @@ const AA_PROFILE_NESTING = `
   mount options=bind /var/lib/lxd/shmounts/** -> /var/lib/lxd/**,
 `
 
+const AA_PROFILE_UNPRIVILEGED = `
+  pivot_root,
+
+  mount options=(rw,make-slave) -> **,
+  mount options=(rw,make-rslave) -> **,
+  mount options=(rw,make-shared) -> **,
+  mount options=(rw,make-rshared) -> **,
+  mount options=(rw,make-private) -> **,
+  mount options=(rw,make-rprivate) -> **,
+  mount options=(rw,make-unbindable) -> **,
+  mount options=(rw,make-runbindable) -> **,
+
+  mount options=(rw,bind),
+  mount options=(rw,rbind),
+`
+
 func mkApparmorName(name string) string {
 	if len(name)+7 >= 253 {
 		hash := sha256.New()
@@ -352,6 +368,12 @@ func getAAProfileContent(c container) string {
 		}
 	}
 
+	if !c.IsPrivileged() {
+		// Apply unprivileged bits
+		profile += "\n  ### Configuration: unprivileged containers\n"
+		profile += strings.TrimLeft(AA_PROFILE_UNPRIVILEGED, "\n")
+	}
+
 	// Append raw.apparmor
 	rawApparmor, ok := c.ExpandedConfig()["raw.apparmor"]
 	if ok {

From 9a15389d21d2c7a42796dc37723bee3259d1e894 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 28 Sep 2016 01:27:52 -0400
Subject: [PATCH 0353/1193] Makefile: Don't recursively include test deps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We only want LXD's own test dependencies, not the ones for every single
package we use.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/Makefile b/Makefile
index 487f51209..5d0d2c5c2 100644
--- a/Makefile
+++ b/Makefile
@@ -55,18 +55,20 @@ gccgo:
 
 .PHONY: dist
 dist:
+	$(eval TMP := $(shell mktemp -d))
 	rm -Rf lxd-$(VERSION) $(ARCHIVE) $(ARCHIVE).gz
-	mkdir -p lxd-$(VERSION)/dist
-	-GOPATH=$(shell pwd)/lxd-$(VERSION)/dist go get -t -v -d ./...
-	-GOPATH=$(shell pwd)/lxd-$(VERSION)/dist go get -t -v -d ./...
-	-GOPATH=$(shell pwd)/lxd-$(VERSION)/dist go get -t -v -d ./...
-	GOPATH=$(shell pwd)/lxd-$(VERSION)/dist go get -t -v -d ./...
-	rm -rf $(shell pwd)/lxd-$(VERSION)/dist/src/github.com/lxc/lxd
-	ln -s ../../../.. ./lxd-$(VERSION)/dist/src/github.com/lxc/lxd
+	mkdir -p lxd-$(VERSION)/
+	-GOPATH=$(TMP) go get -t -v -d ./...
+	-GOPATH=$(TMP) go get -t -v -d ./...
+	-GOPATH=$(TMP) go get -t -v -d ./...
+	GOPATH=$(TMP) go get -t -v -d ./...
+	rm -rf $(TMP)/src/github.com/lxc/lxd
+	ln -s ../../../.. $(TMP)/src/github.com/lxc/lxd
+	mv $(TMP)/ lxd-$(VERSION)/dist
 	git archive --prefix=lxd-$(VERSION)/ --output=$(ARCHIVE) HEAD
 	tar -uf $(ARCHIVE) --exclude-vcs lxd-$(VERSION)/
 	gzip -9 $(ARCHIVE)
-	rm -Rf dist lxd-$(VERSION) $(ARCHIVE)
+	rm -Rf lxd-$(VERSION) $(ARCHIVE)
 
 .PHONY: i18n update-po update-pot build-mo static-analysis
 i18n: update-pot

From 4a007b7c83aa40caa4d3693b034ce2467cdffa03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 29 Sep 2016 00:32:34 -0400
Subject: [PATCH 0354/1193] test: Force UTC timezone
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/test/main.sh b/test/main.sh
index 888c610f2..487a15c9b 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -4,6 +4,9 @@
 # Don't translate lxc output for parsing in it in tests.
 export "LC_ALL=C"
 
+# Force UTC for consistency
+export "TZ=UTC"
+
 if [ -n "${LXD_DEBUG:-}" ]; then
   set -x
   DEBUG="--debug"

From c8d72872b7b1d625be351fa76201584db0f9a318 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 29 Sep 2016 00:37:14 -0400
Subject: [PATCH 0355/1193] test: Fix apparmor version check
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/basic.sh | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 352153655..8030af167 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -279,7 +279,15 @@ test_basic_usage() {
   # check that an apparmor profile is created for this container, that it is
   # unloaded on stop, and that it is deleted when the container is deleted
   lxc launch testimage lxd-apparmor-test
+
+  MAJOR=0
+  MINOR=0
   if [ -f /sys/kernel/security/apparmor/features/domain/version ]; then
+    MAJOR=$(awk -F. '{print $1}' < /sys/kernel/security/apparmor/features/domain/version)
+    MINOR=$(awk -F. '{print $2}' < /sys/kernel/security/apparmor/features/domain/version)
+  fi
+
+  if [ "${MAJOR}" -gt "1" ] || ([ "${MAJOR}" = "1" ] && [ "${MINOR}" -ge "2" ]); then
     aa_namespace="lxd-lxd-apparmor-test_<$(echo "${LXD_DIR}" | sed -e 's/\//-/g' -e 's/^.//')>"
     aa-status | grep ":${aa_namespace}://unconfined"
     lxc stop lxd-apparmor-test --force

From 227726a90d63cab2349aee23291e260344032220 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Oct 2016 01:20:05 -0400
Subject: [PATCH 0356/1193] Add AppVeyor badge (Windows testing)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 44d09f188..fd9476fbd 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ To easily see what LXD is about, you can [try it online](https://linuxcontainers
 ## CI status
 
  * Travis: [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd)
+ * AppVeyor: [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd)
  * Jenkins: [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/)
 
 ## Getting started with LXD

From cf4577fa6414e630d3a687a4a2395a0f42abd779 Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Sat, 1 Oct 2016 08:25:24 +0300
Subject: [PATCH 0357/1193] Add GetOwner stub for Windows (fixes #2438)

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 shared/util_linux.go   |  7 +++++++
 shared/util_windows.go | 11 +++++++++++
 2 files changed, 18 insertions(+)
 create mode 100644 shared/util_windows.go

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 51e0e438b..1a5d94a5e 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -322,6 +322,13 @@ func GetFileStat(p string) (uid int, gid int, major int, minor int,
 	return
 }
 
+func GetOwner(fInfo os.FileInfo) (os.FileMode, int, int) {
+	mode := fInfo.Mode()
+	uid := int(fInfo.Sys().(*syscall.Stat_t).Uid)
+	gid := int(fInfo.Sys().(*syscall.Stat_t).Gid)
+	return mode, uid, gid
+}
+
 func IsMountPoint(name string) bool {
 	_, err := exec.LookPath("mountpoint")
 	if err == nil {
diff --git a/shared/util_windows.go b/shared/util_windows.go
new file mode 100644
index 000000000..b123c889d
--- /dev/null
+++ b/shared/util_windows.go
@@ -0,0 +1,11 @@
+// +build windows
+
+package shared
+
+import (
+	"os"
+)
+
+func GetOwner(fInfo os.FileInfo) (os.FileMode, int, int) {
+	return os.FileMode(0), -1, -1
+}

From e6059e9cc9f678a95fd99af37639280d3e81de82 Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Sat, 1 Oct 2016 08:42:25 +0300
Subject: [PATCH 0358/1193] OS X compatibility per review comments

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 shared/util_linux.go |  7 -------
 shared/util_unix.go  | 15 +++++++++++++++
 2 files changed, 15 insertions(+), 7 deletions(-)
 create mode 100644 shared/util_unix.go

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 1a5d94a5e..51e0e438b 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -322,13 +322,6 @@ func GetFileStat(p string) (uid int, gid int, major int, minor int,
 	return
 }
 
-func GetOwner(fInfo os.FileInfo) (os.FileMode, int, int) {
-	mode := fInfo.Mode()
-	uid := int(fInfo.Sys().(*syscall.Stat_t).Uid)
-	gid := int(fInfo.Sys().(*syscall.Stat_t).Gid)
-	return mode, uid, gid
-}
-
 func IsMountPoint(name string) bool {
 	_, err := exec.LookPath("mountpoint")
 	if err == nil {
diff --git a/shared/util_unix.go b/shared/util_unix.go
new file mode 100644
index 000000000..82bab21ac
--- /dev/null
+++ b/shared/util_unix.go
@@ -0,0 +1,15 @@
+// +build !windows
+
+package shared
+
+import (
+	"os"
+	"syscall"
+)
+
+func GetOwner(fInfo os.FileInfo) (os.FileMode, int, int) {
+	mode := fInfo.Mode()
+	uid := int(fInfo.Sys().(*syscall.Stat_t).Uid)
+	gid := int(fInfo.Sys().(*syscall.Stat_t).Gid)
+	return mode, uid, gid
+}

From df71b2df5927736e93e0dcb4ea30f9681dda7792 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Oct 2016 13:45:53 -0400
Subject: [PATCH 0359/1193] Fix build failure on Linux
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/util_unix.go    | 2 +-
 shared/util_windows.go | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/shared/util_unix.go b/shared/util_unix.go
index 82bab21ac..f87a10933 100644
--- a/shared/util_unix.go
+++ b/shared/util_unix.go
@@ -7,7 +7,7 @@ import (
 	"syscall"
 )
 
-func GetOwner(fInfo os.FileInfo) (os.FileMode, int, int) {
+func GetOwnerMode(fInfo os.FileInfo) (os.FileMode, int, int) {
 	mode := fInfo.Mode()
 	uid := int(fInfo.Sys().(*syscall.Stat_t).Uid)
 	gid := int(fInfo.Sys().(*syscall.Stat_t).Gid)
diff --git a/shared/util_windows.go b/shared/util_windows.go
index b123c889d..7f91ba0fd 100644
--- a/shared/util_windows.go
+++ b/shared/util_windows.go
@@ -6,6 +6,6 @@ import (
 	"os"
 )
 
-func GetOwner(fInfo os.FileInfo) (os.FileMode, int, int) {
+func GetOwnerMode(fInfo os.FileInfo) (os.FileMode, int, int) {
 	return os.FileMode(0), -1, -1
 }

From 26e7f4835685637d7f877f90011e11769737b6af Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Sun, 2 Oct 2016 20:33:38 +0300
Subject: [PATCH 0360/1193] Generate client certificate with proper extended
 usage info

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 lxc/main.go         |  2 +-
 lxd/daemon.go       |  2 +-
 shared/cert.go      | 21 +++++++++++++--------
 shared/cert_test.go |  2 +-
 4 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index 0b9fd572b..d2313ce95 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -123,7 +123,7 @@ func run() error {
 	if !*forceLocal && os.Args[0] != "help" && os.Args[0] != "version" && (!shared.PathExists(certf) || !shared.PathExists(keyf)) {
 		fmt.Fprintf(os.Stderr, i18n.G("Generating a client certificate. This may take a minute...")+"\n")
 
-		err = shared.FindOrGenCert(certf, keyf)
+		err = shared.FindOrGenCert(certf, keyf, true)
 		if err != nil {
 			return err
 		}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index a83afcf46..0388b28ed 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -218,7 +218,7 @@ func readMyCert() (string, string, error) {
 	certf := shared.VarPath("server.crt")
 	keyf := shared.VarPath("server.key")
 	shared.LogDebug("Looking for existing certificates", log.Ctx{"cert": certf, "key": keyf})
-	err := shared.FindOrGenCert(certf, keyf)
+	err := shared.FindOrGenCert(certf, keyf, false)
 
 	return certf, keyf, err
 }
diff --git a/shared/cert.go b/shared/cert.go
index f28c016da..d0c3911c3 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -64,14 +64,14 @@ func mynames() ([]string, error) {
 	return ret, nil
 }
 
-func FindOrGenCert(certf string, keyf string) error {
+func FindOrGenCert(certf string, keyf string, certtype bool) error {
 	if PathExists(certf) && PathExists(keyf) {
 		return nil
 	}
 
 	/* If neither stat succeeded, then this is our first run and we
 	 * need to generate cert and privkey */
-	err := GenCert(certf, keyf)
+	err := GenCert(certf, keyf, certtype)
 	if err != nil {
 		return err
 	}
@@ -80,7 +80,7 @@ func FindOrGenCert(certf string, keyf string) error {
 }
 
 // GenCert will create and populate a certificate file and a key file
-func GenCert(certf string, keyf string) error {
+func GenCert(certf string, keyf string, certtype bool) error {
 	/* Create the basenames if needed */
 	dir := path.Dir(certf)
 	err := os.MkdirAll(dir, 0750)
@@ -93,7 +93,7 @@ func GenCert(certf string, keyf string) error {
 		return err
 	}
 
-	certBytes, keyBytes, err := GenerateMemCert()
+	certBytes, keyBytes, err := GenerateMemCert(certtype)
 	if err != nil {
 		return err
 	}
@@ -116,9 +116,9 @@ func GenCert(certf string, keyf string) error {
 	return nil
 }
 
-// GenerateMemCert creates a certificate and key pair, returning them as byte
-// arrays in memory.
-func GenerateMemCert() ([]byte, []byte, error) {
+// GenerateMemCert creates client or server certificate and key pair,
+// returning them as byte arrays in memory.
+func GenerateMemCert(client bool) ([]byte, []byte, error) {
 	privk, err := rsa.GenerateKey(rand.Reader, 4096)
 	if err != nil {
 		log.Fatalf("failed to generate key")
@@ -167,10 +167,15 @@ func GenerateMemCert() ([]byte, []byte, error) {
 		NotAfter:  validTo,
 
 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
-		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
 		BasicConstraintsValid: true,
 	}
 
+	if client {
+		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
+	} else {
+		template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
+	}
+
 	for _, h := range hosts {
 		if ip := net.ParseIP(h); ip != nil {
 			template.IPAddresses = append(template.IPAddresses, ip)
diff --git a/shared/cert_test.go b/shared/cert_test.go
index 6093f9b5c..60370b688 100644
--- a/shared/cert_test.go
+++ b/shared/cert_test.go
@@ -9,7 +9,7 @@ func TestGenerateMemCert(t *testing.T) {
 	if testing.Short() {
 		t.Skip("skipping cert generation in short mode")
 	}
-	cert, key, err := GenerateMemCert()
+	cert, key, err := GenerateMemCert(false)
 	if err != nil {
 		t.Error(err)
 		return

From b7e4fd70c51fda850b4ec2d05b8baec330c54762 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 29 Sep 2016 17:21:48 +0200
Subject: [PATCH 0361/1193] lxd/containers_post: prepare for push support

Various parts of the code will move into the migration part.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/containers_post.go | 143 +++++++++++++++++++++----------------------------
 1 file changed, 60 insertions(+), 83 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 0556afbc8..46bdb8ebb 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -210,106 +210,83 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 		architecture = 0
 	}
 
-	run := func(op *operation) error {
-		args := containerArgs{
-			Architecture: architecture,
-			BaseImage:    req.Source.BaseImage,
-			Config:       req.Config,
-			Ctype:        cTypeRegular,
-			Devices:      req.Devices,
-			Ephemeral:    req.Ephemeral,
-			Name:         req.Name,
-			Profiles:     req.Profiles,
-		}
-
-		var c container
-		_, _, err := dbImageGet(d.db, req.Source.BaseImage, false, true)
-
-		/* Only create a container from an image if we're going to
-		 * rsync over the top of it. In the case of a better file
-		 * transfer mechanism, let's just use that.
-		 *
-		 * TODO: we could invent some negotiation here, where if the
-		 * source and sink both have the same image, we can clone from
-		 * it, but we have to know before sending the snapshot that
-		 * we're sending the whole thing or just a delta from the
-		 * image, so one extra negotiation round trip is needed. An
-		 * alternative is to move actual container object to a later
-		 * point and just negotiate it over the migration control
-		 * socket. Anyway, it'll happen later :)
-		 */
-		if err == nil && d.Storage.MigrationType() == MigrationFSType_RSYNC {
-			c, err = containerCreateFromImage(d, args, req.Source.BaseImage)
-			if err != nil {
-				return err
-			}
-		} else {
-			c, err = containerCreateAsEmpty(d, args)
-			if err != nil {
-				return err
-			}
-		}
-
-		var cert *x509.Certificate
-		if req.Source.Certificate != "" {
-			certBlock, _ := pem.Decode([]byte(req.Source.Certificate))
-			if certBlock == nil {
-				return fmt.Errorf("Invalid certificate")
-			}
-
-			cert, err = x509.ParseCertificate(certBlock.Bytes)
-			if err != nil {
-				return err
-			}
-		}
+	args := containerArgs{
+		Architecture: architecture,
+		BaseImage:    req.Source.BaseImage,
+		Config:       req.Config,
+		Ctype:        cTypeRegular,
+		Devices:      req.Devices,
+		Ephemeral:    req.Ephemeral,
+		Name:         req.Name,
+		Profiles:     req.Profiles,
+	}
 
-		config, err := shared.GetTLSConfig("", "", cert)
+	var c container
+	_, _, err = dbImageGet(d.db, req.Source.BaseImage, false, true)
+
+	/* Only create a container from an image if we're going to
+	 * rsync over the top of it. In the case of a better file
+	 * transfer mechanism, let's just use that.
+	 *
+	 * TODO: we could invent some negotiation here, where if the
+	 * source and sink both have the same image, we can clone from
+	 * it, but we have to know before sending the snapshot that
+	 * we're sending the whole thing or just a delta from the
+	 * image, so one extra negotiation round trip is needed. An
+	 * alternative is to move actual container object to a later
+	 * point and just negotiate it over the migration control
+	 * socket. Anyway, it'll happen later :)
+	 */
+	if err == nil && d.Storage.MigrationType() == MigrationFSType_RSYNC {
+		c, err = containerCreateFromImage(d, args, req.Source.BaseImage)
 		if err != nil {
-			c.Delete()
-			return err
-		}
-
-		migrationArgs := MigrationSinkArgs{
-			Url: req.Source.Operation,
-			Dialer: websocket.Dialer{
-				TLSClientConfig: config,
-				NetDial:         shared.RFC3493Dialer},
-			Container: c,
-			Secrets:   req.Source.Websockets,
+			return InternalError(err)
 		}
-
-		sink, err := NewMigrationSink(&migrationArgs)
+	} else {
+		c, err = containerCreateAsEmpty(d, args)
 		if err != nil {
-			c.Delete()
-			return err
+			return InternalError(err)
 		}
+	}
 
-		// Start the storage for this container (LVM mount/umount)
-		c.StorageStart()
+	var cert *x509.Certificate
+	if req.Source.Certificate != "" {
+		certBlock, _ := pem.Decode([]byte(req.Source.Certificate))
+		if certBlock == nil {
+			return InternalError(fmt.Errorf("Invalid certificate"))
+		}
 
-		// And finaly run the migration.
-		err = sink()
+		cert, err = x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
-			c.StorageStop()
-			shared.LogError("Error during migration sink", log.Ctx{"err": err})
-			c.Delete()
-			return fmt.Errorf("Error transferring container data: %s", err)
+			return InternalError(err)
 		}
+	}
 
-		defer c.StorageStop()
+	config, err := shared.GetTLSConfig("", "", cert)
+	if err != nil {
+		c.Delete()
+		return InternalError(err)
+	}
 
-		err = c.TemplateApply("copy")
-		if err != nil {
-			return err
-		}
+	migrationArgs := MigrationSinkArgs{
+		Url: req.Source.Operation,
+		Dialer: websocket.Dialer{
+			TLSClientConfig: config,
+			NetDial:         shared.RFC3493Dialer},
+		Container: c,
+		Secrets:   req.Source.Websockets,
+	}
 
-		return nil
+	sink, err := NewMigrationSink(&migrationArgs)
+	if err != nil {
+		c.Delete()
+		return InternalError(err)
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name}
 
-	op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil)
+	op, err := operationCreate(operationClassTask, resources, nil, sink.Do, nil, nil)
 	if err != nil {
 		return InternalError(err)
 	}

From e8fee20ba4aae0a0de8271f9398de151a476b597 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 29 Sep 2016 17:29:22 +0200
Subject: [PATCH 0362/1193] lxd/migrate: prepare push mode

- The code pieces that have been removed from
  lxd/containers_post.go:containerCreateFromMigration() move here.
- Export Do() method for NewMigrationSink().

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/migrate.go | 90 ++++++++++++++++++++++++++++++++++------------------------
 1 file changed, 53 insertions(+), 37 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index b9337fa2f..d083ae3e6 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -512,7 +512,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 }
 
 type migrationSink struct {
-	migrationFields
+	// We are pulling the container from src in pull mode.
+	src migrationFields
 
 	url    string
 	dialer websocket.Dialer
@@ -525,32 +526,32 @@ type MigrationSinkArgs struct {
 	Secrets   map[string]string
 }
 
-func NewMigrationSink(args *MigrationSinkArgs) (func() error, error) {
+func NewMigrationSink(args *MigrationSinkArgs) (*migrationSink, error) {
 	sink := migrationSink{
-		migrationFields{container: args.Container},
-		args.Url,
-		args.Dialer,
+		src:    migrationFields{container: args.Container},
+		url:    args.Url,
+		dialer: args.Dialer,
 	}
 
 	var ok bool
-	sink.controlSecret, ok = args.Secrets["control"]
+	sink.src.controlSecret, ok = args.Secrets["control"]
 	if !ok {
 		return nil, fmt.Errorf("Missing control secret")
 	}
 
-	sink.fsSecret, ok = args.Secrets["fs"]
+	sink.src.fsSecret, ok = args.Secrets["fs"]
 	if !ok {
 		return nil, fmt.Errorf("Missing fs secret")
 	}
 
-	sink.criuSecret, ok = args.Secrets["criu"]
-	sink.live = ok
+	sink.src.criuSecret, ok = args.Secrets["criu"]
+	sink.src.live = ok
 
-	if err := findCriu("destination"); sink.live && err != nil {
+	if err := findCriu("destination"); sink.src.live && err != nil {
 		return nil, err
 	}
 
-	return sink.do, nil
+	return &sink, nil
 }
 
 func (c *migrationSink) connectWithSecret(secret string) (*websocket.Conn, error) {
@@ -562,41 +563,49 @@ func (c *migrationSink) connectWithSecret(secret string) (*websocket.Conn, error
 	return lxd.WebsocketDial(c.dialer, wsUrl)
 }
 
-func (c *migrationSink) do() error {
+func (c *migrationSink) Do(migrateOp *operation) error {
 	var err error
-	c.controlConn, err = c.connectWithSecret(c.controlSecret)
+
+	// Start the storage for this container (LVM mount/umount)
+	c.src.container.StorageStart()
+
+	c.src.controlConn, err = c.connectWithSecret(c.src.controlSecret)
 	if err != nil {
+		c.src.container.StorageStop()
 		return err
 	}
-	defer c.disconnect()
+	defer c.src.disconnect()
 
-	c.fsConn, err = c.connectWithSecret(c.fsSecret)
+	c.src.fsConn, err = c.connectWithSecret(c.src.fsSecret)
 	if err != nil {
-		c.sendControl(err)
+		c.src.container.StorageStop()
+		c.src.sendControl(err)
 		return err
 	}
 
-	if c.live {
-		c.criuConn, err = c.connectWithSecret(c.criuSecret)
+	if c.src.live {
+		c.src.criuConn, err = c.connectWithSecret(c.src.criuSecret)
 		if err != nil {
-			c.sendControl(err)
+			c.src.container.StorageStop()
+			c.src.sendControl(err)
 			return err
 		}
 	}
 
 	header := MigrationHeader{}
-	if err := c.recv(&header); err != nil {
-		c.sendControl(err)
+	if err := c.src.recv(&header); err != nil {
+		c.src.container.StorageStop()
+		c.src.sendControl(err)
 		return err
 	}
 
 	criuType := CRIUType_CRIU_RSYNC.Enum()
-	if !c.live {
+	if !c.src.live {
 		criuType = nil
 	}
 
-	mySink := c.container.Storage().MigrationSink
-	myType := c.container.Storage().MigrationType()
+	mySink := c.src.container.Storage().MigrationSink
+	myType := c.src.container.Storage().MigrationType()
 	resp := MigrationHeader{
 		Fs:   &myType,
 		Criu: criuType,
@@ -610,8 +619,9 @@ func (c *migrationSink) do() error {
 		resp.Fs = &myType
 	}
 
-	if err := c.send(&resp); err != nil {
-		c.sendControl(err)
+	if err := c.src.send(&resp); err != nil {
+		c.src.container.StorageStop()
+		c.src.sendControl(err)
 		return err
 	}
 
@@ -646,7 +656,7 @@ func (c *migrationSink) do() error {
 			 */
 			if len(header.SnapshotNames) != len(header.Snapshots) {
 				for _, name := range header.SnapshotNames {
-					base := snapshotToProtobuf(c.container)
+					base := snapshotToProtobuf(c.src.container)
 					base.Name = &name
 					snapshots = append(snapshots, base)
 				}
@@ -654,12 +664,12 @@ func (c *migrationSink) do() error {
 				snapshots = header.Snapshots
 			}
 
-			if err := mySink(c.live, c.container, header.Snapshots, c.fsConn, srcIdmap); err != nil {
+			if err := mySink(c.src.live, c.src.container, header.Snapshots, c.src.fsConn, srcIdmap); err != nil {
 				fsTransfer <- err
 				return
 			}
 
-			if err := ShiftIfNecessary(c.container, srcIdmap); err != nil {
+			if err := ShiftIfNecessary(c.src.container, srcIdmap); err != nil {
 				fsTransfer <- err
 				return
 			}
@@ -667,7 +677,7 @@ func (c *migrationSink) do() error {
 			fsTransfer <- nil
 		}()
 
-		if c.live {
+		if c.src.live {
 			var err error
 			imagesDir, err = ioutil.TempDir("", "lxd_restore_")
 			if err != nil {
@@ -677,7 +687,7 @@ func (c *migrationSink) do() error {
 
 			defer os.RemoveAll(imagesDir)
 
-			if err := RsyncRecv(shared.AddSlash(imagesDir), c.criuConn); err != nil {
+			if err := RsyncRecv(shared.AddSlash(imagesDir), c.src.criuConn); err != nil {
 				restore <- err
 				return
 			}
@@ -689,8 +699,8 @@ func (c *migrationSink) do() error {
 			return
 		}
 
-		if c.live {
-			err = c.container.Migrate(lxc.MIGRATE_RESTORE, imagesDir, "migration", false, false)
+		if c.src.live {
+			err = c.src.container.Migrate(lxc.MIGRATE_RESTORE, imagesDir, "migration", false, false)
 			if err != nil {
 				restore <- err
 				return
@@ -701,26 +711,32 @@ func (c *migrationSink) do() error {
 		restore <- nil
 	}(c)
 
-	source := c.controlChannel()
+	source := c.src.controlChannel()
+
+	defer c.src.container.StorageStop()
 
 	for {
 		select {
 		case err = <-restore:
-			c.sendControl(err)
+			c.src.sendControl(err)
 			return err
 		case msg, ok := <-source:
 			if !ok {
-				c.disconnect()
+				c.src.disconnect()
 				return fmt.Errorf("Got error reading source")
 			}
 			if !*msg.Success {
-				c.disconnect()
+				c.src.disconnect()
 				return fmt.Errorf(*msg.Message)
 			} else {
 				// The source can only tell us it failed (e.g. if
 				// checkpointing failed). We have to tell the source
 				// whether or not the restore was successful.
 				shared.LogDebugf("Unknown message %v from source", msg)
+				err = c.src.container.TemplateApply("copy")
+				if err != nil {
+					return err
+				}
 			}
 		}
 	}

From 2a24b813d39d713a69cb00f24828d25d6c239e43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Oct 2016 11:18:55 +0200
Subject: [PATCH 0363/1193] zfs: Extra checks and config for ZFS pools
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

LXD assumes that it completely owns whatever storage.zfs_pool_name
points to, so lets make sure it's always empty. Then to avoid potential
problems with the mounts LXD will make, set mountpoint=none.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_zfs.go | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 6700d35c7..b53d2e82b 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -74,6 +74,18 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 		return s, fmt.Errorf("The 'zfs' tool isn't working properly")
 	}
 
+	output, err = exec.Command("zfs", "get", "mountpoint", "-H", "-o", "source", s.zfsPool).CombinedOutput()
+	if err != nil {
+		return s, fmt.Errorf("Unable to query ZFS mountpoint")
+	}
+
+	if strings.TrimSpace(string(output)) != "local" {
+		err = shared.RunCommand("zfs", "set", "mountpoint=none", s.zfsPool)
+		if err != nil {
+			return s, err
+		}
+	}
+
 	return s, nil
 }
 
@@ -1160,6 +1172,17 @@ func storageZFSValidatePoolName(d *Daemon, key string, value string) error {
 		}
 	}
 
+	// Confirm that the new pool is empty
+	s.zfsPool = value
+	subvols, err := s.zfsListSubvolumes("")
+	if err != nil {
+		return err
+	}
+
+	if len(subvols) > 0 {
+		return fmt.Errorf("Provided ZFS pool (or dataset) isn't empty")
+	}
+
 	// Confirm the old pool isn't in use anymore
 	oldPoolname := daemonConfig["storage.zfs_pool_name"].Get()
 	if oldPoolname != "" {

From 3280b15fa683553247dd275f45a312c1491f105c Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 4 Oct 2016 16:35:42 +0000
Subject: [PATCH 0364/1193] migrate: don't use ActionScript if it's not
 available

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go | 172 +++++++++++++++++++++++++++++----------------------------
 1 file changed, 88 insertions(+), 84 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index d083ae3e6..32177b565 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -386,98 +386,102 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 			return abort(fmt.Errorf("Formats other than criu rsync not understood"))
 		}
 
-		/* What happens below is slightly convoluted. Due to various
-		 * complications with networking, there's no easy way for criu
-		 * to exit and leave the container in a frozen state for us to
-		 * somehow resume later.
-		 *
-		 * Instead, we use what criu calls an "action-script", which is
-		 * basically a callback that lets us know when the dump is
-		 * done. (Unfortunately, we can't pass arguments, just an
-		 * executable path, so we write a custom action script with the
-		 * real command we want to run.)
-		 *
-		 * This script then hangs until the migration operation either
-		 * finishes successfully or fails, and exits 1 or 0, which
-		 * causes criu to either leave the container running or kill it
-		 * as we asked.
-		 */
-		dumpDone := make(chan bool, 1)
-		actionScriptOpSecret, err := shared.RandomCryptoString()
-		if err != nil {
-			return abort(err)
-		}
-
-		actionScriptOp, err := operationCreate(
-			operationClassWebsocket,
-			nil,
-			nil,
-			func(op *operation) error {
-				_, err := migrateOp.WaitFinal(-1)
-				if err != nil {
-					return err
-				}
-
-				if migrateOp.status != shared.Success {
-					return fmt.Errorf("restore failed: %s", op.status.String())
-				}
-				return nil
-			},
-			nil,
-			func(op *operation, r *http.Request, w http.ResponseWriter) error {
-				secret := r.FormValue("secret")
-				if secret == "" {
-					return fmt.Errorf("missing secret")
-				}
-
-				if secret != actionScriptOpSecret {
-					return os.ErrPermission
-				}
-
-				c, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
-				if err != nil {
-					return err
-				}
-
-				dumpDone <- true
-
-				closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
-				return c.WriteMessage(websocket.CloseMessage, closeMsg)
-			},
-		)
-		if err != nil {
-			return abort(err)
-		}
-
 		checkpointDir, err := ioutil.TempDir("", "lxd_checkpoint_")
 		if err != nil {
 			return abort(err)
 		}
+		defer os.RemoveAll(checkpointDir)
+
+		if lxc.VersionAtLeast(2, 0, 4) {
+			/* What happens below is slightly convoluted. Due to various
+			 * complications with networking, there's no easy way for criu
+			 * to exit and leave the container in a frozen state for us to
+			 * somehow resume later.
+			 *
+			 * Instead, we use what criu calls an "action-script", which is
+			 * basically a callback that lets us know when the dump is
+			 * done. (Unfortunately, we can't pass arguments, just an
+			 * executable path, so we write a custom action script with the
+			 * real command we want to run.)
+			 *
+			 * This script then hangs until the migration operation either
+			 * finishes successfully or fails, and exits 1 or 0, which
+			 * causes criu to either leave the container running or kill it
+			 * as we asked.
+			 */
+			dumpDone := make(chan bool, 1)
+			actionScriptOpSecret, err := shared.RandomCryptoString()
+			if err != nil {
+				return abort(err)
+			}
 
-		if err := writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret); err != nil {
-			os.RemoveAll(checkpointDir)
-			return abort(err)
-		}
+			actionScriptOp, err := operationCreate(
+				operationClassWebsocket,
+				nil,
+				nil,
+				func(op *operation) error {
+					_, err := migrateOp.WaitFinal(-1)
+					if err != nil {
+						return err
+					}
+
+					if migrateOp.status != shared.Success {
+						return fmt.Errorf("restore failed: %s", op.status.String())
+					}
+					return nil
+				},
+				nil,
+				func(op *operation, r *http.Request, w http.ResponseWriter) error {
+					secret := r.FormValue("secret")
+					if secret == "" {
+						return fmt.Errorf("missing secret")
+					}
+
+					if secret != actionScriptOpSecret {
+						return os.ErrPermission
+					}
+
+					c, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
+					if err != nil {
+						return err
+					}
+
+					dumpDone <- true
+
+					closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
+					return c.WriteMessage(websocket.CloseMessage, closeMsg)
+				},
+			)
+			if err != nil {
+				return abort(err)
+			}
 
-		_, err = actionScriptOp.Run()
-		if err != nil {
-			os.RemoveAll(checkpointDir)
-			return abort(err)
-		}
+			if err := writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret); err != nil {
+				return abort(err)
+			}
 
-		migrateDone := make(chan error, 1)
-		go func() {
-			defer os.RemoveAll(checkpointDir)
-			migrateDone <- s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, true)
-		}()
+			_, err = actionScriptOp.Run()
+			if err != nil {
+				return abort(err)
+			}
 
-		select {
-		/* the checkpoint failed, let's just abort */
-		case err = <-migrateDone:
-			return abort(err)
-		/* the dump finished, let's continue on to the restore */
-		case <-dumpDone:
-			shared.LogDebugf("Dump finished, continuing with restore...")
+			migrateDone := make(chan error, 1)
+			go func() {
+				migrateDone <- s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, true)
+			}()
+
+			select {
+			/* the checkpoint failed, let's just abort */
+			case err = <-migrateDone:
+				return abort(err)
+			/* the dump finished, let's continue on to the restore */
+			case <-dumpDone:
+				shared.LogDebugf("Dump finished, continuing with restore...")
+			}
+		} else {
+			if err := s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, false); err != nil {
+				return abort(err)
+			}
 		}
 
 		/*

From 7cb383e1838b31d9c7cfd135f941f07652e06ed7 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 4 Oct 2016 13:42:08 -0600
Subject: [PATCH 0365/1193] remove logging import

downstream users of shared/ (i.e. juju) don't want to have a log15
dependency.

log.Ctx() is defined as a map[string]interface{}, but the log15 package
does some fancy stuff to normalize it when it gets passed, which is why we
needed to wrap it in a log.Ctx() again. Instead, let's just not unwrap the
type in the first place, which means log15's machinery will see the log.Ctx
users passed us and behave correctly.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/log.go | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/shared/log.go b/shared/log.go
index bfaa8f01d..792ccf685 100644
--- a/shared/log.go
+++ b/shared/log.go
@@ -2,7 +2,6 @@ package shared
 
 import (
 	"fmt"
-	log "gopkg.in/inconshreveable/log15.v2"
 	"runtime"
 )
 
@@ -29,33 +28,33 @@ func init() {
 }
 
 // General wrappers around Logger interface functions.
-func LogDebug(msg string, ctx map[string]interface{}) {
+func LogDebug(msg string, ctx interface{}) {
 	if Log != nil {
-		Log.Debug(msg, log.Ctx(ctx))
+		Log.Debug(msg, ctx)
 	}
 }
 
-func LogInfo(msg string, ctx map[string]interface{}) {
+func LogInfo(msg string, ctx interface{}) {
 	if Log != nil {
-		Log.Info(msg, log.Ctx(ctx))
+		Log.Info(msg, ctx)
 	}
 }
 
-func LogWarn(msg string, ctx map[string]interface{}) {
+func LogWarn(msg string, ctx interface{}) {
 	if Log != nil {
-		Log.Warn(msg, log.Ctx(ctx))
+		Log.Warn(msg, ctx)
 	}
 }
 
-func LogError(msg string, ctx map[string]interface{}) {
+func LogError(msg string, ctx interface{}) {
 	if Log != nil {
-		Log.Error(msg, log.Ctx(ctx))
+		Log.Error(msg, ctx)
 	}
 }
 
-func LogCrit(msg string, ctx map[string]interface{}) {
+func LogCrit(msg string, ctx interface{}) {
 	if Log != nil {
-		Log.Crit(msg, log.Ctx(ctx))
+		Log.Crit(msg, ctx)
 	}
 }
 

From 36c8891b668c60a8f84465f45d59bb80ba559be0 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 4 Oct 2016 15:04:04 -0600
Subject: [PATCH 0366/1193] tests: add a test to make sure we don't
 accidentally include new deps

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 Makefile                       | 1 +
 test/suites/static_analysis.sh | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/Makefile b/Makefile
index 5d0d2c5c2..bfb774df0 100644
--- a/Makefile
+++ b/Makefile
@@ -45,6 +45,7 @@ protobuf:
 
 .PHONY: check
 check: default
+	go get -v -x github.com/rogpeppe/godeps
 	go get -v -x github.com/remyoudompheng/go-misc/deadcode
 	go test -v ./...
 	cd test && ./main.sh
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 174f5fd29..b506a9ce4 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -47,6 +47,12 @@ test_static_analysis() {
       done
     fi
 
+    OUT=$(godeps . ./shared | cut -f1)
+    if [ "${OUT}" != "$(printf "github.com/gorilla/websocket\ngopkg.in/yaml.v2\n")" ]; then
+      echo "ERROR: you added a new dependency to the client or shared; please make sure this is what you want"
+      echo "${OUT}"
+    fi
+
     # Skip the tests which require git
     if ! git status; then
       return

From 4948f25e003f173c67682712f17df04bad88ab55 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 4 Oct 2016 23:40:37 -0600
Subject: [PATCH 0367/1193] make godeps check conditional on godeps existing

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 test/suites/static_analysis.sh | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index b506a9ce4..ae8785642 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -47,10 +47,12 @@ test_static_analysis() {
       done
     fi
 
-    OUT=$(godeps . ./shared | cut -f1)
-    if [ "${OUT}" != "$(printf "github.com/gorilla/websocket\ngopkg.in/yaml.v2\n")" ]; then
-      echo "ERROR: you added a new dependency to the client or shared; please make sure this is what you want"
-      echo "${OUT}"
+    if which godeps >/dev/null 2>&1; then
+      OUT=$(godeps . ./shared | cut -f1)
+      if [ "${OUT}" != "$(printf "github.com/gorilla/websocket\ngopkg.in/yaml.v2\n")" ]; then
+        echo "ERROR: you added a new dependency to the client or shared; please make sure this is what you want"
+        echo "${OUT}"
+      fi
     fi
 
     # Skip the tests which require git

From 2e8978bc9013688ac23aebdd1095f6b5fef2687f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 5 Oct 2016 11:50:03 +0200
Subject: [PATCH 0368/1193] Release LXD 2.0.5
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/flex.go b/shared/flex.go
index e61c8d027..a0f650933 100644
--- a/shared/flex.go
+++ b/shared/flex.go
@@ -3,7 +3,7 @@
  */
 package shared
 
-var Version = "2.0.4"
+var Version = "2.0.5"
 var UserAgent = "LXD " + Version
 
 /*

From a29f1297f43cc1ed881bc773e7cb8c989970b184 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Oct 2016 12:13:53 +0200
Subject: [PATCH 0369/1193] Be more verbose on mkdir failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/nsexec.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/nsexec.go b/lxd/nsexec.go
index 30e2d1ac7..ff553c47d 100644
--- a/lxd/nsexec.go
+++ b/lxd/nsexec.go
@@ -68,7 +68,7 @@ int mkdir_p(const char *dir, mode_t mode)
 		makeme = strndup(orig, dir - orig);
 		if (*makeme) {
 			if (mkdir(makeme, mode) && errno != EEXIST) {
-				fprintf(stderr, "failed to create directory '%s'", makeme);
+				fprintf(stderr, "failed to create directory '%s': %s\n", makeme, strerror(errno));
 				free(makeme);
 				return -1;
 			}

From f03348eb6a9341cadd583ff5bc0e1db453bebca8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Oct 2016 13:06:04 +0200
Subject: [PATCH 0370/1193] Fix forkmount to work with 4.8 and higher
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

A new restriction was placed in the 4.8 kernel that mkdir will return
EOVERFLOW if the resulting uid/gid is outside of the container's map.

This is a problem for us as we only attach to the mount namespace.

So to fix that, we must detect that the kernel supports userns and that
the container is in a userns, then attach.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/nsexec.go | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/lxd/nsexec.go b/lxd/nsexec.go
index ff553c47d..cd59d6fef 100644
--- a/lxd/nsexec.go
+++ b/lxd/nsexec.go
@@ -300,9 +300,43 @@ void create(char *src, char *dest) {
 void forkmount(char *buf, char *cur, ssize_t size) {
 	char *src, *dest, *opts;
 
+	char nspath[PATH_MAX];
+	char userns_source[PATH_MAX];
+	char userns_target[PATH_MAX];
+
 	ADVANCE_ARG_REQUIRED();
 	int pid = atoi(cur);
 
+	sprintf(nspath, "/proc/%d/ns/user", pid);
+	if (access(nspath, F_OK) == 0) {
+		if (readlink("/proc/self/ns/user", userns_source, 18) < 0) {
+			fprintf(stderr, "Failed readlink of source namespace: %s\n", strerror(errno));
+			_exit(1);
+		}
+
+		if (readlink(nspath, userns_target, PATH_MAX) < 0) {
+			fprintf(stderr, "Failed readlink of target namespace: %s\n", strerror(errno));
+			_exit(1);
+		}
+
+		if (strncmp(userns_source, userns_target, PATH_MAX) != 0) {
+			if (dosetns(pid, "user") < 0) {
+				fprintf(stderr, "Failed setns to container user namespace: %s\n", strerror(errno));
+				_exit(1);
+			}
+
+			if (setuid(0) < 0) {
+				fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
+				_exit(1);
+			}
+
+			if (setgid(0) < 0) {
+				fprintf(stderr, "Failed setgid to container root group: %s\n", strerror(errno));
+				_exit(1);
+			}
+		}
+	}
+
 	if (dosetns(pid, "mnt") < 0) {
 		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
 		_exit(1);

From e9bae4ba684f577e966f7a83d293e0c40109e274 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Thu, 6 Oct 2016 17:37:45 +0200
Subject: [PATCH 0371/1193] shared/cert: be more thorough when parsing ip addr

- check that IPv6 and IPv4 is no link-local address
- skip prefix length
- use IP for IP addresses

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 shared/cert.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/shared/cert.go b/shared/cert.go
index d0c3911c3..998f85e24 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -177,8 +177,10 @@ func GenerateMemCert(client bool) ([]byte, []byte, error) {
 	}
 
 	for _, h := range hosts {
-		if ip := net.ParseIP(h); ip != nil {
-			template.IPAddresses = append(template.IPAddresses, ip)
+		if ip, _, err := net.ParseCIDR(h); err == nil {
+			if !ip.IsLinkLocalUnicast() && !ip.IsLinkLocalMulticast() {
+				template.IPAddresses = append(template.IPAddresses, ip)
+			}
 		} else {
 			template.DNSNames = append(template.DNSNames, h)
 		}

From cee2f24a428597f62bbe13f7a25e4d1e4ca80a55 Mon Sep 17 00:00:00 2001
From: Nicolas Lisoski <nicolas at lnico.net>
Date: Fri, 7 Oct 2016 14:04:17 +0200
Subject: [PATCH 0372/1193] Allow dash in parameters to lxc-client
 bash-completion

Signed-off-by: Nicolas Lisoski <nicolas at lnico.net>
---
 config/bash/lxd-client | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/bash/lxd-client b/config/bash/lxd-client
index d1b028537..8bc2b5f35 100644
--- a/config/bash/lxd-client
+++ b/config/bash/lxd-client
@@ -69,7 +69,7 @@ _have lxc && {
     local no_dashargs
     cur=${COMP_WORDS[COMP_CWORD]}
 
-    no_dashargs=(${COMP_WORDS[@]//-*})
+    no_dashargs=(${COMP_WORDS[@]// -*})
     pos=$((COMP_CWORD - (${#COMP_WORDS[@]} - ${#no_dashargs[@]})))
     if [ -z "$cur" ]; then
       pos=$(($pos + 1))

From 22df0ac08770eb8915149d8bb8fed27830331928 Mon Sep 17 00:00:00 2001
From: Nicolas Lisoski <nicolas at lnico.net>
Date: Fri, 7 Oct 2016 14:05:08 +0200
Subject: [PATCH 0373/1193] Fix _lxd_profiles in lxc-client bash-completion

Signed-off-by: Nicolas Lisoski <nicolas at lnico.net>
---
 config/bash/lxd-client | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/bash/lxd-client b/config/bash/lxd-client
index 8bc2b5f35..e22d1a5d1 100644
--- a/config/bash/lxd-client
+++ b/config/bash/lxd-client
@@ -30,7 +30,7 @@ _have lxc && {
 
     _lxd_profiles()
     {
-      COMPREPLY=( $( compgen -W "$( lxc profile list )" "$cur" ) )
+      COMPREPLY=( $( compgen -W "$( lxc profile list | tail -n +4 | awk '{print $2}' | egrep -v '^(\||^$)' )" "$cur" ) )
     }
 
     COMPREPLY=()

From 15e436ce1254a89e27d60673fd25ebcfefc2c748 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 8 Oct 2016 21:26:17 -0400
Subject: [PATCH 0374/1193] init: Ignore ZFS if in a container
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/main.go b/lxd/main.go
index 2769c3df2..11e42fa1c 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -613,7 +613,7 @@ func cmdInit() error {
 
 	// Detect zfs
 	out, err := exec.LookPath("zfs")
-	if err == nil && len(out) != 0 {
+	if err == nil && len(out) != 0 && !runningInUserns {
 		backendsAvailable = append(backendsAvailable, "zfs")
 	}
 

From a3a6a5b52d88faf3bb6fe502fc4b78d903beda8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 9 Oct 2016 15:13:04 -0400
Subject: [PATCH 0375/1193] Fix invalid filename of metadata on export
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This only affects publishing of a container which lacks a metadata.yaml.

Closes #2467

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index e34f2218b..0892d09af 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2805,13 +2805,13 @@ func (c *containerLXC) Export(w io.Writer) error {
 	fnam := filepath.Join(cDir, "metadata.yaml")
 	if !shared.PathExists(fnam) {
 		// Generate a new metadata.yaml
-		f, err := ioutil.TempFile("", "lxd_lxd_metadata_")
+		tempDir, err := ioutil.TempDir("", "lxd_lxd_metadata_")
 		if err != nil {
 			tw.Close()
 			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
-		defer os.Remove(f.Name())
+		defer os.RemoveAll(tempDir)
 
 		// Get the container's architecture
 		var arch string
@@ -2850,25 +2850,28 @@ func (c *containerLXC) Export(w io.Writer) error {
 		}
 
 		// Write the actual file
-		f.Write(data)
-		f.Close()
+		fnam = filepath.Join(tempDir, "metadata.yaml")
+		err = ioutil.WriteFile(fnam, data, 0644)
+		if err != nil {
+			tw.Close()
+			shared.LogError("Failed exporting container", ctxMap)
+			return err
+		}
 
-		fi, err := os.Lstat(f.Name())
+		fi, err := os.Lstat(fnam)
 		if err != nil {
 			tw.Close()
 			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
 
-		tmpOffset := len(path.Dir(f.Name())) + 1
-		if err := c.tarStoreFile(linkmap, tmpOffset, tw, f.Name(), fi); err != nil {
+		tmpOffset := len(path.Dir(fnam)) + 1
+		if err := c.tarStoreFile(linkmap, tmpOffset, tw, fnam, fi); err != nil {
 			tw.Close()
 			shared.LogDebugf("Error writing to tarfile: %s", err)
 			shared.LogError("Failed exporting container", ctxMap)
 			return err
 		}
-
-		fnam = f.Name()
 	} else {
 		// Include metadata.yaml in the tarball
 		fi, err := os.Lstat(fnam)

From 2a41150ec0a7ac4f3ba61f43df6513b1d0171747 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 9 Oct 2016 16:45:17 -0400
Subject: [PATCH 0376/1193] Detect out of disk space unpack errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2201

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go        | 26 ++++++++++++++++++++++----
 lxd/storage_btrfs.go |  2 +-
 lxd/storage_dir.go   |  2 +-
 lxd/storage_lvm.go   |  2 +-
 lxd/storage_zfs.go   |  2 +-
 5 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index bd15d3a4f..0650de382 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -16,6 +16,7 @@ import (
 	"strconv"
 	"strings"
 	"sync"
+	"syscall"
 	"time"
 
 	"github.com/gorilla/mux"
@@ -76,7 +77,7 @@ func detectCompression(fname string) ([]string, string, error) {
 
 }
 
-func unpack(file string, path string) error {
+func unpack(d *Daemon, file string, path string) error {
 	extractArgs, extension, err := detectCompression(file)
 	if err != nil {
 		return err
@@ -115,6 +116,23 @@ func unpack(file string, path string) error {
 
 	output, err := exec.Command(command, args...).CombinedOutput()
 	if err != nil {
+		// Check if we ran out of space
+		fs := syscall.Statfs_t{}
+
+		err1 := syscall.Statfs(path, &fs)
+		if err1 != nil {
+			return err1
+		}
+
+		// Check if we're running out of space
+		if int64(fs.Bfree) < int64(2*fs.Bsize) {
+			if d.Storage.GetStorageType() == storageTypeLvm {
+				return fmt.Errorf("Unable to unpack image, run out of disk space (consider increasing storage.lvm_volume_size).")
+			} else {
+				return fmt.Errorf("Unable to unpack image, run out of disk space.")
+			}
+		}
+
 		co := string(output)
 		shared.LogDebugf("Unpacking failed")
 		shared.LogDebugf(co)
@@ -128,8 +146,8 @@ func unpack(file string, path string) error {
 	return nil
 }
 
-func unpackImage(imagefname string, destpath string) error {
-	err := unpack(imagefname, destpath)
+func unpackImage(d *Daemon, imagefname string, destpath string) error {
+	err := unpack(d, imagefname, destpath)
 	if err != nil {
 		return err
 	}
@@ -141,7 +159,7 @@ func unpackImage(imagefname string, destpath string) error {
 			return fmt.Errorf("Error creating rootfs directory")
 		}
 
-		err = unpack(imagefname+".rootfs", rootfsPath)
+		err = unpack(d, imagefname+".rootfs", rootfsPath)
 		if err != nil {
 			return err
 		}
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 83fa35731..639c85f22 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -413,7 +413,7 @@ func (s *storageBtrfs) ImageCreate(fingerprint string) error {
 		return err
 	}
 
-	if err := unpackImage(imagePath, subvol); err != nil {
+	if err := unpackImage(s.d, imagePath, subvol); err != nil {
 		s.subvolDelete(subvol)
 		return err
 	}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 94eec94cd..3c3a2b0b7 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -60,7 +60,7 @@ func (s *storageDir) ContainerCreateFromImage(
 	}
 
 	imagePath := shared.VarPath("images", imageFingerprint)
-	if err := unpackImage(imagePath, container.Path()); err != nil {
+	if err := unpackImage(s.d, imagePath, container.Path()); err != nil {
 		s.ContainerDelete(container)
 		return err
 	}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 6bfefc5ad..ed73211b0 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -736,7 +736,7 @@ func (s *storageLvm) ImageCreate(fingerprint string) error {
 		return fmt.Errorf("Error mounting image LV: %v", err)
 	}
 
-	unpackErr := unpackImage(finalName, tempLVMountPoint)
+	unpackErr := unpackImage(s.d, finalName, tempLVMountPoint)
 
 	err = tryUnmount(tempLVMountPoint, 0)
 	if err != nil {
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index b53d2e82b..9f774c556 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -627,7 +627,7 @@ func (s *storageZfs) ImageCreate(fingerprint string) error {
 		return err
 	}
 
-	err = unpackImage(imagePath, subvol)
+	err = unpackImage(s.d, imagePath, subvol)
 	if err != nil {
 		return cleanup(err)
 	}

From 60da2ee587420b6553c6af3b73ae29300316609c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 9 Oct 2016 17:04:18 -0400
Subject: [PATCH 0377/1193] init: Detect zfs kernel support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Don't list "zfs" as a supported backend if we're unable to talk to the
kernel module (test using zpool list).

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/lxd/main.go b/lxd/main.go
index 11e42fa1c..a81a50cca 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -614,7 +614,12 @@ func cmdInit() error {
 	// Detect zfs
 	out, err := exec.LookPath("zfs")
 	if err == nil && len(out) != 0 && !runningInUserns {
-		backendsAvailable = append(backendsAvailable, "zfs")
+		_ = shared.RunCommand("modprobe", "zfs")
+
+		err := shared.RunCommand("zpool", "list")
+		if err == nil {
+			backendsAvailable = append(backendsAvailable, "zfs")
+		}
 	}
 
 	reader := bufio.NewReader(os.Stdin)
@@ -908,8 +913,6 @@ they otherwise would.
 	}
 
 	if storageBackend == "zfs" {
-		_ = exec.Command("modprobe", "zfs").Run()
-
 		if storageMode == "loop" {
 			storageDevice = shared.VarPath("zfs.img")
 			f, err := os.Create(storageDevice)

From 6eed9cb6ed070ad3d300d2a27f00506f4d76fea5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 9 Oct 2016 22:24:23 -0400
Subject: [PATCH 0378/1193] More reliable container autostart
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - This makes LXD respect boot.autostart = false as never auto-start the
   container.
 - Additionaly, change our code to store the container state on
   container startup (clearing it on stop) so that LXD properly restores
   containers on hard system restarts.

Closes #2469

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/configuration.md |  2 +-
 lxd/container_lxc.go | 12 ++++++++++++
 lxd/containers.go    | 25 ++++++++++++++++++++++---
 lxd/db_containers.go | 37 +++++++++++++++++++++++++++++++++++++
 4 files changed, 72 insertions(+), 4 deletions(-)

diff --git a/doc/configuration.md b/doc/configuration.md
index 465dd09bd..7f9e12408 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -65,7 +65,7 @@ The currently supported keys are:
 
 Key                         | Type      | Default       | Live update   | Description
 :--                         | :---      | :------       | :----------   | :----------
-boot.autostart              | boolean   | false         | n/a           | Always start the container when LXD starts
+boot.autostart              | boolean   | -             | n/a           | Always start the container when LXD starts (if not set, restore last state)
 boot.autostart.delay        | integer   | 0             | n/a           | Number of seconds to wait after the container started before starting the next one
 boot.autostart.priority     | integer   | 0             | n/a           | What order to start the containers in (starting with highest)
 environment.\*              | string    | -             | yes (exec)    | key/value environment variables to export to the container and set on exec
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0892d09af..842393e61 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1503,6 +1503,12 @@ func (c *containerLXC) OnStart() error {
 		}(c, name, m)
 	}
 
+	// Record current state
+	err = dbContainerSetState(c.daemon.db, c.id, "RUNNING")
+	if err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -1719,6 +1725,12 @@ func (c *containerLXC) OnStop(target string) error {
 		// Trigger a rebalance
 		deviceTaskSchedulerTrigger("container", c.name, "stopped")
 
+		// Record current state
+		err = dbContainerSetState(c.daemon.db, c.id, "STOPPED")
+		if err != nil {
+			return
+		}
+
 		// Destroy ephemeral containers
 		if c.ephemeral {
 			c.Delete()
diff --git a/lxd/containers.go b/lxd/containers.go
index f5b8d17b1..145888f20 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -85,6 +85,7 @@ func (slice containerAutostartList) Swap(i, j int) {
 }
 
 func containersRestart(d *Daemon) error {
+	// Get all the containers
 	result, err := dbContainersList(d.db, cTypeRegular)
 	if err != nil {
 		return err
@@ -103,6 +104,7 @@ func containersRestart(d *Daemon) error {
 
 	sort.Sort(containerAutostartList(containers))
 
+	// Restart the containers
 	for _, c := range containers {
 		config := c.ExpandedConfig()
 		lastState := config["volatile.last_state.power"]
@@ -110,7 +112,7 @@ func containersRestart(d *Daemon) error {
 		autoStart := config["boot.autostart"]
 		autoStartDelay := config["boot.autostart.delay"]
 
-		if lastState == "RUNNING" || shared.IsTrue(autoStart) {
+		if shared.IsTrue(autoStart) || (autoStart == "" && lastState == "RUNNING") {
 			if c.IsRunning() {
 				continue
 			}
@@ -124,34 +126,51 @@ func containersRestart(d *Daemon) error {
 		}
 	}
 
+	// Reset the recorded state (to ensure it's up to date)
 	_, err = dbExec(d.db, "DELETE FROM containers_config WHERE key='volatile.last_state.power'")
 	if err != nil {
 		return err
 	}
 
+	for _, c := range containers {
+		err = c.ConfigKeySet("volatile.last_state.power", c.State())
+		if err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
 func containersShutdown(d *Daemon) error {
+	var wg sync.WaitGroup
+
+	// Get all the containers
 	results, err := dbContainersList(d.db, cTypeRegular)
 	if err != nil {
 		return err
 	}
 
-	var wg sync.WaitGroup
+	// Reset all container states
+	_, err = dbExec(d.db, "DELETE FROM containers_config WHERE key='volatile.last_state.power'")
+	if err != nil {
+		return err
+	}
 
 	for _, r := range results {
+		// Load the container
 		c, err := containerLoadByName(d, r)
 		if err != nil {
 			return err
 		}
 
+		// Record the current state
 		err = c.ConfigKeySet("volatile.last_state.power", c.State())
-
 		if err != nil {
 			return err
 		}
 
+		// Stop the container
 		if c.IsRunning() {
 			wg.Add(1)
 			go func() {
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index 7b0a175f9..b51c75776 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -323,6 +323,43 @@ func dbContainersList(db *sql.DB, cType containerType) ([]string, error) {
 	return ret, nil
 }
 
+func dbContainerSetState(db *sql.DB, id int, state string) error {
+	tx, err := dbBegin(db)
+	if err != nil {
+		return err
+	}
+
+	// Clear any existing entry
+	str := fmt.Sprintf("DELETE FROM containers_config WHERE container_id = ? AND key = 'volatile.last_state.power'")
+	stmt, err := tx.Prepare(str)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	defer stmt.Close()
+
+	if _, err := stmt.Exec(id); err != nil {
+		tx.Rollback()
+		return err
+	}
+
+	// Insert the new one
+	str = fmt.Sprintf("INSERT INTO containers_config (container_id, key, value) VALUES (?, 'volatile.last_state.power', ?)")
+	stmt, err = tx.Prepare(str)
+	if err != nil {
+		tx.Rollback()
+		return err
+	}
+	defer stmt.Close()
+
+	if _, err = stmt.Exec(id, state); err != nil {
+		tx.Rollback()
+		return err
+	}
+
+	return txCommit(tx)
+}
+
 func dbContainerRename(db *sql.DB, oldName string, newName string) error {
 	tx, err := dbBegin(db)
 	if err != nil {

From 89d5d93136eb5d155e5a3309da3b990d102b098f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 11 Oct 2016 00:23:04 -0400
Subject: [PATCH 0379/1193] exec: Remove unused code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_exec.go | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index ec62d6e99..d92bc7bb4 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -333,12 +333,6 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	}
 
 	run := func(op *operation) error {
-		nullDev, err := os.OpenFile(os.DevNull, os.O_RDWR, 0666)
-		if err != nil {
-			return err
-		}
-		defer nullDev.Close()
-
 		cmdResult, cmdErr := c.Exec(post.Command, env, nil, nil, nil)
 		metadata := shared.Jmap{"return": cmdResult}
 		err = op.UpdateMetadata(metadata)

From 0da6fd05a920f0a75cf39cd32966f54f5dabbfaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 11 Oct 2016 00:45:30 -0400
Subject: [PATCH 0380/1193] logs: Don't show invalid logs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_logs.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/container_logs.go b/lxd/container_logs.go
index 70e801e36..0f760fea3 100644
--- a/lxd/container_logs.go
+++ b/lxd/container_logs.go
@@ -34,6 +34,10 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
 	}
 
 	for _, f := range dents {
+		if !validLogFileName(f.Name()) {
+			continue
+		}
+
 		result = append(result, fmt.Sprintf("/%s/containers/%s/logs/%s", shared.APIVersion, name, f.Name()))
 	}
 

From fc138def828f6bb40a458f26ee7ca68b9fae8beb Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 11 Oct 2016 09:00:19 -0600
Subject: [PATCH 0381/1193] disable keepalives in http.Transports

http/transport.go says:

// By default, Transport caches connections for future re-use.
// This may leave many open connections when accessing many hosts.
// This behavior can be managed using Transport's CloseIdleConnections
// method
// and the MaxIdleConnsPerHost and DisableKeepAlives fields.

In particular,

babbageclunk | Yeah - we end up leaking 10 goroutines everytime we destroy a model.

juju uses the client from a long running daemon, which creates a new
http.Transport for each call to the API, which creates a new
http.Transport, which leaks these goroutines.

Perhaps we can fix this more better with the client rewrite, but for now
let's just not do the keepalive, since that's how we're using
http.Transport anyway.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go     | 12 ++++++++----
 lxd/daemon.go | 14 ++++++++------
 2 files changed, 16 insertions(+), 10 deletions(-)

diff --git a/client.go b/client.go
index 3b6a4e20d..206e8e164 100644
--- a/client.go
+++ b/client.go
@@ -250,7 +250,10 @@ func connectViaUnix(c *Client, remote *RemoteConfig) error {
 		}
 		return net.DialUnix("unix", nil, raddr)
 	}
-	c.Http.Transport = &http.Transport{Dial: uDial}
+	c.Http.Transport = &http.Transport{
+		Dial:              uDial,
+		DisableKeepAlives: true,
+	}
 	c.websocketDialer.NetDial = uDial
 	c.Remote = remote
 
@@ -269,9 +272,10 @@ func connectViaHttp(c *Client, remote *RemoteConfig, clientCert, clientKey, serv
 	}
 
 	tr := &http.Transport{
-		TLSClientConfig: tlsconfig,
-		Dial:            shared.RFC3493Dialer,
-		Proxy:           shared.ProxyFromEnvironment,
+		TLSClientConfig:   tlsconfig,
+		Dial:              shared.RFC3493Dialer,
+		Proxy:             shared.ProxyFromEnvironment,
+		DisableKeepAlives: true,
 	}
 
 	c.websocketDialer.NetDial = shared.RFC3493Dialer
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 0388b28ed..78345bc5e 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -127,9 +127,10 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 	}
 
 	tr := &http.Transport{
-		TLSClientConfig: tlsConfig,
-		Dial:            shared.RFC3493Dialer,
-		Proxy:           d.proxy,
+		TLSClientConfig:   tlsConfig,
+		Dial:              shared.RFC3493Dialer,
+		Proxy:             d.proxy,
+		DisableKeepAlives: true,
 	}
 
 	myhttp := http.Client{
@@ -182,9 +183,10 @@ func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, er
 	}
 
 	tr := &http.Transport{
-		TLSClientConfig: tlsConfig,
-		Dial:            shared.RFC3493Dialer,
-		Proxy:           d.proxy,
+		TLSClientConfig:   tlsConfig,
+		Dial:              shared.RFC3493Dialer,
+		Proxy:             d.proxy,
+		DisableKeepAlives: true,
 	}
 	myhttp := http.Client{
 		Transport: tr,

From 899eeb0c7685ba2a30e3f6fe23e804f7bd94d658 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 11 Oct 2016 15:48:24 -0400
Subject: [PATCH 0382/1193] Properly validate memory limits
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2483

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/lxd/container.go b/lxd/container.go
index 45ab54519..51b88c90a 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -90,6 +90,24 @@ func containerValidConfigKey(key string, value string) error {
 	case "limits.disk.priority":
 		return isInt64(key, value)
 	case "limits.memory":
+		if value == "" {
+			return nil
+		}
+
+		if strings.HasSuffix(value, "%") {
+			_, err := strconv.ParseInt(strings.TrimSuffix(value, "%"), 10, 64)
+			if err != nil {
+				return err
+			}
+
+			return nil
+		}
+
+		_, err := shared.ParseByteSizeString(value)
+		if err != nil {
+			return err
+		}
+
 		return nil
 	case "limits.memory.enforce":
 		return isOneOf(key, value, []string{"soft", "hard"})

From b5daa0073de04077e8ad800bd98a7dd8bf259840 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 11 Oct 2016 15:56:46 -0400
Subject: [PATCH 0383/1193] Properly validate CPU allowance
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/lxd/container.go b/lxd/container.go
index 51b88c90a..29275380c 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -84,6 +84,36 @@ func containerValidConfigKey(key string, value string) error {
 	case "limits.cpu":
 		return nil
 	case "limits.cpu.allowance":
+		if value == "" {
+			return nil
+		}
+
+		if strings.HasSuffix(value, "%") {
+			// Percentage based allocation
+			_, err := strconv.Atoi(strings.TrimSuffix(value, "%"))
+			if err != nil {
+				return err
+			}
+
+			return nil
+		}
+
+		// Time based allocation
+		fields := strings.SplitN(value, "/", 2)
+		if len(fields) != 2 {
+			return fmt.Errorf("Invalid allowance: %s", value)
+		}
+
+		_, err := strconv.Atoi(strings.TrimSuffix(fields[0], "ms"))
+		if err != nil {
+			return err
+		}
+
+		_, err = strconv.Atoi(strings.TrimSuffix(fields[1], "ms"))
+		if err != nil {
+			return err
+		}
+
 		return nil
 	case "limits.cpu.priority":
 		return isInt64(key, value)

From 4ad7af18c5a689c3c03df713ecbce3238eb91769 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 11 Oct 2016 16:11:46 -0400
Subject: [PATCH 0384/1193] Improve config validation on update
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Run through initLXC as an extra validation step to prevent us getting in
a weird state where the config was committed to DB but LXD can't read it.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 842393e61..97809b4c3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2310,6 +2310,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			c.localConfig = oldLocalConfig
 			c.localDevices = oldLocalDevices
 			c.profiles = oldProfiles
+			c.c = nil
 			c.initLXC()
 			deviceTaskSchedulerTrigger("container", c.name, "changed")
 		}
@@ -2366,6 +2367,13 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
+	// Run through initLXC to catch anything we missed
+	c.c = nil
+	err = c.initLXC()
+	if err != nil {
+		return err
+	}
+
 	// If apparmor changed, re-validate the apparmor profile
 	for _, key := range changedConfig {
 		if key == "raw.apparmor" || key == "security.nesting" {
@@ -2746,14 +2754,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
-	// Invalidate the go-lxc cache
-	c.c = nil
-
-	err = c.initLXC()
-	if err != nil {
-		return err
-	}
-
 	// Success, update the closure to mark that the changes should be kept.
 	undoChanges = false
 

From b5552c572db2044b0c4d55cb70166c7fa5536ffc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 12 Oct 2016 00:11:58 -0400
Subject: [PATCH 0385/1193] doc: Sort API endpoints in rest-api
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/rest-api.md | 456 ++++++++++++++++++++++++++++----------------------------
 1 file changed, 228 insertions(+), 228 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 21337c14d..ff47c0188 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -636,167 +636,63 @@ Input (none at present):
 
 HTTP code for this should be 202 (Accepted).
 
-## /1.0/containers/\<name\>/state
-### GET
- * Description: current state
+## /1.0/containers/\<name\>/exec
+### POST
+ * Description: run a remote command
  * Authentication: trusted
- * Operation: sync
- * Return: dict representing current state
+ * Operation: async
+ * Return: background operation + optional websocket information or standard error
 
-Output:
+Input (run bash):
 
     {
-        "type": "sync",
-        "status": "Success",
-        "status_code": 200,
-        "metadata": {
-            "status": "Running",
-            "status_code": 103,
-            "disk": {
-                "root": {
-                    "usage": 422330368
-                }
-            },
-            "memory": {
-                "usage": 51126272,
-                "usage_peak": 70246400,
-                "swap_usage": 0,
-                "swap_usage_peak": 0
-            },
-            "network": {
-                "eth0": {
-                    "addresses": [
-                        {
-                            "family": "inet",
-                            "address": "10.0.3.27",
-                            "netmask": "24",
-                            "scope": "global"
-                        },
-                        {
-                            "family": "inet6",
-                            "address": "fe80::216:3eff:feec:65a8",
-                            "netmask": "64",
-                            "scope": "link"
-                        }
-                    ],
-                    "counters": {
-                        "bytes_received": 33942,
-                        "bytes_sent": 30810,
-                        "packets_received": 402,
-                        "packets_sent": 178
-                    },
-                    "hwaddr": "00:16:3e:ec:65:a8",
-                    "host_name": "vethBWTSU5",
-                    "mtu": 1500,
-                    "state": "up",
-                    "type": "broadcast"
-                },
-                "lo": {
-                    "addresses": [
-                        {
-                            "family": "inet",
-                            "address": "127.0.0.1",
-                            "netmask": "8",
-                            "scope": "local"
-                        },
-                        {
-                            "family": "inet6",
-                            "address": "::1",
-                            "netmask": "128",
-                            "scope": "local"
-                        }
-                    ],
-                    "counters": {
-                        "bytes_received": 86816,
-                        "bytes_sent": 86816,
-                        "packets_received": 1226,
-                        "packets_sent": 1226
-                    },
-                    "hwaddr": "",
-                    "host_name": "",
-                    "mtu": 65536,
-                    "state": "up",
-                    "type": "loopback"
-                },
-                "lxdbr0": {
-                    "addresses": [
-                        {
-                            "family": "inet",
-                            "address": "10.0.3.1",
-                            "netmask": "24",
-                            "scope": "global"
-                        },
-                        {
-                            "family": "inet6",
-                            "address": "fe80::68d4:87ff:fe40:7769",
-                            "netmask": "64",
-                            "scope": "link"
-                        }
-                    ],
-                    "counters": {
-                        "bytes_received": 0,
-                        "bytes_sent": 570,
-                        "packets_received": 0,
-                        "packets_sent": 7
-                    },
-                    "hwaddr": "6a:d4:87:40:77:69",
-                    "host_name": "",
-                    "mtu": 1500,
-                    "state": "up",
-                    "type": "broadcast"
-               },
-               "zt0": {
-                    "addresses": [
-                        {
-                            "family": "inet",
-                            "address": "29.17.181.59",
-                            "netmask": "7",
-                            "scope": "global"
-                        },
-                        {
-                            "family": "inet6",
-                            "address": "fd80:56c2:e21c:0:199:9379:e711:b3e1",
-                            "netmask": "88",
-                            "scope": "global"
-                        },
-                        {
-                            "family": "inet6",
-                            "address": "fe80::79:e7ff:fe0d:5123",
-                            "netmask": "64",
-                            "scope": "link"
-                        }
-                    ],
-                    "counters": {
-                        "bytes_received": 0,
-                        "bytes_sent": 806,
-                        "packets_received": 0,
-                        "packets_sent": 9
-                    },
-                    "hwaddr": "02:79:e7:0d:51:23",
-                    "host_name": "",
-                    "mtu": 2800,
-                    "state": "up",
-                    "type": "broadcast"
-                }
-            },
-            "pid": 13663,
-            "processes": 32
+        "command": ["/bin/bash"],       # Command and arguments
+        "environment": {},              # Optional extra environment variables to set
+        "wait-for-websocket": false,    # Whether to wait for a connection before starting the process
+        "interactive": true,            # Whether to allocate a pts device instead of PIPEs
+        "width": 80,                    # Initial width of the terminal (optional)
+        "height": 25,                   # Initial height of the terminal (optional)
+    }
+
+`wait-for-websocket` indicates whether the operation should block and wait for
+a websocket connection to start (so that users can pass stdin and read
+stdout), or simply run to completion with /dev/null as stdin and stdout.
+
+If interactive is set to true, a single websocket is returned and is mapped to a
+pts device for stdin, stdout and stderr of the execed process.
+
+If interactive is set to false (default), three pipes will be setup, one
+for each of stdin, stdout and stderr.
+
+Depending on the state of the interactive flag, one or three different
+websocket/secret pairs will be returned, which are valid for connecting to this
+operations /websocket endpoint.
+
+Return (with wait-for-websocket=true and interactive=false):
+
+    {
+        "fds": {
+            "0": "f5b6c760c0aa37a6430dd2a00c456430282d89f6e1661a077a926ed1bf3d1c21",
+            "1": "464dcf9f8fdce29d0d6478284523a9f26f4a31ae365d94cd38bac41558b797cf",
+            "2": "25b70415b686360e3b03131e33d6d94ee85a7f19b0f8d141d6dca5a1fc7b00eb",
+            "control": "20c479d9532ab6d6c3060f6cdca07c1f177647c9d96f0c143ab61874160bd8a5"
         }
     }
 
-### PUT
- * Description: change the container state
- * Authentication: trusted
- * Operation: async
- * Return: background operation or standard error
+Return (with wait-for-websocket=true and interactive=true):
 
-Input:
+    {
+        "fds": {
+            "0": "f5b6c760c0aa37a6430dd2a00c456430282d89f6e1661a077a926ed1bf3d1c21",
+            "control": "20c479d9532ab6d6c3060f6cdca07c1f177647c9d96f0c143ab61874160bd8a5"
+        }
+    }
+
+When the exec command finishes, its exit status is available from the
+operation's metadata:
 
     {
-        "action": "stop",       # State change action (stop, start, restart, freeze or unfreeze)
-        "timeout": 30,          # A timeout after which the state change is considered as failed
-        "force": true,          # Force the state change (currently only valid for stop and restart where it means killing the container)
-        "stateful": true        # Whether to store or restore runtime state before stopping or startiong (only valid for stop and start, defaults to false)
+        "return": 0
     }
 
 ## /1.0/containers/\<name\>/files
@@ -831,6 +727,36 @@ The following headers may be set by the client:
 This is designed to be easily usable from the command line or even a web
 browser.
 
+## /1.0/containers/\<name\>/logs
+### GET
+* Description: Returns a list of the log files available for this container.
+  Note that this works on containers that have been deleted (or were never
+  created) to enable people to get logs for failed creations.
+* Authentication: trusted
+* Operation: Sync
+* Return: a list of the available log files
+
+Return:
+
+    [
+        "/1.0/containers/blah/logs/forkstart.log",
+        "/1.0/containers/blah/logs/lxc.conf",
+        "/1.0/containers/blah/logs/lxc.log"
+    ]
+
+## /1.0/containers/\<name\>/logs/\<logfile\>
+### GET
+* Description: returns the contents of a particular log file.
+* Authentication: trusted
+* Operation: N/A
+* Return: the contents of the log file
+
+### DELETE
+* Description: delete a particular log file.
+* Authentication: trusted
+* Operation: Sync
+* Return: empty response or standard error
+
 ## /1.0/containers/\<name\>/snapshots
 ### GET
  * Description: List of snapshots
@@ -953,95 +879,169 @@ Input (none at present):
 
 HTTP code for this should be 202 (Accepted).
 
-## /1.0/containers/\<name\>/exec
-### POST
- * Description: run a remote command
+## /1.0/containers/\<name\>/state
+### GET
+ * Description: current state
  * Authentication: trusted
- * Operation: async
- * Return: background operation + optional websocket information or standard error
-
-Input (run bash):
-
-    {
-        "command": ["/bin/bash"],       # Command and arguments
-        "environment": {},              # Optional extra environment variables to set
-        "wait-for-websocket": false,    # Whether to wait for a connection before starting the process
-        "interactive": true,            # Whether to allocate a pts device instead of PIPEs
-        "width": 80,                    # Initial width of the terminal (optional)
-        "height": 25,                   # Initial height of the terminal (optional)
-    }
-
-`wait-for-websocket` indicates whether the operation should block and wait for
-a websocket connection to start (so that users can pass stdin and read
-stdout), or simply run to completion with /dev/null as stdin and stdout.
-
-If interactive is set to true, a single websocket is returned and is mapped to a
-pts device for stdin, stdout and stderr of the execed process.
-
-If interactive is set to false (default), three pipes will be setup, one
-for each of stdin, stdout and stderr.
-
-Depending on the state of the interactive flag, one or three different
-websocket/secret pairs will be returned, which are valid for connecting to this
-operations /websocket endpoint.
+ * Operation: sync
+ * Return: dict representing current state
 
-Return (with wait-for-websocket=true and interactive=false):
+Output:
 
     {
-        "fds": {
-            "0": "f5b6c760c0aa37a6430dd2a00c456430282d89f6e1661a077a926ed1bf3d1c21",
-            "1": "464dcf9f8fdce29d0d6478284523a9f26f4a31ae365d94cd38bac41558b797cf",
-            "2": "25b70415b686360e3b03131e33d6d94ee85a7f19b0f8d141d6dca5a1fc7b00eb",
-            "control": "20c479d9532ab6d6c3060f6cdca07c1f177647c9d96f0c143ab61874160bd8a5"
+        "type": "sync",
+        "status": "Success",
+        "status_code": 200,
+        "metadata": {
+            "status": "Running",
+            "status_code": 103,
+            "disk": {
+                "root": {
+                    "usage": 422330368
+                }
+            },
+            "memory": {
+                "usage": 51126272,
+                "usage_peak": 70246400,
+                "swap_usage": 0,
+                "swap_usage_peak": 0
+            },
+            "network": {
+                "eth0": {
+                    "addresses": [
+                        {
+                            "family": "inet",
+                            "address": "10.0.3.27",
+                            "netmask": "24",
+                            "scope": "global"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "fe80::216:3eff:feec:65a8",
+                            "netmask": "64",
+                            "scope": "link"
+                        }
+                    ],
+                    "counters": {
+                        "bytes_received": 33942,
+                        "bytes_sent": 30810,
+                        "packets_received": 402,
+                        "packets_sent": 178
+                    },
+                    "hwaddr": "00:16:3e:ec:65:a8",
+                    "host_name": "vethBWTSU5",
+                    "mtu": 1500,
+                    "state": "up",
+                    "type": "broadcast"
+                },
+                "lo": {
+                    "addresses": [
+                        {
+                            "family": "inet",
+                            "address": "127.0.0.1",
+                            "netmask": "8",
+                            "scope": "local"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "::1",
+                            "netmask": "128",
+                            "scope": "local"
+                        }
+                    ],
+                    "counters": {
+                        "bytes_received": 86816,
+                        "bytes_sent": 86816,
+                        "packets_received": 1226,
+                        "packets_sent": 1226
+                    },
+                    "hwaddr": "",
+                    "host_name": "",
+                    "mtu": 65536,
+                    "state": "up",
+                    "type": "loopback"
+                },
+                "lxdbr0": {
+                    "addresses": [
+                        {
+                            "family": "inet",
+                            "address": "10.0.3.1",
+                            "netmask": "24",
+                            "scope": "global"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "fe80::68d4:87ff:fe40:7769",
+                            "netmask": "64",
+                            "scope": "link"
+                        }
+                    ],
+                    "counters": {
+                        "bytes_received": 0,
+                        "bytes_sent": 570,
+                        "packets_received": 0,
+                        "packets_sent": 7
+                    },
+                    "hwaddr": "6a:d4:87:40:77:69",
+                    "host_name": "",
+                    "mtu": 1500,
+                    "state": "up",
+                    "type": "broadcast"
+               },
+               "zt0": {
+                    "addresses": [
+                        {
+                            "family": "inet",
+                            "address": "29.17.181.59",
+                            "netmask": "7",
+                            "scope": "global"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "fd80:56c2:e21c:0:199:9379:e711:b3e1",
+                            "netmask": "88",
+                            "scope": "global"
+                        },
+                        {
+                            "family": "inet6",
+                            "address": "fe80::79:e7ff:fe0d:5123",
+                            "netmask": "64",
+                            "scope": "link"
+                        }
+                    ],
+                    "counters": {
+                        "bytes_received": 0,
+                        "bytes_sent": 806,
+                        "packets_received": 0,
+                        "packets_sent": 9
+                    },
+                    "hwaddr": "02:79:e7:0d:51:23",
+                    "host_name": "",
+                    "mtu": 2800,
+                    "state": "up",
+                    "type": "broadcast"
+                }
+            },
+            "pid": 13663,
+            "processes": 32
         }
     }
 
-Return (with wait-for-websocket=true and interactive=true):
-
-    {
-        "fds": {
-            "0": "f5b6c760c0aa37a6430dd2a00c456430282d89f6e1661a077a926ed1bf3d1c21",
-            "control": "20c479d9532ab6d6c3060f6cdca07c1f177647c9d96f0c143ab61874160bd8a5"
-        }
-    }
+### PUT
+ * Description: change the container state
+ * Authentication: trusted
+ * Operation: async
+ * Return: background operation or standard error
 
-When the exec command finishes, its exit status is available from the
-operation's metadata:
+Input:
 
     {
-        "return": 0
+        "action": "stop",       # State change action (stop, start, restart, freeze or unfreeze)
+        "timeout": 30,          # A timeout after which the state change is considered as failed
+        "force": true,          # Force the state change (currently only valid for stop and restart where it means killing the container)
+        "stateful": true        # Whether to store or restore runtime state before stopping or startiong (only valid for stop and start, defaults to false)
     }
 
-## /1.0/containers/\<name\>/logs
-### GET
-* Description: Returns a list of the log files available for this container.
-  Note that this works on containers that have been deleted (or were never
-  created) to enable people to get logs for failed creations.
-* Authentication: trusted
-* Operation: Sync
-* Return: a list of the available log files
-
-Return:
-
-    [
-        "/1.0/containers/blah/logs/forkstart.log",
-        "/1.0/containers/blah/logs/lxc.conf",
-        "/1.0/containers/blah/logs/lxc.log"
-    ]
-
-## /1.0/containers/\<name\>/logs/\<logfile\>
-### GET
-* Description: returns the contents of a particular log file.
-* Authentication: trusted
-* Operation: N/A
-* Return: the contents of the log file
-
-### DELETE
-* Description: delete a particular log file.
-* Authentication: trusted
-* Operation: Sync
-* Return: empty response or standard error
-
 ## /1.0/events
 This URL isn't a real REST API endpoint, instead doing a GET query on it
 will upgrade the connection to a websocket on which notifications will

From c52d378563ba9eb11054134e650396d233f00506 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 12 Oct 2016 00:20:03 -0400
Subject: [PATCH 0386/1193] doc: Use consistent method ordering in rest-api
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/rest-api.md | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index ff47c0188..c85c690d9 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -1210,19 +1210,6 @@ Output:
         "uploaded_at": "2016-02-16T00:44:47Z"
     }
 
-### DELETE
- * Description: Remove an image
- * Authentication: trusted
- * Operation: async
- * Return: background operaton or standard error
-
-Input (none at present):
-
-    {
-    }
-
-HTTP code for this should be 202 (Accepted).
-
 ### PUT
  * Description: Updates the image properties
  * Authentication: trusted
@@ -1242,6 +1229,19 @@ Input:
         "public": true,
     }
 
+### DELETE
+ * Description: Remove an image
+ * Authentication: trusted
+ * Operation: async
+ * Return: background operaton or standard error
+
+Input (none at present):
+
+    {
+    }
+
+HTTP code for this should be 202 (Accepted).
+
 ## /1.0/images/\<fingerprint\>/export
 ### GET (optional ?secret=SECRET)
  * Description: Download the image tarball

From 540af4733365bef3ff033c482d206bdf9067421e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Wed, 12 Oct 2016 20:45:31 +0200
Subject: [PATCH 0387/1193] lxd/container_lxc: record the err from go-lxc

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 97809b4c3..ce9257713 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1751,12 +1751,14 @@ func (c *containerLXC) Freeze() error {
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
+		ctxMap["err"] = err
 		shared.LogError("Failed freezing container", ctxMap)
 		return err
 	}
 
 	err = c.c.Freeze()
 	if err != nil {
+		ctxMap["err"] = err
 		shared.LogError("Failed freezing container", ctxMap)
 		return err
 	}

From 00fdf74ab46cb8decd3af3e53e667b747c15b439 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 13 Oct 2016 17:57:13 -0600
Subject: [PATCH 0388/1193] lxd netcat: remove debugging by file creation

Don't mind me, I'm a barbarian.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/rsync.go | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/lxd/rsync.go b/lxd/rsync.go
index 22f21439e..6369d1c2f 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -198,22 +198,14 @@ func Netcat(args []string) error {
 
 	go func() {
 		io.Copy(os.Stdout, conn)
-		f, _ := os.Create("/tmp/done_stdout")
-		f.Close()
 		conn.Close()
-		f, _ = os.Create("/tmp/done_close")
-		f.Close()
 		wg.Done()
 	}()
 
 	go func() {
 		io.Copy(conn, os.Stdin)
-		f, _ := os.Create("/tmp/done_stdin")
-		f.Close()
 	}()
 
-	f, _ := os.Create("/tmp/done_spawning_goroutines")
-	f.Close()
 	wg.Wait()
 
 	return nil

From bdc79b3f7edf6734592be8050b4028e3fcef8be5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 13 Oct 2016 21:53:51 -0400
Subject: [PATCH 0389/1193] Store the simplestreams cache to disk
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Right now, we store simplestreams data for up to an hour. This is done
through a simple memory cache in LXD.

The goal of this was to decrease load on the simplestreams servers and
that's been working pretty well so far.

One thing that this cache doesn't help much with is dealing with
simplestreams servers being offline or the client being temporarily
disconnected from the internet.

Right now once the cache times out, LXD will refresh it, if that fails,
then the cache is gone.

This commit makes it so that our cache persists across LXD restarts by
serializing it to disk. It also changes its use a bit, so that if we
fail to refresh the cache, we log a warning and bump its expiry for
another hour.

This effectively allows LXD to be used completely offline and work fine
with any image that's already in the local cache.

Closes #2487

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go        |  13 +++++
 lxd/daemon_images.go | 134 ++++++++++++++++++++++++++++++++++++++++++++-------
 shared/util.go       |  13 +++++
 3 files changed, 142 insertions(+), 18 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 78345bc5e..e08db5fbe 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -733,6 +733,9 @@ func (d *Daemon) Init() error {
 	d.lxcpath = shared.VarPath("containers")
 
 	/* Make sure all our directories are available */
+	if err := os.MkdirAll(shared.CachePath(), 0700); err != nil {
+		return err
+	}
 	if err := os.MkdirAll(shared.VarPath("containers"), 0711); err != nil {
 		return err
 	}
@@ -800,6 +803,12 @@ func (d *Daemon) Init() error {
 		if err != nil {
 			return err
 		}
+
+		/* Restore simplestreams cache */
+		err = imageLoadStreamCache(d)
+		if err != nil {
+			return err
+		}
 	}
 
 	/* Log expiry */
@@ -1130,6 +1139,10 @@ func (d *Daemon) Stop() error {
 	d.devlxd.Close()
 	shared.LogInfof("Stopped /dev/lxd handler")
 
+	shared.LogInfof("Saving simplestreams cache")
+	imageSaveStreamCache()
+	shared.LogInfof("Saved simplestreams cache")
+
 	if d.MockMode || forceStop {
 		return nil
 	}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index b7ea9191a..876bd7ab0 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -4,26 +4,79 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"mime"
 	"mime/multipart"
 	"os"
 	"path/filepath"
+	"strings"
 	"sync"
 	"time"
 
+	"gopkg.in/yaml.v2"
+
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
+// Simplestream cache
 type imageStreamCacheEntry struct {
-	ss     *shared.SimpleStreams
-	expiry time.Time
+	Aliases      shared.ImageAliases `yaml:"aliases"`
+	Fingerprints []string            `yaml:"fingerprints"`
+	expiry       time.Time
+	ss           *shared.SimpleStreams
 }
 
 var imageStreamCache = map[string]*imageStreamCacheEntry{}
 var imageStreamCacheLock sync.Mutex
 
+func imageSaveStreamCache() error {
+	data, err := yaml.Marshal(&imageStreamCache)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(shared.CachePath("simplestreams.yaml"), data, 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func imageLoadStreamCache(d *Daemon) error {
+	imageStreamCacheLock.Lock()
+	defer imageStreamCacheLock.Unlock()
+
+	if !shared.PathExists(shared.CachePath("simplestreams.yaml")) {
+		return nil
+	}
+
+	content, err := ioutil.ReadFile(shared.CachePath("simplestreams.yaml"))
+	if err != nil {
+		return err
+	}
+
+	err = yaml.Unmarshal(content, imageStreamCache)
+	if err != nil {
+		return err
+	}
+
+	for url, entry := range imageStreamCache {
+		if entry.ss == nil {
+			ss, err := shared.SimpleStreamsClient(url, d.proxy)
+			if err != nil {
+				return err
+			}
+
+			entry.ss = ss
+		}
+	}
+
+	return nil
+}
+
 // ImageDownload checks if we have that Image Fingerprint else
 // downloads the image from a remote server.
 func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) {
@@ -42,34 +95,79 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		imageStreamCacheLock.Lock()
 		entry, _ := imageStreamCache[server]
 		if entry == nil || entry.expiry.Before(time.Now()) {
-			ss, err = shared.SimpleStreamsClient(server, d.proxy)
-			if err != nil {
+			refresh := func() (*imageStreamCacheEntry, error) {
+				// Setup simplestreams client
+				ss, err = shared.SimpleStreamsClient(server, d.proxy)
+				if err != nil {
+					return nil, err
+				}
+
+				// Get all aliases
+				aliases, err := ss.ListAliases()
+				if err != nil {
+					return nil, err
+				}
+
+				// Get all fingerprints
+				images, err := ss.ListImages()
+				if err != nil {
+					return nil, err
+				}
+
+				fingerprints := []string{}
+				for _, image := range images {
+					fingerprints = append(fingerprints, image.Fingerprint)
+				}
+
+				// Generate cache entry
+				entry = &imageStreamCacheEntry{ss: ss, Aliases: aliases, Fingerprints: fingerprints, expiry: time.Now().Add(time.Hour)}
+				imageStreamCache[server] = entry
+				imageSaveStreamCache()
+
+				return entry, nil
+			}
+
+			newEntry, err := refresh()
+			if err == nil {
+				// Cache refreshed
+				entry = newEntry
+			} else if entry != nil {
+				// Failed to fetch entry but existing cache
+				shared.LogWarn("Unable to refresh cache, using stale entry", log.Ctx{"server": server})
+				entry.expiry = time.Now().Add(time.Hour)
+			} else {
+				// Failed to fetch entry and nothing in cache
 				imageStreamCacheLock.Unlock()
 				return "", err
 			}
-
-			entry = &imageStreamCacheEntry{ss: ss, expiry: time.Now().Add(time.Hour)}
-			imageStreamCache[server] = entry
 		} else {
-			shared.LogDebugf("Using SimpleStreams cache entry for %s, expires at %s", server, entry.expiry)
+			shared.LogDebug("Using SimpleStreams cache entry", log.Ctx{"server": server, "expiry": entry.expiry})
 			ss = entry.ss
 		}
 		imageStreamCacheLock.Unlock()
 
-		target := ss.GetAlias(fp)
-		if target != "" {
-			fp = target
-		}
+		// Expand aliases
+		for _, alias := range entry.Aliases {
+			if alias.Name != fp {
+				continue
+			}
 
-		image, err := ss.GetImageInfo(fp)
-		if err != nil {
-			return "", err
+			fp = alias.Target
+			break
 		}
 
-		if fp == alias {
-			alias = image.Fingerprint
+		// Expand fingerprint
+		for _, fingerprint := range entry.Fingerprints {
+			if !strings.HasPrefix(fingerprint, fp) {
+				continue
+			}
+
+			if fp == alias {
+				alias = fingerprint
+			}
+			fp = fingerprint
+			break
 		}
-		fp = image.Fingerprint
 	} else if protocol == "lxd" {
 		target, err := remoteGetImageFingerprint(d, server, certificate, fp)
 		if err == nil && target != "" {
diff --git a/shared/util.go b/shared/util.go
index dd495d966..72f6bf987 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -93,6 +93,19 @@ func VarPath(path ...string) string {
 	return filepath.Join(items...)
 }
 
+// CachePath returns the directory that LXD should its cache under. If LXD_DIR is
+// set, this path is $LXD_DIR/cache, otherwise it is /var/cache/lxd.
+func CachePath(path ...string) string {
+	varDir := os.Getenv("LXD_DIR")
+	logDir := "/var/cache/lxd"
+	if varDir != "" {
+		logDir = filepath.Join(varDir, "cache")
+	}
+	items := []string{logDir}
+	items = append(items, path...)
+	return filepath.Join(items...)
+}
+
 // LogPath returns the directory that LXD should put logs under. If LXD_DIR is
 // set, this path is $LXD_DIR/logs, otherwise it is /var/log/lxd.
 func LogPath(path ...string) string {

From 909ec516e7dd3d814ba701f33909cc5287210301 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 14 Oct 2016 15:10:32 -0400
Subject: [PATCH 0390/1193] doc: We actually require 2.0.0 or higher
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/requirements.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/requirements.md b/doc/requirements.md
index 7d840f739..883f86e4e 100644
--- a/doc/requirements.md
+++ b/doc/requirements.md
@@ -20,7 +20,7 @@ The following optional features also require extra kernel options:
 As well as any other kernel feature required by the LXC version in use.
 
 ## LXC
-LXD requires LXC 1.1.5 or higher with the following build options:
+LXD requires LXC 2.0.0 or higher with the following build options:
  * apparmor (if using LXD's apparmor support)
  * seccomp
 

From 92a48e71880fa8d6fe8d7e3a8dbd8b0774a8e119 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 14 Oct 2016 15:10:45 -0400
Subject: [PATCH 0391/1193] Remove legacy code from OnStop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This changes OnStop to assume LXC 2.0.0 or higher.

So it now relies on LXC_TARGET being set in the environment (and will
fail if it's not) and also depends on LXC interrupting container restart
on hook failures.

This allows the removal of a good chunk of code and improved logging of
what's going on with the container.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 68 +++++++++++++++++++---------------------------------
 1 file changed, 24 insertions(+), 44 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ce9257713..71db965d3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1633,6 +1633,12 @@ func (c *containerLXC) Shutdown(timeout time.Duration) error {
 }
 
 func (c *containerLXC) OnStop(target string) error {
+	// Validate target
+	if !shared.StringInSlice(target, []string{"stop", "reboot"}) {
+		shared.LogError("Container sent invalid target to OnStop", log.Ctx{"container": c.Name(), "target": target})
+		return fmt.Errorf("Invalid stop target: %s", target)
+	}
+
 	// Get operation
 	op, _ := c.getOperation("")
 	if op != nil && !shared.StringInSlice(op.action, []string{"stop", "shutdown"}) {
@@ -1652,12 +1658,17 @@ func (c *containerLXC) OnStop(target string) error {
 		return err
 	}
 
-	// Unload the apparmor profile
-	if err := AADestroy(c); err != nil {
-		shared.LogError("failed to destroy apparmor namespace", log.Ctx{"container": c.Name(), "err": err})
+	// Log user actions
+	if op == nil {
+		ctxMap := log.Ctx{"name": c.name,
+			"action":    target,
+			"created":   c.creationDate,
+			"ephemeral": c.ephemeral,
+			"stateful":  false}
+
+		shared.LogInfo(fmt.Sprintf("Container initiated %s", target), ctxMap)
 	}
 
-	// FIXME: The go routine can go away once we can rely on LXC_TARGET
 	go func(c *containerLXC, target string, op *lxcContainerOperation) {
 		c.fromHook = false
 
@@ -1666,57 +1677,29 @@ func (c *containerLXC) OnStop(target string) error {
 			defer op.Done(nil)
 		}
 
-		if target == "unknown" && op != nil {
-			target = "stop"
-		}
-
-		if target == "unknown" {
-			time.Sleep(5 * time.Second)
+		// Wait for other post-stop actions to be done
+		c.IsRunning()
 
-			newContainer, err := containerLoadByName(c.daemon, c.Name())
-			if err != nil {
-				return
-			}
-
-			if newContainer.Id() != c.id {
-				return
-			}
-
-			if newContainer.IsRunning() {
-				return
-			}
+		// Unload the apparmor profile
+		err = AADestroy(c)
+		if err != nil {
+			shared.LogError("Failed to destroy apparmor namespace", log.Ctx{"container": c.Name(), "err": err})
 		}
 
 		// Clean all the unix devices
 		err = c.removeUnixDevices()
 		if err != nil {
-			shared.LogError("Unable to remove unix devices", log.Ctx{"err": err})
+			shared.LogError("Unable to remove unix devices", log.Ctx{"container": c.Name(), "err": err})
 		}
 
 		// Clean all the disk devices
 		err = c.removeDiskDevices()
 		if err != nil {
-			shared.LogError("Unable to remove disk devices", log.Ctx{"err": err})
+			shared.LogError("Unable to remove disk devices", log.Ctx{"container": c.Name(), "err": err})
 		}
 
 		// Reboot the container
 		if target == "reboot" {
-
-			/* This part is a hack to workaround a LXC bug where a
-			   failure from a post-stop script doesn't prevent the container to restart. */
-			ephemeral := c.ephemeral
-			args := containerArgs{
-				Architecture: c.Architecture(),
-				Config:       c.LocalConfig(),
-				Devices:      c.LocalDevices(),
-				Ephemeral:    false,
-				Profiles:     c.Profiles(),
-			}
-			c.Update(args, false)
-			c.Stop(false)
-			args.Ephemeral = ephemeral
-			c.Update(args, true)
-
 			// Start the container again
 			c.Start(false)
 			return
@@ -1726,10 +1709,7 @@ func (c *containerLXC) OnStop(target string) error {
 		deviceTaskSchedulerTrigger("container", c.name, "stopped")
 
 		// Record current state
-		err = dbContainerSetState(c.daemon.db, c.id, "STOPPED")
-		if err != nil {
-			return
-		}
+		dbContainerSetState(c.daemon.db, c.id, "STOPPED")
 
 		// Destroy ephemeral containers
 		if c.ephemeral {

From f9c0643a33fadb9be24324002963ab20f56d9ee5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 14 Oct 2016 15:46:11 -0400
Subject: [PATCH 0392/1193] Catch and return more errors in OnStop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 71db965d3..3385d2742 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1671,10 +1671,11 @@ func (c *containerLXC) OnStop(target string) error {
 
 	go func(c *containerLXC, target string, op *lxcContainerOperation) {
 		c.fromHook = false
+		err = nil
 
 		// Unlock on return
 		if op != nil {
-			defer op.Done(nil)
+			defer op.Done(err)
 		}
 
 		// Wait for other post-stop actions to be done
@@ -1701,7 +1702,7 @@ func (c *containerLXC) OnStop(target string) error {
 		// Reboot the container
 		if target == "reboot" {
 			// Start the container again
-			c.Start(false)
+			err = c.Start(false)
 			return
 		}
 
@@ -1709,11 +1710,14 @@ func (c *containerLXC) OnStop(target string) error {
 		deviceTaskSchedulerTrigger("container", c.name, "stopped")
 
 		// Record current state
-		dbContainerSetState(c.daemon.db, c.id, "STOPPED")
+		err = dbContainerSetState(c.daemon.db, c.id, "STOPPED")
+		if err != nil {
+			shared.LogError("Failed to set container state", log.Ctx{"container": c.Name(), "err": err})
+		}
 
 		// Destroy ephemeral containers
 		if c.ephemeral {
-			c.Delete()
+			err = c.Delete()
 		}
 	}(c, target, op)
 

From a1328dd8e851c8263d8dae8d420c46441f350156 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 14 Oct 2016 17:01:28 -0400
Subject: [PATCH 0393/1193] Export all documented certificate fields
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/certificates.go | 1 +
 shared/cert.go      | 1 +
 2 files changed, 2 insertions(+)

diff --git a/lxd/certificates.go b/lxd/certificates.go
index b70f6b117..585ba2184 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -189,6 +189,7 @@ func doCertificateGet(d *Daemon, fingerprint string) (shared.CertInfo, error) {
 
 	resp.Fingerprint = dbCertInfo.Fingerprint
 	resp.Certificate = dbCertInfo.Certificate
+	resp.Name = dbCertInfo.Name
 	if dbCertInfo.Type == 1 {
 		resp.Type = "client"
 	} else {
diff --git a/shared/cert.go b/shared/cert.go
index 998f85e24..ebd5604cf 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -26,6 +26,7 @@ import (
 type CertInfo struct {
 	Certificate string `json:"certificate"`
 	Fingerprint string `json:"fingerprint"`
+	Name        string `json:"name"`
 	Type        string `json:"type"`
 }
 

From 2ddc86de86b5ab4421cecc093cccc1d3785353cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 15 Oct 2016 00:04:24 -0400
Subject: [PATCH 0394/1193] examples: Use .yaml as the yaml extension
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

It's what upstream prefers and what we've been using the most throughout LXD.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config.go  |   2 +-
 lxc/image.go   |   2 +-
 lxc/profile.go |   2 +-
 po/de.po       | 392 +++++++++++++++++++++++++++------------------------
 po/fr.po       | 384 ++++++++++++++++++++++++++------------------------
 po/ja.po       | 436 +++++++++++++++++++++++++++++++--------------------------
 po/lxd.pot     |   8 +-
 7 files changed, 659 insertions(+), 567 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index fff621e92..631589a0a 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -72,7 +72,7 @@ lxc config show [remote:][container] [--expanded]                           Show
 lxc config edit [remote:][container]                                        Edit container or server configuration in external editor.
     Edit configuration, either by launching external editor or reading STDIN.
     Example: lxc config edit <container> # launch editor
-             cat config.yml | lxc config edit <config> # read from config.yml
+             cat config.yaml | lxc config edit <config> # read from config.yaml
 
 lxc config trust list [remote]                                              List all trusted certs.
 lxc config trust add [remote] <certfile.crt>                                Add certfile.crt to trusted hosts.
diff --git a/lxc/image.go b/lxc/image.go
index 07cb28e4f..a602aa998 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -146,7 +146,7 @@ lxc image show [remote:]<image>
 lxc image edit [remote:]<image>
     Edit image, either by launching external editor or reading STDIN.
     Example: lxc image edit <image> # launch editor
-             cat image.yml | lxc image edit <image> # read from image.yml
+             cat image.yaml | lxc image edit <image> # read from image.yaml
 
 lxc image alias create [remote:]<alias> <fingerprint>
     Create a new alias for an existing image.
diff --git a/lxc/profile.go b/lxc/profile.go
index 1c877e42a..9a3575084 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -58,7 +58,7 @@ lxc profile delete <profile>                   Delete a profile.
 lxc profile edit <profile>
     Edit profile, either by launching external editor or reading STDIN.
     Example: lxc profile edit <profile> # launch editor
-             cat profile.yml | lxc profile edit <profile> # read from profile.yml
+             cat profile.yaml | lxc profile edit <profile> # read from profile.yaml
 lxc profile apply <container> <profiles>
     Apply a comma-separated list of profiles to a container, in order.
     All profiles passed in this call (and only those) will be applied
diff --git a/po/de.po b/po/de.po
index 81b8e1104..bc982938f 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-04-25 14:47-0500\n"
+"POT-Creation-Date: 2016-10-26 16:48-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -16,19 +16,19 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:140
+#: lxc/info.go:143
 msgid "  Disk usage:"
 msgstr ""
 
-#: lxc/info.go:163
+#: lxc/info.go:166
 msgid "  Memory usage:"
 msgstr ""
 
-#: lxc/info.go:180
+#: lxc/info.go:183
 msgid "  Network usage:"
 msgstr ""
 
-#: lxc/config.go:37
+#: lxc/config.go:36
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the configuration.\n"
@@ -84,7 +84,7 @@ msgstr ""
 "### Zum Beispiel:\n"
 "###  description: Mein eigenes Abbild\n"
 
-#: lxc/profile.go:27
+#: lxc/profile.go:26
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the profile.\n"
@@ -123,7 +123,7 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:583
+#: lxc/image.go:597
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -133,32 +133,32 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 
-#: lxc/profile.go:251
+#: lxc/profile.go:226
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:604 lxc/image.go:633
+#: lxc/image.go:618 lxc/image.go:647
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:608
+#: lxc/image.go:622
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:378
+#: lxc/list.go:394
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:53
+#: lxc/remote.go:52
 msgid "Accept certificate"
 msgstr "Akzeptiere Zertifikat"
 
-#: lxc/remote.go:256
+#: lxc/remote.go:245
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
 
-#: lxc/image.go:347
+#: lxc/image.go:356
 #, fuzzy
 msgid "Aliases:"
 msgstr "Aliasse:\n"
@@ -167,12 +167,12 @@ msgstr "Aliasse:\n"
 msgid "An environment variable of the form HOME=/home/foo"
 msgstr ""
 
-#: lxc/image.go:330 lxc/info.go:90
+#: lxc/image.go:339 lxc/info.go:93
 #, fuzzy, c-format
 msgid "Architecture: %s"
 msgstr "Architektur: %s\n"
 
-#: lxc/image.go:351
+#: lxc/image.go:360
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
@@ -181,53 +181,53 @@ msgstr ""
 msgid "Available commands:"
 msgstr ""
 
-#: lxc/info.go:172
+#: lxc/info.go:175
 msgid "Bytes received"
 msgstr ""
 
-#: lxc/info.go:173
+#: lxc/info.go:176
 msgid "Bytes sent"
 msgstr ""
 
-#: lxc/config.go:270
+#: lxc/config.go:273
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:379
+#: lxc/list.go:395
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:114
+#: lxc/config.go:113
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr ""
 
-#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182
+#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:417
+#: lxc/profile.go:343
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:206
+#: lxc/remote.go:195
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %x"
 msgstr "Fingerabdruck des Zertifikats: % x\n"
 
-#: lxc/action.go:28
+#: lxc/action.go:33
 #, fuzzy, c-format
 msgid ""
 "Changes state of one or more containers to %s.\n"
 "\n"
-"lxc %s <name> [<name>...]"
+"lxc %s <name> [<name>...]%s"
 msgstr ""
 "Ändert den Laufzustand eines Containers in %s.\n"
 "\n"
 "lxd %s <Name>\n"
 
-#: lxc/remote.go:279
+#: lxc/remote.go:268
 msgid "Client certificate stored at server: "
 msgstr "Gespeichertes Nutzerzertifikat auf dem Server: "
 
@@ -240,12 +240,12 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:215
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:701 lxc/profile.go:190
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
 
-#: lxc/main.go:37
+#: lxc/main.go:29
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
@@ -263,7 +263,7 @@ msgstr ""
 msgid "Container published with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/image.go:155
+#: lxc/image.go:164
 msgid "Copy aliases from source"
 msgstr "Kopiere Aliasse von der Quelle"
 
@@ -279,12 +279,12 @@ msgstr ""
 "\n"
 "lxc copy <Quelle> <Ziel>\n"
 
-#: lxc/image.go:268
+#: lxc/image.go:277
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:221
+#: lxc/remote.go:210
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
@@ -308,7 +308,7 @@ msgid ""
 "lxc snapshot u1 snap0"
 msgstr ""
 
-#: lxc/image.go:335 lxc/info.go:92
+#: lxc/image.go:344 lxc/info.go:95
 #, c-format
 msgid "Created: %s"
 msgstr ""
@@ -323,7 +323,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:607 lxc/image.go:635
+#: lxc/image.go:621 lxc/image.go:649
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -343,33 +343,33 @@ msgstr ""
 "Entfernt einen Container (oder Sicherungspunkt) und alle dazugehörigen\n"
 "Daten (Konfiguration, Sicherungspunkte, ...).\n"
 
-#: lxc/config.go:617
+#: lxc/config.go:647
 #, fuzzy, c-format
 msgid "Device %s added to %s"
 msgstr "Gerät %s wurde zu %s hinzugefügt\n"
 
-#: lxc/config.go:804
+#: lxc/config.go:834
 #, fuzzy, c-format
 msgid "Device %s removed from %s"
 msgstr "Gerät %s wurde von %s entfernt\n"
 
-#: lxc/list.go:462
+#: lxc/list.go:478
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:272
+#: lxc/config.go:275
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:55
+#: lxc/main.go:41
 msgid "Enables debug mode."
 msgstr "Aktiviert Debug Modus"
 
-#: lxc/main.go:54
+#: lxc/main.go:40
 msgid "Enables verbose mode."
 msgstr "Aktiviert ausführliche Ausgabe"
 
-#: lxc/help.go:68
+#: lxc/help.go:69
 msgid "Environment:"
 msgstr ""
 
@@ -388,7 +388,7 @@ msgid ""
 "Execute the specified command in a container.\n"
 "\n"
 "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
-"EDITOR=/usr/bin/vim]... <command>\n"
+"EDITOR=/usr/bin/vim]... [--] <command line>\n"
 "\n"
 "Mode defaults to non-interactive, interactive mode is selected if both stdin "
 "AND stdout are terminals (stderr is ignored)."
@@ -397,16 +397,16 @@ msgstr ""
 "\n"
 "lxc exec <Container> [--env EDITOR=/usr/bin/vim]... <Befehl>\n"
 
-#: lxc/image.go:339
+#: lxc/image.go:348
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:341
+#: lxc/image.go:350
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:269 lxc/image.go:605 lxc/image.go:634
+#: lxc/config.go:272 lxc/image.go:619 lxc/image.go:648
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -414,12 +414,12 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt"
 msgstr ""
 
-#: lxc/image.go:328
+#: lxc/image.go:337
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Fingerabdruck: %s\n"
 
-#: lxc/finger.go:17
+#: lxc/finger.go:15
 #, fuzzy
 msgid ""
 "Fingers the LXD instance to check if it is up and working.\n"
@@ -430,7 +430,7 @@ msgstr ""
 "\n"
 "lxc finger <remote>\n"
 
-#: lxc/action.go:37
+#: lxc/action.go:42 lxc/action.go:43
 msgid "Force the container to shutdown."
 msgstr "Herunterfahren des Containers erzwingen."
 
@@ -438,7 +438,7 @@ msgstr "Herunterfahren des Containers erzwingen."
 msgid "Force the removal of stopped containers."
 msgstr ""
 
-#: lxc/main.go:56
+#: lxc/main.go:42
 msgid "Force using the local unix socket."
 msgstr ""
 
@@ -446,46 +446,51 @@ msgstr ""
 msgid "Format"
 msgstr ""
 
-#: lxc/main.go:138
+#: lxc/main.go:124
 #, fuzzy
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Generiere Nutzerzertifikat. Dies kann wenige Minuten dauern...\n"
 
-#: lxc/list.go:376
+#: lxc/list.go:392
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:377
+#: lxc/list.go:393
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:271
+#: lxc/config.go:274
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:146
+#: lxc/main.go:132
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:57
+#: lxc/main.go:43
 msgid "Ignore aliases when determining what command to run."
 msgstr ""
 
-#: lxc/action.go:40
+#: lxc/action.go:46
 #, fuzzy
 msgid "Ignore the container state (only for start)."
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/image.go:273
+#: lxc/image.go:282
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:419
+#: lxc/image.go:433
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
+#: lxc/image.go:420
+#, c-format
+msgid "Importing the image: %s"
+msgstr ""
+
 #: lxc/init.go:73
 #, fuzzy
 msgid ""
@@ -514,16 +519,21 @@ msgstr ""
 "Beispiel:\n"
 "lxc launch ubuntu u1\n"
 
-#: lxc/remote.go:122
+#: lxc/remote.go:121
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
 
+#: lxc/config.go:253
+#, fuzzy
+msgid "Invalid certificate"
+msgstr "Akzeptiere Zertifikat"
+
 #: lxc/init.go:30 lxc/init.go:35
 msgid "Invalid configuration key"
 msgstr ""
 
-#: lxc/file.go:190
+#: lxc/file.go:195
 #, c-format
 msgid "Invalid source %s"
 msgstr "Ungültige Quelle %s"
@@ -533,15 +543,15 @@ msgstr "Ungültige Quelle %s"
 msgid "Invalid target %s"
 msgstr "Ungültiges Ziel %s"
 
-#: lxc/info.go:121
+#: lxc/info.go:124
 msgid "Ips:"
 msgstr ""
 
-#: lxc/image.go:156
+#: lxc/image.go:165
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:35
+#: lxc/main.go:27
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
@@ -642,11 +652,11 @@ msgstr ""
 "* \"security.privileged=1\" listet alle privilegierten Container\n"
 "* \"s.privileged=1\" ebenfalls\n"
 
-#: lxc/info.go:225
+#: lxc/info.go:228
 msgid "Log:"
 msgstr ""
 
-#: lxc/image.go:154
+#: lxc/image.go:163
 msgid "Make image public"
 msgstr "Veröffentliche Abbild"
 
@@ -655,7 +665,7 @@ msgstr "Veröffentliche Abbild"
 msgid "Make the image public"
 msgstr "Veröffentliche Abbild"
 
-#: lxc/profile.go:48
+#: lxc/profile.go:47
 #, fuzzy
 msgid ""
 "Manage configuration profiles.\n"
@@ -667,13 +677,13 @@ msgid ""
 "specified remote.\n"
 "lxc profile get <profile> <key>                Get profile configuration.\n"
 "lxc profile set <profile> <key> <value>        Set profile configuration.\n"
+"lxc profile unset <profile> <key>              Unset profile configuration.\n"
 "lxc profile delete <profile>                   Delete a profile.\n"
 "lxc profile edit <profile>\n"
 "    Edit profile, either by launching external editor or reading STDIN.\n"
 "    Example: lxc profile edit <profile> # launch editor\n"
-"             cat profile.yml | lxc profile edit <profile> # read from "
-"profile.yml\n"
-"\n"
+"             cat profile.yaml | lxc profile edit <profile> # read from "
+"profile.yaml\n"
 "lxc profile apply <container> <profiles>\n"
 "    Apply a comma-separated list of profiles to a container, in order.\n"
 "    All profiles passed in this call (and only those) will be applied\n"
@@ -682,8 +692,6 @@ msgid ""
 "             lxc profile apply foo default # Only default is active\n"
 "             lxc profile apply '' # no profiles are applied anymore\n"
 "             lxc profile apply bar,default # Apply default second now\n"
-"lxc profile apply-add <container> <profile>\n"
-"lxc profile apply-remove <container> <profile>\n"
 "\n"
 "Devices:\n"
 "lxc profile device list <profile>                                   List "
@@ -737,7 +745,7 @@ msgstr ""
 "Containern hinzu,\n"
 "    die dieses Profil verwenden.\n"
 
-#: lxc/config.go:58
+#: lxc/config.go:57
 #, fuzzy
 msgid ""
 "Manage configuration.\n"
@@ -770,8 +778,8 @@ msgid ""
 "    Edit configuration, either by launching external editor or reading "
 "STDIN.\n"
 "    Example: lxc config edit <container> # launch editor\n"
-"             cat config.yml | lxc config edit <config> # read from config."
-"yml\n"
+"             cat config.yaml | lxc config edit <config> # read from config."
+"yaml\n"
 "\n"
 "lxc config trust list [remote]                                              "
 "List all trusted certs.\n"
@@ -782,7 +790,7 @@ msgid ""
 "\n"
 "Examples:\n"
 "To mount host's /share/c1 onto /opt in the container:\n"
-"   lxc config device add [remote:]container1 <device-name> disk source=/"
+"    lxc config device add [remote:]container1 <device-name> disk source=/"
 "share/c1 path=opt\n"
 "\n"
 "To set an lxc config value:\n"
@@ -851,7 +859,7 @@ msgstr ""
 "<Quelle> bei pull und <Ziel> bei push sind jeweils von der Form <Container "
 "Name>/<Pfad>\n"
 
-#: lxc/remote.go:39
+#: lxc/remote.go:38
 #, fuzzy
 msgid ""
 "Manage remote LXD servers.\n"
@@ -923,9 +931,18 @@ msgid ""
 "lxc image delete [remote:]<image>\n"
 "    Delete an image from the LXD image store.\n"
 "\n"
-"lxc image export [remote:]<image>\n"
+"lxc image export [remote:]<image> [target]\n"
 "    Export an image from the LXD image store into a distributable tarball.\n"
 "\n"
+"    The output target is optional and defaults to the working directory.\n"
+"    The target may be an existing directory, file name, or \"-\" to specify\n"
+"    stdout.  The target MUST be a directory when exporting a split image.\n"
+"    If the target is a directory, the image's name (each part's name for\n"
+"    split images) as found in the database will be used for the exported\n"
+"    image.  If the target is a file (not a directory and not stdout), then\n"
+"    the appropriate extension will be appended to the provided file name\n"
+"    based on the algorithm used to compress the image. \n"
+"\n"
 "lxc image info [remote:]<image>\n"
 "    Print everything LXD knows about a given image.\n"
 "\n"
@@ -940,7 +957,7 @@ msgid ""
 "lxc image edit [remote:]<image>\n"
 "    Edit image, either by launching external editor or reading STDIN.\n"
 "    Example: lxc image edit <image> # launch editor\n"
-"             cat image.yml | lxc image edit <image> # read from image.yml\n"
+"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
 "\n"
 "lxc image alias create [remote:]<alias> <fingerprint>\n"
 "    Create a new alias for an existing image.\n"
@@ -953,15 +970,15 @@ msgid ""
 "image alias name.\n"
 msgstr ""
 
-#: lxc/info.go:147
+#: lxc/info.go:150
 msgid "Memory (current)"
 msgstr ""
 
-#: lxc/info.go:151
+#: lxc/info.go:154
 msgid "Memory (peak)"
 msgstr ""
 
-#: lxc/help.go:86
+#: lxc/help.go:87
 msgid "Missing summary."
 msgstr "Fehlende Zusammenfassung."
 
@@ -980,12 +997,12 @@ msgid ""
 "lxc monitor --type=logging"
 msgstr ""
 
-#: lxc/file.go:178
+#: lxc/file.go:183
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "Mehr als eine Datei herunterzuladen, aber das Ziel ist kein Verzeichnis"
 
-#: lxc/move.go:17
+#: lxc/move.go:16
 #, fuzzy
 msgid ""
 "Move containers within or in between lxd instances.\n"
@@ -1001,16 +1018,16 @@ msgstr ""
 "\n"
 "lxc move <Quelle> <Ziel>\n"
 
-#: lxc/action.go:63
+#: lxc/action.go:69
 #, fuzzy
 msgid "Must supply container name for: "
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
-#: lxc/list.go:380 lxc/remote.go:363
+#: lxc/list.go:396 lxc/remote.go:352
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:337 lxc/remote.go:342
+#: lxc/remote.go:326 lxc/remote.go:331
 msgid "NO"
 msgstr ""
 
@@ -1019,32 +1036,32 @@ msgstr ""
 msgid "Name: %s"
 msgstr ""
 
-#: lxc/image.go:157 lxc/publish.go:33
+#: lxc/image.go:166 lxc/publish.go:33
 msgid "New alias to define at target"
 msgstr ""
 
-#: lxc/config.go:281
+#: lxc/config.go:284
 #, fuzzy
 msgid "No certificate provided to add"
 msgstr "Kein Zertifikat zum hinzufügen bereitgestellt"
 
-#: lxc/config.go:304
+#: lxc/config.go:307
 msgid "No fingerprint specified."
 msgstr "Kein Fingerabdruck angegeben."
 
-#: lxc/remote.go:107
+#: lxc/remote.go:106
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:411
+#: lxc/image.go:425
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
-#: lxc/help.go:63 lxc/main.go:122
+#: lxc/help.go:63 lxc/main.go:108
 msgid "Options:"
 msgstr ""
 
-#: lxc/image.go:506
+#: lxc/image.go:520
 #, c-format
 msgid "Output is in %s"
 msgstr ""
@@ -1053,49 +1070,49 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:464
+#: lxc/list.go:480
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:381
+#: lxc/list.go:397
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:382
+#: lxc/list.go:398
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:365
+#: lxc/remote.go:354
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:606 lxc/remote.go:366
+#: lxc/image.go:620 lxc/remote.go:355
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:174
+#: lxc/info.go:177
 msgid "Packets received"
 msgstr ""
 
-#: lxc/info.go:175
+#: lxc/info.go:178
 msgid "Packets sent"
 msgstr ""
 
-#: lxc/help.go:69
+#: lxc/help.go:70
 #, fuzzy
 msgid "Path to an alternate client configuration directory."
 msgstr "Alternatives config Verzeichnis."
 
-#: lxc/help.go:70
+#: lxc/help.go:71
 #, fuzzy
 msgid "Path to an alternate server directory."
 msgstr "Alternatives config Verzeichnis."
 
-#: lxc/main.go:39
-msgid "Permisson denied, are you in the lxd group?"
+#: lxc/main.go:31
+msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
-#: lxc/info.go:103
+#: lxc/info.go:106
 #, c-format
 msgid "Pid: %d"
 msgstr ""
@@ -1111,11 +1128,11 @@ msgstr ""
 "\n"
 "lxd help [—all]\n"
 
-#: lxc/profile.go:216
+#: lxc/profile.go:191
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:501 lxc/config.go:566 lxc/image.go:688
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:702
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -1142,56 +1159,46 @@ msgstr ""
 "\n"
 "lxc version\n"
 
-#: lxc/info.go:127
+#: lxc/info.go:130
 #, fuzzy, c-format
 msgid "Processes: %d"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:272
+#: lxc/profile.go:228
 #, fuzzy, c-format
-msgid "Profile %s added to %s"
+msgid "Profile %s applied to %s"
 msgstr "Profil %s wurde auf %s angewandt\n"
 
-#: lxc/profile.go:167
+#: lxc/profile.go:142
 #, fuzzy, c-format
 msgid "Profile %s created"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:237
+#: lxc/profile.go:212
 #, fuzzy, c-format
 msgid "Profile %s deleted"
 msgstr "Profil %s gelöscht\n"
 
-#: lxc/profile.go:303
-#, fuzzy, c-format
-msgid "Profile %s removed from %s"
-msgstr "Gerät %s wurde von %s entfernt\n"
-
 #: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
 #, fuzzy
 msgid "Profile to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/profile.go:253
-#, fuzzy, c-format
-msgid "Profiles %s applied to %s"
-msgstr "Profil %s wurde auf %s angewandt\n"
-
-#: lxc/info.go:101
+#: lxc/info.go:104
 #, fuzzy, c-format
 msgid "Profiles: %s"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/image.go:343
+#: lxc/image.go:352
 #, fuzzy
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
 
-#: lxc/remote.go:56
+#: lxc/remote.go:55
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:331
+#: lxc/image.go:340
 #, fuzzy, c-format
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
@@ -1204,10 +1211,15 @@ msgid ""
 "value]..."
 msgstr ""
 
-#: lxc/remote.go:54
+#: lxc/remote.go:53
 msgid "Remote admin password"
 msgstr "Entferntes Administrator Passwort"
 
+#: lxc/info.go:91
+#, c-format
+msgid "Remote: %s"
+msgstr ""
+
 #: lxc/delete.go:42
 #, c-format
 msgid "Remove %s (yes/no): "
@@ -1217,7 +1229,7 @@ msgstr ""
 msgid "Require user confirmation."
 msgstr ""
 
-#: lxc/info.go:124
+#: lxc/info.go:127
 msgid "Resources:"
 msgstr ""
 
@@ -1226,32 +1238,32 @@ msgstr ""
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:609
+#: lxc/image.go:623
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:383
+#: lxc/list.go:399
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:384
+#: lxc/list.go:400
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:356
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:214
+#: lxc/remote.go:203
 msgid "Server certificate NACKed by user"
 msgstr "Server Zertifikat vom Benutzer nicht akzeptiert"
 
-#: lxc/remote.go:276
+#: lxc/remote.go:265
 msgid "Server doesn't trust us after adding our cert"
 msgstr ""
 "Der Server vertraut uns nicht nachdem er unser Zertifikat hinzugefügt hat"
 
-#: lxc/remote.go:55
+#: lxc/remote.go:54
 msgid "Server protocol (lxd or simplestreams)"
 msgstr ""
 
@@ -1285,20 +1297,24 @@ msgstr "Setzt die uid der Datei beim Übertragen"
 msgid "Show all commands (not just interesting ones)"
 msgstr "Zeigt alle Befehle (nicht nur die interessanten)"
 
+#: lxc/help.go:67
+msgid "Show client version."
+msgstr ""
+
 #: lxc/info.go:36
 msgid "Show the container's last 100 log lines?"
 msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 
-#: lxc/image.go:329
+#: lxc/image.go:338
 #, fuzzy, c-format
 msgid "Size: %.2fMB"
 msgstr "Größe: %.2vMB\n"
 
-#: lxc/info.go:194
+#: lxc/info.go:197
 msgid "Snapshots:"
 msgstr ""
 
-#: lxc/image.go:353
+#: lxc/image.go:362
 msgid "Source:"
 msgstr ""
 
@@ -1307,7 +1323,7 @@ msgstr ""
 msgid "Starting %s"
 msgstr ""
 
-#: lxc/info.go:95
+#: lxc/info.go:98
 #, c-format
 msgid "Status: %s"
 msgstr ""
@@ -1320,20 +1336,20 @@ msgstr ""
 msgid "Stopping container failed!"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/action.go:39
+#: lxc/action.go:45
 #, fuzzy
 msgid "Store the container state (only for stop)."
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/info.go:155
+#: lxc/info.go:158
 msgid "Swap (current)"
 msgstr ""
 
-#: lxc/info.go:159
+#: lxc/info.go:162
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:385
+#: lxc/list.go:401
 msgid "TYPE"
 msgstr ""
 
@@ -1347,8 +1363,8 @@ msgid ""
 "restarted."
 msgstr ""
 
-#: lxc/config.go:645 lxc/config.go:657 lxc/config.go:690 lxc/config.go:708
-#: lxc/config.go:746 lxc/config.go:764
+#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738
+#: lxc/config.go:776 lxc/config.go:794
 #, fuzzy
 msgid "The device doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
@@ -1358,59 +1374,63 @@ msgstr "entfernte Instanz %s existiert nicht"
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
 
+#: lxc/main.go:176
+msgid "The opposite of `lxc pause` is `lxc start`."
+msgstr ""
+
 #: lxc/publish.go:62
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
 
-#: lxc/action.go:36
+#: lxc/action.go:41
 msgid "Time to wait for the container before killing it."
 msgstr "Wartezeit bevor der Container gestoppt wird."
 
-#: lxc/image.go:332
+#: lxc/image.go:341
 #, fuzzy
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
 
-#: lxc/main.go:147
+#: lxc/main.go:133
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:402
+#: lxc/image.go:412
 #, c-format
 msgid "Transferring image: %d%%"
 msgstr ""
 
-#: lxc/action.go:93 lxc/launch.go:132
+#: lxc/action.go:99 lxc/launch.go:137
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
 
-#: lxc/info.go:97
+#: lxc/info.go:100
 msgid "Type: ephemeral"
 msgstr ""
 
-#: lxc/info.go:99
+#: lxc/info.go:102
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:610
+#: lxc/image.go:624
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:364
+#: lxc/remote.go:353
 msgid "URL"
 msgstr ""
 
-#: lxc/remote.go:82
+#: lxc/remote.go:81
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:337
+#: lxc/image.go:346
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
 
-#: lxc/main.go:122
+#: lxc/main.go:108
 #, fuzzy, c-format
 msgid "Usage: %s"
 msgstr ""
@@ -1442,15 +1462,15 @@ msgstr ""
 msgid "Whether or not to snapshot the container's running state"
 msgstr "Zustand des laufenden Containers sichern oder nicht"
 
-#: lxc/config.go:33
+#: lxc/config.go:32
 msgid "Whether to show the expanded configuration"
 msgstr ""
 
-#: lxc/remote.go:339 lxc/remote.go:344
+#: lxc/remote.go:328 lxc/remote.go:333
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:66
+#: lxc/main.go:52
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
@@ -1459,19 +1479,19 @@ msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "Falsche Anzahl an Objekten im Abbild, Container oder Sicherungspunkt gelesen."
 
-#: lxc/action.go:89
+#: lxc/action.go:95
 msgid "bad result type from action"
 msgstr ""
 
-#: lxc/copy.go:78
+#: lxc/copy.go:99
 msgid "can't copy to the same container name"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/remote.go:327
+#: lxc/remote.go:316
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:353
+#: lxc/remote.go:342
 msgid "default"
 msgstr ""
 
@@ -1479,20 +1499,20 @@ msgstr ""
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
-#: lxc/image.go:323
+#: lxc/image.go:332
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:325
+#: lxc/image.go:334
 msgid "enabled"
 msgstr ""
 
-#: lxc/main.go:25 lxc/main.go:159
+#: lxc/main.go:22 lxc/main.go:145
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "Fehler: %v\n"
 
-#: lxc/help.go:40 lxc/main.go:117
+#: lxc/help.go:40 lxc/main.go:103
 #, fuzzy, c-format
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
@@ -1501,66 +1521,66 @@ msgstr "Fehler: unbekannter Befehl: %s\n"
 msgid "got bad version"
 msgstr "Versionskonflikt"
 
-#: lxc/image.go:318 lxc/image.go:586
+#: lxc/image.go:327 lxc/image.go:600
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:101
+#: lxc/copy.go:122
 msgid "not all the profiles from the source exist on the target"
 msgstr "nicht alle Profile der Quelle sind am Ziel vorhanden."
 
-#: lxc/remote.go:207
+#: lxc/remote.go:196
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "OK (y/n)? "
 
-#: lxc/main.go:266 lxc/main.go:270
+#: lxc/main.go:265 lxc/main.go:269
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:389
+#: lxc/remote.go:378
 #, c-format
 msgid "remote %s already exists"
 msgstr "entfernte Instanz %s existiert bereits"
 
-#: lxc/remote.go:319 lxc/remote.go:381 lxc/remote.go:416 lxc/remote.go:432
+#: lxc/remote.go:308 lxc/remote.go:370 lxc/remote.go:405 lxc/remote.go:421
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/remote.go:302
+#: lxc/remote.go:291
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "entfernte Instanz %s existiert als <%s>"
 
-#: lxc/remote.go:323 lxc/remote.go:385 lxc/remote.go:420
+#: lxc/remote.go:312 lxc/remote.go:374 lxc/remote.go:409
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
 
-#: lxc/info.go:205
+#: lxc/info.go:208
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:207
+#: lxc/info.go:210
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:201
+#: lxc/info.go:204
 #, c-format
 msgid "taken at %s"
 msgstr ""
 
-#: lxc/exec.go:166
+#: lxc/exec.go:163
 msgid "unreachable return reached"
 msgstr ""
 
-#: lxc/main.go:199
+#: lxc/main.go:205
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:45 lxc/image.go:320 lxc/image.go:590
+#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:604
 msgid "yes"
 msgstr ""
 
@@ -1569,6 +1589,14 @@ msgid "you must specify a source container name"
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
 #, fuzzy
+#~ msgid "Profile %s added to %s"
+#~ msgstr "Profil %s wurde auf %s angewandt\n"
+
+#, fuzzy
+#~ msgid "Profile %s removed from %s"
+#~ msgstr "Gerät %s wurde von %s entfernt\n"
+
+#, fuzzy
 #~ msgid "For example: 'lxd-images import ubuntu --alias ubuntu'."
 #~ msgstr "Zum Beispiel: 'lxd-images import ubuntu --alias ubuntu'.\n"
 
diff --git a/po/fr.po b/po/fr.po
index 90f536b97..dafba1fff 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-04-25 14:47-0500\n"
+"POT-Creation-Date: 2016-10-26 16:48-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -16,19 +16,19 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:140
+#: lxc/info.go:143
 msgid "  Disk usage:"
 msgstr ""
 
-#: lxc/info.go:163
+#: lxc/info.go:166
 msgid "  Memory usage:"
 msgstr ""
 
-#: lxc/info.go:180
+#: lxc/info.go:183
 msgid "  Network usage:"
 msgstr ""
 
-#: lxc/config.go:37
+#: lxc/config.go:36
 msgid ""
 "### This is a yaml representation of the configuration.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -59,7 +59,7 @@ msgid ""
 "###  description: My custom image"
 msgstr ""
 
-#: lxc/profile.go:27
+#: lxc/profile.go:26
 msgid ""
 "### This is a yaml representation of the profile.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -80,7 +80,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:583
+#: lxc/image.go:597
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -90,32 +90,32 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 
-#: lxc/profile.go:251
+#: lxc/profile.go:226
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:604 lxc/image.go:633
+#: lxc/image.go:618 lxc/image.go:647
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:608
+#: lxc/image.go:622
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:378
+#: lxc/list.go:394
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:53
+#: lxc/remote.go:52
 msgid "Accept certificate"
 msgstr ""
 
-#: lxc/remote.go:256
+#: lxc/remote.go:245
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
 
-#: lxc/image.go:347
+#: lxc/image.go:356
 msgid "Aliases:"
 msgstr ""
 
@@ -123,12 +123,12 @@ msgstr ""
 msgid "An environment variable of the form HOME=/home/foo"
 msgstr ""
 
-#: lxc/image.go:330 lxc/info.go:90
+#: lxc/image.go:339 lxc/info.go:93
 #, c-format
 msgid "Architecture: %s"
 msgstr ""
 
-#: lxc/image.go:351
+#: lxc/image.go:360
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
@@ -137,50 +137,50 @@ msgstr ""
 msgid "Available commands:"
 msgstr ""
 
-#: lxc/info.go:172
+#: lxc/info.go:175
 msgid "Bytes received"
 msgstr ""
 
-#: lxc/info.go:173
+#: lxc/info.go:176
 msgid "Bytes sent"
 msgstr ""
 
-#: lxc/config.go:270
+#: lxc/config.go:273
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:379
+#: lxc/list.go:395
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:114
+#: lxc/config.go:113
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr ""
 
-#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182
+#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:417
+#: lxc/profile.go:343
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:206
+#: lxc/remote.go:195
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %x"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/action.go:28
+#: lxc/action.go:33
 #, fuzzy, c-format
 msgid ""
 "Changes state of one or more containers to %s.\n"
 "\n"
-"lxc %s <name> [<name>...]"
+"lxc %s <name> [<name>...]%s"
 msgstr "Change l'état du conteneur à %s.\n"
 
-#: lxc/remote.go:279
+#: lxc/remote.go:268
 msgid "Client certificate stored at server: "
 msgstr "Certificat client enregistré avec le serveur: "
 
@@ -192,12 +192,12 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:215
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:701 lxc/profile.go:190
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
 
-#: lxc/main.go:37
+#: lxc/main.go:29
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
@@ -215,7 +215,7 @@ msgstr ""
 msgid "Container published with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/image.go:155
+#: lxc/image.go:164
 msgid "Copy aliases from source"
 msgstr ""
 
@@ -227,12 +227,12 @@ msgid ""
 "ephemeral|e]"
 msgstr ""
 
-#: lxc/image.go:268
+#: lxc/image.go:277
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:221
+#: lxc/remote.go:210
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
@@ -256,7 +256,7 @@ msgid ""
 "lxc snapshot u1 snap0"
 msgstr ""
 
-#: lxc/image.go:335 lxc/info.go:92
+#: lxc/image.go:344 lxc/info.go:95
 #, c-format
 msgid "Created: %s"
 msgstr ""
@@ -270,7 +270,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:607 lxc/image.go:635
+#: lxc/image.go:621 lxc/image.go:649
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -285,33 +285,33 @@ msgid ""
 "snapshots, ...)."
 msgstr ""
 
-#: lxc/config.go:617
+#: lxc/config.go:647
 #, c-format
 msgid "Device %s added to %s"
 msgstr ""
 
-#: lxc/config.go:804
+#: lxc/config.go:834
 #, c-format
 msgid "Device %s removed from %s"
 msgstr ""
 
-#: lxc/list.go:462
+#: lxc/list.go:478
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:272
+#: lxc/config.go:275
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:55
+#: lxc/main.go:41
 msgid "Enables debug mode."
 msgstr "Active le mode de déboguage."
 
-#: lxc/main.go:54
+#: lxc/main.go:40
 msgid "Enables verbose mode."
 msgstr "Active le mode verbeux."
 
-#: lxc/help.go:68
+#: lxc/help.go:69
 msgid "Environment:"
 msgstr ""
 
@@ -330,22 +330,22 @@ msgid ""
 "Execute the specified command in a container.\n"
 "\n"
 "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
-"EDITOR=/usr/bin/vim]... <command>\n"
+"EDITOR=/usr/bin/vim]... [--] <command line>\n"
 "\n"
 "Mode defaults to non-interactive, interactive mode is selected if both stdin "
 "AND stdout are terminals (stderr is ignored)."
 msgstr "Exécute la commande spécifiée dans un conteneur.\n"
 
-#: lxc/image.go:339
+#: lxc/image.go:348
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:341
+#: lxc/image.go:350
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:269 lxc/image.go:605 lxc/image.go:634
+#: lxc/config.go:272 lxc/image.go:619 lxc/image.go:648
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -353,12 +353,12 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt"
 msgstr ""
 
-#: lxc/image.go:328
+#: lxc/image.go:337
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/finger.go:17
+#: lxc/finger.go:15
 #, fuzzy
 msgid ""
 "Fingers the LXD instance to check if it is up and working.\n"
@@ -366,7 +366,7 @@ msgid ""
 "lxc finger <remote>"
 msgstr "Contacte LXD pour voir s'il est fonctionel.\n"
 
-#: lxc/action.go:37
+#: lxc/action.go:42 lxc/action.go:43
 msgid "Force the container to shutdown."
 msgstr "Force l'arrêt du conteneur."
 
@@ -374,7 +374,7 @@ msgstr "Force l'arrêt du conteneur."
 msgid "Force the removal of stopped containers."
 msgstr ""
 
-#: lxc/main.go:56
+#: lxc/main.go:42
 msgid "Force using the local unix socket."
 msgstr ""
 
@@ -382,46 +382,51 @@ msgstr ""
 msgid "Format"
 msgstr ""
 
-#: lxc/main.go:138
+#: lxc/main.go:124
 #, fuzzy
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Géneration d'un certificat client. Ceci peut prendre une minute...\n"
 
-#: lxc/list.go:376
+#: lxc/list.go:392
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:377
+#: lxc/list.go:393
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:271
+#: lxc/config.go:274
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:146
+#: lxc/main.go:132
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:57
+#: lxc/main.go:43
 msgid "Ignore aliases when determining what command to run."
 msgstr ""
 
-#: lxc/action.go:40
+#: lxc/action.go:46
 #, fuzzy
 msgid "Ignore the container state (only for start)."
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/image.go:273
+#: lxc/image.go:282
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:419
+#: lxc/image.go:433
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
+#: lxc/image.go:420
+#, c-format
+msgid "Importing the image: %s"
+msgstr ""
+
 #: lxc/init.go:73
 msgid ""
 "Initialize a container from a particular image.\n"
@@ -438,17 +443,22 @@ msgid ""
 "lxc init ubuntu u1"
 msgstr ""
 
-#: lxc/remote.go:122
+#: lxc/remote.go:121
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
 
+#: lxc/config.go:253
+#, fuzzy
+msgid "Invalid certificate"
+msgstr "Gérer la configuration.\n"
+
 #: lxc/init.go:30 lxc/init.go:35
 #, fuzzy
 msgid "Invalid configuration key"
 msgstr "Gérer la configuration.\n"
 
-#: lxc/file.go:190
+#: lxc/file.go:195
 #, c-format
 msgid "Invalid source %s"
 msgstr "Source invalide %s"
@@ -458,15 +468,15 @@ msgstr "Source invalide %s"
 msgid "Invalid target %s"
 msgstr "Destination invalide %s"
 
-#: lxc/info.go:121
+#: lxc/info.go:124
 msgid "Ips:"
 msgstr ""
 
-#: lxc/image.go:156
+#: lxc/image.go:165
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:35
+#: lxc/main.go:27
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
@@ -533,11 +543,11 @@ msgid ""
 "Fast column layout: nsacPt"
 msgstr ""
 
-#: lxc/info.go:225
+#: lxc/info.go:228
 msgid "Log:"
 msgstr ""
 
-#: lxc/image.go:154
+#: lxc/image.go:163
 msgid "Make image public"
 msgstr ""
 
@@ -545,7 +555,7 @@ msgstr ""
 msgid "Make the image public"
 msgstr ""
 
-#: lxc/profile.go:48
+#: lxc/profile.go:47
 msgid ""
 "Manage configuration profiles.\n"
 "\n"
@@ -556,13 +566,13 @@ msgid ""
 "specified remote.\n"
 "lxc profile get <profile> <key>                Get profile configuration.\n"
 "lxc profile set <profile> <key> <value>        Set profile configuration.\n"
+"lxc profile unset <profile> <key>              Unset profile configuration.\n"
 "lxc profile delete <profile>                   Delete a profile.\n"
 "lxc profile edit <profile>\n"
 "    Edit profile, either by launching external editor or reading STDIN.\n"
 "    Example: lxc profile edit <profile> # launch editor\n"
-"             cat profile.yml | lxc profile edit <profile> # read from "
-"profile.yml\n"
-"\n"
+"             cat profile.yaml | lxc profile edit <profile> # read from "
+"profile.yaml\n"
 "lxc profile apply <container> <profiles>\n"
 "    Apply a comma-separated list of profiles to a container, in order.\n"
 "    All profiles passed in this call (and only those) will be applied\n"
@@ -571,8 +581,6 @@ msgid ""
 "             lxc profile apply foo default # Only default is active\n"
 "             lxc profile apply '' # no profiles are applied anymore\n"
 "             lxc profile apply bar,default # Apply default second now\n"
-"lxc profile apply-add <container> <profile>\n"
-"lxc profile apply-remove <container> <profile>\n"
 "\n"
 "Devices:\n"
 "lxc profile device list <profile>                                   List "
@@ -593,7 +601,7 @@ msgid ""
 "    using the specified profile."
 msgstr ""
 
-#: lxc/config.go:58
+#: lxc/config.go:57
 msgid ""
 "Manage configuration.\n"
 "\n"
@@ -625,8 +633,8 @@ msgid ""
 "    Edit configuration, either by launching external editor or reading "
 "STDIN.\n"
 "    Example: lxc config edit <container> # launch editor\n"
-"             cat config.yml | lxc config edit <config> # read from config."
-"yml\n"
+"             cat config.yaml | lxc config edit <config> # read from config."
+"yaml\n"
 "\n"
 "lxc config trust list [remote]                                              "
 "List all trusted certs.\n"
@@ -637,7 +645,7 @@ msgid ""
 "\n"
 "Examples:\n"
 "To mount host's /share/c1 onto /opt in the container:\n"
-"   lxc config device add [remote:]container1 <device-name> disk source=/"
+"    lxc config device add [remote:]container1 <device-name> disk source=/"
 "share/c1 path=opt\n"
 "\n"
 "To set an lxc config value:\n"
@@ -665,7 +673,7 @@ msgid ""
 "case of edit are <container name>/<path>"
 msgstr ""
 
-#: lxc/remote.go:39
+#: lxc/remote.go:38
 msgid ""
 "Manage remote LXD servers.\n"
 "\n"
@@ -720,9 +728,18 @@ msgid ""
 "lxc image delete [remote:]<image>\n"
 "    Delete an image from the LXD image store.\n"
 "\n"
-"lxc image export [remote:]<image>\n"
+"lxc image export [remote:]<image> [target]\n"
 "    Export an image from the LXD image store into a distributable tarball.\n"
 "\n"
+"    The output target is optional and defaults to the working directory.\n"
+"    The target may be an existing directory, file name, or \"-\" to specify\n"
+"    stdout.  The target MUST be a directory when exporting a split image.\n"
+"    If the target is a directory, the image's name (each part's name for\n"
+"    split images) as found in the database will be used for the exported\n"
+"    image.  If the target is a file (not a directory and not stdout), then\n"
+"    the appropriate extension will be appended to the provided file name\n"
+"    based on the algorithm used to compress the image. \n"
+"\n"
 "lxc image info [remote:]<image>\n"
 "    Print everything LXD knows about a given image.\n"
 "\n"
@@ -737,7 +754,7 @@ msgid ""
 "lxc image edit [remote:]<image>\n"
 "    Edit image, either by launching external editor or reading STDIN.\n"
 "    Example: lxc image edit <image> # launch editor\n"
-"             cat image.yml | lxc image edit <image> # read from image.yml\n"
+"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
 "\n"
 "lxc image alias create [remote:]<alias> <fingerprint>\n"
 "    Create a new alias for an existing image.\n"
@@ -750,15 +767,15 @@ msgid ""
 "image alias name.\n"
 msgstr ""
 
-#: lxc/info.go:147
+#: lxc/info.go:150
 msgid "Memory (current)"
 msgstr ""
 
-#: lxc/info.go:151
+#: lxc/info.go:154
 msgid "Memory (peak)"
 msgstr ""
 
-#: lxc/help.go:86
+#: lxc/help.go:87
 msgid "Missing summary."
 msgstr "Sommaire manquant."
 
@@ -777,12 +794,12 @@ msgid ""
 "lxc monitor --type=logging"
 msgstr ""
 
-#: lxc/file.go:178
+#: lxc/file.go:183
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "Plusieurs fichiers à télécharger mais la destination n'est pas un dossier"
 
-#: lxc/move.go:17
+#: lxc/move.go:16
 msgid ""
 "Move containers within or in between lxd instances.\n"
 "\n"
@@ -794,15 +811,15 @@ msgid ""
 "    Rename a local container.\n"
 msgstr ""
 
-#: lxc/action.go:63
+#: lxc/action.go:69
 msgid "Must supply container name for: "
 msgstr ""
 
-#: lxc/list.go:380 lxc/remote.go:363
+#: lxc/list.go:396 lxc/remote.go:352
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:337 lxc/remote.go:342
+#: lxc/remote.go:326 lxc/remote.go:331
 msgid "NO"
 msgstr ""
 
@@ -811,33 +828,33 @@ msgstr ""
 msgid "Name: %s"
 msgstr ""
 
-#: lxc/image.go:157 lxc/publish.go:33
+#: lxc/image.go:166 lxc/publish.go:33
 msgid "New alias to define at target"
 msgstr ""
 
-#: lxc/config.go:281
+#: lxc/config.go:284
 #, fuzzy
 msgid "No certificate provided to add"
 msgstr "Un certificat n'a pas été fournis"
 
-#: lxc/config.go:304
+#: lxc/config.go:307
 msgid "No fingerprint specified."
 msgstr "Aucune empreinte n'a été spécifié."
 
-#: lxc/remote.go:107
+#: lxc/remote.go:106
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:411
+#: lxc/image.go:425
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
-#: lxc/help.go:63 lxc/main.go:122
+#: lxc/help.go:63 lxc/main.go:108
 #, fuzzy
 msgid "Options:"
 msgstr "Opération %s"
 
-#: lxc/image.go:506
+#: lxc/image.go:520
 #, c-format
 msgid "Output is in %s"
 msgstr ""
@@ -846,49 +863,49 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:464
+#: lxc/list.go:480
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:381
+#: lxc/list.go:397
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:382
+#: lxc/list.go:398
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:365
+#: lxc/remote.go:354
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:606 lxc/remote.go:366
+#: lxc/image.go:620 lxc/remote.go:355
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:174
+#: lxc/info.go:177
 msgid "Packets received"
 msgstr ""
 
-#: lxc/info.go:175
+#: lxc/info.go:178
 msgid "Packets sent"
 msgstr ""
 
-#: lxc/help.go:69
+#: lxc/help.go:70
 #, fuzzy
 msgid "Path to an alternate client configuration directory."
 msgstr "Dossier de configuration alternatif."
 
-#: lxc/help.go:70
+#: lxc/help.go:71
 #, fuzzy
 msgid "Path to an alternate server directory."
 msgstr "Dossier de configuration alternatif."
 
-#: lxc/main.go:39
-msgid "Permisson denied, are you in the lxd group?"
+#: lxc/main.go:31
+msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
-#: lxc/info.go:103
+#: lxc/info.go:106
 #, c-format
 msgid "Pid: %d"
 msgstr ""
@@ -901,11 +918,11 @@ msgid ""
 "lxd help [--all]"
 msgstr "Explique comment utiliser LXD.\n"
 
-#: lxc/profile.go:216
+#: lxc/profile.go:191
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:501 lxc/config.go:566 lxc/image.go:688
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:702
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -929,54 +946,44 @@ msgid ""
 "lxc version"
 msgstr "Montre le numéro de version de LXD.\n"
 
-#: lxc/info.go:127
+#: lxc/info.go:130
 #, fuzzy, c-format
 msgid "Processes: %d"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:272
+#: lxc/profile.go:228
 #, fuzzy, c-format
-msgid "Profile %s added to %s"
+msgid "Profile %s applied to %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:167
+#: lxc/profile.go:142
 #, c-format
 msgid "Profile %s created"
 msgstr ""
 
-#: lxc/profile.go:237
+#: lxc/profile.go:212
 #, c-format
 msgid "Profile %s deleted"
 msgstr ""
 
-#: lxc/profile.go:303
-#, c-format
-msgid "Profile %s removed from %s"
-msgstr ""
-
 #: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
 msgid "Profile to apply to the new container"
 msgstr ""
 
-#: lxc/profile.go:253
-#, c-format
-msgid "Profiles %s applied to %s"
-msgstr ""
-
-#: lxc/info.go:101
+#: lxc/info.go:104
 #, fuzzy, c-format
 msgid "Profiles: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:343
+#: lxc/image.go:352
 msgid "Properties:"
 msgstr ""
 
-#: lxc/remote.go:56
+#: lxc/remote.go:55
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:331
+#: lxc/image.go:340
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -989,10 +996,15 @@ msgid ""
 "value]..."
 msgstr ""
 
-#: lxc/remote.go:54
+#: lxc/remote.go:53
 msgid "Remote admin password"
 msgstr ""
 
+#: lxc/info.go:91
+#, c-format
+msgid "Remote: %s"
+msgstr ""
+
 #: lxc/delete.go:42
 #, c-format
 msgid "Remove %s (yes/no): "
@@ -1002,7 +1014,7 @@ msgstr ""
 msgid "Require user confirmation."
 msgstr ""
 
-#: lxc/info.go:124
+#: lxc/info.go:127
 msgid "Resources:"
 msgstr ""
 
@@ -1011,31 +1023,31 @@ msgstr ""
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:609
+#: lxc/image.go:623
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:383
+#: lxc/list.go:399
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:384
+#: lxc/list.go:400
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:356
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:214
+#: lxc/remote.go:203
 msgid "Server certificate NACKed by user"
 msgstr "Le certificat serveur a été rejeté par l'utilisateur"
 
-#: lxc/remote.go:276
+#: lxc/remote.go:265
 msgid "Server doesn't trust us after adding our cert"
 msgstr "Identification refuse après l'ajout du certificat client"
 
-#: lxc/remote.go:55
+#: lxc/remote.go:54
 msgid "Server protocol (lxd or simplestreams)"
 msgstr ""
 
@@ -1069,20 +1081,24 @@ msgstr "Définit le uid lors de l'envoi"
 msgid "Show all commands (not just interesting ones)"
 msgstr "Affiche toutes les comandes (pas seulement les intéresantes)"
 
+#: lxc/help.go:67
+msgid "Show client version."
+msgstr ""
+
 #: lxc/info.go:36
 msgid "Show the container's last 100 log lines?"
 msgstr ""
 
-#: lxc/image.go:329
+#: lxc/image.go:338
 #, c-format
 msgid "Size: %.2fMB"
 msgstr ""
 
-#: lxc/info.go:194
+#: lxc/info.go:197
 msgid "Snapshots:"
 msgstr ""
 
-#: lxc/image.go:353
+#: lxc/image.go:362
 msgid "Source:"
 msgstr ""
 
@@ -1091,7 +1107,7 @@ msgstr ""
 msgid "Starting %s"
 msgstr ""
 
-#: lxc/info.go:95
+#: lxc/info.go:98
 #, c-format
 msgid "Status: %s"
 msgstr ""
@@ -1104,20 +1120,20 @@ msgstr ""
 msgid "Stopping container failed!"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/action.go:39
+#: lxc/action.go:45
 #, fuzzy
 msgid "Store the container state (only for stop)."
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/info.go:155
+#: lxc/info.go:158
 msgid "Swap (current)"
 msgstr ""
 
-#: lxc/info.go:159
+#: lxc/info.go:162
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:385
+#: lxc/list.go:401
 msgid "TYPE"
 msgstr ""
 
@@ -1131,8 +1147,8 @@ msgid ""
 "restarted."
 msgstr ""
 
-#: lxc/config.go:645 lxc/config.go:657 lxc/config.go:690 lxc/config.go:708
-#: lxc/config.go:746 lxc/config.go:764
+#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738
+#: lxc/config.go:776 lxc/config.go:794
 #, fuzzy
 msgid "The device doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
@@ -1142,58 +1158,62 @@ msgstr "le serveur distant %s n'existe pas"
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
 
+#: lxc/main.go:176
+msgid "The opposite of `lxc pause` is `lxc start`."
+msgstr ""
+
 #: lxc/publish.go:62
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
 
-#: lxc/action.go:36
+#: lxc/action.go:41
 msgid "Time to wait for the container before killing it."
 msgstr "Temps d'attente avant de tuer le conteneur."
 
-#: lxc/image.go:332
+#: lxc/image.go:341
 msgid "Timestamps:"
 msgstr ""
 
-#: lxc/main.go:147
+#: lxc/main.go:133
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:402
+#: lxc/image.go:412
 #, c-format
 msgid "Transferring image: %d%%"
 msgstr ""
 
-#: lxc/action.go:93 lxc/launch.go:132
+#: lxc/action.go:99 lxc/launch.go:137
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
 
-#: lxc/info.go:97
+#: lxc/info.go:100
 msgid "Type: ephemeral"
 msgstr ""
 
-#: lxc/info.go:99
+#: lxc/info.go:102
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:610
+#: lxc/image.go:624
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:364
+#: lxc/remote.go:353
 msgid "URL"
 msgstr ""
 
-#: lxc/remote.go:82
+#: lxc/remote.go:81
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:337
+#: lxc/image.go:346
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
 
-#: lxc/main.go:122
+#: lxc/main.go:108
 #, fuzzy, c-format
 msgid "Usage: %s"
 msgstr ""
@@ -1228,15 +1248,15 @@ msgstr ""
 "Est-ce que l'état de fonctionement du conteneur doit être inclus dans "
 "l'instantané (snapshot)"
 
-#: lxc/config.go:33
+#: lxc/config.go:32
 msgid "Whether to show the expanded configuration"
 msgstr ""
 
-#: lxc/remote.go:339 lxc/remote.go:344
+#: lxc/remote.go:328 lxc/remote.go:333
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:66
+#: lxc/main.go:52
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
@@ -1245,19 +1265,19 @@ msgstr ""
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr "nombre de propriété invalide pour la ressource"
 
-#: lxc/action.go:89
+#: lxc/action.go:95
 msgid "bad result type from action"
 msgstr "mauvais type de réponse pour l'action!"
 
-#: lxc/copy.go:78
+#: lxc/copy.go:99
 msgid "can't copy to the same container name"
 msgstr ""
 
-#: lxc/remote.go:327
+#: lxc/remote.go:316
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:353
+#: lxc/remote.go:342
 msgid "default"
 msgstr ""
 
@@ -1266,20 +1286,20 @@ msgstr ""
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
 
-#: lxc/image.go:323
+#: lxc/image.go:332
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:325
+#: lxc/image.go:334
 msgid "enabled"
 msgstr ""
 
-#: lxc/main.go:25 lxc/main.go:159
+#: lxc/main.go:22 lxc/main.go:145
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "erreur: %v\n"
 
-#: lxc/help.go:40 lxc/main.go:117
+#: lxc/help.go:40 lxc/main.go:103
 #, fuzzy, c-format
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
@@ -1288,66 +1308,66 @@ msgstr "erreur: comande inconnue: %s\n"
 msgid "got bad version"
 msgstr "reçu une version invalide"
 
-#: lxc/image.go:318 lxc/image.go:586
+#: lxc/image.go:327 lxc/image.go:600
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:101
+#: lxc/copy.go:122
 msgid "not all the profiles from the source exist on the target"
 msgstr ""
 
-#: lxc/remote.go:207
+#: lxc/remote.go:196
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:266 lxc/main.go:270
+#: lxc/main.go:265 lxc/main.go:269
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:389
+#: lxc/remote.go:378
 #, c-format
 msgid "remote %s already exists"
 msgstr "le serveur distant %s existe déjà"
 
-#: lxc/remote.go:319 lxc/remote.go:381 lxc/remote.go:416 lxc/remote.go:432
+#: lxc/remote.go:308 lxc/remote.go:370 lxc/remote.go:405 lxc/remote.go:421
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/remote.go:302
+#: lxc/remote.go:291
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "le serveur distant %s existe en tant que <%s>"
 
-#: lxc/remote.go:323 lxc/remote.go:385 lxc/remote.go:420
+#: lxc/remote.go:312 lxc/remote.go:374 lxc/remote.go:409
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
 
-#: lxc/info.go:205
+#: lxc/info.go:208
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:207
+#: lxc/info.go:210
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:201
+#: lxc/info.go:204
 #, c-format
 msgid "taken at %s"
 msgstr ""
 
-#: lxc/exec.go:166
+#: lxc/exec.go:163
 msgid "unreachable return reached"
 msgstr "Un retour inacessible à été atteint"
 
-#: lxc/main.go:199
+#: lxc/main.go:205
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:45 lxc/image.go:320 lxc/image.go:590
+#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:604
 msgid "yes"
 msgstr ""
 
diff --git a/po/ja.po b/po/ja.po
index 4a65d634e..578db4d3e 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-04-25 14:47-0500\n"
+"POT-Creation-Date: 2016-10-26 16:48-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -16,19 +16,19 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:140
+#: lxc/info.go:143
 msgid "  Disk usage:"
 msgstr "  ディスク使用量:"
 
-#: lxc/info.go:163
+#: lxc/info.go:166
 msgid "  Memory usage:"
 msgstr "  メモリ消費量:"
 
-#: lxc/info.go:180
+#: lxc/info.go:183
 msgid "  Network usage:"
 msgstr "  ネットワーク使用状況:"
 
-#: lxc/config.go:37
+#: lxc/config.go:36
 msgid ""
 "### This is a yaml representation of the configuration.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -59,7 +59,7 @@ msgid ""
 "###  description: My custom image"
 msgstr ""
 
-#: lxc/profile.go:27
+#: lxc/profile.go:26
 msgid ""
 "### This is a yaml representation of the profile.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -80,7 +80,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:583
+#: lxc/image.go:597
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -89,32 +89,32 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' はスナップショットの名前には使用できません。"
 
-#: lxc/profile.go:251
+#: lxc/profile.go:226
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:604 lxc/image.go:633
+#: lxc/image.go:618 lxc/image.go:647
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:608
+#: lxc/image.go:622
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:378
+#: lxc/list.go:394
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:53
+#: lxc/remote.go:52
 msgid "Accept certificate"
 msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
 
-#: lxc/remote.go:256
+#: lxc/remote.go:245
 #, c-format
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
 
-#: lxc/image.go:347
+#: lxc/image.go:356
 msgid "Aliases:"
 msgstr "エイリアス:"
 
@@ -122,12 +122,12 @@ msgstr "エイリアス:"
 msgid "An environment variable of the form HOME=/home/foo"
 msgstr "環境変数を HOME=/home/foo の形式で指定します"
 
-#: lxc/image.go:330 lxc/info.go:90
+#: lxc/image.go:339 lxc/info.go:93
 #, c-format
 msgid "Architecture: %s"
 msgstr "アーキテクチャ: %s"
 
-#: lxc/image.go:351
+#: lxc/image.go:360
 #, c-format
 msgid "Auto update: %s"
 msgstr "自動更新: %s"
@@ -136,53 +136,53 @@ msgstr "自動更新: %s"
 msgid "Available commands:"
 msgstr "使用可能なコマンド:"
 
-#: lxc/info.go:172
+#: lxc/info.go:175
 msgid "Bytes received"
 msgstr "受信バイト数"
 
-#: lxc/info.go:173
+#: lxc/info.go:176
 msgid "Bytes sent"
 msgstr "送信バイト数"
 
-#: lxc/config.go:270
+#: lxc/config.go:273
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:379
+#: lxc/list.go:395
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:114
+#: lxc/config.go:113
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr "標準入力から読み込めません: %s"
 
-#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182
+#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr "キー '%s' が指定されていないので削除できません。"
 
-#: lxc/profile.go:417
+#: lxc/profile.go:343
 msgid "Cannot provide container name to list"
 msgstr "コンテナ名を取得できません"
 
-#: lxc/remote.go:206
+#: lxc/remote.go:195
 #, c-format
 msgid "Certificate fingerprint: %x"
 msgstr "証明書のフィンガープリント: %x"
 
-#: lxc/action.go:28
-#, c-format
+#: lxc/action.go:33
+#, fuzzy, c-format
 msgid ""
 "Changes state of one or more containers to %s.\n"
 "\n"
-"lxc %s <name> [<name>...]"
+"lxc %s <name> [<name>...]%s"
 msgstr ""
 "1つまたは複数のコンテナの状態を %s に変更します。\n"
 "\n"
 "lxc %s <name> [<name>...]"
 
-#: lxc/remote.go:279
+#: lxc/remote.go:268
 msgid "Client certificate stored at server: "
 msgstr "クライアント証明書がサーバに格納されました: "
 
@@ -194,12 +194,12 @@ msgstr "カラムレイアウト"
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:500 lxc/config.go:565 lxc/image.go:687 lxc/profile.go:215
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:701 lxc/profile.go:190
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
 
-#: lxc/main.go:37
+#: lxc/main.go:29
 msgid "Connection refused; is LXD running?"
 msgstr "接続が拒否されました。LXDが実行されていますか?"
 
@@ -217,7 +217,7 @@ msgstr "コンテナ名: %s"
 msgid "Container published with fingerprint: %s"
 msgstr "コンテナは以下のフィンガープリントで publish されます: %s"
 
-#: lxc/image.go:155
+#: lxc/image.go:164
 msgid "Copy aliases from source"
 msgstr "ソースからエイリアスをコピーしました"
 
@@ -233,12 +233,12 @@ msgstr ""
 "lxc copy [remote:]<source container> [remote:]<destination container> [--"
 "ephemeral|e]"
 
-#: lxc/image.go:268
+#: lxc/image.go:277
 #, c-format
 msgid "Copying the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/remote.go:221
+#: lxc/remote.go:210
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
@@ -274,7 +274,7 @@ msgstr ""
 "例:\n"
 "lxc snapshot u1 snap0"
 
-#: lxc/image.go:335 lxc/info.go:92
+#: lxc/image.go:344 lxc/info.go:95
 #, c-format
 msgid "Created: %s"
 msgstr "作成日時: %s"
@@ -288,7 +288,7 @@ msgstr "%s を作成中"
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:607 lxc/image.go:635
+#: lxc/image.go:621 lxc/image.go:649
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -310,33 +310,33 @@ msgstr ""
 "付属するデータ (設定、スナップショット、...) と一緒にコンテナもしくはコンテ\n"
 "ナのスナップショットを消去します。"
 
-#: lxc/config.go:617
+#: lxc/config.go:647
 #, c-format
 msgid "Device %s added to %s"
 msgstr "デバイス %s が %s に追加されました"
 
-#: lxc/config.go:804
+#: lxc/config.go:834
 #, c-format
 msgid "Device %s removed from %s"
 msgstr "デバイス %s が %s から削除されました"
 
-#: lxc/list.go:462
+#: lxc/list.go:478
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:272
+#: lxc/config.go:275
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:55
+#: lxc/main.go:41
 msgid "Enables debug mode."
 msgstr "デバッグモードを有効にします。"
 
-#: lxc/main.go:54
+#: lxc/main.go:40
 msgid "Enables verbose mode."
 msgstr "詳細モードを有効にします。"
 
-#: lxc/help.go:68
+#: lxc/help.go:69
 msgid "Environment:"
 msgstr "環境変数:"
 
@@ -350,11 +350,12 @@ msgid "Event type to listen for"
 msgstr "Listenするイベントタイプ"
 
 #: lxc/exec.go:45
+#, fuzzy
 msgid ""
 "Execute the specified command in a container.\n"
 "\n"
 "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
-"EDITOR=/usr/bin/vim]... <command>\n"
+"EDITOR=/usr/bin/vim]... [--] <command line>\n"
 "\n"
 "Mode defaults to non-interactive, interactive mode is selected if both stdin "
 "AND stdout are terminals (stderr is ignored)."
@@ -367,16 +368,16 @@ msgstr ""
 "デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナ\n"
 "ルの場合は interactive モードが選択されます (標準エラー出力は無視されます)。"
 
-#: lxc/image.go:339
+#: lxc/image.go:348
 #, c-format
 msgid "Expires: %s"
 msgstr "失効日時: %s"
 
-#: lxc/image.go:341
+#: lxc/image.go:350
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/config.go:269 lxc/image.go:605 lxc/image.go:634
+#: lxc/config.go:272 lxc/image.go:619 lxc/image.go:648
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -384,12 +385,12 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
-#: lxc/image.go:328
+#: lxc/image.go:337
 #, c-format
 msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
 
-#: lxc/finger.go:17
+#: lxc/finger.go:15
 msgid ""
 "Fingers the LXD instance to check if it is up and working.\n"
 "\n"
@@ -399,7 +400,7 @@ msgstr ""
 "\n"
 "lxc finger <remote>"
 
-#: lxc/action.go:37
+#: lxc/action.go:42 lxc/action.go:43
 msgid "Force the container to shutdown."
 msgstr "コンテナを強制シャットダウンします。"
 
@@ -407,7 +408,7 @@ msgstr "コンテナを強制シャットダウンします。"
 msgid "Force the removal of stopped containers."
 msgstr "停止したコンテナを強制的に削除します。"
 
-#: lxc/main.go:56
+#: lxc/main.go:42
 msgid "Force using the local unix socket."
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
@@ -415,44 +416,49 @@ msgstr "強制的にローカルのUNIXソケットを使います。"
 msgid "Format"
 msgstr ""
 
-#: lxc/main.go:138
+#: lxc/main.go:124
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "クライアント証明書を生成します。1分ぐらいかかります..."
 
-#: lxc/list.go:376
+#: lxc/list.go:392
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:377
+#: lxc/list.go:393
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:271
+#: lxc/config.go:274
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:146
+#: lxc/main.go:132
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要があります"
 
-#: lxc/main.go:57
+#: lxc/main.go:43
 msgid "Ignore aliases when determining what command to run."
 msgstr "どのコマンドを実行するか決める際にエイリアスを無視します。"
 
-#: lxc/action.go:40
+#: lxc/action.go:46
 msgid "Ignore the container state (only for start)."
 msgstr "コンテナの状態を無視します (startのみ)。"
 
-#: lxc/image.go:273
+#: lxc/image.go:282
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:419
+#: lxc/image.go:433
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
 
+#: lxc/image.go:420
+#, fuzzy, c-format
+msgid "Importing the image: %s"
+msgstr "イメージのコピー中: %s"
+
 #: lxc/init.go:73
 msgid ""
 "Initialize a container from a particular image.\n"
@@ -481,16 +487,21 @@ msgstr ""
 "例:\n"
 "lxc init ubuntu u1"
 
-#: lxc/remote.go:122
+#: lxc/remote.go:121
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr "不正な URL スキーム \"%s\" (\"%s\" 内)"
 
+#: lxc/config.go:253
+#, fuzzy
+msgid "Invalid certificate"
+msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
+
 #: lxc/init.go:30 lxc/init.go:35
 msgid "Invalid configuration key"
 msgstr "正しくない設定項目 (key) です"
 
-#: lxc/file.go:190
+#: lxc/file.go:195
 #, c-format
 msgid "Invalid source %s"
 msgstr "不正なソース %s"
@@ -500,15 +511,15 @@ msgstr "不正なソース %s"
 msgid "Invalid target %s"
 msgstr "不正な送り先 %s"
 
-#: lxc/info.go:121
+#: lxc/info.go:124
 msgid "Ips:"
 msgstr "IPアドレス:"
 
-#: lxc/image.go:156
+#: lxc/image.go:165
 msgid "Keep the image up to date after initial copy"
 msgstr "最初にコピーした後も常にイメージを最新の状態に保つ"
 
-#: lxc/main.go:35
+#: lxc/main.go:27
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
@@ -529,7 +540,8 @@ msgid ""
 msgstr ""
 "指定したイメージからコンテナを起動します。\n"
 "\n"
-"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
+"<profile>...] [--config|-c <key=value>...]\n"
 "\n"
 "指定したイメージと名前を使ってコンテナを起動します。\n"
 "\n"
@@ -597,17 +609,21 @@ msgstr ""
 "lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
 "\n"
 "フィルタの指定:\n"
-"* 単一の \"web\" のようなキーワードを指定すると、名前が \"web\" ではじまるコンテ\n"
+"* 単一の \"web\" のようなキーワードを指定すると、名前が \"web\" ではじまるコ"
+"ンテ\n"
 "  ナが一覧表示されます。\n"
 "* コンテナ名の正規表現 (例: .*web.*01$)\n"
-"* 設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することがで\n"
+"* 設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することが"
+"で\n"
 "  きます:\n"
-" * \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定されている\n"
+" * \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定され"
+"ている\n"
 "  コンテナをすべて一覧表示します。\n"
 " * \"u.blah=abc\" は上記と同じ意味になります。\n"
 " * \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
 " * \"s.privilaged=1\" は上記と同じ意味になります。\n"
-" * 設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:.*)\n"
+" * 設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:."
+"*)\n"
 "\n"
 "表のカラムの指定:\n"
 "* 4 - IPv4 アドレス\n"
@@ -624,11 +640,11 @@ msgstr ""
 "デフォルトのカラムレイアウト: ns46tS\n"
 "Fast モードのカラムレイアウト: nsacPt"
 
-#: lxc/info.go:225
+#: lxc/info.go:228
 msgid "Log:"
 msgstr "ログ:"
 
-#: lxc/image.go:154
+#: lxc/image.go:163
 msgid "Make image public"
 msgstr "イメージを public にする"
 
@@ -636,7 +652,8 @@ msgstr "イメージを public にする"
 msgid "Make the image public"
 msgstr "イメージを public にする"
 
-#: lxc/profile.go:48
+#: lxc/profile.go:47
+#, fuzzy
 msgid ""
 "Manage configuration profiles.\n"
 "\n"
@@ -647,13 +664,13 @@ msgid ""
 "specified remote.\n"
 "lxc profile get <profile> <key>                Get profile configuration.\n"
 "lxc profile set <profile> <key> <value>        Set profile configuration.\n"
+"lxc profile unset <profile> <key>              Unset profile configuration.\n"
 "lxc profile delete <profile>                   Delete a profile.\n"
 "lxc profile edit <profile>\n"
 "    Edit profile, either by launching external editor or reading STDIN.\n"
 "    Example: lxc profile edit <profile> # launch editor\n"
-"             cat profile.yml | lxc profile edit <profile> # read from "
-"profile.yml\n"
-"\n"
+"             cat profile.yaml | lxc profile edit <profile> # read from "
+"profile.yaml\n"
 "lxc profile apply <container> <profiles>\n"
 "    Apply a comma-separated list of profiles to a container, in order.\n"
 "    All profiles passed in this call (and only those) will be applied\n"
@@ -662,8 +679,6 @@ msgid ""
 "             lxc profile apply foo default # Only default is active\n"
 "             lxc profile apply '' # no profiles are applied anymore\n"
 "             lxc profile apply bar,default # Apply default second now\n"
-"lxc profile apply-add <container> <profile>\n"
-"lxc profile apply-remove <container> <profile>\n"
 "\n"
 "Devices:\n"
 "lxc profile device list <profile>                                   List "
@@ -702,7 +717,8 @@ msgstr ""
 "lxc profile edit <profile>\n"
 "    プロファイルを編集します。外部エディタもしくはSTDINから読み込みます。\n"
 "    例: lxc profile edit <profile> # エディタの起動\n"
-"        cat profile.yml | lxc profile edit <profile> # profile.yml から読み込み\n"
+"        cat profile.yml | lxc profile edit <profile> # profile.yml から読み込"
+"み\n"
 "\n"
 "lxc profile apply <container> <profiles>\n"
 "    プロファイルのコンマ区切りのリストをコンテナに順番に適用します。\n"
@@ -727,11 +743,13 @@ msgstr ""
 "    デバイスプロパティを設定します\n"
 "lxc profile device unset <[remote:]profile> <name> <key>\n"
 "    デバイスプロパティを削除します\n"
-"lxc profile device add <profile name> <device name> <device type> [key=value]...\n"
+"lxc profile device add <profile name> <device name> <device type> "
+"[key=value]...\n"
 "    ディスクやNICのようなプロファイルデバイスを指定したプロファイルを使って\n"
 "    コンテナに追加します。"
 
-#: lxc/config.go:58
+#: lxc/config.go:57
+#, fuzzy
 msgid ""
 "Manage configuration.\n"
 "\n"
@@ -763,8 +781,8 @@ msgid ""
 "    Edit configuration, either by launching external editor or reading "
 "STDIN.\n"
 "    Example: lxc config edit <container> # launch editor\n"
-"             cat config.yml | lxc config edit <config> # read from config."
-"yml\n"
+"             cat config.yaml | lxc config edit <config> # read from config."
+"yaml\n"
 "\n"
 "lxc config trust list [remote]                                              "
 "List all trusted certs.\n"
@@ -775,7 +793,7 @@ msgid ""
 "\n"
 "Examples:\n"
 "To mount host's /share/c1 onto /opt in the container:\n"
-"   lxc config device add [remote:]container1 <device-name> disk source=/"
+"    lxc config device add [remote:]container1 <device-name> disk source=/"
 "share/c1 path=opt\n"
 "\n"
 "To set an lxc config value:\n"
@@ -866,7 +884,7 @@ msgstr ""
 "pull の場合の <source>、push の場合の <target>、edit の場合の <file> は、い\n"
 "ずれも <container name>/<path> の形式です。"
 
-#: lxc/remote.go:39
+#: lxc/remote.go:38
 msgid ""
 "Manage remote LXD servers.\n"
 "\n"
@@ -906,6 +924,7 @@ msgstr ""
 "    デフォルトに設定されているリモートホストを表示します。"
 
 #: lxc/image.go:93
+#, fuzzy
 msgid ""
 "Manipulate container images.\n"
 "\n"
@@ -939,9 +958,18 @@ msgid ""
 "lxc image delete [remote:]<image>\n"
 "    Delete an image from the LXD image store.\n"
 "\n"
-"lxc image export [remote:]<image>\n"
+"lxc image export [remote:]<image> [target]\n"
 "    Export an image from the LXD image store into a distributable tarball.\n"
 "\n"
+"    The output target is optional and defaults to the working directory.\n"
+"    The target may be an existing directory, file name, or \"-\" to specify\n"
+"    stdout.  The target MUST be a directory when exporting a split image.\n"
+"    If the target is a directory, the image's name (each part's name for\n"
+"    split images) as found in the database will be used for the exported\n"
+"    image.  If the target is a file (not a directory and not stdout), then\n"
+"    the appropriate extension will be appended to the provided file name\n"
+"    based on the algorithm used to compress the image. \n"
+"\n"
 "lxc image info [remote:]<image>\n"
 "    Print everything LXD knows about a given image.\n"
 "\n"
@@ -956,7 +984,7 @@ msgid ""
 "lxc image edit [remote:]<image>\n"
 "    Edit image, either by launching external editor or reading STDIN.\n"
 "    Example: lxc image edit <image> # launch editor\n"
-"             cat image.yml | lxc image edit <image> # read from image.yml\n"
+"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
 "\n"
 "lxc image alias create [remote:]<alias> <fingerprint>\n"
 "    Create a new alias for an existing image.\n"
@@ -983,11 +1011,14 @@ msgstr ""
 "る場合は) エイリアスで参照できます。\n"
 "\n"
 "\n"
-"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS].. [prop=value]\n"
+"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
+"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
+"alias=ALIAS].. [prop=value]\n"
 "    イメージの tarball (複数も可能) を LXD のイメージストアにインポートしま\n"
 "    す。\n"
 "\n"
-"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update]\n"
+"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
+"[--public] [--auto-update]\n"
 "    ネットワーク経由である LXD デーモンから他の LXD デーモンへイメージを\n"
 "    コピーします。\n"
 "\n"
@@ -1024,18 +1055,19 @@ msgstr ""
 "    エイリアスを削除します。\n"
 "\n"
 "lxc image alias list [remote:] [filter]\n"
-"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリアス\n"
+"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリア"
+"ス\n"
 "    名の一部をフィルタとして指定できます。\n"
 
-#: lxc/info.go:147
+#: lxc/info.go:150
 msgid "Memory (current)"
 msgstr "メモリ (現在値)"
 
-#: lxc/info.go:151
+#: lxc/info.go:154
 msgid "Memory (peak)"
 msgstr "メモリ (ピーク)"
 
-#: lxc/help.go:86
+#: lxc/help.go:87
 msgid "Missing summary."
 msgstr "サマリーはありません。"
 
@@ -1065,13 +1097,13 @@ msgstr ""
 "例:\n"
 "lxc monitor --type=logging"
 
-#: lxc/file.go:178
+#: lxc/file.go:183
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "ダウンロード対象のファイルが複数ありますが、コピー先がディレクトリではありま"
 "せん。"
 
-#: lxc/move.go:17
+#: lxc/move.go:16
 msgid ""
 "Move containers within or in between lxd instances.\n"
 "\n"
@@ -1091,15 +1123,15 @@ msgstr ""
 "lxc move <old name> <new name>\n"
 "    ローカルのコンテナをリネームします。\n"
 
-#: lxc/action.go:63
+#: lxc/action.go:69
 msgid "Must supply container name for: "
 msgstr "コンテナ名を指定する必要があります: "
 
-#: lxc/list.go:380 lxc/remote.go:363
+#: lxc/list.go:396 lxc/remote.go:352
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:337 lxc/remote.go:342
+#: lxc/remote.go:326 lxc/remote.go:331
 msgid "NO"
 msgstr ""
 
@@ -1108,31 +1140,31 @@ msgstr ""
 msgid "Name: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/image.go:157 lxc/publish.go:33
+#: lxc/image.go:166 lxc/publish.go:33
 msgid "New alias to define at target"
 msgstr "新しいエイリアスを定義する"
 
-#: lxc/config.go:281
+#: lxc/config.go:284
 msgid "No certificate provided to add"
 msgstr "追加すべき証明書が提供されていません"
 
-#: lxc/config.go:304
+#: lxc/config.go:307
 msgid "No fingerprint specified."
 msgstr "フィンガープリントが指定されていません。"
 
-#: lxc/remote.go:107
+#: lxc/remote.go:106
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
-#: lxc/image.go:411
+#: lxc/image.go:425
 msgid "Only https:// is supported for remote image import."
 msgstr "リモートイメージのインポートは https:// のみをサポートします。"
 
-#: lxc/help.go:63 lxc/main.go:122
+#: lxc/help.go:63 lxc/main.go:108
 msgid "Options:"
 msgstr "オプション:"
 
-#: lxc/image.go:506
+#: lxc/image.go:520
 #, c-format
 msgid "Output is in %s"
 msgstr "%s に出力されます"
@@ -1141,47 +1173,48 @@ msgstr "%s に出力されます"
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)"
 
-#: lxc/list.go:464
+#: lxc/list.go:480
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:381
+#: lxc/list.go:397
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:382
+#: lxc/list.go:398
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:365
+#: lxc/remote.go:354
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:606 lxc/remote.go:366
+#: lxc/image.go:620 lxc/remote.go:355
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:174
+#: lxc/info.go:177
 msgid "Packets received"
 msgstr "受信パケット"
 
-#: lxc/info.go:175
+#: lxc/info.go:178
 msgid "Packets sent"
 msgstr "送信パケット"
 
-#: lxc/help.go:69
+#: lxc/help.go:70
 msgid "Path to an alternate client configuration directory."
 msgstr "別のクライアント用設定ディレクトリ"
 
-#: lxc/help.go:70
+#: lxc/help.go:71
 msgid "Path to an alternate server directory."
 msgstr "別のサーバ用設定ディレクトリ"
 
-#: lxc/main.go:39
-msgid "Permisson denied, are you in the lxd group?"
+#: lxc/main.go:31
+#, fuzzy
+msgid "Permission denied, are you in the lxd group?"
 msgstr "アクセスが拒否されました。lxd グループに所属していますか?"
 
-#: lxc/info.go:103
+#: lxc/info.go:106
 #, c-format
 msgid "Pid: %d"
 msgstr ""
@@ -1196,11 +1229,11 @@ msgstr ""
 "\n"
 "lxd help [--all]"
 
-#: lxc/profile.go:216
+#: lxc/profile.go:191
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:501 lxc/config.go:566 lxc/image.go:688
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:702
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -1226,54 +1259,44 @@ msgstr ""
 "\n"
 "lxc version"
 
-#: lxc/info.go:127
+#: lxc/info.go:130
 #, c-format
 msgid "Processes: %d"
 msgstr "プロセス数: %d"
 
-#: lxc/profile.go:272
-#, c-format
-msgid "Profile %s added to %s"
+#: lxc/profile.go:228
+#, fuzzy, c-format
+msgid "Profile %s applied to %s"
 msgstr "プロファイル %s が %s に追加されました"
 
-#: lxc/profile.go:167
+#: lxc/profile.go:142
 #, c-format
 msgid "Profile %s created"
 msgstr "プロファイル %s を作成しました"
 
-#: lxc/profile.go:237
+#: lxc/profile.go:212
 #, c-format
 msgid "Profile %s deleted"
 msgstr "プロファイル %s を削除しました"
 
-#: lxc/profile.go:303
-#, c-format
-msgid "Profile %s removed from %s"
-msgstr "プロファイル %s が %s から削除されました"
-
 #: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
 msgid "Profile to apply to the new container"
 msgstr "新しいコンテナに適用するプロファイル"
 
-#: lxc/profile.go:253
-#, c-format
-msgid "Profiles %s applied to %s"
-msgstr "プロファイル %s が %s に追加されました"
-
-#: lxc/info.go:101
+#: lxc/info.go:104
 #, c-format
 msgid "Profiles: %s"
 msgstr "プロファイル: %s"
 
-#: lxc/image.go:343
+#: lxc/image.go:352
 msgid "Properties:"
 msgstr "プロパティ:"
 
-#: lxc/remote.go:56
+#: lxc/remote.go:55
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
-#: lxc/image.go:331
+#: lxc/image.go:340
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -1290,10 +1313,15 @@ msgstr ""
 "lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
 "value]..."
 
-#: lxc/remote.go:54
+#: lxc/remote.go:53
 msgid "Remote admin password"
 msgstr "リモートの管理者パスワード"
 
+#: lxc/info.go:91
+#, fuzzy, c-format
+msgid "Remote: %s"
+msgstr "コンテナ名: %s"
+
 #: lxc/delete.go:42
 #, c-format
 msgid "Remove %s (yes/no): "
@@ -1303,7 +1331,7 @@ msgstr "%s を消去しますか (yes/no): "
 msgid "Require user confirmation."
 msgstr "ユーザの確認を要求する。"
 
-#: lxc/info.go:124
+#: lxc/info.go:127
 msgid "Resources:"
 msgstr "リソース:"
 
@@ -1312,31 +1340,31 @@ msgstr "リソース:"
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:609
+#: lxc/image.go:623
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:383
+#: lxc/list.go:399
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:384
+#: lxc/list.go:400
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:356
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:214
+#: lxc/remote.go:203
 msgid "Server certificate NACKed by user"
 msgstr "ユーザによりサーバ証明書が拒否されました"
 
-#: lxc/remote.go:276
+#: lxc/remote.go:265
 msgid "Server doesn't trust us after adding our cert"
 msgstr "サーバが我々の証明書を追加した後我々を信頼していません"
 
-#: lxc/remote.go:55
+#: lxc/remote.go:54
 msgid "Server protocol (lxd or simplestreams)"
 msgstr "サーバのプロトコル (lxd or simplestreams)"
 
@@ -1380,20 +1408,24 @@ msgstr "プッシュ時にファイルのuidを設定します"
 msgid "Show all commands (not just interesting ones)"
 msgstr "全てコマンドを表示します (主なコマンドだけではなく)"
 
+#: lxc/help.go:67
+msgid "Show client version."
+msgstr ""
+
 #: lxc/info.go:36
 msgid "Show the container's last 100 log lines?"
 msgstr "コンテナログの最後の 100 行を表示しますか?"
 
-#: lxc/image.go:329
+#: lxc/image.go:338
 #, c-format
 msgid "Size: %.2fMB"
 msgstr "サイズ: %.2fMB"
 
-#: lxc/info.go:194
+#: lxc/info.go:197
 msgid "Snapshots:"
 msgstr "スナップショット:"
 
-#: lxc/image.go:353
+#: lxc/image.go:362
 msgid "Source:"
 msgstr "取得元:"
 
@@ -1402,7 +1434,7 @@ msgstr "取得元:"
 msgid "Starting %s"
 msgstr "%s を起動中"
 
-#: lxc/info.go:95
+#: lxc/info.go:98
 #, c-format
 msgid "Status: %s"
 msgstr "状態: %s"
@@ -1415,19 +1447,19 @@ msgstr "実行中の場合、コンテナを停止します"
 msgid "Stopping container failed!"
 msgstr "コンテナの停止に失敗しました!"
 
-#: lxc/action.go:39
+#: lxc/action.go:45
 msgid "Store the container state (only for stop)."
 msgstr "コンテナの状態を保存します (stopのみ)。"
 
-#: lxc/info.go:155
+#: lxc/info.go:158
 msgid "Swap (current)"
 msgstr "Swap (現在値)"
 
-#: lxc/info.go:159
+#: lxc/info.go:162
 msgid "Swap (peak)"
 msgstr "Swap (ピーク)"
 
-#: lxc/list.go:385
+#: lxc/list.go:401
 msgid "TYPE"
 msgstr ""
 
@@ -1443,15 +1475,20 @@ msgstr ""
 "コンテナは現在実行中です。停止して、再起動するために --force を使用してくだ\n"
 "さい。"
 
-#: lxc/config.go:645 lxc/config.go:657 lxc/config.go:690 lxc/config.go:708
-#: lxc/config.go:746 lxc/config.go:764
+#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738
+#: lxc/config.go:776 lxc/config.go:794
 msgid "The device doesn't exist"
 msgstr "デバイスが存在しません"
 
 #: lxc/init.go:277
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
-msgstr "ローカルイメージ '%s' が見つかりません。代わりに '%s:' を試してみてください。"
+msgstr ""
+"ローカルイメージ '%s' が見つかりません。代わりに '%s:' を試してみてください。"
+
+#: lxc/main.go:176
+msgid "The opposite of `lxc pause` is `lxc start`."
+msgstr ""
 
 #: lxc/publish.go:62
 msgid "There is no \"image name\".  Did you want an alias?"
@@ -1460,56 +1497,57 @@ msgstr ""
 "だ\n"
 "さい。"
 
-#: lxc/action.go:36
+#: lxc/action.go:41
 msgid "Time to wait for the container before killing it."
 msgstr "コンテナを強制停止するまでの時間"
 
-#: lxc/image.go:332
+#: lxc/image.go:341
 msgid "Timestamps:"
 msgstr "タイムスタンプ:"
 
-#: lxc/main.go:147
+#: lxc/main.go:133
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
-"初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてくだ\n"
+"初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてく"
+"だ\n"
 "さい。"
 
-#: lxc/image.go:402
+#: lxc/image.go:412
 #, c-format
 msgid "Transferring image: %d%%"
 msgstr "イメージを転送中: %d%%"
 
-#: lxc/action.go:93 lxc/launch.go:132
+#: lxc/action.go:99 lxc/launch.go:137
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr "更に情報を得るために `lxc info --show-log` を実行してみてください"
 
-#: lxc/info.go:97
+#: lxc/info.go:100
 msgid "Type: ephemeral"
 msgstr "タイプ: ephemeral"
 
-#: lxc/info.go:99
+#: lxc/info.go:102
 msgid "Type: persistent"
 msgstr "タイプ: persistent"
 
-#: lxc/image.go:610
+#: lxc/image.go:624
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:364
+#: lxc/remote.go:353
 msgid "URL"
 msgstr ""
 
-#: lxc/remote.go:82
+#: lxc/remote.go:81
 msgid "Unable to read remote TLS certificate"
 msgstr "リモートの TLS 証明書を読めません"
 
-#: lxc/image.go:337
+#: lxc/image.go:346
 #, c-format
 msgid "Uploaded: %s"
 msgstr "アップロード日時: %s"
 
-#: lxc/main.go:122
+#: lxc/main.go:108
 #, c-format
 msgid "Usage: %s"
 msgstr "使い方: %s"
@@ -1533,15 +1571,15 @@ msgstr ""
 msgid "Whether or not to snapshot the container's running state"
 msgstr "コンテナの稼動状態のスナップショットを取得するかどうか"
 
-#: lxc/config.go:33
+#: lxc/config.go:32
 msgid "Whether to show the expanded configuration"
 msgstr "拡張した設定を表示するかどうか"
 
-#: lxc/remote.go:339 lxc/remote.go:344
+#: lxc/remote.go:328 lxc/remote.go:333
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:66
+#: lxc/main.go:52
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
@@ -1550,19 +1588,19 @@ msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "イメージ、コンテナ、スナップショットのいずれかからスキャンされた数が不正"
 
-#: lxc/action.go:89
+#: lxc/action.go:95
 msgid "bad result type from action"
 msgstr "アクションからの結果タイプが不正です"
 
-#: lxc/copy.go:78
+#: lxc/copy.go:99
 msgid "can't copy to the same container name"
 msgstr "同じコンテナ名へはコピーできません"
 
-#: lxc/remote.go:327
+#: lxc/remote.go:316
 msgid "can't remove the default remote"
 msgstr "デフォルトのリモートは削除できません"
 
-#: lxc/remote.go:353
+#: lxc/remote.go:342
 msgid "default"
 msgstr ""
 
@@ -1572,20 +1610,20 @@ msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
 "した"
 
-#: lxc/image.go:323
+#: lxc/image.go:332
 msgid "disabled"
 msgstr "無効"
 
-#: lxc/image.go:325
+#: lxc/image.go:334
 msgid "enabled"
 msgstr "有効"
 
-#: lxc/main.go:25 lxc/main.go:159
+#: lxc/main.go:22 lxc/main.go:145
 #, c-format
 msgid "error: %v"
 msgstr "エラー: %v"
 
-#: lxc/help.go:40 lxc/main.go:117
+#: lxc/help.go:40 lxc/main.go:103
 #, c-format
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
@@ -1594,65 +1632,65 @@ msgstr "エラー: 未知のコマンド: %s"
 msgid "got bad version"
 msgstr "不正なバージョンを得ました"
 
-#: lxc/image.go:318 lxc/image.go:586
+#: lxc/image.go:327 lxc/image.go:600
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:101
+#: lxc/copy.go:122
 msgid "not all the profiles from the source exist on the target"
 msgstr "コピー元の全てのプロファイルがターゲットに存在しません"
 
-#: lxc/remote.go:207
+#: lxc/remote.go:196
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:266 lxc/main.go:270
+#: lxc/main.go:265 lxc/main.go:269
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr "エイリアスの処理が失敗しました %s\n"
 
-#: lxc/remote.go:389
+#: lxc/remote.go:378
 #, c-format
 msgid "remote %s already exists"
 msgstr "リモート %s は既に存在します"
 
-#: lxc/remote.go:319 lxc/remote.go:381 lxc/remote.go:416 lxc/remote.go:432
+#: lxc/remote.go:308 lxc/remote.go:370 lxc/remote.go:405 lxc/remote.go:421
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "リモート %s は存在しません"
 
-#: lxc/remote.go:302
+#: lxc/remote.go:291
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "リモート %s は <%s> として存在します"
 
-#: lxc/remote.go:323 lxc/remote.go:385 lxc/remote.go:420
+#: lxc/remote.go:312 lxc/remote.go:374 lxc/remote.go:409
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr "リモート %s は static ですので変更できません"
 
-#: lxc/info.go:205
+#: lxc/info.go:208
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:207
+#: lxc/info.go:210
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:201
+#: lxc/info.go:204
 #, c-format
 msgid "taken at %s"
 msgstr "%s に取得しました"
 
-#: lxc/exec.go:166
+#: lxc/exec.go:163
 msgid "unreachable return reached"
 msgstr "到達しないはずのreturnに到達しました"
 
-#: lxc/main.go:199
+#: lxc/main.go:205
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:45 lxc/image.go:320 lxc/image.go:590
+#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:604
 msgid "yes"
 msgstr ""
 
@@ -1660,6 +1698,12 @@ msgstr ""
 msgid "you must specify a source container name"
 msgstr "コピー元のコンテナ名を指定してください"
 
+#~ msgid "Profile %s added to %s"
+#~ msgstr "プロファイル %s が %s に追加されました"
+
+#~ msgid "Profile %s removed from %s"
+#~ msgstr "プロファイル %s が %s から削除されました"
+
 #, fuzzy
 #~ msgid "Bad image property: %s"
 #~ msgstr "(不正なイメージプロパティ形式: %s\n"
diff --git a/po/lxd.pot b/po/lxd.pot
index 531222519..5ebfe9dea 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-09-23 19:38-0400\n"
+        "POT-Creation-Date: 2016-10-26 16:48-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -533,7 +533,7 @@ msgid   "Manage configuration profiles.\n"
         "lxc profile edit <profile>\n"
         "    Edit profile, either by launching external editor or reading STDIN.\n"
         "    Example: lxc profile edit <profile> # launch editor\n"
-        "             cat profile.yml | lxc profile edit <profile> # read from profile.yml\n"
+        "             cat profile.yaml | lxc profile edit <profile> # read from profile.yaml\n"
         "lxc profile apply <container> <profiles>\n"
         "    Apply a comma-separated list of profiles to a container, in order.\n"
         "    All profiles passed in this call (and only those) will be applied\n"
@@ -573,7 +573,7 @@ msgid   "Manage configuration.\n"
         "lxc config edit [remote:][container]                                        Edit container or server configuration in external editor.\n"
         "    Edit configuration, either by launching external editor or reading STDIN.\n"
         "    Example: lxc config edit <container> # launch editor\n"
-        "             cat config.yml | lxc config edit <config> # read from config.yml\n"
+        "             cat config.yaml | lxc config edit <config> # read from config.yaml\n"
         "\n"
         "lxc config trust list [remote]                                              List all trusted certs.\n"
         "lxc config trust add [remote] <certfile.crt>                                Add certfile.crt to trusted hosts.\n"
@@ -672,7 +672,7 @@ msgid   "Manipulate container images.\n"
         "lxc image edit [remote:]<image>\n"
         "    Edit image, either by launching external editor or reading STDIN.\n"
         "    Example: lxc image edit <image> # launch editor\n"
-        "             cat image.yml | lxc image edit <image> # read from image.yml\n"
+        "             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
         "\n"
         "lxc image alias create [remote:]<alias> <fingerprint>\n"
         "    Create a new alias for an existing image.\n"

From 0984ed6f2c1f6647e956c644a5c631cc3f9afbfb Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Tue, 11 Oct 2016 15:04:25 +0200
Subject: [PATCH 0395/1193] lxd/container_lxc: add prepareExec() helper fun

We will add another Exec*() style function that does the same setup operations
as Exec(). So let's create a common helper that does the setup for both.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 24 +++++++++++++++++++-----
 1 file changed, 19 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 3385d2742..98fd14431 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3409,14 +3409,24 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 	return nil
 }
 
-func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error) {
+func (c *containerLXC) prepareExec(waitOnPid bool, command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) ([]string, *exec.Cmd) {
 	envSlice := []string{}
 
 	for k, v := range env {
 		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
 	}
 
-	args := []string{execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
+	// Setting "wait"/"nowait" as the second argument in cmd.Args allows us
+	// to tell execContainer() that it should call an appropriate go-lxc
+	// wrapper for c->attach() that executes the command we requested in the
+	// container and, depending on whether we passed "wait" or "nowait" does
+	// wait or not wait for it to exit.
+	var args []string
+	if waitOnPid {
+		args = []string{execPath, "forkexec", "wait", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
+	} else {
+		args = []string{execPath, "forkexec", "nowait", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
+	}
 
 	args = append(args, "--")
 	args = append(args, "env")
@@ -3434,23 +3444,27 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	cmd.Stderr = stderr
 
 	shared.LogInfo("Executing command", log.Ctx{"environment": envSlice, "args": args})
+	return envSlice, &cmd
+}
 
+func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error) {
+	envSlice, cmd := c.prepareExec(true, command, env, stdin, stdout, stderr)
 	err := cmd.Run()
 	if err != nil {
 		exitErr, ok := err.(*exec.ExitError)
 		if ok {
 			status, ok := exitErr.Sys().(syscall.WaitStatus)
 			if ok {
-				shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": args, "exit_status": status.ExitStatus()})
+				shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": cmd.Args, "exit_status": status.ExitStatus()})
 				return status.ExitStatus(), nil
 			}
 		}
 
-		shared.LogInfo("Failed executing command", log.Ctx{"environment": envSlice, "args": args, "err": err})
+		shared.LogInfo("Failed executing command", log.Ctx{"environment": envSlice, "args": cmd.Args, "err": err})
 		return -1, err
 	}
 
-	shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": args})
+	shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": cmd.Args})
 	return 0, nil
 }
 

From dc7166db423aff97e79729c8788cee8a0e56e194 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 17 Oct 2016 11:00:50 -0600
Subject: [PATCH 0396/1193] migration: fix a race for collecting logs

The problem here is that with the new action script method for waiting
until the restore is successful to finish the dump. The issue is that the
action script waits until the operation is done, but the operation deleted
the logs. Since the Migrate function is the one that actually collects the
logs, the logs got deleted before the Migrate function had a chance to
collect them.

Instead, let's make the goroutine that is calling Migrate responsible for
cleaning up the checkpoint directory, so that the logs are always
guaranteed to be collected. This means we have to be more explicit
elsewhere about deleting the logs in error cases, but we avoid the race.
In the legacy case, we can still just use the defer, since the Migrate
call returns immediately in the function itself.

Fixes a race condition seen in #2505

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index 32177b565..ad762a1b3 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -390,7 +390,6 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		if err != nil {
 			return abort(err)
 		}
-		defer os.RemoveAll(checkpointDir)
 
 		if lxc.VersionAtLeast(2, 0, 4) {
 			/* What happens below is slightly convoluted. Due to various
@@ -412,6 +411,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 			dumpDone := make(chan bool, 1)
 			actionScriptOpSecret, err := shared.RandomCryptoString()
 			if err != nil {
+				os.RemoveAll(checkpointDir)
 				return abort(err)
 			}
 
@@ -453,21 +453,25 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				},
 			)
 			if err != nil {
+				os.RemoveAll(checkpointDir)
 				return abort(err)
 			}
 
 			if err := writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret); err != nil {
+				os.RemoveAll(checkpointDir)
 				return abort(err)
 			}
 
 			_, err = actionScriptOp.Run()
 			if err != nil {
+				os.RemoveAll(checkpointDir)
 				return abort(err)
 			}
 
 			migrateDone := make(chan error, 1)
 			go func() {
 				migrateDone <- s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, true)
+				os.RemoveAll(checkpointDir)
 			}()
 
 			select {
@@ -479,6 +483,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				shared.LogDebugf("Dump finished, continuing with restore...")
 			}
 		} else {
+			defer os.RemoveAll(checkpointDir)
 			if err := s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, false); err != nil {
 				return abort(err)
 			}

From 190bab045d564533746f514f29b810a871886ca6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Oct 2016 16:47:32 -0400
Subject: [PATCH 0397/1193] Don't make unnecessary image copies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2508

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go | 23 +++++++++++------------
 1 file changed, 11 insertions(+), 12 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 0650de382..0a28003ce 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -460,14 +460,14 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 	sha256 := sha256.New()
 	var size int64
 
-	// Create a temporary file for the image tarball
-	imageTarf, err := ioutil.TempFile(builddir, "lxd_tar_")
-	if err != nil {
-		return info, err
-	}
-	defer os.Remove(imageTarf.Name())
-
 	if ctype == "multipart/form-data" {
+		// Create a temporary file for the image tarball
+		imageTarf, err := ioutil.TempFile(builddir, "lxd_tar_")
+		if err != nil {
+			return info, err
+		}
+		defer os.Remove(imageTarf.Name())
+
 		// Parse the POST data
 		post.Seek(0, 0)
 		mr := multipart.NewReader(post, ctypeParams["boundary"])
@@ -569,9 +569,8 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		}
 	} else {
 		post.Seek(0, 0)
-		size, err = io.Copy(io.MultiWriter(imageTarf, sha256), post)
+		size, err = io.Copy(sha256, post)
 		info.Size = size
-		imageTarf.Close()
 		logger.Debug("Tar size", log.Ctx{"size": size})
 		if err != nil {
 			logger.Error(
@@ -597,7 +596,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			return info, err
 		}
 
-		imageMeta, err = getImageMetadata(imageTarf.Name())
+		imageMeta, err = getImageMetadata(post.Name())
 		if err != nil {
 			logger.Error(
 				"Failed to get image metadata",
@@ -606,13 +605,13 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		}
 
 		imgfname := shared.VarPath("images", info.Fingerprint)
-		err = shared.FileMove(imageTarf.Name(), imgfname)
+		err = shared.FileMove(post.Name(), imgfname)
 		if err != nil {
 			logger.Error(
 				"Failed to move the tarfile",
 				log.Ctx{
 					"err":    err,
-					"source": imageTarf.Name(),
+					"source": post.Name(),
 					"dest":   imgfname})
 			return info, err
 		}

From 3086366ca7a6ca92a04ac9bfea09e34a6c69174f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Oct 2016 17:22:20 -0400
Subject: [PATCH 0398/1193] travis: Run the client tests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 .travis.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.travis.yml b/.travis.yml
index 538039603..d78cf8a5a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,6 +19,9 @@ install:
 
 script:
   - "make client"
+  - "go test ./"
+  - "go test ./shared"
+  - "go test ./lxc"
 
 notifications:
   webhooks: https://linuxcontainers.org/webhook-lxcbot/

From 49ab3dc31bee49398ee0ab59dd73a1973aecffb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Oct 2016 17:32:04 -0400
Subject: [PATCH 0399/1193] Fix tests of client on Windows/Mac
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2449

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client_test.go => client_linux_test.go | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename client_test.go => client_linux_test.go (100%)

diff --git a/client_test.go b/client_linux_test.go
similarity index 100%
rename from client_test.go
rename to client_linux_test.go

From 299e198c0bb8faa0a6aaaeb89132489e08e6dbd8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Oct 2016 17:36:26 -0400
Subject: [PATCH 0400/1193] shared: Move Linux specific tests away
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2449

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/util_linux_test.go | 141 ++++++++++++++++++++++++++++++++++++++++++++++
 shared/util_test.go       | 133 -------------------------------------------
 2 files changed, 141 insertions(+), 133 deletions(-)
 create mode 100644 shared/util_linux_test.go

diff --git a/shared/util_linux_test.go b/shared/util_linux_test.go
new file mode 100644
index 000000000..11b1ebb3c
--- /dev/null
+++ b/shared/util_linux_test.go
@@ -0,0 +1,141 @@
+package shared
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+	"syscall"
+	"testing"
+)
+
+func TestGetAllXattr(t *testing.T) {
+	var (
+		err       error
+		testxattr = map[string]string{
+			"user.checksum": "asdfsf13434qwf1324",
+			"user.random":   "This is a test",
+		}
+	)
+	xattrFile, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	defer os.Remove(xattrFile.Name())
+	xattrFile.Close()
+
+	xattrDir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	defer os.Remove(xattrDir)
+
+	for k, v := range testxattr {
+		err = syscall.Setxattr(xattrFile.Name(), k, []byte(v), 0)
+		if err == syscall.ENOTSUP {
+			t.Log(err)
+			return
+		}
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		err = syscall.Setxattr(xattrDir, k, []byte(v), 0)
+		if err == syscall.ENOTSUP {
+			t.Log(err)
+			return
+		}
+		if err != nil {
+			t.Error(err)
+			return
+		}
+	}
+
+	// Test retrieval of extended attributes for regular files.
+	h, err := GetAllXattr(xattrFile.Name())
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if h == nil {
+		t.Errorf("Expected to find extended attributes but did not find any.")
+		return
+	}
+
+	for k, v := range h {
+		found, ok := h[k]
+		if !ok || found != testxattr[k] {
+			t.Errorf("Expected to find extended attribute %s with a value of %s on regular file but did not find it.", k, v)
+			return
+		}
+	}
+
+	// Test retrieval of extended attributes for directories.
+	h, err = GetAllXattr(xattrDir)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	if h == nil {
+		t.Errorf("Expected to find extended attributes but did not find any.")
+		return
+	}
+
+	for k, v := range h {
+		found, ok := h[k]
+		if !ok || found != testxattr[k] {
+			t.Errorf("Expected to find extended attribute %s with a value of %s on directory but did not find it.", k, v)
+			return
+		}
+	}
+}
+
+func TestReadLastNLines(t *testing.T) {
+	source, err := ioutil.TempFile("", "")
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	defer os.Remove(source.Name())
+
+	for i := 0; i < 50; i++ {
+		fmt.Fprintf(source, "%d\n", i)
+	}
+
+	lines, err := ReadLastNLines(source, 100)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	split := strings.Split(lines, "\n")
+	for i := 0; i < 50; i++ {
+		if fmt.Sprintf("%d", i) != split[i] {
+			t.Error(fmt.Sprintf("got %s expected %d", split[i], i))
+			return
+		}
+	}
+
+	source.Seek(0, 0)
+	for i := 0; i < 150; i++ {
+		fmt.Fprintf(source, "%d\n", i)
+	}
+
+	lines, err = ReadLastNLines(source, 100)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+
+	split = strings.Split(lines, "\n")
+	for i := 0; i < 100; i++ {
+		if fmt.Sprintf("%d", i+50) != split[i] {
+			t.Error(fmt.Sprintf("got %s expected %d", split[i], i))
+			return
+		}
+	}
+}
diff --git a/shared/util_test.go b/shared/util_test.go
index c469846c1..458c8fca9 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -6,96 +6,9 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"strings"
-	"syscall"
 	"testing"
 )
 
-func TestGetAllXattr(t *testing.T) {
-	var (
-		err       error
-		testxattr = map[string]string{
-			"user.checksum": "asdfsf13434qwf1324",
-			"user.random":   "This is a test",
-		}
-	)
-	xattrFile, err := ioutil.TempFile("", "")
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer os.Remove(xattrFile.Name())
-	xattrFile.Close()
-
-	xattrDir, err := ioutil.TempDir("", "")
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer os.Remove(xattrDir)
-
-	for k, v := range testxattr {
-		err = syscall.Setxattr(xattrFile.Name(), k, []byte(v), 0)
-		if err == syscall.ENOTSUP {
-			t.Log(err)
-			return
-		}
-		if err != nil {
-			t.Error(err)
-			return
-		}
-		err = syscall.Setxattr(xattrDir, k, []byte(v), 0)
-		if err == syscall.ENOTSUP {
-			t.Log(err)
-			return
-		}
-		if err != nil {
-			t.Error(err)
-			return
-		}
-	}
-
-	// Test retrieval of extended attributes for regular files.
-	h, err := GetAllXattr(xattrFile.Name())
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if h == nil {
-		t.Errorf("Expected to find extended attributes but did not find any.")
-		return
-	}
-
-	for k, v := range h {
-		found, ok := h[k]
-		if !ok || found != testxattr[k] {
-			t.Errorf("Expected to find extended attribute %s with a value of %s on regular file but did not find it.", k, v)
-			return
-		}
-	}
-
-	// Test retrieval of extended attributes for directories.
-	h, err = GetAllXattr(xattrDir)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if h == nil {
-		t.Errorf("Expected to find extended attributes but did not find any.")
-		return
-	}
-
-	for k, v := range h {
-		found, ok := h[k]
-		if !ok || found != testxattr[k] {
-			t.Errorf("Expected to find extended attribute %s with a value of %s on directory but did not find it.", k, v)
-			return
-		}
-	}
-}
-
 func TestFileCopy(t *testing.T) {
 	helloWorld := []byte("hello world\n")
 	source, err := ioutil.TempFile("", "")
@@ -143,52 +56,6 @@ func TestFileCopy(t *testing.T) {
 	}
 }
 
-func TestReadLastNLines(t *testing.T) {
-	source, err := ioutil.TempFile("", "")
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	defer os.Remove(source.Name())
-
-	for i := 0; i < 50; i++ {
-		fmt.Fprintf(source, "%d\n", i)
-	}
-
-	lines, err := ReadLastNLines(source, 100)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	split := strings.Split(lines, "\n")
-	for i := 0; i < 50; i++ {
-		if fmt.Sprintf("%d", i) != split[i] {
-			t.Error(fmt.Sprintf("got %s expected %d", split[i], i))
-			return
-		}
-	}
-
-	source.Seek(0, 0)
-	for i := 0; i < 150; i++ {
-		fmt.Fprintf(source, "%d\n", i)
-	}
-
-	lines, err = ReadLastNLines(source, 100)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	split = strings.Split(lines, "\n")
-	for i := 0; i < 100; i++ {
-		if fmt.Sprintf("%d", i+50) != split[i] {
-			t.Error(fmt.Sprintf("got %s expected %d", split[i], i))
-			return
-		}
-	}
-}
-
 func TestReaderToChannel(t *testing.T) {
 	buf := make([]byte, 1*1024*1024)
 	rand.Read(buf)

From 060c143e705cc4894762e10b8424d5fac9c3802e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Oct 2016 17:49:28 -0400
Subject: [PATCH 0401/1193] travis: Update to match Jenkins Go versions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 .travis.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index d78cf8a5a..73e98ada8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,8 +4,8 @@ os:
   - osx
 
 go:
-  - 1.5
   - 1.6
+  - 1.7
   - tip
 
 matrix:

From 49e614645b67543f08b38b8284f7f173cc9cf534 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 18 Oct 2016 09:37:20 -0600
Subject: [PATCH 0402/1193] don't use the operation as a marker of success

This caused several problems similar to #2512. Instead, let's just use
another channel.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go | 25 +++++++++++++++----------
 1 file changed, 15 insertions(+), 10 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index ad762a1b3..2abc649ad 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -379,6 +379,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		return abort(err)
 	}
 
+	restoreSuccess := make(chan bool, 1)
+	dumpSuccess := make(chan error, 1)
 	if s.live {
 		if header.Criu == nil {
 			return abort(fmt.Errorf("Got no CRIU socket type for live migration"))
@@ -420,13 +422,9 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				nil,
 				nil,
 				func(op *operation) error {
-					_, err := migrateOp.WaitFinal(-1)
-					if err != nil {
-						return err
-					}
-
-					if migrateOp.status != shared.Success {
-						return fmt.Errorf("restore failed: %s", op.status.String())
+					result := <-restoreSuccess
+					if !result {
+						return fmt.Errorf("restore failed, failing CRIU")
 					}
 					return nil
 				},
@@ -468,15 +466,14 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				return abort(err)
 			}
 
-			migrateDone := make(chan error, 1)
 			go func() {
-				migrateDone <- s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, true)
+				dumpSuccess <- s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, true)
 				os.RemoveAll(checkpointDir)
 			}()
 
 			select {
 			/* the checkpoint failed, let's just abort */
-			case err = <-migrateDone:
+			case err = <-dumpSuccess:
 				return abort(err)
 			/* the dump finished, let's continue on to the restore */
 			case <-dumpDone:
@@ -513,6 +510,14 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		return err
 	}
 
+	if s.live {
+		restoreSuccess <- *msg.Success
+		err := <-dumpSuccess
+		if err != nil {
+			shared.LogErrorf("dump failed after successful restore?: %q", err)
+		}
+	}
+
 	if !*msg.Success {
 		return fmt.Errorf(*msg.Message)
 	}

From 86c2800e57fbe06b25fdd15e2915befe1a99acf1 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 18 Oct 2016 09:39:34 -0600
Subject: [PATCH 0403/1193] copy: wait on the source operation too

The source might need to do some cleanup (e.g. wait for the container to
actually be killed by CRIU in the case of a live migration), so let's wait
for it too.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/copy.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxc/copy.go b/lxc/copy.go
index 74631c491..39e1d884c 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -173,6 +173,10 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 			continue
 		}
 
+		if err := source.WaitForSuccess(sourceWSResponse.Operation); err != nil {
+			return err
+		}
+
 		if err = dest.WaitForSuccess(migration.Operation); err != nil {
 			return err
 		}

From 24ce305973fe85a50c8e5c219a659c422ab752ca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 18 Oct 2016 17:12:13 -0400
Subject: [PATCH 0404/1193] Only load kernel modules if not loaded
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 8 ++++----
 lxd/main.go          | 2 +-
 lxd/storage_zfs.go   | 2 +-
 lxd/util.go          | 9 +++++++++
 4 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 98fd14431..1e54e6459 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1095,9 +1095,9 @@ func (c *containerLXC) startCommon() (string, error) {
 	if kernelModules != "" {
 		for _, module := range strings.Split(kernelModules, ",") {
 			module = strings.TrimPrefix(module, " ")
-			out, err := exec.Command("modprobe", module).CombinedOutput()
+			err := loadModule(module)
 			if err != nil {
-				return "", fmt.Errorf("Failed to load kernel module '%s': %s", module, out)
+				return "", fmt.Errorf("Failed to load kernel module '%s': %s", module, err)
 			}
 		}
 	}
@@ -2430,9 +2430,9 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			} else if key == "linux.kernel_modules" && value != "" {
 				for _, module := range strings.Split(value, ",") {
 					module = strings.TrimPrefix(module, " ")
-					out, err := exec.Command("modprobe", module).CombinedOutput()
+					err := loadModule(module)
 					if err != nil {
-						return fmt.Errorf("Failed to load kernel module '%s': %s", module, out)
+						return fmt.Errorf("Failed to load kernel module '%s': %s", module, err)
 					}
 				}
 			} else if key == "limits.disk.priority" {
diff --git a/lxd/main.go b/lxd/main.go
index a81a50cca..dc424b477 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -614,7 +614,7 @@ func cmdInit() error {
 	// Detect zfs
 	out, err := exec.LookPath("zfs")
 	if err == nil && len(out) != 0 && !runningInUserns {
-		_ = shared.RunCommand("modprobe", "zfs")
+		_ = loadModule("zfs")
 
 		err := shared.RunCommand("zpool", "list")
 		if err == nil {
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 9f774c556..c6df6ec21 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -52,7 +52,7 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 	err = s.zfsCheckPool(s.zfsPool)
 	if err != nil {
 		if shared.PathExists(shared.VarPath("zfs.img")) {
-			_ = exec.Command("modprobe", "zfs").Run()
+			_ = loadModule("zfs")
 
 			output, err := exec.Command("zpool", "import",
 				"-d", shared.VarPath(), s.zfsPool).CombinedOutput()
diff --git a/lxd/util.go b/lxd/util.go
index 331d28456..736cb6393 100644
--- a/lxd/util.go
+++ b/lxd/util.go
@@ -3,6 +3,7 @@ package main
 import (
 	"bytes"
 	"encoding/json"
+	"fmt"
 	"io"
 	"net/http"
 
@@ -27,3 +28,11 @@ func WriteJSON(w http.ResponseWriter, body interface{}) error {
 
 	return err
 }
+
+func loadModule(module string) error {
+	if shared.PathExists(fmt.Sprintf("/sys/module/%s", module)) {
+		return nil
+	}
+
+	return shared.RunCommand("modprobe", module)
+}

From db5cec8209af042cb6702e1a7d1c52979cee1a07 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 19 Oct 2016 22:45:19 -0400
Subject: [PATCH 0405/1193] Fix typo in lxc list help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 2 +-
 po/de.po    | 4 ++--
 po/fr.po    | 4 ++--
 po/ja.po    | 5 +++--
 po/lxd.pot  | 4 ++--
 5 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index fb4ab362b..cd70398a1 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -72,7 +72,7 @@ lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]
 The filters are:
 * A single keyword like "web" which will list any container with a name starting by "web".
 * A regular expression on the container name. (e.g. .*web.*01$)
-* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:
+* A key/value pair referring to a configuration item. For those, the namespace can be abbreviated to the smallest unambiguous identifier:
  * "user.blah=abc" will list all containers with the "blah" user property set to "abc".
  * "u.blah=abc" will do the same
  * "security.privileged=1" will list all privileged containers
diff --git a/po/de.po b/po/de.po
index bc982938f..e4ca002c0 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-10-26 16:48-0400\n"
+"POT-Creation-Date: 2016-10-26 17:06-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -613,7 +613,7 @@ msgid ""
 "starting by \"web\".\n"
 "* A regular expression on the container name. (e.g. .*web.*01$)\n"
 "* A key/value pair referring to a configuration item. For those, the "
-"namespace can be abreviated to the smallest unambiguous identifier:\n"
+"namespace can be abbreviated to the smallest unambiguous identifier:\n"
 " * \"user.blah=abc\" will list all containers with the \"blah\" user "
 "property set to \"abc\".\n"
 " * \"u.blah=abc\" will do the same\n"
diff --git a/po/fr.po b/po/fr.po
index dafba1fff..77c01e87a 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-10-26 16:48-0400\n"
+"POT-Creation-Date: 2016-10-26 17:06-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -518,7 +518,7 @@ msgid ""
 "starting by \"web\".\n"
 "* A regular expression on the container name. (e.g. .*web.*01$)\n"
 "* A key/value pair referring to a configuration item. For those, the "
-"namespace can be abreviated to the smallest unambiguous identifier:\n"
+"namespace can be abbreviated to the smallest unambiguous identifier:\n"
 " * \"user.blah=abc\" will list all containers with the \"blah\" user "
 "property set to \"abc\".\n"
 " * \"u.blah=abc\" will do the same\n"
diff --git a/po/ja.po b/po/ja.po
index 578db4d3e..80decff81 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-10-26 16:48-0400\n"
+"POT-Creation-Date: 2016-10-26 17:06-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -570,6 +570,7 @@ msgstr ""
 " lxc info [<remote>:]"
 
 #: lxc/list.go:67
+#, fuzzy
 msgid ""
 "Lists the available resources.\n"
 "\n"
@@ -580,7 +581,7 @@ msgid ""
 "starting by \"web\".\n"
 "* A regular expression on the container name. (e.g. .*web.*01$)\n"
 "* A key/value pair referring to a configuration item. For those, the "
-"namespace can be abreviated to the smallest unambiguous identifier:\n"
+"namespace can be abbreviated to the smallest unambiguous identifier:\n"
 " * \"user.blah=abc\" will list all containers with the \"blah\" user "
 "property set to \"abc\".\n"
 " * \"u.blah=abc\" will do the same\n"
diff --git a/po/lxd.pot b/po/lxd.pot
index 5ebfe9dea..8a46a0fd6 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-10-26 16:48-0400\n"
+        "POT-Creation-Date: 2016-10-26 17:06-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -484,7 +484,7 @@ msgid   "Lists the available resources.\n"
         "The filters are:\n"
         "* A single keyword like \"web\" which will list any container with a name starting by \"web\".\n"
         "* A regular expression on the container name. (e.g. .*web.*01$)\n"
-        "* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:\n"
+        "* A key/value pair referring to a configuration item. For those, the namespace can be abbreviated to the smallest unambiguous identifier:\n"
         " * \"user.blah=abc\" will list all containers with the \"blah\" user property set to \"abc\".\n"
         " * \"u.blah=abc\" will do the same\n"
         " * \"security.privileged=1\" will list all privileged containers\n"

From 0b58499e00980806bcea4f8e8476c1ecfac38551 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 20 Oct 2016 08:42:53 -0600
Subject: [PATCH 0406/1193] set term to "dumb" on windows

Closes #2288

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/exec.go         | 2 +-
 lxc/exec_unix.go    | 4 ++++
 lxc/exec_windows.go | 4 ++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/lxc/exec.go b/lxc/exec.go
index f68bb0a3f..925db5f47 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -96,7 +96,7 @@ func (c *execCmd) run(config *lxd.Config, args []string) error {
 	}
 
 	env := map[string]string{"HOME": "/root", "USER": "root"}
-	if myTerm, ok := os.LookupEnv("TERM"); ok {
+	if myTerm, ok := c.getTERM(); ok {
 		env["TERM"] = myTerm
 	}
 
diff --git a/lxc/exec_unix.go b/lxc/exec_unix.go
index d22653ed1..0d4d95217 100644
--- a/lxc/exec_unix.go
+++ b/lxc/exec_unix.go
@@ -18,6 +18,10 @@ func (c *execCmd) getStdout() io.WriteCloser {
 	return os.Stdout
 }
 
+func (c *execCmd) getTERM() (string, bool) {
+	return os.LookupEnv("TERM")
+}
+
 func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) {
 	ch := make(chan os.Signal)
 	signal.Notify(ch, syscall.SIGWINCH)
diff --git a/lxc/exec_windows.go b/lxc/exec_windows.go
index 586ffb033..950c615cd 100644
--- a/lxc/exec_windows.go
+++ b/lxc/exec_windows.go
@@ -28,6 +28,10 @@ func (c *execCmd) getStdout() io.WriteCloser {
 	return &WrappedWriteCloser{os.Stdout, colorable.NewColorableStdout()}
 }
 
+func (c *execCmd) getTERM() (string, bool) {
+	return "dumb", true
+}
+
 func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) {
 	// TODO: figure out what the equivalent of signal.SIGWINCH is on
 	// windows and use that; for now if you resize your terminal it just

From 9b7f8bca27b388bc61634d870140c5a1c4dd2165 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 20 Oct 2016 12:24:11 -0400
Subject: [PATCH 0407/1193] Add appveyor config to git
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2537

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 .appveyor.yml | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 .appveyor.yml

diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 000000000..45f4ebd7a
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,32 @@
+version: '{branch}.{build}'
+image: Visual Studio 2015
+clone_folder: c:\gopath\src\github.com\lxc\lxd
+environment:
+  GOPATH: c:\gopath
+install:
+- cmd: >-
+    echo %PATH%
+
+    echo %GOPATH%
+
+    set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
+
+    go version
+
+    go env
+build_script:
+- cmd: >-
+    go get -t -v -d ./...
+
+    go install -v ./lxc
+test_script:
+- cmd: >-
+    cd c:\gopath\src\github.com\lxc\lxd
+
+    go test ./
+
+    go test ./shared
+
+    go test ./lxc
+
+    lxc version

From 644a64bfcc3abdef9ea17e52c8b5cc27c6ed56ca Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Fri, 21 Oct 2016 08:24:42 +0300
Subject: [PATCH 0408/1193] Cleanup appveyor.yml before modifications

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 .appveyor.yml | 19 ++++++-------------
 1 file changed, 6 insertions(+), 13 deletions(-)

diff --git a/.appveyor.yml b/.appveyor.yml
index 45f4ebd7a..d9f2ae9f7 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -1,32 +1,25 @@
 version: '{branch}.{build}'
-image: Visual Studio 2015
 clone_folder: c:\gopath\src\github.com\lxc\lxd
 environment:
   GOPATH: c:\gopath
+
 install:
-- cmd: >-
+- cmd: |-
     echo %PATH%
-
     echo %GOPATH%
-
     set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
-
     go version
-
     go env
+
 build_script:
-- cmd: >-
+- cmd: |-
     go get -t -v -d ./...
-
     go install -v ./lxc
+
 test_script:
-- cmd: >-
+- cmd: |-
     cd c:\gopath\src\github.com\lxc\lxd
-
     go test ./
-
     go test ./shared
-
     go test ./lxc
-
     lxc version

From 37c37a06078a041ee44a31b28ef500a19d47cf58 Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Fri, 21 Oct 2016 08:43:24 +0300
Subject: [PATCH 0409/1193] Do verbose testing for test names and timings

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 .appveyor.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.appveyor.yml b/.appveyor.yml
index d9f2ae9f7..e68af0d7a 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -19,7 +19,7 @@ build_script:
 test_script:
 - cmd: |-
     cd c:\gopath\src\github.com\lxc\lxd
-    go test ./
-    go test ./shared
-    go test ./lxc
+    go test -v ./
+    go test -v ./shared
+    go test -v ./lxc
     lxc version

From 31c8e1ec13cabbf00fc8a07fab2c91217f5da5db Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Fri, 21 Oct 2016 08:52:43 +0300
Subject: [PATCH 0410/1193] Publish compiled binaries for download

Artifacts can only be packaged from build dir

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 .appveyor.yml | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/.appveyor.yml b/.appveyor.yml
index e68af0d7a..252bffa69 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -18,8 +18,14 @@ build_script:
 
 test_script:
 - cmd: |-
-    cd c:\gopath\src\github.com\lxc\lxd
     go test -v ./
     go test -v ./shared
     go test -v ./lxc
     lxc version
+
+after_test:
+- cmd: |-
+    copy c:\gopath\bin\lxc.exe .
+
+artifacts:
+- path: lxc.exe

From 8699e15ddec9cbcc78c8d3f1425bf961eba3163e Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Fri, 21 Oct 2016 12:33:15 +0300
Subject: [PATCH 0411/1193] Create archive with platform specifier in its name

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 .appveyor.yml | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/.appveyor.yml b/.appveyor.yml
index 252bffa69..d9cbb6c17 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -21,11 +21,13 @@ test_script:
     go test -v ./
     go test -v ./shared
     go test -v ./lxc
-    lxc version
 
 after_test:
-- cmd: |-
-    copy c:\gopath\bin\lxc.exe .
+  # powershell capture command output into environment variable
+  - ps: $env:VERSION = lxc version
+  - echo %VERSION%
+  # pack lxc as an artifact for upload
+  - 7z a lxc-%VERSION%-windows-amd64.zip c:\gopath\bin\lxc.exe
 
 artifacts:
-- path: lxc.exe
+- path: "*.zip"

From 6a8f7400c0d75c0ef34d53deeae78b3a16bea5c5 Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Fri, 21 Oct 2016 15:47:59 +0300
Subject: [PATCH 0412/1193] README.md add official Windows support =)

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 README.md | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/README.md b/README.md
index fd9476fbd..eaa7576ec 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,15 @@ shell you're going to interact with lxd from.
 After you've got LXD installed and a session with the right permissions, you
 can take your [first steps](#first-steps).
 
+#### Getting started with LXD on Windows
+
+LXD server is not available on Windows, but it is possible to use
+[`lxc` client](https://ci.appveyor.com/project/lxc/lxd/branch/master/artifacts)
+with
+[some limitations](https://github.com/lxc/lxd/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20windows)
+to control remote containers.
+
+
 ## Using the REST API
 The LXD REST API can be used locally via unauthenticated Unix socket or remotely via SSL encapsulated TCP.
 

From 922ad731e02e259645b6e9b481e14f6643edb370 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 24 Oct 2016 18:33:32 +0000
Subject: [PATCH 0413/1193] also clean up apparmor stuff in OnStart when
 something fails

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 1e54e6459..ffef58e7f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1451,6 +1451,7 @@ func (c *containerLXC) OnStart() error {
 		// Run any template that needs running
 		err = c.templateApplyNow(c.localConfig[key])
 		if err != nil {
+			AADestroy(c)
 			c.StorageStop()
 			return err
 		}
@@ -1458,6 +1459,7 @@ func (c *containerLXC) OnStart() error {
 		// Remove the volatile key from the DB
 		err := dbContainerConfigRemove(c.daemon.db, c.id, key)
 		if err != nil {
+			AADestroy(c)
 			c.StorageStop()
 			return err
 		}
@@ -1465,6 +1467,7 @@ func (c *containerLXC) OnStart() error {
 
 	err = c.templateApplyNow("start")
 	if err != nil {
+		AADestroy(c)
 		c.StorageStop()
 		return err
 	}

From d373957a5bb75c43053a85baf8943c1c91f372b2 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 24 Oct 2016 18:38:25 +0000
Subject: [PATCH 0414/1193] log OnStart/OnStop hook errors

The problem here is that liblxc doesn't log errors in hooks, it just logs
the exit code, so if a hook fails and someone isn't running in debug mode,
we really have no way to figure out what went wrong. Let's at least log the
error that the hook gave.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/api_internal.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index ad3244681..51c11e36e 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -6,6 +6,10 @@ import (
 	"strconv"
 
 	"github.com/gorilla/mux"
+
+	"github.com/lxc/lxd/shared"
+
+	log "gopkg.in/inconshreveable/log15.v2"
 )
 
 var apiInternal = []Command{
@@ -55,6 +59,7 @@ func internalContainerOnStart(d *Daemon, r *http.Request) Response {
 
 	err = c.OnStart()
 	if err != nil {
+		shared.Log.Error("start hook failed", log.Ctx{"container": c.Name(), "err": err})
 		return SmartError(err)
 	}
 
@@ -79,6 +84,7 @@ func internalContainerOnStop(d *Daemon, r *http.Request) Response {
 
 	err = c.OnStop(target)
 	if err != nil {
+		shared.Log.Error("stop hook failed", log.Ctx{"container": c.Name(), "err": err})
 		return SmartError(err)
 	}
 

From 1a06338e7cab7fc01234e02c3cdbd24139261381 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 24 Oct 2016 15:20:49 -0400
Subject: [PATCH 0415/1193] Fix remote add with Go tip
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/remote.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index 5a098fe26..83a82a797 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -97,7 +97,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 	/* Complex remote URL parsing */
 	remoteURL, err := url.Parse(addr)
 	if err != nil {
-		return err
+		remoteURL = &url.URL{Host: addr}
 	}
 
 	// Fast track simplestreams

From 6ba3b32fbb71bc5a68f1f331126cfce904bc01ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 24 Oct 2016 15:50:25 -0400
Subject: [PATCH 0416/1193] Set LXC loglevel to match daemon
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2528

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ffef58e7f..b7b6da308 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -526,7 +526,14 @@ func (c *containerLXC) initLXC() error {
 		return err
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.loglevel", "0")
+	logLevel := "warn"
+	if debug {
+	} else if verbose {
+		logLevel = "info"
+	} else {
+		logLevel = "trace"
+	}
+	err = lxcSetConfigItem(cc, "lxc.loglevel", logLevel)
 	if err != nil {
 		return err
 	}

From 75a417ba724611f8a0e255f66ed6bb4878adfc3d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 25 Oct 2016 15:24:12 -0400
Subject: [PATCH 0417/1193] Don't destroy ephemeral container on restart
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2555

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_state.go | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/lxd/container_state.go b/lxd/container_state.go
index 2b73320da..c01cefc6f 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -109,6 +109,30 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 		}
 	case shared.Restart:
 		do = func(op *operation) error {
+			ephemeral := c.IsEphemeral()
+
+			if ephemeral {
+				// Unset ephemeral flag
+				args := containerArgs{
+					Architecture: c.Architecture(),
+					Config:       c.LocalConfig(),
+					Devices:      c.LocalDevices(),
+					Ephemeral:    false,
+					Profiles:     c.Profiles(),
+				}
+
+				err := c.Update(args, false)
+				if err != nil {
+					return err
+				}
+
+				// On function return, set the flag back on
+				defer func() {
+					args.Ephemeral = ephemeral
+					c.Update(args, true)
+				}()
+			}
+
 			if raw.Timeout == 0 || raw.Force {
 				err = c.Stop(false)
 				if err != nil {

From 9c517e34045409228bdbcc051a1f6646a7fe078f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Sun, 23 Oct 2016 16:41:10 +0200
Subject: [PATCH 0418/1193] devices: only call deviceGetAttributes() if needed

For USB and GPU devices we will pass the major and minor device numbers, as well
as the device type. So we should not waste extra cycles on calling
deviceGetAttributes() to retrieve the exact same information again.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b7b6da308..3ab8a1fd2 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3989,13 +3989,39 @@ func (c *containerLXC) removeUnixDevice(m shared.Device) error {
 	devName := fmt.Sprintf("unix.%s", strings.Replace(tgtPath, "/", "-", -1))
 	devPath := filepath.Join(c.DevicesPath(), devName)
 
-	// Remove the device cgroup rule
-	dType, dMajor, dMinor, err := deviceGetAttributes(devPath)
-	if err != nil {
-		return err
+	// Check if we've been passed major and minor numbers already.
+	var tmp int
+	var err error
+	dMajor := -1
+	if m["major"] != "" {
+		tmp, err = strconv.Atoi(m["major"])
+		if err == nil {
+			dMajor = tmp
+		}
+	}
+
+	dMinor := -1
+	if m["minor"] != "" {
+		tmp, err = strconv.Atoi(m["minor"])
+		if err == nil {
+			dMinor = tmp
+		}
+	}
+
+	dType := ""
+	if m["type"] != "" {
+		dType = m["type"]
+	}
+
+	if dType == "" || dMajor < 0 || dMinor < 0 {
+		dType, dMajor, dMinor, err = deviceGetAttributes(devPath)
+		if err != nil {
+			return err
+		}
 	}
 
 	if c.IsPrivileged() && !runningInUserns && cgDevicesController {
+		// Remove the device cgroup rule
 		err = c.CGroupSet("devices.deny", fmt.Sprintf("%s %d:%d rwm", dType, dMajor, dMinor))
 		if err != nil {
 			return err

From 5f9b84332744c2890516a30999f42bc890fc9a4a Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 24 Oct 2016 15:19:12 -0600
Subject: [PATCH 0419/1193] migration: start migration storage at the right
 time

In particular: the migration storage might have been in use after a
migration, so it's not safe to stop it like we were before (presumably it
was giving EBUSY, but since we didn't cehck the return code it was ok).

Instead, let's only start it before we need it, and stop it when we're
done. The OnStart hook will start the storage again if it is needed by the
actual container, so we don't need to take care of that.

Closes #2505

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/migrate.go | 10 ----------
 lxd/storage.go |  5 +++++
 2 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index 2abc649ad..2b510ef0d 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -580,19 +580,14 @@ func (c *migrationSink) connectWithSecret(secret string) (*websocket.Conn, error
 func (c *migrationSink) Do(migrateOp *operation) error {
 	var err error
 
-	// Start the storage for this container (LVM mount/umount)
-	c.src.container.StorageStart()
-
 	c.src.controlConn, err = c.connectWithSecret(c.src.controlSecret)
 	if err != nil {
-		c.src.container.StorageStop()
 		return err
 	}
 	defer c.src.disconnect()
 
 	c.src.fsConn, err = c.connectWithSecret(c.src.fsSecret)
 	if err != nil {
-		c.src.container.StorageStop()
 		c.src.sendControl(err)
 		return err
 	}
@@ -600,7 +595,6 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 	if c.src.live {
 		c.src.criuConn, err = c.connectWithSecret(c.src.criuSecret)
 		if err != nil {
-			c.src.container.StorageStop()
 			c.src.sendControl(err)
 			return err
 		}
@@ -608,7 +602,6 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 
 	header := MigrationHeader{}
 	if err := c.src.recv(&header); err != nil {
-		c.src.container.StorageStop()
 		c.src.sendControl(err)
 		return err
 	}
@@ -634,7 +627,6 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 	}
 
 	if err := c.src.send(&resp); err != nil {
-		c.src.container.StorageStop()
 		c.src.sendControl(err)
 		return err
 	}
@@ -727,8 +719,6 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 
 	source := c.src.controlChannel()
 
-	defer c.src.container.StorageStop()
-
 	for {
 		select {
 		case err = <-restore:
diff --git a/lxd/storage.go b/lxd/storage.go
index 7c25bfd3f..5bb94dc09 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -702,6 +702,11 @@ func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, c
 			return err
 		}
 	} else {
+		if err := container.StorageStart(); err != nil {
+			return err
+		}
+		defer container.StorageStop()
+
 		for _, snap := range snapshots {
 			if err := RsyncRecv(shared.AddSlash(container.Path()), conn); err != nil {
 				return err

From 9b158fc722f439a482ecb421edefffb7cc57863f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 26 Oct 2016 19:29:01 -0400
Subject: [PATCH 0420/1193] Revert "lxd/container_lxc: add prepareExec() helper
 fun"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Not suitable for stable-2.0 right now.

This reverts commit 0984ed6f2c1f6647e956c644a5c631cc3f9afbfb.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 24 +++++-------------------
 1 file changed, 5 insertions(+), 19 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 3ab8a1fd2..fd4adcf1a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3419,24 +3419,14 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 	return nil
 }
 
-func (c *containerLXC) prepareExec(waitOnPid bool, command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) ([]string, *exec.Cmd) {
+func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error) {
 	envSlice := []string{}
 
 	for k, v := range env {
 		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
 	}
 
-	// Setting "wait"/"nowait" as the second argument in cmd.Args allows us
-	// to tell execContainer() that it should call an appropriate go-lxc
-	// wrapper for c->attach() that executes the command we requested in the
-	// container and, depending on whether we passed "wait" or "nowait" does
-	// wait or not wait for it to exit.
-	var args []string
-	if waitOnPid {
-		args = []string{execPath, "forkexec", "wait", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
-	} else {
-		args = []string{execPath, "forkexec", "nowait", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
-	}
+	args := []string{execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
 
 	args = append(args, "--")
 	args = append(args, "env")
@@ -3454,27 +3444,23 @@ func (c *containerLXC) prepareExec(waitOnPid bool, command []string, env map[str
 	cmd.Stderr = stderr
 
 	shared.LogInfo("Executing command", log.Ctx{"environment": envSlice, "args": args})
-	return envSlice, &cmd
-}
 
-func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error) {
-	envSlice, cmd := c.prepareExec(true, command, env, stdin, stdout, stderr)
 	err := cmd.Run()
 	if err != nil {
 		exitErr, ok := err.(*exec.ExitError)
 		if ok {
 			status, ok := exitErr.Sys().(syscall.WaitStatus)
 			if ok {
-				shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": cmd.Args, "exit_status": status.ExitStatus()})
+				shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": args, "exit_status": status.ExitStatus()})
 				return status.ExitStatus(), nil
 			}
 		}
 
-		shared.LogInfo("Failed executing command", log.Ctx{"environment": envSlice, "args": cmd.Args, "err": err})
+		shared.LogInfo("Failed executing command", log.Ctx{"environment": envSlice, "args": args, "err": err})
 		return -1, err
 	}
 
-	shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": cmd.Args})
+	shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": args})
 	return 0, nil
 }
 

From efbaf34d844a564b071c8c112fa47c911c5ecfac Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Wed, 26 Oct 2016 14:19:35 +0200
Subject: [PATCH 0421/1193] lxd: save properties on publish

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxc/publish.go       |  8 ++++++++
 lxd/container.go     |  2 +-
 lxd/container_lxc.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 lxd/images.go        |  2 +-
 4 files changed, 63 insertions(+), 4 deletions(-)

diff --git a/lxc/publish.go b/lxc/publish.go
index b09675cf4..331797586 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -132,6 +132,14 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 
 	var fp string
 
+	// We should only set the properties field if there actually are any.
+	// Otherwise we will only delete any existing properties on publish.
+	// This is something which only direct callers of the API are allowed to
+	// do.
+	if len(properties) == 0 {
+		properties = nil
+	}
+
 	// Optimized local publish
 	if cRemote == iRemote {
 		fp, err = d.ImageFromContainer(cName, c.makePublic, c.pAliases, properties)
diff --git a/lxd/container.go b/lxd/container.go
index 29275380c..88769e984 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -406,7 +406,7 @@ type container interface {
 	Update(newConfig containerArgs, userRequested bool) error
 
 	Delete() error
-	Export(w io.Writer) error
+	Export(w io.Writer, properties map[string]string) error
 
 	// Live configuration
 	CGroupGet(key string) (string, error)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fd4adcf1a..2fc0c6497 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2756,7 +2756,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	return nil
 }
 
-func (c *containerLXC) Export(w io.Writer) error {
+func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 	ctxMap := log.Ctx{"name": c.name,
 		"created":   c.creationDate,
 		"ephemeral": c.ephemeral}
@@ -2849,6 +2849,7 @@ func (c *containerLXC) Export(w io.Writer) error {
 		meta := imageMetadata{}
 		meta.Architecture = arch
 		meta.CreationDate = time.Now().UTC().Unix()
+		meta.Properties = properties
 
 		data, err := yaml.Marshal(&meta)
 		if err != nil {
@@ -2881,6 +2882,50 @@ func (c *containerLXC) Export(w io.Writer) error {
 			return err
 		}
 	} else {
+		if properties != nil {
+			// Parse the metadata
+			content, err := ioutil.ReadFile(fnam)
+			if err != nil {
+				tw.Close()
+				shared.LogError("Failed exporting container", ctxMap)
+				return err
+			}
+
+			metadata := new(imageMetadata)
+			err = yaml.Unmarshal(content, &metadata)
+			if err != nil {
+				tw.Close()
+				shared.LogError("Failed exporting container", ctxMap)
+				return err
+			}
+			metadata.Properties = properties
+
+			// Generate a new metadata.yaml
+			tempDir, err := ioutil.TempDir("", "lxd_lxd_metadata_")
+			if err != nil {
+				tw.Close()
+				shared.LogError("Failed exporting container", ctxMap)
+				return err
+			}
+			defer os.RemoveAll(tempDir)
+
+			data, err := yaml.Marshal(&metadata)
+			if err != nil {
+				tw.Close()
+				shared.LogError("Failed exporting container", ctxMap)
+				return err
+			}
+
+			// Write the actual file
+			fnam = filepath.Join(tempDir, "metadata.yaml")
+			err = ioutil.WriteFile(fnam, data, 0644)
+			if err != nil {
+				tw.Close()
+				shared.LogError("Failed exporting container", ctxMap)
+				return err
+			}
+		}
+
 		// Include metadata.yaml in the tarball
 		fi, err := os.Lstat(fnam)
 		if err != nil {
@@ -2890,7 +2935,13 @@ func (c *containerLXC) Export(w io.Writer) error {
 			return err
 		}
 
-		if err := c.tarStoreFile(linkmap, offset, tw, fnam, fi); err != nil {
+		if properties != nil {
+			tmpOffset := len(path.Dir(fnam)) + 1
+			err = c.tarStoreFile(linkmap, tmpOffset, tw, fnam, fi)
+		} else {
+			err = c.tarStoreFile(linkmap, offset, tw, fnam, fi)
+		}
+		if err != nil {
 			tw.Close()
 			shared.LogDebugf("Error writing to tarfile: %s", err)
 			shared.LogError("Failed exporting container", ctxMap)
diff --git a/lxd/images.go b/lxd/images.go
index 0a28003ce..db138de75 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -269,7 +269,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq,
 	}
 	defer os.Remove(tarfile.Name())
 
-	if err := c.Export(tarfile); err != nil {
+	if err := c.Export(tarfile, req.Properties); err != nil {
 		tarfile.Close()
 		return info, err
 	}

From de996091d04fc4cdaaf443cf2d7525b33ab96690 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 26 Oct 2016 14:39:18 -0600
Subject: [PATCH 0422/1193] readme: bump liblxc version required

we need 2.0.0 becuse of the migrate symbol

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index eaa7576ec..6f3e62cb4 100644
--- a/README.md
+++ b/README.md
@@ -85,7 +85,7 @@ The `hello-ubuntu.json` file referenced above could contain something like:
 
 ## Building from source
 
-We recommend having the latest versions of liblxc (>= 1.1 required) and CRIU
+We recommend having the latest versions of liblxc (>= 2.0.0 required) and CRIU
 (>= 1.7 recommended) available for LXD development. Additionally, LXD requires
 Golang 1.5 or later to work. All the right versions dependencies are available
 via the LXD PPA:

From 3bc628e78acf8cd0e6478e44f90fb8a74c927a4e Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Wed, 26 Oct 2016 23:07:02 +0300
Subject: [PATCH 0423/1193] doc/debugging.md: Add hacking guide

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 doc/debugging.md | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)
 create mode 100644 doc/debugging.md

diff --git a/doc/debugging.md b/doc/debugging.md
new file mode 100644
index 000000000..dbbca8698
--- /dev/null
+++ b/doc/debugging.md
@@ -0,0 +1,28 @@
+Here are different ways to help troubleshooting `lxc` and `lxd` code.
+
+#### lxc --debug
+
+Adding `--debug` flag to any client command will give extra information
+about internals. If there is no useful info, it can be added with the
+logging call:
+
+    shared.LogDebugf("Hello: %s", "Debug")
+    
+#### lxc monitor
+
+This command will monitor messages as they appear on remote server.
+
+#### lxd --debug
+
+Shutting down `lxd` server and running it in foreground with `--debug`
+flag will bring a lot of (hopefully) useful info:
+
+    systemctl stop lxd lxd.socket
+    lxd --debug
+
+### Testing REST API through browser
+
+There are browser plugins that provide convenient interface to create,
+modify and replay web requests. Usually they won't pass through LXD
+authorization level. To make that possible, find certificate generated
+for `lxc` and import it into browser.

From 6f2153924da168c1ab706c9ddbb53f688dc997bc Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Thu, 27 Oct 2016 08:01:24 +0300
Subject: [PATCH 0424/1193] More hacking documentation

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 doc/debugging.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 48 insertions(+), 6 deletions(-)

diff --git a/doc/debugging.md b/doc/debugging.md
index dbbca8698..2c14976d0 100644
--- a/doc/debugging.md
+++ b/doc/debugging.md
@@ -18,11 +18,53 @@ Shutting down `lxd` server and running it in foreground with `--debug`
 flag will bring a lot of (hopefully) useful info:
 
     systemctl stop lxd lxd.socket
-    lxd --debug
+    lxd --debug --group lxd
 
-### Testing REST API through browser
+`--group lxd` is needed to grant access to unprivileged users in this
+group.
 
-There are browser plugins that provide convenient interface to create,
-modify and replay web requests. Usually they won't pass through LXD
-authorization level. To make that possible, find certificate generated
-for `lxc` and import it into browser.
+
+### REST API through local socket
+
+On server side the most easy way is to communicate with LXD through
+local socket. This command accesses `GET /1.0` and formats JSON into
+human readable form using [jq](https://stedolan.github.io/jq/tutorial/)
+utility:
+
+    curl --unix-socket /var/lib/lxd/unix.socket lxd/1.0 | jq .
+
+See [rest-api.md](rest-api.md) for available API.
+
+
+### REST API through HTTPS
+
+[HTTPS connection to LXD](lxd-ssl-authentication.md) requires valid
+client certificate, generated in `~/.config/lxc/client.crt` on
+first `lxc remote add`. This certificate should be passed to
+connection tools for authentication and encryption.
+
+Examining certificate. In case you are curious:
+
+    openssl.exe x509 -in client.crt -purpose
+
+Among the lines you should see:
+
+    Certificate purposes:
+    SSL client : Yes
+
+#### with command line tools
+
+    wget --no-check-certificate https://127.0.0.1:8443/1.0 --certificate=$HOME/.config/lxc/client.crt --private-key=$HOME/.config/lxc/client.key -O - -q
+
+#### with browser
+
+Some browser plugins provide convenient interface to create, modify
+and replay web requests. To authenticate againsg LXD server, convert
+`lxc` client certificate into importable format and import it into
+browser.
+
+For example this produces `client.pfx` in Windows-compatible format:
+
+    openssl pkcs12 -clcerts -inkey client.key -in client.crt -export -out client.pfx
+
+After that, opening https://127.0.0.1:8443/1.0 should work as expected.

From 3a8db37352b473c956c4f5a10b7bbf4bc224cb2d Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 27 Oct 2016 15:03:29 +0000
Subject: [PATCH 0425/1193] stateful start: start storage when necessary

Similar to 38d794806a8ca781a0ed69798c8a6e9ea8ea7a70, we need to start the
storage at the right time for stateful start and stop. In particular, the
onstart hook will start the storage for us, but we need to be sure and
start it so that we can uidshift the images.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 2fc0c6497..78e74264c 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3058,9 +3058,20 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 		 * namespace.
 		 */
 		if !c.IsPrivileged() {
-			if err := c.IdmapSet().ShiftRootfs(stateDir); err != nil {
+			err = c.StorageStart()
+			if err != nil {
 				return err
 			}
+
+			err = c.IdmapSet().ShiftRootfs(stateDir)
+			err2 := c.StorageStop()
+			if err != nil {
+				return err
+			}
+
+			if err2 != nil {
+				return err2
+			}
 		}
 
 		configPath := filepath.Join(c.LogPath(), "lxc.conf")

From 926425b530b1bd7e72931ddb151a2e19a2039f16 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Oct 2016 11:30:34 -0400
Subject: [PATCH 0426/1193] lxc-to-lxd: Check that source path exists (disk)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2572

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index aa60011c0..48754c54d 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -238,6 +238,9 @@ def convert_container(container_name, args):
         # Deal with optional mounts
         if "optional" in mount[3].split(","):
             device['optional'] = "true"
+        elif not os.path.exists(mount[0]):
+            print("Invalid mount configuration, source path doesn't exist.")
+            return False
 
         # Set the source
         device['source'] = mount[0]

From d0e56b2edae39a75b5f890bc4744d8b5e5fecd2c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Oct 2016 12:40:11 -0400
Subject: [PATCH 0427/1193] Document user.network-config
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This was recently introduced in the cloud images.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/configuration.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/configuration.md b/doc/configuration.md
index 7f9e12408..a35a37c7d 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -106,6 +106,7 @@ user.network\_mode          | string        | dhcp              | One of "dhcp"
 user.meta-data              | string        | -                 | Cloud-init meta-data, content is appended to seed value.
 user.user-data              | string        | #!cloud-config    | Cloud-init user-data, content is used as seed value.
 user.vendor-data            | string        | #!cloud-config    | Cloud-init vendor-data, content is used as seed value.
+user.network-config         | string        | DHCP on eth0      | Cloud-init network-config, content is used as seed value.
 
 Note that while a type is defined above as a convenience, all values are
 stored as strings and should be exported over the REST API as strings

From 315fd7e3f780d748822427b85da4762902ea37ec Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Thu, 27 Oct 2016 19:55:36 +0300
Subject: [PATCH 0428/1193] Remove .exe

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 doc/debugging.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/debugging.md b/doc/debugging.md
index 2c14976d0..0f91c76ef 100644
--- a/doc/debugging.md
+++ b/doc/debugging.md
@@ -45,7 +45,7 @@ connection tools for authentication and encryption.
 
 Examining certificate. In case you are curious:
 
-    openssl.exe x509 -in client.crt -purpose
+    openssl x509 -in client.crt -purpose
 
 Among the lines you should see:
 

From aad8be420d0006fd92fb2d3820bd9239ebb724b7 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 28 Oct 2016 15:33:13 +0200
Subject: [PATCH 0429/1193] devices: call deviceGetAttributes() when needed

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 33 +++++++++++++++++++++++++++++----
 1 file changed, 29 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 78e74264c..90aec4242 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4009,13 +4009,38 @@ func (c *containerLXC) insertUnixDevice(name string, m shared.Device) error {
 		return fmt.Errorf("Failed to add mount for device: %s", err)
 	}
 
-	// Add the new device cgroup rule
-	dType, dMajor, dMinor, err := deviceGetAttributes(devPath)
-	if err != nil {
-		return fmt.Errorf("Failed to get device attributes: %s", err)
+	// Check if we've been passed major and minor numbers already.
+	var tmp int
+	dMajor := -1
+	if m["major"] != "" {
+		tmp, err = strconv.Atoi(m["major"])
+		if err == nil {
+			dMajor = tmp
+		}
+	}
+
+	dMinor := -1
+	if m["minor"] != "" {
+		tmp, err = strconv.Atoi(m["minor"])
+		if err == nil {
+			dMinor = tmp
+		}
+	}
+
+	dType := ""
+	if m["type"] != "" {
+		dType = m["type"]
+	}
+
+	if dType == "" || dMajor < 0 || dMinor < 0 {
+		dType, dMajor, dMinor, err = deviceGetAttributes(devPath)
+		if err != nil {
+			return err
+		}
 	}
 
 	if c.IsPrivileged() && !runningInUserns && cgDevicesController {
+		// 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")
 		}

From 2d72d0132599e2950d2e7314e5bec5acee391932 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Fri, 28 Oct 2016 19:16:57 +0200
Subject: [PATCH 0430/1193] raw LXC: skip leading whitespace

We do the same in LXC itself. If we don't we might unnecessarily check comment
that have leading whitespace.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/container_lxc.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 90aec4242..f6d7a5195 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -99,6 +99,9 @@ func lxcValidConfig(rawLxc string) error {
 			continue
 		}
 
+		// Skip whitespace {"\t", " "}
+		line = strings.TrimLeft(line, "\t ")
+
 		// Ignore comments
 		if strings.HasPrefix(line, "#") {
 			continue

From 757b6ce758678e4d6694da4d6eedbc90db0eef88 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 31 Oct 2016 19:44:05 +0000
Subject: [PATCH 0431/1193] correctly set liblxc loglevel to debug when in
 --debug mode

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f6d7a5195..e35cd7ea0 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -531,6 +531,7 @@ func (c *containerLXC) initLXC() error {
 
 	logLevel := "warn"
 	if debug {
+		logLevel = "debug"
 	} else if verbose {
 		logLevel = "info"
 	} else {

From 6b6c7e859b83b4268baecab27756a73ea00b41c2 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 31 Oct 2016 14:03:22 -0600
Subject: [PATCH 0432/1193] log: fix the log level settings for liblxc in other
 cases

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index e35cd7ea0..beadf9611 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -531,12 +531,11 @@ func (c *containerLXC) initLXC() error {
 
 	logLevel := "warn"
 	if debug {
-		logLevel = "debug"
+		logLevel = "trace"
 	} else if verbose {
 		logLevel = "info"
-	} else {
-		logLevel = "trace"
 	}
+
 	err = lxcSetConfigItem(cc, "lxc.loglevel", logLevel)
 	if err != nil {
 		return err

From e731429d75d2c3af932551e474a9cc724b2b8676 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 4 Nov 2016 14:48:18 -0600
Subject: [PATCH 0433/1193] Timeout container freeze on stop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Sometimes we can't actually freeze the container, so give up after 5
seconds, restore the processes so that LXC can then normally kill them.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index beadf9611..9c5e3d787 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1585,7 +1585,17 @@ func (c *containerLXC) Stop(stateful bool) error {
 	}
 
 	// Attempt to freeze the container first, helps massively with fork bombs
-	c.Freeze()
+	freezer := make(chan bool, 1)
+	go func() {
+		c.Freeze()
+		freezer <- true
+	}()
+
+	select {
+	case <-freezer:
+	case <-time.After(time.Second * 5):
+		c.Unfreeze()
+	}
 
 	if err := c.c.Stop(); err != nil {
 		op.Done(err)

From 6ec23c9b20ba85c9ab4451a3628caff96f5915e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 4 Nov 2016 14:51:11 -0600
Subject: [PATCH 0434/1193] Add /snap/bin to PATH even if only /snap exists
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

/snap/bin is created later on by snapd, so lets just assume that it will
exist and append /snap/bin so long as /snap exists.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_exec.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index d92bc7bb4..83435ad63 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -285,7 +285,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	_, ok := env["PATH"]
 	if !ok {
 		env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
-		if shared.PathExists(fmt.Sprintf("%s/snap/bin", c.RootfsPath())) {
+		if shared.PathExists(fmt.Sprintf("%s/snap", c.RootfsPath())) {
 			env["PATH"] = fmt.Sprintf("%s:/snap/bin", env["PATH"])
 		}
 	}

From ba3f607fd442be45739676cd60e45677f697b518 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 5 Nov 2016 09:28:21 -0600
Subject: [PATCH 0435/1193] Don't update images at all if interval is 0
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index e08db5fbe..23d2a3887 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1033,8 +1033,13 @@ func (d *Daemon) Ready() error {
 	/* Auto-update images */
 	d.resetAutoUpdateChan = make(chan bool)
 	go func() {
-		autoUpdateImages(d)
+		// Initial image sync
+		interval := daemonConfig["images.auto_update_interval"].GetInt64()
+		if interval > 0 {
+			autoUpdateImages(d)
+		}
 
+		// Background image sync
 		for {
 			interval := daemonConfig["images.auto_update_interval"].GetInt64()
 			if interval > 0 {

From faf7e9e60154fbbf0730ede3c4485f537eba9157 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 9 Nov 2016 11:49:46 +0200
Subject: [PATCH 0436/1193] lxc-to-lxd: Migrate lxc.aa_profile if set
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 48754c54d..b1f2a8769 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -282,8 +282,7 @@ def convert_container(container_name, args):
         if value[0] == "lxc-container-default-with-nesting":
             config['security.nesting'] = "true"
         elif value[0] != "lxc-container-default":
-            print("Unsupported custom apparmor profile, skipping...")
-            return False
+            config["raw.lxc"] = "lxc.aa_profile=%s" % value[0]
 
     # Convert seccomp
     print("Processing container seccomp configuration")

From 6b555739b645712e1cc9c6a7ddbc52b43f72538c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 9 Nov 2016 11:51:39 +0200
Subject: [PATCH 0437/1193] lxc-to-lxd: Consistent logging
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index b1f2a8769..1be9f6654 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -318,7 +318,7 @@ def convert_container(container_name, args):
            'profiles': ["default"]}
 
     # Set the container architecture if set in LXC
-    print("Converting container architecture configuration")
+    print("Processing container architecture configuration")
     arches = {'i686': "i686",
               'x86_64': "x86_64",
               'armhf': "armv7l",

From 839aac5f5bdbfba390143ca244a66924265e51b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 9 Nov 2016 11:53:17 +0200
Subject: [PATCH 0438/1193] lxc-to-lxd: Formatting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 1be9f6654..f609df754 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -417,8 +417,13 @@ if not os.geteuid() == 0:
 if (not args.containers and not args.all) or (args.containers and args.all):
     parser.error("You must either pass container names or --all")
 
+count = 0
 for container_name in lxc.list_containers(config_path=args.lxcpath):
     if args.containers and container_name not in args.containers:
         continue
 
+    if count > 0:
+        print("")
+
     convert_container(container_name, args)
+    count += 1

From 8b432939854f2c52d367d4d0ab66fa1c8d2385ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 9 Nov 2016 12:00:25 +0200
Subject: [PATCH 0439/1193] lxc-to-lxd: Print summary and proper exit code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index f609df754..d08ee804e 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -5,6 +5,7 @@ import json
 import lxc
 import os
 import subprocess
+import sys
 from pylxd.client import Client
 from pylxd import exceptions
 
@@ -417,6 +418,8 @@ if not os.geteuid() == 0:
 if (not args.containers and not args.all) or (args.containers and args.all):
     parser.error("You must either pass container names or --all")
 
+# Run migration
+results = {}
 count = 0
 for container_name in lxc.list_containers(config_path=args.lxcpath):
     if args.containers and container_name not in args.containers:
@@ -425,5 +428,17 @@ for container_name in lxc.list_containers(config_path=args.lxcpath):
     if count > 0:
         print("")
 
-    convert_container(container_name, args)
+    results[container_name] = convert_container(container_name, args)
     count += 1
+
+# Print summary
+print("")
+print("==> Migration summary")
+for name, result in results.items():
+    if result:
+        print("%s: SUCCESS" % name)
+    else:
+        print("%s: FAILURE" % name)
+
+if False in results.values():
+    sys.exit(1)

From 6ac005720bd7ac0313f29f6bba41a2bfbb54c4c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 9 Nov 2016 12:23:17 +0200
Subject: [PATCH 0440/1193] lxc-to-lxd: Don't fail dry-run with runnning
 containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index d08ee804e..34244654d 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -88,10 +88,6 @@ def convert_container(container_name, args):
         print("Invalid container configuration, skipping...")
         return False
 
-    if container.running:
-        print("Only stopped containers can be migrated, skipping...")
-        return False
-
     # As some keys can't be queried over the API, parse the config ourselves
     print("Parsing LXC configuration")
     lxc_config = config_parse(container.config_file_name)
@@ -348,6 +344,10 @@ def convert_container(container_name, args):
     if args.dry_run:
         return True
 
+    if container.running:
+        print("Only stopped containers can be migrated, skipping...")
+        return False
+
     try:
         print("Creating the container")
         lxd.containers.create(new, wait=True)

From 72ef84143b43a1137ed2858ed1e0ff1d7d8f9373 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 11 Nov 2016 12:35:14 +0200
Subject: [PATCH 0441/1193] activateifneeded: Immediately exit when no DB
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/main.go b/lxd/main.go
index dc424b477..0fa3b944b 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -480,6 +480,11 @@ func cmdActivateIfNeeded() error {
 		lxcpath:               shared.VarPath("containers"),
 	}
 
+	if !shared.PathExists(shared.VarPath("lxd.db")) {
+		shared.LogDebugf("No DB, so no need to start the daemon now.")
+		return nil
+	}
+
 	err := initializeDbObject(d, shared.VarPath("lxd.db"))
 	if err != nil {
 		return err

From 55201ca09d47d06000037e08f74d71fe2cd5a2d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Nov 2016 15:07:33 -0500
Subject: [PATCH 0442/1193] lxc-to-lxd: Drop dependency on pylxd
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 76 +++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 61 insertions(+), 15 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 34244654d..2c3472f23 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -1,13 +1,24 @@
 #!/usr/bin/env python3
-
 import argparse
+import http.client
 import json
 import lxc
 import os
+import socket
 import subprocess
 import sys
-from pylxd.client import Client
-from pylxd import exceptions
+
+
+# Unix connection to LXD
+class UnixHTTPConnection(http.client.HTTPConnection):
+    def __init__(self, path):
+        http.client.HTTPConnection.__init__(self, 'localhost')
+        self.path = path
+
+    def connect(self):
+        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        sock.connect(self.path)
+        self.sock = sock
 
 
 # Fetch a config key as a list
@@ -72,13 +83,39 @@ def config_parse(path):
     return config
 
 
-# Convert a LXC container to a LXD one
-def convert_container(container_name, args):
-    # Connect to LXD
-    if args.lxdpath:
-        os.environ['LXD_DIR'] = args.lxdpath
-    lxd = Client()
+def container_exists(lxd_socket, container_name):
+    lxd = UnixHTTPConnection(lxd_socket)
+    lxd.request("GET", "/1.0/containers/%s" % container_name)
+    if lxd.getresponse().status == 404:
+        return False
+
+    return True
+
+
+def container_create(lxd_socket, args):
+    # Define the container
+    lxd = UnixHTTPConnection(lxd_socket)
+    lxd.request("POST", "/1.0/containers", json.dumps(args))
+    r = lxd.getresponse()
 
+    # Decode the response
+    resp = json.loads(r.read().decode())
+    if resp["type"] == "error":
+        raise Exception("Failed to define container: %s" % resp["error"])
+
+    # Wait for result
+    lxd = UnixHTTPConnection(lxd_socket)
+    lxd.request("GET", "%s/wait" % resp["operation"])
+    r = lxd.getresponse()
+
+    # Decode the response
+    resp = json.loads(r.read().decode())
+    if resp["type"] == "error":
+        raise Exception("Failed to define container: %s" % resp["error"])
+
+
+# Convert a LXC container to a LXD one
+def convert_container(lxd_socket, container_name, args):
     print("==> Processing container: %s" % container_name)
 
     # Load the container
@@ -104,12 +141,9 @@ def convert_container(container_name, args):
 
     # Make sure we don't have a conflict
     print("Checking for existing containers")
-    try:
-        lxd.containers.get(container_name)
+    if container_exists(lxd_socket, container_name):
         print("Container already exists, skipping...")
         return False
-    except (NameError, exceptions.LXDAPIException):
-        pass
 
     # Validate lxc.utsname
     print("Validating container name")
@@ -350,7 +384,7 @@ def convert_container(container_name, args):
 
     try:
         print("Creating the container")
-        lxd.containers.create(new, wait=True)
+        container_create(lxd_socket, new)
     except Exception as e:
         raise
         print("Failed to create the container: %s" % e)
@@ -389,6 +423,7 @@ def convert_container(container_name, args):
     with open(container.config_file_name, "a") as fd:
         fd.write("lxd.migrated=true\n")
     print("Container is ready to use")
+    return True
 
 
 # Argument parsing
@@ -418,6 +453,16 @@ if not os.geteuid() == 0:
 if (not args.containers and not args.all) or (args.containers and args.all):
     parser.error("You must either pass container names or --all")
 
+# Connect to LXD
+if args.lxdpath:
+    lxd_socket = args.lxdpath
+else:
+    lxd_socket = "/var/lib/lxd/unix.socket"
+
+if not os.path.exists(lxd_socket):
+    print("LXD isn't running.")
+    sys.exit(1)
+
 # Run migration
 results = {}
 count = 0
@@ -428,7 +473,8 @@ for container_name in lxc.list_containers(config_path=args.lxcpath):
     if count > 0:
         print("")
 
-    results[container_name] = convert_container(container_name, args)
+    results[container_name] = convert_container(lxd_socket,
+                                                container_name, args)
     count += 1
 
 # Print summary

From 4975e5fd30f78c0f4056105e76cff898784140bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Nov 2016 15:09:29 -0500
Subject: [PATCH 0443/1193] lxc-to-lxd: Fix lxdpath handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 2c3472f23..604d4027e 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -391,7 +391,7 @@ def convert_container(lxd_socket, container_name, args):
         return False
 
     # Transfer the filesystem
-    lxd_rootfs = os.path.join("/var/lib/lxd/", "containers",
+    lxd_rootfs = os.path.join(args.lxdpath, "containers",
                               container_name, "rootfs")
 
     if args.copy_rootfs:
@@ -440,7 +440,7 @@ parser.add_argument("--copy-rootfs", action="store_true", default=False,
                     help="Copy the container rootfs rather than moving it")
 parser.add_argument("--lxcpath", type=str, default=False,
                     help="Alternate LXC path")
-parser.add_argument("--lxdpath", type=str, default=False,
+parser.add_argument("--lxdpath", type=str, default="/var/lib/lxd",
                     help="Alternate LXD path")
 parser.add_argument(dest='containers', metavar="CONTAINER", type=str,
                     help="Container to import", nargs="*")
@@ -454,10 +454,7 @@ if (not args.containers and not args.all) or (args.containers and args.all):
     parser.error("You must either pass container names or --all")
 
 # Connect to LXD
-if args.lxdpath:
-    lxd_socket = args.lxdpath
-else:
-    lxd_socket = "/var/lib/lxd/unix.socket"
+lxd_socket = os.path.join(args.lxdpath, "unix.socket")
 
 if not os.path.exists(lxd_socket):
     print("LXD isn't running.")

From d2bf9f34fd2ac3058a3d2b1adca55c8542408ea5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Nov 2016 15:12:03 -0500
Subject: [PATCH 0444/1193] lxc-to-lxd: Better output with no container
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 604d4027e..516d12b23 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -475,6 +475,10 @@ for container_name in lxc.list_containers(config_path=args.lxcpath):
     count += 1
 
 # Print summary
+if not results:
+    print("No container to migrate")
+    sys.exit(0)
+
 print("")
 print("==> Migration summary")
 for name, result in results.items():

From 34f988726ab32cb0bafb2a6a7519d9e2815ce3e3 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 16 Nov 2016 12:18:16 -0700
Subject: [PATCH 0445/1193] idmapset: fix typo in Intersects

It doesn't make sense to add e's map range to i; this is a typo, and we
should be using i's map range instead.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/idmapset_linux.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index b3d81d6fc..4e59d69ee 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -44,7 +44,7 @@ func (e *IdmapEntry) Intersects(i IdmapEntry) bool {
 			return true
 		case is_between(e.Hostid+e.Maprange, i.Hostid, i.Hostid+i.Maprange):
 			return true
-		case is_between(i.Hostid+e.Maprange, e.Hostid, e.Hostid+e.Maprange):
+		case is_between(i.Hostid+i.Maprange, e.Hostid, e.Hostid+e.Maprange):
 			return true
 		case is_between(e.Nsid, i.Nsid, i.Nsid+i.Maprange):
 			return true
@@ -52,7 +52,7 @@ func (e *IdmapEntry) Intersects(i IdmapEntry) bool {
 			return true
 		case is_between(e.Nsid+e.Maprange, i.Nsid, i.Nsid+i.Maprange):
 			return true
-		case is_between(i.Nsid+e.Maprange, e.Nsid, e.Nsid+e.Maprange):
+		case is_between(i.Nsid+i.Maprange, e.Nsid, e.Nsid+e.Maprange):
 			return true
 		}
 	}

From cde06a6decbe441706a2e16b2158123475541322 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at canonical.com>
Date: Wed, 16 Nov 2016 20:51:32 +0100
Subject: [PATCH 0446/1193] lxd/storage_dir: rsync + freeze + rsync unfreeze

This tries to ensure that dir backed containers are in a consistent state when
snapshotted.

Signed-off-by: Christian Brauner <christian.brauner at canonical.com>
---
 lxd/storage_dir.go | 36 ++++++++++++++++++++++++++++++------
 1 file changed, 30 insertions(+), 6 deletions(-)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 3c3a2b0b7..172948327 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -195,13 +195,37 @@ func (s *storageDir) ContainerSnapshotCreate(
 	/*
 	 * Copy by using rsync
 	 */
-	output, err := storageRsyncCopy(oldPath, newPath)
-	if err != nil {
-		s.ContainerDelete(snapshotContainer)
-		s.log.Error("ContainerSnapshotCreate: rsync failed",
-			log.Ctx{"output": string(output)})
+	rsync := func(snapshotContainer container, oldPath string, newPath string) error {
+		output, err := storageRsyncCopy(oldPath, newPath)
+		if err != nil {
+			s.ContainerDelete(snapshotContainer)
+			s.log.Error("ContainerSnapshotCreate: rsync failed",
+				log.Ctx{"output": string(output)})
 
-		return fmt.Errorf("rsync failed: %s", string(output))
+			return fmt.Errorf("rsync failed: %s", string(output))
+		}
+		return nil
+	}
+
+	if err := rsync(snapshotContainer, oldPath, newPath); err != nil {
+		return err
+	}
+
+	if sourceContainer.IsRunning() {
+		/* This is done to ensure consistency when snapshotting. But we
+		 * probably shouldn't fail just because of that.
+		 */
+		s.log.Debug("ContainerSnapshotCreate: trying to freeze and rsync again to ensure consistency.")
+		if err := sourceContainer.Freeze(); err != nil {
+			s.log.Warn("ContainerSnapshotCreate: trying to freeze and rsync again failed.")
+			return nil
+		}
+
+		if err := rsync(snapshotContainer, oldPath, newPath); err != nil {
+			return err
+		}
+
+		defer sourceContainer.Unfreeze()
 	}
 
 	return nil

From c76886777bc31b5349d7aa70380aa7ba4e9a8583 Mon Sep 17 00:00:00 2001
From: Akshay Karle <akshay.a.karle at gmail.com>
Date: Wed, 16 Nov 2016 18:38:08 -0500
Subject: [PATCH 0447/1193] update README to specify docker installation
 details

Signed-off-by: Akshay Karle <akshay.a.karle at gmail.com>
---
 README.md | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 6f3e62cb4..bef7d8b7d 100644
--- a/README.md
+++ b/README.md
@@ -324,7 +324,7 @@ Yes. The easiest way to do that is using a privileged container:
 1.a) create a container.
 
     lxc launch ubuntu privilegedContainerName -c security.privileged=true
-    
+
 1.b) or, if your container already exists.
 
         lxc config set privilegedContainerName security.privileged true
@@ -343,4 +343,8 @@ apply the docker profile to your container.
 Note that the docker profile does not provide a network interface, so the
 common case will want to compose the default and docker profiles.
 
+Also note that Docker coming from [upstream](https://apt.dockerproject.org/repo) doesn't currently run as is inside the lxd container. Look at issue [#2621](https://github.com/lxc/lxd/issues/2621) for more details. You need to download the docker coming from Ubuntu (docker.io package) to get this working. So once you are in the lxd container run
+
+    sudo apt-get install -y docker.io runc containerd
+
 The container must be using the Ubuntu 1.10.2-0ubuntu4 or newer docker package.

From 88a66fba84f11c0154a9eb4d88b824eb1aa969db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 10 Nov 2016 10:39:30 +0200
Subject: [PATCH 0448/1193] Attach to userns on file operations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Without this, we can't retrieve or push files to containers that use a
self mounted squashfs as their root.

Because we still support the case where the container isn't running
through direct filesystem access, we need to make sure to only remap
uid/gid in that case.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go      |   2 +
 lxd/container_exec.go |   2 +-
 lxd/container_lxc.go  | 136 ++++++++++++++++++++++++++++++++++------
 lxd/nsexec.go         | 169 ++++++++++++++++++++++++++++++++++++++++----------
 4 files changed, 255 insertions(+), 54 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 88769e984..10de98c3d 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -415,7 +415,9 @@ type container interface {
 
 	// File handling
 	FilePull(srcpath string, dstpath string) (int, int, os.FileMode, error)
+	FileExists(path string) error
 	FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error
+	FileRemove(path string) error
 
 	// Command execution
 	Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error)
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 83435ad63..d2e4cff8d 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -285,7 +285,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	_, ok := env["PATH"]
 	if !ok {
 		env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
-		if shared.PathExists(fmt.Sprintf("%s/snap", c.RootfsPath())) {
+		if c.FileExists("/snap") == nil {
 			env["PATH"] = fmt.Sprintf("%s:/snap/bin", env["PATH"])
 		}
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 9c5e3d787..cb6d5aa0d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3313,6 +3313,54 @@ func (c *containerLXC) templateApplyNow(trigger string) error {
 	return nil
 }
 
+func (c *containerLXC) FileExists(path string) error {
+	// Setup container storage if needed
+	if !c.IsRunning() {
+		err := c.StorageStart()
+		if err != nil {
+			return err
+		}
+	}
+
+	// Check if the file exists in the container
+	out, err := exec.Command(
+		execPath,
+		"forkcheckfile",
+		c.RootfsPath(),
+		fmt.Sprintf("%d", c.InitPID()),
+		path,
+	).CombinedOutput()
+
+	// Tear down container storage if needed
+	if !c.IsRunning() {
+		err := c.StorageStop()
+		if err != nil {
+			return err
+		}
+	}
+
+	// Process forkcheckfile response
+	if string(out) != "" {
+		if strings.HasPrefix(string(out), "error:") {
+			return fmt.Errorf(strings.TrimPrefix(strings.TrimSuffix(string(out), "\n"), "error: "))
+		}
+
+		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+			shared.LogDebugf("forkcheckfile: %s", line)
+		}
+	}
+
+	if err != nil {
+		return fmt.Errorf(
+			"Error calling 'lxd forkcheckfile %s %d': err='%v'",
+			path,
+			c.InitPID(),
+			err)
+	}
+
+	return nil
+}
+
 func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.FileMode, error) {
 	// Setup container storage if needed
 	if !c.IsRunning() {
@@ -3409,13 +3457,15 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 	}
 
 	// Unmap uid and gid if needed
-	idmapset, err := c.LastIdmapSet()
-	if err != nil {
-		return -1, -1, 0, err
-	}
+	if !c.IsRunning() {
+		idmapset, err := c.LastIdmapSet()
+		if err != nil {
+			return -1, -1, 0, err
+		}
 
-	if idmapset != nil {
-		uid, gid = idmapset.ShiftFromNs(uid, gid)
+		if idmapset != nil {
+			uid, gid = idmapset.ShiftFromNs(uid, gid)
+		}
 	}
 
 	return uid, gid, os.FileMode(mode), nil
@@ -3426,14 +3476,16 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 	var rootGid = 0
 
 	// Map uid and gid if needed
-	idmapset, err := c.LastIdmapSet()
-	if err != nil {
-		return err
-	}
+	if !c.IsRunning() {
+		idmapset, err := c.LastIdmapSet()
+		if err != nil {
+			return err
+		}
 
-	if idmapset != nil {
-		uid, gid = idmapset.ShiftIntoNs(uid, gid)
-		rootUid, rootGid = idmapset.ShiftIntoNs(0, 0)
+		if idmapset != nil {
+			uid, gid = idmapset.ShiftIntoNs(uid, gid)
+			rootUid, rootGid = idmapset.ShiftIntoNs(0, 0)
+		}
 	}
 
 	// Setup container storage if needed
@@ -3494,6 +3546,54 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 	return nil
 }
 
+func (c *containerLXC) FileRemove(path string) error {
+	// Setup container storage if needed
+	if !c.IsRunning() {
+		err := c.StorageStart()
+		if err != nil {
+			return err
+		}
+	}
+
+	// Remove the file from the container
+	out, err := exec.Command(
+		execPath,
+		"forkremovefile",
+		c.RootfsPath(),
+		fmt.Sprintf("%d", c.InitPID()),
+		path,
+	).CombinedOutput()
+
+	// Tear down container storage if needed
+	if !c.IsRunning() {
+		err := c.StorageStop()
+		if err != nil {
+			return err
+		}
+	}
+
+	// Process forkremovefile response
+	if string(out) != "" {
+		if strings.HasPrefix(string(out), "error:") {
+			return fmt.Errorf(strings.TrimPrefix(strings.TrimSuffix(string(out), "\n"), "error: "))
+		}
+
+		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+			shared.LogDebugf("forkremovefile: %s", line)
+		}
+	}
+
+	if err != nil {
+		return fmt.Errorf(
+			"Error calling 'lxd forkremovefile %s %d': err='%v'",
+			path,
+			c.InitPID(),
+			err)
+	}
+
+	return nil
+}
+
 func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error) {
 	envSlice := []string{}
 
@@ -4115,15 +4215,13 @@ func (c *containerLXC) removeUnixDevice(m shared.Device) error {
 	}
 
 	// Remove the bind-mount from the container
-	ctnPath := fmt.Sprintf("/proc/%d/root/%s", pid, tgtPath)
-
-	if shared.PathExists(ctnPath) {
+	if c.FileExists(tgtPath) == nil {
 		err = c.removeMount(m["path"])
 		if err != nil {
 			return fmt.Errorf("Error unmounting the device: %s", err)
 		}
 
-		err = os.Remove(ctnPath)
+		err = c.FileRemove(tgtPath)
 		if err != nil {
 			return fmt.Errorf("Error removing the device: %s", err)
 		}
@@ -4575,9 +4673,7 @@ func (c *containerLXC) removeDiskDevice(name string, m shared.Device) error {
 	devPath := filepath.Join(c.DevicesPath(), devName)
 
 	// Remove the bind-mount from the container
-	ctnPath := fmt.Sprintf("/proc/%d/root/%s", pid, tgtPath)
-
-	if shared.PathExists(ctnPath) {
+	if c.FileExists(tgtPath) == nil {
 		err := c.removeMount(m["path"])
 		if err != nil {
 			return fmt.Errorf("Error unmounting the device: %s", err)
diff --git a/lxd/nsexec.go b/lxd/nsexec.go
index cd59d6fef..018c31f5a 100644
--- a/lxd/nsexec.go
+++ b/lxd/nsexec.go
@@ -125,6 +125,42 @@ int dosetns(int pid, char *nstype) {
 	return 0;
 }
 
+void attach_userns(int pid) {
+	char nspath[PATH_MAX];
+	char userns_source[PATH_MAX];
+	char userns_target[PATH_MAX];
+
+	sprintf(nspath, "/proc/%d/ns/user", pid);
+	if (access(nspath, F_OK) == 0) {
+		if (readlink("/proc/self/ns/user", userns_source, 18) < 0) {
+			fprintf(stderr, "Failed readlink of source namespace: %s\n", strerror(errno));
+			_exit(1);
+		}
+
+		if (readlink(nspath, userns_target, PATH_MAX) < 0) {
+			fprintf(stderr, "Failed readlink of target namespace: %s\n", strerror(errno));
+			_exit(1);
+		}
+
+		if (strncmp(userns_source, userns_target, PATH_MAX) != 0) {
+			if (dosetns(pid, "user") < 0) {
+				fprintf(stderr, "Failed setns to container user namespace: %s\n", strerror(errno));
+				_exit(1);
+			}
+
+			if (setuid(0) < 0) {
+				fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
+				_exit(1);
+			}
+
+			if (setgid(0) < 0) {
+				fprintf(stderr, "Failed setgid to container root group: %s\n", strerror(errno));
+				_exit(1);
+			}
+		}
+	}
+}
+
 int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is_put, uid_t uid, gid_t gid, mode_t mode, uid_t defaultUid, gid_t defaultGid, mode_t defaultMode) {
 	int host_fd, container_fd;
 	int ret = -1;
@@ -143,6 +179,8 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 		container_open_flags |= O_CREAT;
 
 	if (pid > 0) {
+		attach_userns(pid);
+
 		if (dosetns(pid, "mnt") < 0) {
 			error("error: setns");
 			goto close_host;
@@ -300,42 +338,10 @@ void create(char *src, char *dest) {
 void forkmount(char *buf, char *cur, ssize_t size) {
 	char *src, *dest, *opts;
 
-	char nspath[PATH_MAX];
-	char userns_source[PATH_MAX];
-	char userns_target[PATH_MAX];
-
 	ADVANCE_ARG_REQUIRED();
 	int pid = atoi(cur);
 
-	sprintf(nspath, "/proc/%d/ns/user", pid);
-	if (access(nspath, F_OK) == 0) {
-		if (readlink("/proc/self/ns/user", userns_source, 18) < 0) {
-			fprintf(stderr, "Failed readlink of source namespace: %s\n", strerror(errno));
-			_exit(1);
-		}
-
-		if (readlink(nspath, userns_target, PATH_MAX) < 0) {
-			fprintf(stderr, "Failed readlink of target namespace: %s\n", strerror(errno));
-			_exit(1);
-		}
-
-		if (strncmp(userns_source, userns_target, PATH_MAX) != 0) {
-			if (dosetns(pid, "user") < 0) {
-				fprintf(stderr, "Failed setns to container user namespace: %s\n", strerror(errno));
-				_exit(1);
-			}
-
-			if (setuid(0) < 0) {
-				fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
-				_exit(1);
-			}
-
-			if (setgid(0) < 0) {
-				fprintf(stderr, "Failed setgid to container root group: %s\n", strerror(errno));
-				_exit(1);
-			}
-		}
-	}
+	attach_userns(pid);
 
 	if (dosetns(pid, "mnt") < 0) {
 		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
@@ -438,6 +444,99 @@ void forkdofile(char *buf, char *cur, bool is_put, ssize_t size) {
 	_exit(manip_file_in_ns(rootfs, pid, source, target, is_put, uid, gid, mode, defaultUid, defaultGid, defaultMode));
 }
 
+void forkcheckfile(char *buf, char *cur, bool is_put, ssize_t size) {
+	char *command = cur, *rootfs = NULL, *path = NULL;
+	pid_t pid;
+
+	ADVANCE_ARG_REQUIRED();
+	rootfs = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	pid = atoi(cur);
+
+	ADVANCE_ARG_REQUIRED();
+	path = cur;
+
+	if (pid > 0) {
+		attach_userns(pid);
+
+		if (dosetns(pid, "mnt") < 0) {
+			error("error: setns");
+			_exit(1);
+		}
+	} else {
+		if (chroot(rootfs) < 0) {
+			error("error: chroot");
+			_exit(1);
+		}
+
+		if (chdir("/") < 0) {
+			error("error: chdir");
+			_exit(1);
+		}
+	}
+
+	if (access(path, F_OK) < 0) {
+		fprintf(stderr, "Path doesn't exist: %s\n", strerror(errno));
+		_exit(1);
+	}
+
+	_exit(0);
+}
+
+void forkremovefile(char *buf, char *cur, bool is_put, ssize_t size) {
+	char *command = cur, *rootfs = NULL, *path = NULL;
+	pid_t pid;
+	struct stat sb;
+
+	ADVANCE_ARG_REQUIRED();
+	rootfs = cur;
+
+	ADVANCE_ARG_REQUIRED();
+	pid = atoi(cur);
+
+	ADVANCE_ARG_REQUIRED();
+	path = cur;
+
+	if (pid > 0) {
+		attach_userns(pid);
+
+		if (dosetns(pid, "mnt") < 0) {
+			error("error: setns");
+			_exit(1);
+		}
+	} else {
+		if (chroot(rootfs) < 0) {
+			error("error: chroot");
+			_exit(1);
+		}
+
+		if (chdir("/") < 0) {
+			error("error: chdir");
+			_exit(1);
+		}
+	}
+
+	if (stat(path, &sb) < 0) {
+		error("error: stat");
+		_exit(1);
+	}
+
+	if ((sb.st_mode & S_IFMT) == S_IFDIR) {
+		if (rmdir(path) < 0) {
+			fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno));
+			_exit(1);
+		}
+	} else {
+		if (unlink(path) < 0) {
+			fprintf(stderr, "Failed to remove %s: %s\n", path, strerror(errno));
+			_exit(1);
+		}
+	}
+
+	_exit(0);
+}
+
 void forkgetnet(char *buf, char *cur, ssize_t size) {
 	ADVANCE_ARG_REQUIRED();
 	int pid = atoi(cur);
@@ -482,6 +581,10 @@ __attribute__((constructor)) void init(void) {
 		forkdofile(buf, cur, true, size);
 	} else if (strcmp(cur, "forkgetfile") == 0) {
 		forkdofile(buf, cur, false, size);
+	} else if (strcmp(cur, "forkcheckfile") == 0) {
+		forkcheckfile(buf, cur, false, size);
+	} else if (strcmp(cur, "forkremovefile") == 0) {
+		forkremovefile(buf, cur, false, size);
 	} else if (strcmp(cur, "forkmount") == 0) {
 		forkmount(buf, cur, size);
 	} else if (strcmp(cur, "forkumount") == 0) {

From 0b5e70ed6d73a815904a63d62fc9ea68bbabbca4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 16 Nov 2016 11:57:30 -0500
Subject: [PATCH 0449/1193] client: Rework progress handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Use one common approach to progress tracking and reporting.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go  | 34 +++++++++++-----------
 lxc/init.go   | 13 ++++-----
 lxc/launch.go |  4 ++-
 lxc/main.go   | 38 ++++++++++++++++++++++++
 po/lxd.pot    | 92 +++++++++++++++++++++++++++++------------------------------
 5 files changed, 109 insertions(+), 72 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index a602aa998..68aedf035 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -273,14 +273,12 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		progressHandler := func(progress string) {
-			fmt.Printf(i18n.G("Copying the image: %s")+"\r", progress)
-		}
-
-		err = d.CopyImage(inName, dest, c.copyAliases, c.addAliases, c.publicImage, c.autoUpdate, progressHandler)
+		progress := ProgressRenderer{Format: i18n.G("Copying the image: %s")}
+		err = d.CopyImage(inName, dest, c.copyAliases, c.addAliases, c.publicImage, c.autoUpdate, progress.Update)
 		if err == nil {
-			fmt.Println(i18n.G("Image copied successfully!"))
+			progress.Done(i18n.G("Image copied successfully!"))
 		}
+
 		return err
 
 	case "delete":
@@ -408,29 +406,29 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		handler := func(percent int) {
-			fmt.Printf(i18n.G("Transferring image: %d%%")+"\r", percent)
-			if percent == 100 {
-				fmt.Printf("\n")
-			}
-		}
-
 		if strings.HasPrefix(imageFile, "https://") {
-			progressHandler := func(progress string) {
-				fmt.Printf(i18n.G("Importing the image: %s")+"\r", progress)
+			progress := ProgressRenderer{Format: i18n.G("Importing the image: %s")}
+			fingerprint, err = d.PostImageURL(imageFile, properties, c.publicImage, c.addAliases, progress.Update)
+			if err == nil {
+				progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
 			}
-
-			fingerprint, err = d.PostImageURL(imageFile, properties, c.publicImage, c.addAliases, progressHandler)
 		} else if strings.HasPrefix(imageFile, "http://") {
 			return fmt.Errorf(i18n.G("Only https:// is supported for remote image import."))
 		} else {
+			progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")}
+			handler := func(percent int) {
+				progress.Update(fmt.Sprintf("%d%%", percent))
+			}
+
 			fingerprint, err = d.PostImage(imageFile, rootfsFile, properties, c.publicImage, c.addAliases, handler)
+			if err == nil {
+				progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
+			}
 		}
 
 		if err != nil {
 			return err
 		}
-		fmt.Printf(i18n.G("Image imported with fingerprint: %s")+"\n", fingerprint)
 
 		return nil
 
diff --git a/lxc/init.go b/lxc/init.go
index 9cad5f8ad..6209d854f 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -188,9 +188,11 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	c.initProgressTracker(d, resp.Operation)
+	progress := ProgressRenderer{}
+	c.initProgressTracker(d, &progress, resp.Operation)
 
 	err = d.WaitForSuccess(resp.Operation)
+	progress.Done("")
 
 	if err != nil {
 		return err
@@ -213,7 +215,8 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 	return nil
 }
 
-func (c *initCmd) initProgressTracker(d *lxd.Client, operation string) {
+func (c *initCmd) initProgressTracker(d *lxd.Client, progress *ProgressRenderer, operation string) {
+	progress.Format = i18n.G("Retrieving image: %s")
 	handler := func(msg interface{}) {
 		if msg == nil {
 			return
@@ -244,11 +247,7 @@ func (c *initCmd) initProgressTracker(d *lxd.Client, operation string) {
 		opMd := md["metadata"].(map[string]interface{})
 		_, ok := opMd["download_progress"]
 		if ok {
-			fmt.Printf(i18n.G("Retrieving image: %s")+"\r", opMd["download_progress"].(string))
-		}
-
-		if opMd["download_progress"].(string) == "100%" {
-			fmt.Printf("\n")
+			progress.Update(opMd["download_progress"].(string))
 		}
 	}
 	go d.Monitor([]string{"operation"}, handler)
diff --git a/lxc/launch.go b/lxc/launch.go
index d31ae6c99..8d751ed68 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -87,7 +87,8 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	c.init.initProgressTracker(d, resp.Operation)
+	progress := ProgressRenderer{}
+	c.init.initProgressTracker(d, &progress, resp.Operation)
 
 	if name == "" {
 		op, err := resp.MetadataAsOperation()
@@ -120,6 +121,7 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 	if err = d.WaitForSuccess(resp.Operation); err != nil {
 		return err
 	}
+	progress.Done("")
 
 	fmt.Printf(i18n.G("Starting %s")+"\n", name)
 	resp, err = d.Action(name, shared.Start, -1, false, false)
diff --git a/lxc/main.go b/lxc/main.go
index d2313ce95..e601b2cc6 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -269,3 +269,41 @@ func execIfAliases(config *lxd.Config, origArgs []string) {
 	fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), ret)
 	os.Exit(5)
 }
+
+type ProgressRenderer struct {
+	Format string
+
+	maxLength int
+}
+
+func (p *ProgressRenderer) Done(msg string) {
+	if msg != "" {
+		msg += "\n"
+	}
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("%s\r", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print(msg)
+}
+
+func (p *ProgressRenderer) Update(status string) {
+	msg := "%s"
+	if p.Format != "" {
+		msg = p.Format
+	}
+
+	msg = fmt.Sprintf(msg, status)
+	msg += "\r"
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("%s\r", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print(msg)
+}
diff --git a/po/lxd.pot b/po/lxd.pot
index 8a46a0fd6..b0ac8e78e 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-10-26 17:06-0400\n"
+        "POT-Creation-Date: 2016-11-17 12:28-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -77,7 +77,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:597
+#: lxc/image.go:595
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -90,11 +90,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:618 lxc/image.go:647
+#: lxc/image.go:616 lxc/image.go:645
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:622
+#: lxc/image.go:620
 msgid   "ARCH"
 msgstr  ""
 
@@ -111,7 +111,7 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:356
+#: lxc/image.go:354
 msgid   "Aliases:"
 msgstr  ""
 
@@ -119,12 +119,12 @@ msgstr  ""
 msgid   "An environment variable of the form HOME=/home/foo"
 msgstr  ""
 
-#: lxc/image.go:339 lxc/info.go:93
+#: lxc/image.go:337 lxc/info.go:93
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:360
+#: lxc/image.go:358
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
@@ -187,7 +187,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:701 lxc/profile.go:190
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:699 lxc/profile.go:190
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -200,12 +200,12 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/init.go:210
+#: lxc/init.go:212
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
 
-#: lxc/publish.go:141 lxc/publish.go:156
+#: lxc/publish.go:149 lxc/publish.go:164
 #, c-format
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
@@ -220,7 +220,7 @@ msgid   "Copy containers within or in between lxd instances.\n"
         "lxc copy [remote:]<source container> [remote:]<destination container> [--ephemeral|e]"
 msgstr  ""
 
-#: lxc/image.go:277
+#: lxc/image.go:276
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
@@ -245,12 +245,12 @@ msgid   "Create a read-only snapshot of a container.\n"
         "lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:344 lxc/info.go:95
+#: lxc/image.go:342 lxc/info.go:95
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:177 lxc/launch.go:118
+#: lxc/init.go:177 lxc/launch.go:119
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
@@ -259,7 +259,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:621 lxc/image.go:649
+#: lxc/image.go:619 lxc/image.go:647
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -317,16 +317,16 @@ msgid   "Execute the specified command in a container.\n"
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
 
-#: lxc/image.go:348
+#: lxc/image.go:346
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:350
+#: lxc/image.go:348
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:272 lxc/image.go:619 lxc/image.go:648
+#: lxc/config.go:272 lxc/image.go:617 lxc/image.go:646
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -334,7 +334,7 @@ msgstr  ""
 msgid   "Fast mode (same as --columns=nsacPt"
 msgstr  ""
 
-#: lxc/image.go:337
+#: lxc/image.go:335
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -389,16 +389,16 @@ msgstr  ""
 msgid   "Ignore the container state (only for start)."
 msgstr  ""
 
-#: lxc/image.go:282
+#: lxc/image.go:279
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:433
+#: lxc/image.go:413 lxc/image.go:425
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:420
+#: lxc/image.go:410
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
@@ -757,7 +757,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:425
+#: lxc/image.go:416
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -765,7 +765,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:520
+#: lxc/image.go:518
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -790,7 +790,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:620 lxc/remote.go:355
+#: lxc/image.go:618 lxc/remote.go:355
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -829,7 +829,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:702
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:700
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -880,7 +880,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:352
+#: lxc/image.go:350
 msgid   "Properties:"
 msgstr  ""
 
@@ -888,7 +888,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:340
+#: lxc/image.go:338
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -921,12 +921,12 @@ msgstr  ""
 msgid   "Resources:"
 msgstr  ""
 
-#: lxc/init.go:247
+#: lxc/init.go:219
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:623
+#: lxc/image.go:621
 msgid   "SIZE"
 msgstr  ""
 
@@ -991,7 +991,7 @@ msgstr  ""
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/image.go:338
+#: lxc/image.go:336
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -1000,11 +1000,11 @@ msgstr  ""
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/image.go:362
+#: lxc/image.go:360
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/launch.go:124
+#: lxc/launch.go:126
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -1050,7 +1050,7 @@ msgstr  ""
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:277
+#: lxc/init.go:276
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -1067,7 +1067,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it."
 msgstr  ""
 
-#: lxc/image.go:341
+#: lxc/image.go:339
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -1075,12 +1075,12 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:412
+#: lxc/image.go:418
 #, c-format
-msgid   "Transferring image: %d%%"
+msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:99 lxc/launch.go:137
+#: lxc/action.go:99 lxc/launch.go:139
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1093,7 +1093,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:624
+#: lxc/image.go:622
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1105,7 +1105,7 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:346
+#: lxc/image.go:344
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1143,7 +1143,7 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:111
+#: lxc/launch.go:112
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
@@ -1163,15 +1163,15 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
+#: lxc/init.go:202 lxc/init.go:207 lxc/launch.go:96 lxc/launch.go:101
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:332
+#: lxc/image.go:330
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:334
+#: lxc/image.go:332
 msgid   "enabled"
 msgstr  ""
 
@@ -1185,11 +1185,11 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:115
+#: lxc/launch.go:116
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:327 lxc/image.go:600
+#: lxc/image.go:325 lxc/image.go:598
 msgid   "no"
 msgstr  ""
 
@@ -1247,7 +1247,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:604
+#: lxc/delete.go:45 lxc/image.go:327 lxc/image.go:602
 msgid   "yes"
 msgstr  ""
 

From a49cfdb6e6589502db065c167a256cd7b168b93a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 16 Nov 2016 15:21:34 -0500
Subject: [PATCH 0450/1193] On progress updates, keep cursor at end of line
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index e601b2cc6..41fd6ead8 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -284,9 +284,10 @@ func (p *ProgressRenderer) Done(msg string) {
 	if len(msg) > p.maxLength {
 		p.maxLength = len(msg)
 	} else {
-		fmt.Printf("%s\r", strings.Repeat(" ", p.maxLength))
+		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
 	}
 
+	fmt.Print("\r")
 	fmt.Print(msg)
 }
 
@@ -296,13 +297,12 @@ func (p *ProgressRenderer) Update(status string) {
 		msg = p.Format
 	}
 
-	msg = fmt.Sprintf(msg, status)
-	msg += "\r"
+	msg = fmt.Sprintf("\r"+msg, status)
 
 	if len(msg) > p.maxLength {
 		p.maxLength = len(msg)
 	} else {
-		fmt.Printf("%s\r", strings.Repeat(" ", p.maxLength))
+		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
 	}
 
 	fmt.Print(msg)

From 60157451c641f00df82b53e75b559afaac4e3c00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 16 Nov 2016 23:35:52 -0500
Subject: [PATCH 0451/1193] Simplify rsync code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/rsync.go | 86 ++++++++++++++++++++++++++++--------------------------------
 1 file changed, 40 insertions(+), 46 deletions(-)

diff --git a/lxd/rsync.go b/lxd/rsync.go
index 6369d1c2f..49554b962 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -14,44 +14,6 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
-func rsyncWebsocket(path string, cmd *exec.Cmd, conn *websocket.Conn) error {
-	stdin, err := cmd.StdinPipe()
-	if err != nil {
-		return err
-	}
-
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		return err
-	}
-
-	stderr, err := cmd.StderrPipe()
-	if err != nil {
-		return err
-	}
-
-	if err := cmd.Start(); err != nil {
-		return err
-	}
-
-	readDone, writeDone := shared.WebsocketMirror(conn, stdin, stdout)
-	data, err2 := ioutil.ReadAll(stderr)
-	if err2 != nil {
-		shared.LogDebugf("error reading rsync stderr: %s", err2)
-		return err2
-	}
-
-	err = cmd.Wait()
-	if err != nil {
-		shared.LogDebugf("rsync recv error for path %s: %s: %s", path, err, string(data))
-	}
-
-	<-readDone
-	<-writeDone
-
-	return err
-}
-
 func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	/*
 	 * It's sort of unfortunate, but there's no library call to get a
@@ -153,8 +115,11 @@ func RsyncSend(path string, conn *websocket.Conn) error {
 	return err
 }
 
-func rsyncRecvCmd(path string) *exec.Cmd {
-	return exec.Command("rsync",
+// RsyncRecv sets up the receiving half of the websocket to rsync (the other
+// half set up by RsyncSend), putting the contents in the directory specified
+// by path.
+func RsyncRecv(path string, conn *websocket.Conn) error {
+	cmd := exec.Command("rsync",
 		"--server",
 		"-vlogDtpre.iLsfx",
 		"--numeric-ids",
@@ -162,13 +127,42 @@ func rsyncRecvCmd(path string) *exec.Cmd {
 		"--partial",
 		".",
 		path)
-}
 
-// RsyncRecv sets up the receiving half of the websocket to rsync (the other
-// half set up by RsyncSend), putting the contents in the directory specified
-// by path.
-func RsyncRecv(path string, conn *websocket.Conn) error {
-	return rsyncWebsocket(path, rsyncRecvCmd(path), conn)
+	stdin, err := cmd.StdinPipe()
+	if err != nil {
+		return err
+	}
+
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		return err
+	}
+
+	stderr, err := cmd.StderrPipe()
+	if err != nil {
+		return err
+	}
+
+	if err := cmd.Start(); err != nil {
+		return err
+	}
+
+	readDone, writeDone := shared.WebsocketMirror(conn, stdin, stdout)
+	data, err2 := ioutil.ReadAll(stderr)
+	if err2 != nil {
+		shared.LogDebugf("error reading rsync stderr: %s", err2)
+		return err2
+	}
+
+	err = cmd.Wait()
+	if err != nil {
+		shared.LogDebugf("rsync recv error for path %s: %s: %s", path, err, string(data))
+	}
+
+	<-readDone
+	<-writeDone
+
+	return err
 }
 
 // Netcat is called with:

From d02774e84a86564db7ad4b60a9fbe0ab8c4ee2c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 17 Nov 2016 22:57:20 -0500
Subject: [PATCH 0452/1193] simplestreams: Cleanup properties
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change removes the workaround "aliases" property and also clears
any unused properties from the image entry.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams.go | 27 ++++++++++++++++++++++-----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index f387c703c..ab9c7da34 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -175,7 +175,6 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 			image.Filename = filename
 			image.Fingerprint = fingerprint
 			image.Properties = map[string]string{
-				"aliases":      product.Aliases,
 				"os":           product.OperatingSystem,
 				"release":      product.Release,
 				"version":      product.Version,
@@ -185,6 +184,21 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 				"description":  description,
 			}
 
+			// Add the provided aliases
+			if product.Aliases != "" {
+				image.Aliases = []ImageAlias{}
+				for _, entry := range strings.Split(product.Aliases, ",") {
+					image.Aliases = append(image.Aliases, ImageAlias{Name: entry})
+				}
+			}
+
+			// Clear unset properties
+			for k, v := range image.Properties {
+				if v == "" {
+					delete(image.Properties, k)
+				}
+			}
+
 			// Attempt to parse the EOL
 			image.ExpiryDate = time.Unix(0, 0).UTC()
 			if product.SupportedEOL != "" {
@@ -380,19 +394,22 @@ func (s *SimpleStreams) applyAliases(images []ImageInfo) ([]ImageInfo, map[strin
 
 	newImages := []ImageInfo{}
 	for _, image := range images {
-		if image.Properties["aliases"] != "" {
-			aliases := strings.Split(image.Properties["aliases"], ",")
+		if image.Aliases != nil {
+			// Build a new list of aliases from the provided ones
+			aliases := image.Aliases
+			image.Aliases = nil
+
 			for _, entry := range aliases {
 				// Short
 				if image.Architecture == architectureName {
-					alias := addAlias(fmt.Sprintf("%s", entry), image.Fingerprint)
+					alias := addAlias(fmt.Sprintf("%s", entry.Name), image.Fingerprint)
 					if alias != nil {
 						image.Aliases = append(image.Aliases, *alias)
 					}
 				}
 
 				// Medium
-				alias := addAlias(fmt.Sprintf("%s/%s", entry, image.Properties["architecture"]), image.Fingerprint)
+				alias := addAlias(fmt.Sprintf("%s/%s", entry.Name, image.Properties["architecture"]), image.Fingerprint)
 				if alias != nil {
 					image.Aliases = append(image.Aliases, *alias)
 				}

From 40f0286fd8002156c9fec99caca01e8c566ee31f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 17 Nov 2016 23:18:10 -0500
Subject: [PATCH 0453/1193] tests: Reduce verbosity under LXD_DEBUG
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 58 insertions(+), 5 deletions(-)

diff --git a/test/main.sh b/test/main.sh
index 487a15c9b..2c29218ff 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -51,7 +51,11 @@ if [ -z "${LXD_BACKEND:-}" ]; then
 fi
 
 spawn_lxd() {
+  # Don't trace internal functions
   set +x
+  OLD_DEBUG=${LXD_DEBUG:-}
+  LXD_DEBUG=""
+
   # LXD_DIR is local here because since $(lxc) is actually a function, it
   # overwrites the environment and we would lose LXD_DIR's value otherwise.
 
@@ -91,24 +95,44 @@ spawn_lxd() {
 
   echo "==> Setting trust password"
   LXD_DIR="${lxddir}" lxc config set core.trust_password foo
-  if [ -n "${LXD_DEBUG:-}" ]; then
-    set -x
-  fi
 
   echo "==> Configuring storage backend"
   "$LXD_BACKEND"_configure "${lxddir}"
+
+  # Trace everything again
+  if [ -n "${OLD_DEBUG:-}" ]; then
+    LXD_DEBUG="${OLD_DEBUG}"
+    set -x
+  fi
 }
 
 lxc() {
+  # Don't trace internal functions
+  set +x
+  OLD_DEBUG=${LXD_DEBUG:-}
+  LXD_DEBUG=""
+
+  # Call lxc_remote
   LXC_LOCAL=1
   lxc_remote "$@"
   RET=$?
   unset LXC_LOCAL
+
+  # Trace everything again
+  if [ -n "${OLD_DEBUG:-}" ]; then
+    LXD_DEBUG="${OLD_DEBUG}"
+    set -x
+  fi
+
   return ${RET}
 }
 
 lxc_remote() {
+  # Don't trace internal functions
   set +x
+  OLD_DEBUG=${LXD_DEBUG:-}
+  LXD_DEBUG=""
+
   injected=0
   cmd=$(which lxc)
 
@@ -129,10 +153,16 @@ lxc_remote() {
   if [ "${injected}" = "0" ]; then
     cmd="${cmd} ${DEBUG-}"
   fi
-  if [ -n "${LXD_DEBUG:-}" ]; then
+  eval "${cmd}"
+  RET=$?
+
+  # Trace everything again
+  if [ -n "${OLD_DEBUG:-}" ]; then
+    LXD_DEBUG="${OLD_DEBUG}"
     set -x
   fi
-  eval "${cmd}"
+
+  return ${RET}
 }
 
 my_curl() {
@@ -180,6 +210,11 @@ check_empty_table() {
 }
 
 kill_lxd() {
+  # Don't trace internal functions
+  set +x
+  OLD_DEBUG=${LXD_DEBUG:-}
+  LXD_DEBUG=""
+
   # LXD_DIR is local here because since $(lxc) is actually a function, it
   # overwrites the environment and we would lose LXD_DIR's value otherwise.
 
@@ -271,9 +306,21 @@ kill_lxd() {
 
   # Remove the daemon from the list
   sed "\|^${daemon_dir}|d" -i "${TEST_DIR}/daemons"
+
+  # Trace everything again
+  if [ -n "${OLD_DEBUG:-}" ]; then
+    LXD_DEBUG="${OLD_DEBUG}"
+    set -x
+  fi
 }
 
 cleanup() {
+  # Don't trace internal functions
+  set +x
+  OLD_DEBUG=${LXD_DEBUG:-}
+  LXD_DEBUG=""
+
+  # Allow for failures during cleanup
   set +e
 
   # Allow for inspection
@@ -307,6 +354,12 @@ cleanup() {
   if [ "${TEST_RESULT}" != "success" ]; then
     echo "failed test: ${TEST_CURRENT}"
   fi
+
+  # Trace everything again
+  if [ -n "${OLD_DEBUG:-}" ]; then
+    LXD_DEBUG="${OLD_DEBUG}"
+    set -x
+  fi
 }
 
 wipe() {

From 867905eedb148a03e84c8d81859cac3169af3374 Mon Sep 17 00:00:00 2001
From: Harm Aarts <harmaarts at gmail.com>
Date: Fri, 18 Nov 2016 19:34:14 +0100
Subject: [PATCH 0454/1193] doc: update 'delete' help text.

It is now a little terser without losing meaning.

Signed-off-by: Harm Aarts <harmaarts at gmail.com>
---
 lxc/delete.go | 2 +-
 po/lxd.pot    | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxc/delete.go b/lxc/delete.go
index afa3d7de9..660f983b4 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -23,7 +23,7 @@ func (c *deleteCmd) showByDefault() bool {
 
 func (c *deleteCmd) usage() string {
 	return i18n.G(
-		`Delete containers or container snapshots.
+		`Delete containers or snapshots.
 
 lxc delete [remote:]<container>[/<snapshot>] [remote:][<container>[/<snapshot>]...]
 
diff --git a/po/lxd.pot b/po/lxd.pot
index b0ac8e78e..36c22eb12 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-11-17 12:28-0500\n"
+        "POT-Creation-Date: 2016-11-22 23:12-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -264,7 +264,7 @@ msgid   "DESCRIPTION"
 msgstr  ""
 
 #: lxc/delete.go:25
-msgid   "Delete containers or container snapshots.\n"
+msgid   "Delete containers or snapshots.\n"
         "\n"
         "lxc delete [remote:]<container>[/<snapshot>] [remote:][<container>[/<snapshot>]...]\n"
         "\n"

From bcc47a70765666ead4b454aebebc7179de7dfd65 Mon Sep 17 00:00:00 2001
From: Harm Aarts <harmaarts at gmail.com>
Date: Fri, 18 Nov 2016 19:36:13 +0100
Subject: [PATCH 0455/1193] doc: update 'finger' help text.

It is now shorter and more clear.

Signed-off-by: Harm Aarts <harmaarts at gmail.com>
---
 lxc/finger.go |  2 +-
 po/lxd.pot    | 14 +++++++-------
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/lxc/finger.go b/lxc/finger.go
index 2f0749fe3..d500e18c4 100644
--- a/lxc/finger.go
+++ b/lxc/finger.go
@@ -13,7 +13,7 @@ func (c *fingerCmd) showByDefault() bool {
 
 func (c *fingerCmd) usage() string {
 	return i18n.G(
-		`Fingers the LXD instance to check if it is up and working.
+		`Check if the LXD instance is up.
 
 lxc finger <remote>`)
 }
diff --git a/po/lxd.pot b/po/lxd.pot
index 36c22eb12..da8f1ecbd 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-11-22 23:12-0500\n"
+        "POT-Creation-Date: 2016-11-22 23:13-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -175,6 +175,12 @@ msgid   "Changes state of one or more containers to %s.\n"
         "lxc %s <name> [<name>...]%s"
 msgstr  ""
 
+#: lxc/finger.go:15
+msgid   "Check if the LXD instance is up.\n"
+        "\n"
+        "lxc finger <remote>"
+msgstr  ""
+
 #: lxc/remote.go:268
 msgid   "Client certificate stored at server: "
 msgstr  ""
@@ -339,12 +345,6 @@ msgstr  ""
 msgid   "Fingerprint: %s"
 msgstr  ""
 
-#: lxc/finger.go:15
-msgid   "Fingers the LXD instance to check if it is up and working.\n"
-        "\n"
-        "lxc finger <remote>"
-msgstr  ""
-
 #: lxc/action.go:42 lxc/action.go:43
 msgid   "Force the container to shutdown."
 msgstr  ""

From edb06407acb145ff11c8512a527ebc56d0f38a7d Mon Sep 17 00:00:00 2001
From: Harm Aarts <harmaarts at gmail.com>
Date: Fri, 18 Nov 2016 19:37:16 +0100
Subject: [PATCH 0456/1193] doc: update 'restore' help text.

Made the subject of the sentence the thing on which the operation works.

Signed-off-by: Harm Aarts <harmaarts at gmail.com>
---
 lxc/restore.go | 2 +-
 po/lxd.pot     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/restore.go b/lxc/restore.go
index 440babfee..3e4f8d52e 100644
--- a/lxc/restore.go
+++ b/lxc/restore.go
@@ -19,7 +19,7 @@ func (c *restoreCmd) showByDefault() bool {
 
 func (c *restoreCmd) usage() string {
 	return i18n.G(
-		`Set the current state of a resource back to a snapshot.
+		`Set the current state of a container back to a snapshot.
 
 lxc restore [remote:]<container> <snapshot name> [--stateful]
 
diff --git a/po/lxd.pot b/po/lxd.pot
index da8f1ecbd..86fef584f 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -955,7 +955,7 @@ msgid   "Server protocol (lxd or simplestreams)"
 msgstr  ""
 
 #: lxc/restore.go:21
-msgid   "Set the current state of a resource back to a snapshot.\n"
+msgid   "Set the current state of a container back to a snapshot.\n"
         "\n"
         "lxc restore [remote:]<container> <snapshot name> [--stateful]\n"
         "\n"

From fcc20ea56b137d6d23e548d0a0bc2d02d9b34cd2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 19 Nov 2016 18:43:31 -0500
Subject: [PATCH 0457/1193] test: Better fix LXD_DEBUG
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The previous change didn't really work due to internal function calls.
Instead, lets just focus on silencing things once we hit a failure.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 67 ++++++++----------------------------------------------------
 1 file changed, 8 insertions(+), 59 deletions(-)

diff --git a/test/main.sh b/test/main.sh
index 2c29218ff..42202dd5d 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -51,11 +51,7 @@ if [ -z "${LXD_BACKEND:-}" ]; then
 fi
 
 spawn_lxd() {
-  # Don't trace internal functions
   set +x
-  OLD_DEBUG=${LXD_DEBUG:-}
-  LXD_DEBUG=""
-
   # LXD_DIR is local here because since $(lxc) is actually a function, it
   # overwrites the environment and we would lose LXD_DIR's value otherwise.
 
@@ -95,44 +91,24 @@ spawn_lxd() {
 
   echo "==> Setting trust password"
   LXD_DIR="${lxddir}" lxc config set core.trust_password foo
+  if [ -n "${LXD_DEBUG:-}" ]; then
+    set -x
+  fi
 
   echo "==> Configuring storage backend"
   "$LXD_BACKEND"_configure "${lxddir}"
-
-  # Trace everything again
-  if [ -n "${OLD_DEBUG:-}" ]; then
-    LXD_DEBUG="${OLD_DEBUG}"
-    set -x
-  fi
 }
 
 lxc() {
-  # Don't trace internal functions
-  set +x
-  OLD_DEBUG=${LXD_DEBUG:-}
-  LXD_DEBUG=""
-
-  # Call lxc_remote
   LXC_LOCAL=1
   lxc_remote "$@"
   RET=$?
   unset LXC_LOCAL
-
-  # Trace everything again
-  if [ -n "${OLD_DEBUG:-}" ]; then
-    LXD_DEBUG="${OLD_DEBUG}"
-    set -x
-  fi
-
   return ${RET}
 }
 
 lxc_remote() {
-  # Don't trace internal functions
   set +x
-  OLD_DEBUG=${LXD_DEBUG:-}
-  LXD_DEBUG=""
-
   injected=0
   cmd=$(which lxc)
 
@@ -153,16 +129,10 @@ lxc_remote() {
   if [ "${injected}" = "0" ]; then
     cmd="${cmd} ${DEBUG-}"
   fi
-  eval "${cmd}"
-  RET=$?
-
-  # Trace everything again
-  if [ -n "${OLD_DEBUG:-}" ]; then
-    LXD_DEBUG="${OLD_DEBUG}"
+  if [ -n "${LXD_DEBUG:-}" ]; then
     set -x
   fi
-
-  return ${RET}
+  eval "${cmd}"
 }
 
 my_curl() {
@@ -210,11 +180,6 @@ check_empty_table() {
 }
 
 kill_lxd() {
-  # Don't trace internal functions
-  set +x
-  OLD_DEBUG=${LXD_DEBUG:-}
-  LXD_DEBUG=""
-
   # LXD_DIR is local here because since $(lxc) is actually a function, it
   # overwrites the environment and we would lose LXD_DIR's value otherwise.
 
@@ -306,22 +271,12 @@ kill_lxd() {
 
   # Remove the daemon from the list
   sed "\|^${daemon_dir}|d" -i "${TEST_DIR}/daemons"
-
-  # Trace everything again
-  if [ -n "${OLD_DEBUG:-}" ]; then
-    LXD_DEBUG="${OLD_DEBUG}"
-    set -x
-  fi
 }
 
 cleanup() {
-  # Don't trace internal functions
-  set +x
-  OLD_DEBUG=${LXD_DEBUG:-}
-  LXD_DEBUG=""
-
-  # Allow for failures during cleanup
-  set +e
+  # Allow for failures and stop tracing everything
+  set +ex
+  LXD_DEBUG=
 
   # Allow for inspection
   if [ -n "${LXD_INSPECT:-}" ]; then
@@ -354,12 +309,6 @@ cleanup() {
   if [ "${TEST_RESULT}" != "success" ]; then
     echo "failed test: ${TEST_CURRENT}"
   fi
-
-  # Trace everything again
-  if [ -n "${OLD_DEBUG:-}" ]; then
-    LXD_DEBUG="${OLD_DEBUG}"
-    set -x
-  fi
 }
 
 wipe() {

From 8addc9bee5a0c760e116311209e02a85932486f8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 19 Nov 2016 18:54:31 -0500
Subject: [PATCH 0458/1193] test: Don't depend on main.sh for filemanip
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Otherwise if main.sh is owned by a host uid which cannot be mapped into
the container, the transfer will fail.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/filemanip.sh | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index 07ca1488e..f63906838 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -4,12 +4,14 @@ test_filemanip() {
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
 
+  echo "test" > "${TEST_DIR}"/filemanip
+
   lxc launch testimage filemanip
   lxc exec filemanip -- ln -s /tmp/ /tmp/outside
-  lxc file push main.sh filemanip/tmp/outside/
+  lxc file push "${TEST_DIR}"/filemanip filemanip/tmp/outside/
 
-  [ ! -f /tmp/main.sh ]
-  lxc exec filemanip -- ls /tmp/main.sh
+  [ ! -f /tmp/filemanip ]
+  lxc exec filemanip -- ls /tmp/filemanip
 
   # missing files should return 404
   err=$(my_curl -o /dev/null -w "%{http_code}" -X GET "https://${LXD_ADDR}/1.0/containers/filemanip/files?path=/tmp/foo")

From 27585162853e78b4917be2ffae96f79eadda5e67 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 19 Nov 2016 19:09:52 -0500
Subject: [PATCH 0459/1193] Clarify container delete failure error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index cb6d5aa0d..df2dac988 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2050,7 +2050,7 @@ func (c *containerLXC) Delete() error {
 		// Delete the container from disk
 		if shared.PathExists(c.Path()) {
 			if err := c.storage.ContainerDelete(c); err != nil {
-				shared.LogError("Failed deleting container", ctxMap)
+				shared.LogError("Failed deleting container storage", ctxMap)
 				return err
 			}
 		}
@@ -2058,7 +2058,7 @@ func (c *containerLXC) Delete() error {
 
 	// Remove the database record
 	if err := dbContainerRemove(c.daemon.db, c.Name()); err != nil {
-		shared.LogError("Failed deleting container", ctxMap)
+		shared.LogError("Failed deleting container entry", ctxMap)
 		return err
 	}
 

From 882b14233c9ed1932b0889d0cc6bd8b40517b7cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 19 Nov 2016 19:10:03 -0500
Subject: [PATCH 0460/1193] Don't double delete ephemeral containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_state.go | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/lxd/container_state.go b/lxd/container_state.go
index c01cefc6f..1a4ca8cb0 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -80,10 +80,6 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 					return err
 				}
 
-				if c.IsEphemeral() {
-					c.Delete()
-				}
-
 				return nil
 			}
 		} else {
@@ -100,10 +96,6 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 					return err
 				}
 
-				if c.IsEphemeral() {
-					c.Delete()
-				}
-
 				return nil
 			}
 		}

From bbc20707cd7f144c9325fa05c3f35a93f83b10bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 19 Nov 2016 22:49:37 -0500
Subject: [PATCH 0461/1193] Better handle concurent stop/shutdown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2612

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index df2dac988..9ce7e98bb 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -297,7 +297,7 @@ type containerLXC struct {
 func (c *containerLXC) createOperation(action string, timeout int) (*lxcContainerOperation, error) {
 	op, _ := c.getOperation("")
 	if op != nil {
-		return nil, fmt.Errorf("Container is already running a %s operation", op.action)
+		return nil, fmt.Errorf("Container is busy running a %s operation", op.action)
 	}
 
 	lxcContainerOperationsLock.Lock()
@@ -1604,13 +1604,13 @@ func (c *containerLXC) Stop(stateful bool) error {
 	}
 
 	err = op.Wait()
-	if err != nil {
+	if err != nil && c.IsRunning() {
 		shared.LogError("Failed stopping container", ctxMap)
 		return err
 	}
 
 	shared.LogInfo("Stopped container", ctxMap)
-	return err
+	return nil
 }
 
 func (c *containerLXC) Shutdown(timeout time.Duration) error {
@@ -1623,7 +1623,7 @@ func (c *containerLXC) Shutdown(timeout time.Duration) error {
 	}
 
 	ctxMap = log.Ctx{"name": c.name,
-		"action":        op.action,
+		"action":        "shutdown",
 		"creation date": c.creationDate,
 		"ephemeral":     c.ephemeral,
 		"timeout":       timeout}
@@ -1645,14 +1645,14 @@ func (c *containerLXC) Shutdown(timeout time.Duration) error {
 	}
 
 	err = op.Wait()
-	if err != nil {
+	if err != nil && c.IsRunning() {
 		shared.LogError("Failed shutting down container", ctxMap)
 		return err
 	}
 
 	shared.LogInfo("Shut down container", ctxMap)
 
-	return err
+	return nil
 }
 
 func (c *containerLXC) OnStop(target string) error {

From 442799a66479bc94c726f65c8561738c5574cd99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 19 Nov 2016 22:57:32 -0500
Subject: [PATCH 0462/1193] Improve container error handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 9ce7e98bb..64304ac29 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1528,6 +1528,12 @@ func (c *containerLXC) OnStart() error {
 // Stop functions
 func (c *containerLXC) Stop(stateful bool) error {
 	var ctxMap log.Ctx
+
+	// Check that we're not already stopped
+	if !c.IsRunning() {
+		return fmt.Errorf("The container is already stopped")
+	}
+
 	// Setup a new operation
 	op, err := c.createOperation("stop", 30)
 	if err != nil {
@@ -1616,6 +1622,11 @@ func (c *containerLXC) Stop(stateful bool) error {
 func (c *containerLXC) Shutdown(timeout time.Duration) error {
 	var ctxMap log.Ctx
 
+	// Check that we're not already stopped
+	if !c.IsRunning() {
+		return fmt.Errorf("The container is already stopped")
+	}
+
 	// Setup a new operation
 	op, err := c.createOperation("shutdown", 30)
 	if err != nil {
@@ -1753,6 +1764,16 @@ func (c *containerLXC) Freeze() error {
 		"creation date": c.creationDate,
 		"ephemeral":     c.ephemeral}
 
+	// Check that we're not already frozen
+	if c.IsFrozen() {
+		return fmt.Errorf("The container is already frozen")
+	}
+
+	// Check that we're running
+	if !c.IsRunning() {
+		return fmt.Errorf("The container isn't running")
+	}
+
 	shared.LogInfo("Freezing container", ctxMap)
 
 	// Load the go-lxc struct
@@ -1780,6 +1801,16 @@ func (c *containerLXC) Unfreeze() error {
 		"creation date": c.creationDate,
 		"ephemeral":     c.ephemeral}
 
+	// Check that we're frozen
+	if !c.IsFrozen() {
+		return fmt.Errorf("The container is already running")
+	}
+
+	// Check that we're running
+	if !c.IsRunning() {
+		return fmt.Errorf("The container isn't running")
+	}
+
 	shared.LogInfo("Unfreezing container", ctxMap)
 
 	// Load the go-lxc struct

From e3912ff591d134b41b998b9867cccf489bbe7020 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 19 Nov 2016 23:21:18 -0500
Subject: [PATCH 0463/1193] Improve container locking mechanism
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Removes adjustable timeout (we were using 30s everywhere)
 - Merge stop and shutdown operations
 - Allows for re-usable operations
 - Make Shutdown() re-use any existing stop operation which was
   triggered by a prior call to Shutdown()
 - Make Stop() re-use any existng stop operations which was triggered by
   a prior call to Shutdown()

This effectively still prevents Start() from kicking in before a
container is fully shutdown and it also still prevents concurrent calls
to Stop(), but it now allows for multiple calls to Shutdown() to work
(just sending the shutdown signal multiple times) and it also allows for
a forceful shutdown to work after a clean shutdown attempt.

Closes #2612

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 59 +++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 19 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 64304ac29..aa1b40ea3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -30,29 +30,45 @@ import (
 
 // Operation locking
 type lxcContainerOperation struct {
-	action   string
-	chanDone chan error
-	err      error
-	id       int
-	timeout  int
+	action    string
+	chanDone  chan error
+	chanReset chan bool
+	err       error
+	id        int
+	reusable  bool
 }
 
-func (op *lxcContainerOperation) Create(id int, action string, timeout int) *lxcContainerOperation {
+func (op *lxcContainerOperation) Create(id int, action string, reusable bool) *lxcContainerOperation {
 	op.id = id
 	op.action = action
-	op.timeout = timeout
+	op.reusable = reusable
 	op.chanDone = make(chan error, 0)
+	op.chanReset = make(chan bool, 0)
 
-	if timeout > 1 {
-		go func(op *lxcContainerOperation) {
-			time.Sleep(time.Second * time.Duration(op.timeout))
-			op.Done(fmt.Errorf("Container %s operation timed out after %d seconds", op.action, op.timeout))
-		}(op)
-	}
+	go func(op *lxcContainerOperation) {
+		for {
+			select {
+			case <-op.chanReset:
+				continue
+			case <-time.After(time.Second * 30):
+				op.Done(fmt.Errorf("Container %s operation timed out after 30 seconds", op.action))
+				return
+			}
+		}
+	}(op)
 
 	return op
 }
 
+func (op *lxcContainerOperation) Reset() error {
+	if !op.reusable {
+		return fmt.Errorf("Can't reset a non-reusable operation")
+	}
+
+	op.chanReset <- true
+	return nil
+}
+
 func (op *lxcContainerOperation) Wait() error {
 	<-op.chanDone
 
@@ -294,9 +310,14 @@ type containerLXC struct {
 	storage  storage
 }
 
-func (c *containerLXC) createOperation(action string, timeout int) (*lxcContainerOperation, error) {
+func (c *containerLXC) createOperation(action string, reusable bool, reuse bool) (*lxcContainerOperation, error) {
 	op, _ := c.getOperation("")
 	if op != nil {
+		if reuse && op.reusable {
+			op.Reset()
+			return op, nil
+		}
+
 		return nil, fmt.Errorf("Container is busy running a %s operation", op.action)
 	}
 
@@ -304,7 +325,7 @@ func (c *containerLXC) createOperation(action string, timeout int) (*lxcContaine
 	defer lxcContainerOperationsLock.Unlock()
 
 	op = &lxcContainerOperation{}
-	op.Create(c.id, action, timeout)
+	op.Create(c.id, action, reusable)
 	lxcContainerOperations[c.id] = op
 
 	return lxcContainerOperations[c.id], nil
@@ -1317,7 +1338,7 @@ func (c *containerLXC) Start(stateful bool) error {
 	var ctxMap log.Ctx
 
 	// Setup a new operation
-	op, err := c.createOperation("start", 30)
+	op, err := c.createOperation("start", false, false)
 	if err != nil {
 		return err
 	}
@@ -1535,7 +1556,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 	}
 
 	// Setup a new operation
-	op, err := c.createOperation("stop", 30)
+	op, err := c.createOperation("stop", false, true)
 	if err != nil {
 		return err
 	}
@@ -1628,7 +1649,7 @@ func (c *containerLXC) Shutdown(timeout time.Duration) error {
 	}
 
 	// Setup a new operation
-	op, err := c.createOperation("shutdown", 30)
+	op, err := c.createOperation("stop", true, true)
 	if err != nil {
 		return err
 	}
@@ -1675,7 +1696,7 @@ func (c *containerLXC) OnStop(target string) error {
 
 	// Get operation
 	op, _ := c.getOperation("")
-	if op != nil && !shared.StringInSlice(op.action, []string{"stop", "shutdown"}) {
+	if op != nil && op.action != "stop" {
 		return fmt.Errorf("Container is already running a %s operation", op.action)
 	}
 

From b349ccabfbb47f6fdb30e66f0057344f04bf924c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 20 Nov 2016 00:28:48 -0500
Subject: [PATCH 0464/1193] zfs: Fix 10s delay on removing used images
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

For some reason "zfs rename" attempts to unmount and remount all
descendants. This behavior, combined with what looks like lack of
support for mount namespaces in ZFS, causes "zfs rename" to fail despite
having succesfuly renamed the dataset.

So instead of believing the return code, always check if the rename happened.

Closes #2617

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_zfs.go | 36 +++++++++++++++++++++++++-----------
 1 file changed, 25 insertions(+), 11 deletions(-)

diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index c6df6ec21..bc0fd430f 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -8,6 +8,7 @@ import (
 	"strconv"
 	"strings"
 	"syscall"
+	"time"
 
 	"github.com/gorilla/websocket"
 
@@ -891,20 +892,33 @@ func (s *storageZfs) zfsGet(path string, key string) (string, error) {
 }
 
 func (s *storageZfs) zfsRename(source string, dest string) error {
-	output, err := tryExec(
-		"zfs",
-		"rename",
-		"-p",
-		fmt.Sprintf("%s/%s", s.zfsPool, source),
-		fmt.Sprintf("%s/%s", s.zfsPool, dest))
-	if err != nil {
-		if s.zfsExists(source) || !s.zfsExists(dest) {
-			s.log.Error("zfs rename failed", log.Ctx{"output": string(output)})
-			return fmt.Errorf("Failed to rename ZFS filesystem: %s", output)
+	var err error
+	var output []byte
+
+	for i := 0; i < 20; i++ {
+		output, err = exec.Command(
+			"zfs",
+			"rename",
+			"-p",
+			fmt.Sprintf("%s/%s", s.zfsPool, source),
+			fmt.Sprintf("%s/%s", s.zfsPool, dest)).CombinedOutput()
+
+		// Success
+		if err == nil {
+			return nil
+		}
+
+		// zfs rename can fail because of descendants, yet still manage the rename
+		if !s.zfsExists(source) && s.zfsExists(dest) {
+			return nil
 		}
+
+		time.Sleep(500 * time.Millisecond)
 	}
 
-	return nil
+	// Timeout
+	s.log.Error("zfs rename failed", log.Ctx{"output": string(output)})
+	return fmt.Errorf("Failed to rename ZFS filesystem: %s", output)
 }
 
 func (s *storageZfs) zfsSet(path string, key string, value string) error {

From fca52e8756347d6206de270259019f175481f704 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Nov 2016 21:21:26 -0500
Subject: [PATCH 0465/1193] test: Implement LXD_VERBOSE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/test/main.sh b/test/main.sh
index 42202dd5d..6052a370c 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -7,8 +7,15 @@ export "LC_ALL=C"
 # Force UTC for consistency
 export "TZ=UTC"
 
-if [ -n "${LXD_DEBUG:-}" ]; then
+if [ -n "${LXD_VERBOSE:-}" ] || [ -n "${LXD_DEBUG:-}" ]; then
   set -x
+fi
+
+if [ -n "${LXD_VERBOSE:-}" ]; then
+  DEBUG="--verbose"
+fi
+
+if [ -n "${LXD_DEBUG:-}" ]; then
   DEBUG="--debug"
 fi
 
@@ -91,7 +98,7 @@ spawn_lxd() {
 
   echo "==> Setting trust password"
   LXD_DIR="${lxddir}" lxc config set core.trust_password foo
-  if [ -n "${LXD_DEBUG:-}" ]; then
+  if [ -n "${DEBUG:-}" ]; then
     set -x
   fi
 
@@ -129,7 +136,7 @@ lxc_remote() {
   if [ "${injected}" = "0" ]; then
     cmd="${cmd} ${DEBUG-}"
   fi
-  if [ -n "${LXD_DEBUG:-}" ]; then
+  if [ -n "${DEBUG:-}" ]; then
     set -x
   fi
   eval "${cmd}"
@@ -276,7 +283,7 @@ kill_lxd() {
 cleanup() {
   # Allow for failures and stop tracing everything
   set +ex
-  LXD_DEBUG=
+  DEBUG=
 
   # Allow for inspection
   if [ -n "${LXD_INSPECT:-}" ]; then

From 61741cdd212c4ff544d29f000696799b9a48f4df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Nov 2016 21:54:00 -0500
Subject: [PATCH 0466/1193] test: Cleanup leftover containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This way we don't have leftover containers running while we move on to
the next testsuite.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/basic.sh     | 4 ++--
 test/suites/devlxd.sh    | 2 +-
 test/suites/migration.sh | 7 ++++---
 3 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 8030af167..c2a799d1a 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -66,7 +66,7 @@ test_basic_usage() {
 
   lxc image export splitimage "${LXD_DIR}"
   [ "${sum}" = "$(cat "${LXD_DIR}/meta-${sum}.tar.xz" "${LXD_DIR}/${sum}.tar.xz" | sha256sum | cut -d' ' -f1)" ]
-  
+
   # Delete the split image and exported files
   rm "${LXD_DIR}/${sum}.tar.xz"
   rm "${LXD_DIR}/meta-${sum}.tar.xz"
@@ -79,7 +79,7 @@ test_basic_usage() {
 
   lxc image export splitimage "${LXD_DIR}"
   [ "${sum}" = "$(cat "${LXD_DIR}/meta-${sum}.tar.xz" "${LXD_DIR}/${sum}.tar.xz" | sha256sum | cut -d' ' -f1)" ]
-  
+
   # Delete the split image and exported files
   rm "${LXD_DIR}/${sum}.tar.xz"
   rm "${LXD_DIR}/meta-${sum}.tar.xz"
diff --git a/test/suites/devlxd.sh b/test/suites/devlxd.sh
index 3e4f13851..722d247ec 100644
--- a/test/suites/devlxd.sh
+++ b/test/suites/devlxd.sh
@@ -18,5 +18,5 @@ test_devlxd() {
   lxc config set devlxd user.foo bar
   lxc exec devlxd devlxd-client user.foo | grep bar
 
-  lxc stop devlxd --force
+  lxc delete devlxd --force
 }
diff --git a/test/suites/migration.sh b/test/suites/migration.sh
index 0c865f6a5..593b9b6ea 100644
--- a/test/suites/migration.sh
+++ b/test/suites/migration.sh
@@ -44,6 +44,7 @@ test_migration() {
   if [ "${LXD_BACKEND}" != "lvm" ]; then
     [ -d "${LXD2_DIR}/containers/nonlive3/rootfs/bin" ]
   fi
+  lxc_remote delete l2:nonlive3 --force
 
   lxc_remote copy l2:nonlive l2:nonlive2
   # should have the same base image tag
@@ -57,11 +58,11 @@ test_migration() {
 
   lxc_remote start l1:nonlive2
   lxc_remote list l1: | grep RUNNING | grep nonlive2
-  lxc_remote stop l1:nonlive2 --force
+  lxc_remote delete l1:nonlive2 l2:nonlive2 --force
 
   lxc_remote start l2:nonlive
   lxc_remote list l2: | grep RUNNING | grep nonlive
-  lxc_remote stop l2:nonlive --force
+  lxc_remote delete l2:nonlive --force
 
   if ! which criu >/dev/null 2>&1; then
     echo "==> SKIP: live migration with CRIU (missing binary)"
@@ -75,5 +76,5 @@ test_migration() {
 
   lxc_remote stop --stateful l1:migratee
   lxc_remote start l1:migratee
-  lxc_remote stop --force l1:migratee
+  lxc_remote delete --force l1:migratee
 }

From 31cb27262f4cf04a0b14fee72fd29b5803c9f86f Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 10 Nov 2016 09:38:40 +0200
Subject: [PATCH 0467/1193] add the id_map API extension

This first bit adds support for security.idmap.{size,isolated}, which allow
for configuring the idmaps of each individual container.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 doc/api-extensions.md    |  11 +++
 lxd/api_1.0.go           |  18 ++++-
 lxd/container.go         |  44 ++++++++++++
 lxd/container_lxc.go     | 177 ++++++++++++++++++++++++++++++++++++++++++++++-
 lxd/container_test.go    |  82 ++++++++++++++++++++++
 lxd/main_test.go         |   7 ++
 shared/idmapset_linux.go |  14 ++++
 7 files changed, 347 insertions(+), 6 deletions(-)
 create mode 100644 doc/api-extensions.md

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
new file mode 100644
index 000000000..811c382d3
--- /dev/null
+++ b/doc/api-extensions.md
@@ -0,0 +1,11 @@
+# API extensions
+
+The changes below were introduced to the LXD API after the 1.0 API was finalized.
+
+They are all backward compatible and can be detected by client tools by
+looking at the api\_extensions field in GET /1.0/.
+
+
+## id\_map
+Enables setting the `security.idmap.isolated` and `security.idmap.isolated`,
+`security.idmap.size`, and `raw.id_map` fields.
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index fd8b4f1c4..521786f2c 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -45,9 +45,21 @@ var api10 = []Command{
 
 func api10Get(d *Daemon, r *http.Request) Response {
 	body := shared.Jmap{
-		"api_extensions": []string{},
-		"api_status":     "stable",
-		"api_version":    shared.APIVersion,
+		/* List of API extensions in the order they were added.
+		 *
+		 * The following kind of changes require an addition to api_extensions:
+		 *  - New configuration key
+		 *  - New valid values for a configuration key
+		 *  - New REST API endpoint
+		 *  - New argument inside an existing REST API call
+		 *  - New HTTPs authentication mechanisms or protocols
+		 */
+		"api_extensions": []string{
+			"id_map",
+		},
+
+		"api_status":  "stable",
+		"api_version": shared.APIVersion,
 	}
 
 	if d.isTrustedClient(r) {
diff --git a/lxd/container.go b/lxd/container.go
index 10de98c3d..2fa3fe5ed 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"io"
 	"os"
@@ -74,6 +75,19 @@ func containerValidConfigKey(key string, value string) error {
 		return nil
 	}
 
+	isUint32 := func(key string, value string) error {
+		if value == "" {
+			return nil
+		}
+
+		_, err := strconv.ParseInt(value, 10, 32)
+		if err != nil {
+			return fmt.Errorf("Invalid value for uint32: %s: %v", value, err)
+		}
+
+		return nil
+	}
+
 	switch key {
 	case "boot.autostart":
 		return isBool(key, value)
@@ -155,6 +169,10 @@ func containerValidConfigKey(key string, value string) error {
 		return isBool(key, value)
 	case "security.nesting":
 		return isBool(key, value)
+	case "security.idmap.size":
+		return isUint32(key, value)
+	case "security.idmap.isolated":
+		return isBool(key, value)
 	case "raw.apparmor":
 		return nil
 	case "raw.lxc":
@@ -167,6 +185,10 @@ func containerValidConfigKey(key string, value string) error {
 		return nil
 	case "volatile.last_state.power":
 		return nil
+	case "volatile.idmap.next":
+		return nil
+	case "volatile.idmap.base":
+		return nil
 	}
 
 	if strings.HasPrefix(key, "volatile.") {
@@ -683,6 +705,28 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	// Wipe any existing log for this container name
 	os.RemoveAll(shared.LogPath(args.Name))
 
+	idmap, base, err := findIdmap(
+		d,
+		args.Name,
+		args.Config["security.idmap.isolated"],
+		args.Config["security.idmap.size"],
+	)
+	if err != nil {
+		return nil, err
+	}
+	var jsonIdmap string
+	if idmap != nil {
+		idmapBytes, err := json.Marshal(idmap.Idmap)
+		if err != nil {
+			return nil, err
+		}
+		jsonIdmap = string(idmapBytes)
+	} else {
+		jsonIdmap = "[]"
+	}
+	args.Config["volatile.idmap.next"] = jsonIdmap
+	args.Config["volatile.idmap.base"] = fmt.Sprintf("%v", base)
+
 	// Create the container entry
 	id, err := dbContainerCreate(d.db, args)
 	if err != nil {
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index aa1b40ea3..04739e091 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -13,6 +13,7 @@ import (
 	"path"
 	"path/filepath"
 	"reflect"
+	"sort"
 	"strconv"
 	"strings"
 	"sync"
@@ -250,6 +251,12 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		return nil, err
 	}
 
+	err = c.ConfigKeySet("volatile.idmap.next", jsonIdmap)
+	if err != nil {
+		c.Delete()
+		return nil, err
+	}
+
 	return c, nil
 }
 
@@ -360,6 +367,134 @@ func (c *containerLXC) waitOperation() error {
 	return nil
 }
 
+func idmapSize(daemon *Daemon, isolatedStr string, size string) (int, error) {
+	isolated := false
+	if isolatedStr == "true" {
+		isolated = true
+	}
+
+	var idMapSize int
+	if size == "" || size == "auto" {
+		if isolated {
+			idMapSize = 65536
+		} else {
+			if len(daemon.IdmapSet.Idmap) != 2 {
+				return 0, fmt.Errorf("bad initial idmap: %v", daemon.IdmapSet)
+			}
+
+			idMapSize = daemon.IdmapSet.Idmap[0].Maprange
+		}
+	} else {
+		size, err := strconv.ParseInt(size, 10, 32)
+		if err != nil {
+			return 0, err
+		}
+
+		idMapSize = int(size)
+	}
+
+	return idMapSize, nil
+}
+
+var idmapLock sync.Mutex
+
+func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string) (*shared.IdmapSet, int, error) {
+	isolated := false
+	if isolatedStr == "true" {
+		isolated = true
+	}
+
+	if !isolated {
+		return daemon.IdmapSet, 0, nil
+	}
+
+	idmapLock.Lock()
+	defer idmapLock.Unlock()
+
+	cs, err := dbContainersList(daemon.db, cTypeRegular)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	offset := daemon.IdmapSet.Idmap[0].Hostid + 65536
+	size, err := idmapSize(daemon, isolatedStr, configSize)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	mapentries := shared.ByHostid{}
+	for _, name := range cs {
+		/* Don't change our map Just Because. */
+		if name == cName {
+			continue
+		}
+
+		container, err := containerLoadByName(daemon, name)
+		if err != nil {
+			return nil, 0, err
+		}
+
+		if container.ExpandedConfig()["security.idmap.isolated"] != "true" {
+			continue
+		}
+
+		cBase, err := strconv.ParseInt(container.ExpandedConfig()["volatile.idmap.base"], 10, 32)
+		if err != nil {
+			return nil, 0, err
+		}
+
+		cSize, err := idmapSize(daemon, container.ExpandedConfig()["security.idmap.isolated"], container.ExpandedConfig()["security.idmap.size"])
+		if err != nil {
+			return nil, 0, err
+		}
+
+		mapentries = append(mapentries, &shared.IdmapEntry{Hostid: int(cBase), Maprange: cSize})
+	}
+
+	sort.Sort(mapentries)
+
+	for i := range mapentries {
+		if i == 0 {
+			if mapentries[0].Hostid < offset+size {
+				offset = mapentries[0].Hostid + mapentries[0].Maprange
+				continue
+			}
+
+			set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+				shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+				shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+			}}
+
+			return set, offset, nil
+		}
+
+		if mapentries[i-1].Hostid+mapentries[i-1].Maprange > offset {
+			continue
+		}
+
+		offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
+		if offset+size < mapentries[i].Nsid {
+			set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+				shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+				shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+			}}
+
+			return set, offset, nil
+		}
+	}
+
+	if offset+size < daemon.IdmapSet.Idmap[0].Hostid+daemon.IdmapSet.Idmap[0].Maprange {
+		set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+			shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+			shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+		}}
+
+		return set, offset, nil
+	}
+
+	return nil, 0, fmt.Errorf("no map range available")
+}
+
 func (c *containerLXC) init() error {
 	// Compute the expanded config and device list
 	err := c.expandConfig()
@@ -377,7 +512,11 @@ func (c *containerLXC) init() error {
 		if c.daemon.IdmapSet == nil {
 			return fmt.Errorf("LXD doesn't have a uid/gid allocation. In this mode, only privileged containers are supported.")
 		}
-		c.idmapset = c.daemon.IdmapSet
+
+		c.idmapset, err = c.NextIdmapSet()
+		if err != nil {
+			return err
+		}
 	}
 
 	return nil
@@ -2377,6 +2516,29 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 	}()
 
+	// update the idmap
+	idmap, base, err := findIdmap(
+		c.daemon,
+		c.Name(),
+		args.Config["security.idmap.isolated"],
+		args.Config["security.idmap.size"],
+	)
+	if err != nil {
+		return err
+	}
+	var jsonIdmap string
+	if idmap != nil {
+		idmapBytes, err := json.Marshal(idmap.Idmap)
+		if err != nil {
+			return err
+		}
+		jsonIdmap = string(idmapBytes)
+	} else {
+		jsonIdmap = "[]"
+	}
+	args.Config["volatile.idmap.next"] = jsonIdmap
+	args.Config["volatile.idmap.base"] = fmt.Sprintf("%v", base)
+
 	// Apply the various changes
 	c.architecture = args.Architecture
 	c.ephemeral = args.Ephemeral
@@ -5170,8 +5332,8 @@ func (c *containerLXC) LocalDevices() shared.Devices {
 	return c.localDevices
 }
 
-func (c *containerLXC) LastIdmapSet() (*shared.IdmapSet, error) {
-	lastJsonIdmap := c.LocalConfig()["volatile.last_state.idmap"]
+func (c *containerLXC) idmapsetFromConfig(k string) (*shared.IdmapSet, error) {
+	lastJsonIdmap := c.LocalConfig()[k]
 
 	if lastJsonIdmap == "" {
 		return c.IdmapSet(), nil
@@ -5190,6 +5352,15 @@ func (c *containerLXC) LastIdmapSet() (*shared.IdmapSet, error) {
 	return lastIdmap, nil
 }
 
+func (c *containerLXC) NextIdmapSet() (*shared.IdmapSet, error) {
+	return c.idmapsetFromConfig("volatile.idmap.next")
+
+}
+
+func (c *containerLXC) LastIdmapSet() (*shared.IdmapSet, error) {
+	return c.idmapsetFromConfig("volatile.last_state.idmap")
+}
+
 func (c *containerLXC) Daemon() *Daemon {
 	// FIXME: This function should go away
 	return c.daemon
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 295c5aede..d55394d4e 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -220,3 +220,85 @@ func (suite *lxdTestSuite) TestContainer_Rename() {
 	suite.Req.Nil(c.Rename("testFoo2"), "Failed to rename the container.")
 	suite.Req.Equal(shared.VarPath("containers", "testFoo2"), c.Path())
 }
+
+func (suite *lxdTestSuite) TestContainer_findIdmap_isolated() {
+	c1, err := containerCreateInternal(suite.d, containerArgs{
+		Ctype: cTypeRegular,
+		Name:  "isol-1",
+		Config: map[string]string{
+			"security.idmap.isolated": "true",
+		},
+	})
+	suite.Req.Nil(err)
+	defer c1.Delete()
+
+	c2, err := containerCreateInternal(suite.d, containerArgs{
+		Ctype: cTypeRegular,
+		Name:  "isol-2",
+		Config: map[string]string{
+			"security.idmap.isolated": "true",
+		},
+	})
+	suite.Req.Nil(err)
+	defer c2.Delete()
+
+	map1, err := c1.(*containerLXC).NextIdmapSet()
+	suite.Req.Nil(err)
+	map2, err := c2.(*containerLXC).NextIdmapSet()
+	suite.Req.Nil(err)
+
+	host := suite.d.IdmapSet.Idmap[0]
+
+	for i := 0; i < 2; i++ {
+		suite.Req.Equal(host.Hostid+65536, map1.Idmap[i].Hostid, "hostids don't match %d", i)
+		suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(65536, map1.Idmap[i].Maprange, "incorrect maprange")
+	}
+
+	for i := 0; i < 2; i++ {
+		suite.Req.Equal(host.Hostid+65536*2, map2.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
+	}
+}
+
+func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
+	c1, err := containerCreateInternal(suite.d, containerArgs{
+		Ctype: cTypeRegular,
+		Name:  "isol-1",
+		Config: map[string]string{
+			"security.idmap.isolated": "false",
+		},
+	})
+	suite.Req.Nil(err)
+	defer c1.Delete()
+
+	c2, err := containerCreateInternal(suite.d, containerArgs{
+		Ctype: cTypeRegular,
+		Name:  "isol-2",
+		Config: map[string]string{
+			"security.idmap.isolated": "true",
+		},
+	})
+	suite.Req.Nil(err)
+	defer c2.Delete()
+
+	map1, err := c1.(*containerLXC).NextIdmapSet()
+	suite.Req.Nil(err)
+	map2, err := c2.(*containerLXC).NextIdmapSet()
+	suite.Req.Nil(err)
+
+	host := suite.d.IdmapSet.Idmap[0]
+
+	for i := 0; i < 2; i++ {
+		suite.Req.Equal(host.Hostid, map1.Idmap[i].Hostid, "hostids don't match %d", i)
+		suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(host.Maprange, map1.Idmap[i].Maprange, "incorrect maprange")
+	}
+
+	for i := 0; i < 2; i++ {
+		suite.Req.Equal(host.Hostid+65536+1, map2.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
+	}
+}
diff --git a/lxd/main_test.go b/lxd/main_test.go
index 7a61429d7..262c54170 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -8,6 +8,8 @@ import (
 
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
+
+	"github.com/lxc/lxd/shared"
 )
 
 func mockStartDaemon() (*Daemon, error) {
@@ -21,6 +23,11 @@ func mockStartDaemon() (*Daemon, error) {
 		return nil, err
 	}
 
+	d.IdmapSet = &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+		shared.IdmapEntry{Isuid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
+		shared.IdmapEntry{Isgid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
+	}}
+
 	// Call this after Init so we have a log object.
 	storageConfig := make(map[string]interface{})
 	d.Storage = &storageLogWrapper{w: &storageMock{d: d}}
diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 4e59d69ee..b08601ad4 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -123,6 +123,20 @@ func (e *IdmapEntry) shift_from_ns(id int) (int, error) {
 	return id - e.Hostid + e.Nsid, nil
 }
 
+type ByHostid []*IdmapEntry
+
+func (s ByHostid) Len() int {
+	return len(s)
+}
+
+func (s ByHostid) Swap(i, j int) {
+	s[i], s[j] = s[j], s[i]
+}
+
+func (s ByHostid) Less(i, j int) bool {
+	return s[i].Hostid < s[j].Hostid
+}
+
 /* taken from http://blog.golang.org/slices (which is under BSD licence) */
 func Extend(slice []IdmapEntry, element IdmapEntry) []IdmapEntry {
 	n := len(slice)

From c11085237ac67bca40f9617efe292e5c6c2cf696 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 16 Nov 2016 17:19:50 -0700
Subject: [PATCH 0468/1193] add support for raw.idmap

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container.go         |   3 ++
 lxd/container_lxc.go     | 132 ++++++++++++++++++++++++++++++++++++++++-------
 lxd/container_test.go    |  38 +++++++++++++-
 shared/idmapset_linux.go |  68 ++++++++++++++++++++++++
 shared/idmapset_test.go  |  83 +++++++++++++++++++++++++++++
 test/suites/filemanip.sh |   5 ++
 6 files changed, 308 insertions(+), 21 deletions(-)
 create mode 100644 shared/idmapset_test.go

diff --git a/lxd/container.go b/lxd/container.go
index 2fa3fe5ed..a49de0b92 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -175,6 +175,8 @@ func containerValidConfigKey(key string, value string) error {
 		return isBool(key, value)
 	case "raw.apparmor":
 		return nil
+	case "raw.idmap":
+		return nil
 	case "raw.lxc":
 		return lxcValidConfig(value)
 	case "volatile.apply_template":
@@ -710,6 +712,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 		args.Name,
 		args.Config["security.idmap.isolated"],
 		args.Config["security.idmap.size"],
+		args.Config["raw.idmap"],
 	)
 	if err != nil {
 		return nil, err
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 04739e091..254dbde9c 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -398,14 +398,107 @@ func idmapSize(daemon *Daemon, isolatedStr string, size string) (int, error) {
 
 var idmapLock sync.Mutex
 
-func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string) (*shared.IdmapSet, int, error) {
+func parseRawIdmap(value string) ([]shared.IdmapEntry, error) {
+	getRange := func(r string) (int, int, error) {
+		entries := strings.Split(r, "-")
+		if len(entries) > 2 {
+			return -1, -1, fmt.Errorf("invalid raw.idmap range %s", r)
+		}
+
+		base, err := strconv.Atoi(entries[0])
+		if err != nil {
+			return -1, -1, err
+		}
+
+		size := 1
+		if len(entries) > 1 {
+			size, err = strconv.Atoi(entries[1])
+			if err != nil {
+				return -1, -1, err
+			}
+
+			size -= base
+		}
+
+		return base, size, nil
+	}
+
+	ret := shared.IdmapSet{}
+
+	for _, line := range strings.Split(value, "\n") {
+		if line == "" {
+			continue
+		}
+
+		entries := strings.Split(line, " ")
+		if len(entries) != 3 {
+			return nil, fmt.Errorf("invalid raw.idmap line %s", line)
+		}
+
+		outsideBase, outsideSize, err := getRange(entries[1])
+		if err != nil {
+			return nil, err
+		}
+
+		insideBase, insideSize, err := getRange(entries[2])
+		if err != nil {
+			return nil, err
+		}
+
+		if insideSize != outsideSize {
+			return nil, fmt.Errorf("idmap ranges of different sizes %s", line)
+		}
+
+		entry := shared.IdmapEntry{
+			Hostid:   outsideBase,
+			Nsid:     insideBase,
+			Maprange: insideSize,
+		}
+
+		switch entries[0] {
+		case "both":
+			entry.Isuid = true
+			ret.AddSafe(entry)
+			ret.AddSafe(shared.IdmapEntry{
+				Isgid:    true,
+				Hostid:   entry.Hostid,
+				Nsid:     entry.Nsid,
+				Maprange: entry.Maprange,
+			})
+		case "uid":
+			entry.Isuid = true
+			ret.AddSafe(entry)
+		case "gid":
+			entry.Isgid = true
+			ret.AddSafe(entry)
+		default:
+			return nil, fmt.Errorf("invalid raw.idmap type %s", line)
+		}
+	}
+
+	return ret.Idmap, nil
+}
+
+func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string, rawIdmap string) (*shared.IdmapSet, int, error) {
 	isolated := false
 	if isolatedStr == "true" {
 		isolated = true
 	}
 
+	rawMaps, err := parseRawIdmap(rawIdmap)
+	if err != nil {
+		return nil, 0, err
+	}
+
 	if !isolated {
-		return daemon.IdmapSet, 0, nil
+		newIdmapset := shared.IdmapSet{Idmap: make([]shared.IdmapEntry, len(daemon.IdmapSet.Idmap))}
+		copy(newIdmapset.Idmap, daemon.IdmapSet.Idmap)
+
+		for _, ent := range rawMaps {
+			newIdmapset.AddSafe(ent)
+		}
+
+		return &newIdmapset, 0, nil
 	}
 
 	idmapLock.Lock()
@@ -453,6 +546,19 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 
 	sort.Sort(mapentries)
 
+	mkIdmap := func(offset int, size int) *shared.IdmapSet {
+		set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+			shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+			shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+		}}
+
+		for _, ent := range rawMaps {
+			set.AddSafe(ent)
+		}
+
+		return set
+	}
+
 	for i := range mapentries {
 		if i == 0 {
 			if mapentries[0].Hostid < offset+size {
@@ -460,12 +566,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 				continue
 			}
 
-			set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
-				shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
-				shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
-			}}
-
-			return set, offset, nil
+			return mkIdmap(offset, size), offset, nil
 		}
 
 		if mapentries[i-1].Hostid+mapentries[i-1].Maprange > offset {
@@ -474,22 +575,12 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 
 		offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
 		if offset+size < mapentries[i].Nsid {
-			set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
-				shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
-				shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
-			}}
-
-			return set, offset, nil
+			return mkIdmap(offset, size), offset, nil
 		}
 	}
 
 	if offset+size < daemon.IdmapSet.Idmap[0].Hostid+daemon.IdmapSet.Idmap[0].Maprange {
-		set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
-			shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
-			shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
-		}}
-
-		return set, offset, nil
+		return mkIdmap(offset, size), offset, nil
 	}
 
 	return nil, 0, fmt.Errorf("no map range available")
@@ -2522,6 +2613,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		c.Name(),
 		args.Config["security.idmap.isolated"],
 		args.Config["security.idmap.size"],
+		args.Config["raw.idmap"],
 	)
 	if err != nil {
 		return err
diff --git a/lxd/container_test.go b/lxd/container_test.go
index d55394d4e..e754c049a 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -297,8 +297,44 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
 	}
 
 	for i := 0; i < 2; i++ {
-		suite.Req.Equal(host.Hostid+65536+1, map2.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(host.Hostid+65536, map2.Idmap[i].Hostid, "hostids don't match")
 		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
 		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
 	}
 }
+
+func (suite *lxdTestSuite) TestContainer_findIdmap_raw() {
+	c1, err := containerCreateInternal(suite.d, containerArgs{
+		Ctype: cTypeRegular,
+		Name:  "isol-1",
+		Config: map[string]string{
+			"security.idmap.isolated": "false",
+			"raw.idmap":               "both 1000 1000",
+		},
+	})
+	suite.Req.Nil(err)
+	defer c1.Delete()
+
+	map1, err := c1.(*containerLXC).NextIdmapSet()
+	suite.Req.Nil(err)
+
+	host := suite.d.IdmapSet.Idmap[0]
+
+	for _, i := range []int{0, 3} {
+		suite.Req.Equal(host.Hostid, map1.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(1000, map1.Idmap[i].Maprange, "incorrect maprange")
+	}
+
+	for _, i := range []int{1, 4} {
+		suite.Req.Equal(1000, map1.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(1000, map1.Idmap[i].Nsid, "invalid nsid")
+		suite.Req.Equal(1, map1.Idmap[i].Maprange, "incorrect maprange")
+	}
+
+	for _, i := range []int{2, 5} {
+		suite.Req.Equal(host.Hostid+1001, map1.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(1001, map1.Idmap[i].Nsid, "invalid nsid")
+		suite.Req.Equal(host.Maprange-1000-1, map1.Idmap[i].Maprange, "incorrect maprange")
+	}
+}
diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index b08601ad4..90ba6dcf1 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -35,6 +35,23 @@ func is_between(x, low, high int) bool {
 	return x >= low && x < high
 }
 
+func (e *IdmapEntry) HostidsIntersect(i IdmapEntry) bool {
+	if (e.Isuid && i.Isuid) || (e.Isgid && i.Isgid) {
+		switch {
+		case is_between(e.Hostid, i.Hostid, i.Hostid+i.Maprange):
+			return true
+		case is_between(i.Hostid, e.Hostid, e.Hostid+e.Maprange):
+			return true
+		case is_between(e.Hostid+e.Maprange, i.Hostid, i.Hostid+i.Maprange):
+			return true
+		case is_between(i.Hostid+i.Maprange, e.Hostid, e.Hostid+e.Maprange):
+			return true
+		}
+	}
+
+	return false
+}
+
 func (e *IdmapEntry) Intersects(i IdmapEntry) bool {
 	if (e.Isuid && i.Isuid) || (e.Isgid && i.Isgid) {
 		switch {
@@ -169,6 +186,57 @@ func (m IdmapSet) Intersects(i IdmapEntry) bool {
 	return false
 }
 
+/* AddSafe adds an entry to the idmap set, breaking apart any ranges that the
+ * new idmap intersects with in the process.
+ */
+func (m *IdmapSet) AddSafe(i IdmapEntry) error {
+	result := []IdmapEntry{}
+	added := false
+	for _, e := range m.Idmap {
+		if !e.Intersects(i) {
+			result = append(result, e)
+			continue
+		}
+
+		if e.HostidsIntersect(i) {
+			return fmt.Errorf("can't map the same host UID twice")
+		}
+
+		added = true
+
+		lower := IdmapEntry{
+			Isuid:    e.Isuid,
+			Isgid:    e.Isgid,
+			Hostid:   e.Hostid,
+			Nsid:     e.Nsid,
+			Maprange: i.Nsid - e.Nsid,
+		}
+
+		upper := IdmapEntry{
+			Isuid:    e.Isuid,
+			Isgid:    e.Isgid,
+			Hostid:   e.Hostid + lower.Maprange + i.Maprange,
+			Nsid:     i.Nsid + i.Maprange,
+			Maprange: e.Maprange - i.Maprange - lower.Maprange,
+		}
+
+		if lower.Maprange > 0 {
+			result = append(result, lower)
+		}
+		result = append(result, i)
+		if upper.Maprange > 0 {
+			result = append(result, upper)
+		}
+	}
+
+	if !added {
+		result = append(result, i)
+	}
+
+	m.Idmap = result
+	return nil
+}
+
 func (m IdmapSet) ToLxcString() []string {
 	var lines []string
 	for _, e := range m.Idmap {
diff --git a/shared/idmapset_test.go b/shared/idmapset_test.go
new file mode 100644
index 000000000..a0e62808b
--- /dev/null
+++ b/shared/idmapset_test.go
@@ -0,0 +1,83 @@
+package shared
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestIdmapSetAddSafe_split(t *testing.T) {
+	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+
+	if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 500, Maprange: 10}); err != nil {
+		t.Error(err)
+		return
+	}
+
+	if orig.Idmap[0].Hostid != 1000 || orig.Idmap[0].Nsid != 0 || orig.Idmap[0].Maprange != 500 {
+		t.Error(fmt.Errorf("bad range: %v", orig.Idmap[0]))
+		return
+	}
+
+	if orig.Idmap[1].Hostid != 500 || orig.Idmap[1].Nsid != 500 || orig.Idmap[1].Maprange != 10 {
+		t.Error(fmt.Errorf("bad range: %v", orig.Idmap[1]))
+		return
+	}
+
+	if orig.Idmap[2].Hostid != 1510 || orig.Idmap[2].Nsid != 510 || orig.Idmap[2].Maprange != 490 {
+		t.Error(fmt.Errorf("bad range: %v", orig.Idmap[2]))
+		return
+	}
+
+	if len(orig.Idmap) != 3 {
+		t.Error("too many idmap entries")
+		return
+	}
+}
+
+func TestIdmapSetAddSafe_lower(t *testing.T) {
+	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+
+	if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 0, Maprange: 10}); err != nil {
+		t.Error(err)
+		return
+	}
+
+	if orig.Idmap[0].Hostid != 500 || orig.Idmap[0].Nsid != 0 || orig.Idmap[0].Maprange != 10 {
+		t.Error(fmt.Errorf("bad range: %v", orig.Idmap[0]))
+		return
+	}
+
+	if orig.Idmap[1].Hostid != 1010 || orig.Idmap[1].Nsid != 10 || orig.Idmap[1].Maprange != 990 {
+		t.Error(fmt.Errorf("bad range: %v", orig.Idmap[1]))
+		return
+	}
+
+	if len(orig.Idmap) != 2 {
+		t.Error("too many idmap entries")
+		return
+	}
+}
+
+func TestIdmapSetAddSafe_upper(t *testing.T) {
+	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+
+	if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 995, Maprange: 10}); err != nil {
+		t.Error(err)
+		return
+	}
+
+	if orig.Idmap[0].Hostid != 1000 || orig.Idmap[0].Nsid != 0 || orig.Idmap[0].Maprange != 995 {
+		t.Error(fmt.Errorf("bad range: %v", orig.Idmap[0]))
+		return
+	}
+
+	if orig.Idmap[1].Hostid != 500 || orig.Idmap[1].Nsid != 995 || orig.Idmap[1].Maprange != 10 {
+		t.Error(fmt.Errorf("bad range: %v", orig.Idmap[1]))
+		return
+	}
+
+	if len(orig.Idmap) != 2 {
+		t.Error("too many idmap entries")
+		return
+	}
+}
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index f63906838..3318d9969 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -18,4 +18,9 @@ test_filemanip() {
   [ "${err}" -eq "404" ]
 
   lxc delete filemanip -f
+
+  if [ "${LXD_BACKEND}" != "lvm" ]; then
+    lxc launch testimage idmap -c "raw.idmap=\"both 0 0\""
+    [ "$(stat -c %u "${LXD_DIR}/containers/idmap/rootfs")" = "0" ]
+  fi
 }

From 644c01736446b1715e6ce3c30e6b04ad852abe8e Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 16 Nov 2016 17:57:21 -0700
Subject: [PATCH 0469/1193] add configuration information about idmap

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 doc/configuration.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/doc/configuration.md b/doc/configuration.md
index a35a37c7d..cb5d0b035 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -82,6 +82,9 @@ limits.processes            | integer   | - (max)       | yes           | Maximu
 linux.kernel\_modules       | string    | -             | yes           | Comma separated list of kernel modules to load before starting the container
 raw.apparmor                | blob      | -             | yes           | Apparmor profile entries to be appended to the generated profile
 raw.lxc                     | blob      | -             | no            | Raw LXC configuration to be appended to the generated one
+raw.idmap                   | blob      | -             | no            | Raw idmap configuration (e.g. "both 1000 1000")
+security.idmap.isolated     | boolean   | false         | no            | Use an idmap for this container that is unique among containers with isolated set.
+security.idmap.size         | integer   | -             | no            | The size of the idmap to use
 security.nesting            | boolean   | false         | yes           | Support running lxd (nested) inside the container
 security.privileged         | boolean   | false         | no            | Runs the container in privileged mode
 user.\*                     | string    | -             | n/a           | Free form user key/value storage (can be used in search)
@@ -94,6 +97,8 @@ volatile.\<name\>.hwaddr    | string    | -             | Network device MAC add
 volatile.\<name\>.name      | string    | -             | Network device name (when no name propery is set on the device itself)
 volatile.apply\_template    | string    | -             | The name of a template hook which should be triggered upon next startup
 volatile.base\_image        | string    | -             | The hash of the image the container was created from, if any.
+volatile.idmap.base         | integer   | -             | The first id in the container's primary idmap range
+volatile.idmap.next         | string    | -             | The idmap to use next time the container starts
 volatile.last\_state.idmap  | string    | -             | Serialized container uid/gid map
 volatile.last\_state.power  | string    | -             | Container state as of last host shutdown
 

From 12b9c465b0cde0c892637eb569c8e47c2d7a9232 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 16 Nov 2016 18:23:03 -0700
Subject: [PATCH 0470/1193] s/idmapset_test/idmapset_test_linux

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/{idmapset_test.go => idmapset_test_linux.go} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename shared/{idmapset_test.go => idmapset_test_linux.go} (100%)

diff --git a/shared/idmapset_test.go b/shared/idmapset_test_linux.go
similarity index 100%
rename from shared/idmapset_test.go
rename to shared/idmapset_test_linux.go

From 5352cc1ee1cad2ba1fd0e8cb48ade4d38b02b52f Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 17 Nov 2016 06:18:05 -0700
Subject: [PATCH 0471/1193] doc: update doc/userns-idmap.md for multiple idmaps

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 doc/userns-idmap.md | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/doc/userns-idmap.md b/doc/userns-idmap.md
index a244f653e..6c36c7c2e 100644
--- a/doc/userns-idmap.md
+++ b/doc/userns-idmap.md
@@ -47,3 +47,40 @@ as its default map.
 # Varying ranges between hosts
 The source map is sent when moving containers between hosts so that they
 can be remapped on the receiving host.
+
+# Different idmaps per container
+LXD supports using different idmaps per container, to further isolate
+containers from each other. This is controlled with two per-container
+configuration keys, `security.idmap.isolated` and `security.idmap.size`.
+
+Containers with `security.idmap.isolated` will have a unique id range computed
+for them among the other containers with `security.idmap.isolated` set (if none
+is available, setting this key will simply fail).
+
+Containers with `security.idmap.size` set will have their id range set to this
+size. Isolated containers without this property set default to a id range of
+size 65536; this allows for POSIX compliance and a "nobody" user inside the
+container.
+
+These properties require a container reboot to take effect.
+
+# Custom idmaps
+LXD also supports customizing bits of the idmap, e.g. to allow users to bind
+mount parts of the host's filesystem into a container without the need for any
+uid-shifting filesystem. The per-container configuration key for this is
+`raw.idmap`, and looks like:
+
+    both 1000 1000
+    uid 50-60 500-510
+    gid 10000-110000 10000-20000
+
+The first line configures both the uid and gid 1000 on the host to map to uid
+1000 inside the container (this can be used for example to bind mount a user's
+home directory into a container).
+
+The second and third lines map only the uid or gid ranges into the container,
+respectively. The second entry per line is the source id, i.e. the id on the
+host, and the third entry is the range inside the container. These ranges must
+be the same size.
+
+This property requires a container reboot to take effect.

From 6841e3a33c762c7f23c81b8ceba1ca51cd848841 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 17 Nov 2016 11:07:50 -0700
Subject: [PATCH 0472/1193] fix overlap logic in findIdmap

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 254dbde9c..6f3521769 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -570,13 +570,15 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 		}
 
 		if mapentries[i-1].Hostid+mapentries[i-1].Maprange > offset {
+			offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
 			continue
 		}
 
 		offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
-		if offset+size < mapentries[i].Nsid {
+		if offset+size < mapentries[i].Hostid {
 			return mkIdmap(offset, size), offset, nil
 		}
+		offset = mapentries[i].Hostid + mapentries[i].Maprange
 	}
 
 	if offset+size < daemon.IdmapSet.Idmap[0].Hostid+daemon.IdmapSet.Idmap[0].Maprange {

From 0733c4472f1a2d5a1104acbb03590af4c0229b5a Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 17 Nov 2016 12:04:37 -0700
Subject: [PATCH 0473/1193] fix off-by-one error in range calculation

The ranges are inclusive on both ends, so they can't overlap *at all*.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go  | 10 +++++-----
 lxd/container_test.go |  6 +++---
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 6f3521769..5f3d3edaf 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -509,7 +509,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 		return nil, 0, err
 	}
 
-	offset := daemon.IdmapSet.Idmap[0].Hostid + 65536
+	offset := daemon.IdmapSet.Idmap[0].Hostid + 65536 + 1
 	size, err := idmapSize(daemon, isolatedStr, configSize)
 	if err != nil {
 		return nil, 0, err
@@ -562,7 +562,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 	for i := range mapentries {
 		if i == 0 {
 			if mapentries[0].Hostid < offset+size {
-				offset = mapentries[0].Hostid + mapentries[0].Maprange
+				offset = mapentries[0].Hostid + mapentries[0].Maprange + 1
 				continue
 			}
 
@@ -570,15 +570,15 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 		}
 
 		if mapentries[i-1].Hostid+mapentries[i-1].Maprange > offset {
-			offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
+			offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange + 1
 			continue
 		}
 
-		offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
+		offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange + 1
 		if offset+size < mapentries[i].Hostid {
 			return mkIdmap(offset, size), offset, nil
 		}
-		offset = mapentries[i].Hostid + mapentries[i].Maprange
+		offset = mapentries[i].Hostid + mapentries[i].Maprange + 1
 	}
 
 	if offset+size < daemon.IdmapSet.Idmap[0].Hostid+daemon.IdmapSet.Idmap[0].Maprange {
diff --git a/lxd/container_test.go b/lxd/container_test.go
index e754c049a..1a75018e7 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -250,13 +250,13 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_isolated() {
 	host := suite.d.IdmapSet.Idmap[0]
 
 	for i := 0; i < 2; i++ {
-		suite.Req.Equal(host.Hostid+65536, map1.Idmap[i].Hostid, "hostids don't match %d", i)
+		suite.Req.Equal(host.Hostid+65536+1, map1.Idmap[i].Hostid, "hostids don't match %d", i)
 		suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
 		suite.Req.Equal(65536, map1.Idmap[i].Maprange, "incorrect maprange")
 	}
 
 	for i := 0; i < 2; i++ {
-		suite.Req.Equal(host.Hostid+65536*2, map2.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(host.Hostid+(65536+1)*2, map2.Idmap[i].Hostid, "hostids don't match")
 		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
 		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
 	}
@@ -297,7 +297,7 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
 	}
 
 	for i := 0; i < 2; i++ {
-		suite.Req.Equal(host.Hostid+65536, map2.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(host.Hostid+65536+1, map2.Idmap[i].Hostid, "hostids don't match")
 		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
 		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
 	}

From e7105a16fa3edaf41f51fcfdd3ff8f1d5191f483 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 17 Nov 2016 12:05:44 -0700
Subject: [PATCH 0474/1193] add a test for container range exhaustion

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_test.go    | 43 +++++++++++++++++++++++++++++++++++++++++++
 shared/idmapset_linux.go | 10 ++++++++++
 2 files changed, 53 insertions(+)

diff --git a/lxd/container_test.go b/lxd/container_test.go
index 1a75018e7..da715218a 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -1,6 +1,8 @@
 package main
 
 import (
+	"fmt"
+
 	"github.com/lxc/lxd/shared"
 )
 
@@ -338,3 +340,44 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_raw() {
 		suite.Req.Equal(host.Maprange-1000-1, map1.Idmap[i].Maprange, "incorrect maprange")
 	}
 }
+
+func (suite *lxdTestSuite) TestContainer_findIdmap_maxed() {
+	maps := []*shared.IdmapSet{}
+
+	for i := 0; i < 7; i++ {
+		c, err := containerCreateInternal(suite.d, containerArgs{
+			Ctype: cTypeRegular,
+			Name:  fmt.Sprintf("isol-%d", i),
+			Config: map[string]string{
+				"security.idmap.isolated": "true",
+			},
+		})
+
+		/* we should fail if there are no ids left */
+		if i != 6 {
+			suite.Req.Nil(err)
+		} else {
+			suite.Req.NotNil(err)
+			return
+		}
+
+		defer c.Delete()
+
+		m, err := c.(*containerLXC).NextIdmapSet()
+		suite.Req.Nil(err)
+
+		maps = append(maps, m)
+	}
+
+	for i, m1 := range maps {
+		for j, m2 := range maps {
+			if m1 == m2 {
+				continue
+			}
+
+			for _, e := range m2.Idmap {
+				suite.Req.False(m1.HostidsIntersect(e), "%d and %d's idmaps intersect %v %v", i, j, m1, m2)
+			}
+		}
+	}
+}
diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 90ba6dcf1..a2b0db13c 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -180,6 +180,16 @@ func (m IdmapSet) Len() int {
 func (m IdmapSet) Intersects(i IdmapEntry) bool {
 	for _, e := range m.Idmap {
 		if i.Intersects(e) {
+			fmt.Printf("%v and %v intersect\n", i, e)
+			return true
+		}
+	}
+	return false
+}
+
+func (m IdmapSet) HostidsIntersect(i IdmapEntry) bool {
+	for _, e := range m.Idmap {
+		if i.HostidsIntersect(e) {
 			return true
 		}
 	}

From 2673058031c46178b5293b553025fa310301f1d5 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 17 Nov 2016 12:05:56 -0700
Subject: [PATCH 0475/1193] add a test for IdmapSet Intersection

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/idmapset_test_linux.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/shared/idmapset_test_linux.go b/shared/idmapset_test_linux.go
index a0e62808b..448086e20 100644
--- a/shared/idmapset_test_linux.go
+++ b/shared/idmapset_test_linux.go
@@ -81,3 +81,12 @@ func TestIdmapSetAddSafe_upper(t *testing.T) {
 		return
 	}
 }
+
+func TestIdmapSetIntersects(t *testing.T) {
+	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 165536, Nsid: 0, Maprange: 65536}}}
+
+	if orig.Intersects(IdmapEntry{Isuid: true, Hostid: 231072, Nsid: 0, Maprange: 65536}) {
+		t.Error("ranges don't intersect")
+		return
+	}
+}

From f85c1f2570eac1c63484e49d4ad185eec337d52c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 17 Nov 2016 17:11:53 -0500
Subject: [PATCH 0476/1193] doc: Tweak userns-idmap documentation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/userns-idmap.md | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/doc/userns-idmap.md b/doc/userns-idmap.md
index 6c36c7c2e..4e83610f7 100644
--- a/doc/userns-idmap.md
+++ b/doc/userns-idmap.md
@@ -17,11 +17,6 @@ running as uid 100000.
 Allocations should always be of at least 65536 uids and gids to cover
 the POSIX range including root (0) and nobody (65534).
 
-
-To simplify things, at this point, we will only deal with identical
-allocations for uids and gids and only support a single contiguous range
-per container.
-
 # Kernel support
 User namespaces require a kernel >= 3.12, LXD will start even on older
 kernels but will refuse to start containers.
@@ -35,7 +30,7 @@ If the range is shorter than 65536 (which includes no range at all),
 then LXD will fail to create or start any container until this is corrected.
 
 If some but not all of /etc/subuid, /etc/subgid, newuidmap (path lookup)
-and newgidmap (path lookup) can't be found on the system, LXD will fail
+and newgidmap (path lookup) can be found on the system, LXD will fail
 the startup of any container until this is corrected as this shows a
 broken shadow setup.
 

From 76dc87e174ecca40c551110ab2aeb59f5feb2dfb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Nov 2016 19:12:24 -0500
Subject: [PATCH 0477/1193] idmap: Fix handling on container/profile update
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Don't re-generated unless something changed.
 - Use the expanded config to also respect profiles.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 55 +++++++++++++++++++++++++++-------------------------
 1 file changed, 29 insertions(+), 26 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5f3d3edaf..217557df6 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2609,30 +2609,6 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 	}()
 
-	// update the idmap
-	idmap, base, err := findIdmap(
-		c.daemon,
-		c.Name(),
-		args.Config["security.idmap.isolated"],
-		args.Config["security.idmap.size"],
-		args.Config["raw.idmap"],
-	)
-	if err != nil {
-		return err
-	}
-	var jsonIdmap string
-	if idmap != nil {
-		idmapBytes, err := json.Marshal(idmap.Idmap)
-		if err != nil {
-			return err
-		}
-		jsonIdmap = string(idmapBytes)
-	} else {
-		jsonIdmap = "[]"
-	}
-	args.Config["volatile.idmap.next"] = jsonIdmap
-	args.Config["volatile.idmap.base"] = fmt.Sprintf("%v", base)
-
 	// Apply the various changes
 	c.architecture = args.Architecture
 	c.ephemeral = args.Ephemeral
@@ -2691,14 +2667,41 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
-	// If apparmor changed, re-validate the apparmor profile
 	for _, key := range changedConfig {
-		if key == "raw.apparmor" || key == "security.nesting" {
+		// If apparmor changed, re-validate the apparmor profile
+		if shared.StringInSlice(key, []string{"raw.apparmor", "security.nesting"}) {
 			err = AAParseProfile(c)
 			if err != nil {
 				return err
 			}
 		}
+
+		if shared.StringInSlice(key, []string{"security.idmap.isolated", "security.idmap.size", "raw.idmap"}) {
+			// update the idmap
+			idmap, base, err := findIdmap(
+				c.daemon,
+				c.Name(),
+				c.expandedConfig["security.idmap.isolated"],
+				c.expandedConfig["security.idmap.size"],
+				c.expandedConfig["raw.idmap"],
+			)
+			if err != nil {
+				return err
+			}
+
+			var jsonIdmap string
+			if idmap != nil {
+				idmapBytes, err := json.Marshal(idmap.Idmap)
+				if err != nil {
+					return err
+				}
+				jsonIdmap = string(idmapBytes)
+			} else {
+				jsonIdmap = "[]"
+			}
+			c.localConfig["volatile.idmap.next"] = jsonIdmap
+			c.localConfig["volatile.idmap.base"] = fmt.Sprintf("%v", base)
+		}
 	}
 
 	// Apply disk quota changes

From 0ecda01817dae0041349ec36e619d338d653220d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Nov 2016 19:19:54 -0500
Subject: [PATCH 0478/1193] idmap: Properly parse booleans
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 217557df6..eeeee1408 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -369,7 +369,7 @@ func (c *containerLXC) waitOperation() error {
 
 func idmapSize(daemon *Daemon, isolatedStr string, size string) (int, error) {
 	isolated := false
-	if isolatedStr == "true" {
+	if shared.IsTrue(isolatedStr) {
 		isolated = true
 	}
 
@@ -481,7 +481,7 @@ func parseRawIdmap(value string) ([]shared.IdmapEntry, error) {
 
 func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string, rawIdmap string) (*shared.IdmapSet, int, error) {
 	isolated := false
-	if isolatedStr == "true" {
+	if shared.IsTrue(isolatedStr) {
 		isolated = true
 	}
 
@@ -527,7 +527,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 			return nil, 0, err
 		}
 
-		if container.ExpandedConfig()["security.idmap.isolated"] != "true" {
+		if !shared.IsTrue(container.ExpandedConfig()["security.idmap.isolated"]) {
 			continue
 		}
 

From 062c9e19e77b04dbb469ac43782bffa9c78e0688 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Nov 2016 19:31:10 -0500
Subject: [PATCH 0479/1193] idmap: Grow fallback allocation to work isolated
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

On systems that don't have shadow support for idmaps, we fallback to a
fixed map. Until now this map was made fo 100000 uid/gid. This was
absolutely fine until we added support for multiple ranges.

Since we can usually assume that such systems aren't allocating chunks
of uid/gid to other users, lets just take a big chunk of a billion
uid/gid for use by LXD.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index a2b0db13c..6851ce0fc 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -453,9 +453,9 @@ func DefaultIdmapSet() (*IdmapSet, error) {
 	}
 
 	umin := 1000000
-	urange := 100000
+	urange := 1000000000
 	gmin := 1000000
-	grange := 100000
+	grange := 1000000000
 
 	newuidmap, _ := exec.LookPath("newuidmap")
 	newgidmap, _ := exec.LookPath("newgidmap")

From 346bcea19e598fb2b3771b3ccbdc71eedef1c323 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Nov 2016 20:29:12 -0500
Subject: [PATCH 0480/1193] idmap: Respect profiles
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Move idmap initialization to containerLXCCreate and use the expanded
configuration as input so we can respect any profile that the container
uses.

This also lets us skip some of the old idmap code, detecting and storing
the next map only once.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go     | 24 ------------------------
 lxd/container_lxc.go | 28 ++++++++++++++++++++++++++--
 2 files changed, 26 insertions(+), 26 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index a49de0b92..0da9be95b 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
 	"io"
 	"os"
@@ -707,29 +706,6 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	// Wipe any existing log for this container name
 	os.RemoveAll(shared.LogPath(args.Name))
 
-	idmap, base, err := findIdmap(
-		d,
-		args.Name,
-		args.Config["security.idmap.isolated"],
-		args.Config["security.idmap.size"],
-		args.Config["raw.idmap"],
-	)
-	if err != nil {
-		return nil, err
-	}
-	var jsonIdmap string
-	if idmap != nil {
-		idmapBytes, err := json.Marshal(idmap.Idmap)
-		if err != nil {
-			return nil, err
-		}
-		jsonIdmap = string(idmapBytes)
-	} else {
-		jsonIdmap = "[]"
-	}
-	args.Config["volatile.idmap.next"] = jsonIdmap
-	args.Config["volatile.idmap.base"] = fmt.Sprintf("%v", base)
-
 	// Create the container entry
 	id, err := dbContainerCreate(d.db, args)
 	if err != nil {
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index eeeee1408..7d8f0cd97 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -232,7 +232,18 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Setup initial idmap config
-	idmap := c.IdmapSet()
+	idmap, base, err := findIdmap(
+		d,
+		args.Name,
+		c.expandedConfig["security.idmap.isolated"],
+		c.expandedConfig["security.idmap.size"],
+		c.expandedConfig["raw.idmap"],
+	)
+	if err != nil {
+		c.Delete()
+		return nil, err
+	}
+
 	var jsonIdmap string
 	if idmap != nil {
 		idmapBytes, err := json.Marshal(idmap.Idmap)
@@ -245,13 +256,26 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		jsonIdmap = "[]"
 	}
 
+	err = c.ConfigKeySet("volatile.idmap.next", jsonIdmap)
+	if err != nil {
+		c.Delete()
+		return nil, err
+	}
+
+	err = c.ConfigKeySet("volatile.idmap.base", fmt.Sprintf("%v", base))
+	if err != nil {
+		c.Delete()
+		return nil, err
+	}
+
 	err = c.ConfigKeySet("volatile.last_state.idmap", jsonIdmap)
 	if err != nil {
 		c.Delete()
 		return nil, err
 	}
 
-	err = c.ConfigKeySet("volatile.idmap.next", jsonIdmap)
+	// Re-run init to update the idmap
+	err = c.init()
 	if err != nil {
 		c.Delete()
 		return nil, err

From d428ca4d9b24ec4cc29be7b3478a5684dd37f32a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Nov 2016 21:22:49 -0500
Subject: [PATCH 0481/1193] idmap: Support isolated container copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We need to keep the old idmap around in volatile so that container
copies can properly remap uid/gid.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go   | 11 +++++++----
 lxd/containers_post.go |  2 +-
 2 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7d8f0cd97..bf43720ea 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -268,10 +268,13 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		return nil, err
 	}
 
-	err = c.ConfigKeySet("volatile.last_state.idmap", jsonIdmap)
-	if err != nil {
-		c.Delete()
-		return nil, err
+	// Set last_state to the map we have on disk
+	if c.localConfig["volatile.last_state.idmap"] == "" {
+		err = c.ConfigKeySet("volatile.last_state.idmap", jsonIdmap)
+		if err != nil {
+			c.Delete()
+			return nil, err
+		}
 	}
 
 	// Re-run init to update the idmap
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 46bdb8ebb..6db76ba2f 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -312,7 +312,7 @@ func createFromCopy(d *Daemon, req *containerPostReq) Response {
 	}
 
 	for key, value := range sourceConfig {
-		if len(key) > 8 && key[0:8] == "volatile" && key[9:] != "base_image" {
+		if len(key) > 8 && key[0:8] == "volatile" && !shared.StringInSlice(key[9:], []string{"base_image", "last_state.idmap"}) {
 			shared.LogDebug("Skipping volatile key from copy source",
 				log.Ctx{"key": key})
 			continue

From 8660fa3600198780ada5a106caa44633c3454187 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Nov 2016 22:41:29 -0500
Subject: [PATCH 0482/1193] Improve performance of update by only doing changes
 once
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 56 +++++++++++++++++++++++++---------------------------
 1 file changed, 27 insertions(+), 29 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index bf43720ea..dc8bb1004 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2694,41 +2694,39 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
-	for _, key := range changedConfig {
-		// If apparmor changed, re-validate the apparmor profile
-		if shared.StringInSlice(key, []string{"raw.apparmor", "security.nesting"}) {
-			err = AAParseProfile(c)
-			if err != nil {
-				return err
-			}
+	// If apparmor changed, re-validate the apparmor profile
+	if shared.StringInSlice("raw.apparmor", changedConfig) || shared.StringInSlice("security.nesting", changedConfig) {
+		err = AAParseProfile(c)
+		if err != nil {
+			return err
+		}
+	}
+
+	if shared.StringInSlice("security.idmap.isolated", changedConfig) || shared.StringInSlice("security.idmap.size", changedConfig) || shared.StringInSlice("raw.idmap", changedConfig) {
+		// update the idmap
+		idmap, base, err := findIdmap(
+			c.daemon,
+			c.Name(),
+			c.expandedConfig["security.idmap.isolated"],
+			c.expandedConfig["security.idmap.size"],
+			c.expandedConfig["raw.idmap"],
+		)
+		if err != nil {
+			return err
 		}
 
-		if shared.StringInSlice(key, []string{"security.idmap.isolated", "security.idmap.size", "raw.idmap"}) {
-			// update the idmap
-			idmap, base, err := findIdmap(
-				c.daemon,
-				c.Name(),
-				c.expandedConfig["security.idmap.isolated"],
-				c.expandedConfig["security.idmap.size"],
-				c.expandedConfig["raw.idmap"],
-			)
+		var jsonIdmap string
+		if idmap != nil {
+			idmapBytes, err := json.Marshal(idmap.Idmap)
 			if err != nil {
 				return err
 			}
-
-			var jsonIdmap string
-			if idmap != nil {
-				idmapBytes, err := json.Marshal(idmap.Idmap)
-				if err != nil {
-					return err
-				}
-				jsonIdmap = string(idmapBytes)
-			} else {
-				jsonIdmap = "[]"
-			}
-			c.localConfig["volatile.idmap.next"] = jsonIdmap
-			c.localConfig["volatile.idmap.base"] = fmt.Sprintf("%v", base)
+			jsonIdmap = string(idmapBytes)
+		} else {
+			jsonIdmap = "[]"
 		}
+		c.localConfig["volatile.idmap.next"] = jsonIdmap
+		c.localConfig["volatile.idmap.base"] = fmt.Sprintf("%v", base)
 	}
 
 	// Apply disk quota changes

From 563e3e6d7ca318419828ea0eadd207ece4f52d03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Nov 2016 23:03:31 -0500
Subject: [PATCH 0483/1193] Always call Update() on profile updates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/profiles.go | 17 +++++------------
 1 file changed, 5 insertions(+), 12 deletions(-)

diff --git a/lxd/profiles.go b/lxd/profiles.go
index 2d774acf1..9f74e24c0 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -121,7 +121,7 @@ func profileGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, resp)
 }
 
-func getRunningContainersWithProfile(d *Daemon, profile string) []container {
+func getContainersWithProfile(d *Daemon, profile string) []container {
 	results := []container{}
 
 	output, err := dbProfileContainersGet(d.db, profile)
@@ -137,6 +137,7 @@ func getRunningContainersWithProfile(d *Daemon, profile string) []container {
 		}
 		results = append(results, c)
 	}
+
 	return results
 }
 
@@ -159,16 +160,8 @@ func profilePut(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
-	// Get the running container list
-	clist := getRunningContainersWithProfile(d, name)
-	var containers []container
-	for _, c := range clist {
-		if !c.IsRunning() {
-			continue
-		}
-
-		containers = append(containers, c)
-	}
+	// Get the container list
+	containers := getContainersWithProfile(d, name)
 
 	// Update the database
 	id, profile, err := dbProfileGet(d.db, name)
@@ -293,7 +286,7 @@ func profileDelete(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	clist := getRunningContainersWithProfile(d, name)
+	clist := getContainersWithProfile(d, name)
 	if len(clist) != 0 {
 		return BadRequest(fmt.Errorf("Profile is currently in use"))
 	}

From 6ea4c975cd3cffcea9924c4676902f5c39daeee4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Nov 2016 00:22:56 -0500
Subject: [PATCH 0484/1193] Revert "fix off-by-one error in range calculation"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This reverts commit f07bb77592fc5a42b268f80cf1862b3de04f2549.

The commit above basically says that without this change, the last id of
a range would be the same as the first of the next range. That's not
actually true in practice, at least not with the map that's passed to LXC.

I confirmed that touching a file in a container as uid 0 and then
touching a file in the next container as the last mapped uid, does show
files being owned by a different user/group.

root at lxd-demo01:~# lxc config show tryit-rich | grep volatile.idmap.
  volatile.idmap.base: "565536"
  volatile.idmap.next: '[{"Isuid":true,"Isgid":false,"Hostid":565536,"Nsid":0,"Maprange":100000},{"Isuid":false,"Isgid":true,"Hostid":565536,"Nsid":0,"Maprange":100000}]'
root at lxd-demo01:~# lxc config show tryit-enormous | grep volatile.idmap.
  volatile.idmap.base: "665536"
  volatile.idmap.next: '[{"Isuid":true,"Isgid":false,"Hostid":665536,"Nsid":0,"Maprange":100000},{"Isuid":false,"Isgid":true,"Hostid":665536,"Nsid":0,"Maprange":100000}]'

root at lxd-demo01:~# lxc exec tryit-rich touch /a
root at lxd-demo01:~# lxc exec tryit-rich chown 100000:100000 /a
chown: changing ownership of '/a': Invalid argument
root at lxd-demo01:~# lxc exec tryit-rich chown 99999:99999 /a
root at lxd-demo01:~# lxc exec tryit-enormous touch /a
root at lxd-demo01:~# ls -lh /var/lib/lxd/containers/tryit-rich/rootfs/a /var/lib/lxd/containers/tryit-enormous/rootfs/a
-rw-r--r-- 1 665535 665535 0 Nov 22 05:30 /var/lib/lxd/containers/tryit-rich/rootfs/a
-rw-r--r-- 1 665536 665536 0 Nov 22 05:30 /var/lib/lxd/containers/tryit-enormous/rootfs/a

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go  | 10 +++++-----
 lxd/container_test.go |  6 +++---
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index dc8bb1004..fcd2e4f1a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -536,7 +536,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 		return nil, 0, err
 	}
 
-	offset := daemon.IdmapSet.Idmap[0].Hostid + 65536 + 1
+	offset := daemon.IdmapSet.Idmap[0].Hostid + 65536
 	size, err := idmapSize(daemon, isolatedStr, configSize)
 	if err != nil {
 		return nil, 0, err
@@ -589,7 +589,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 	for i := range mapentries {
 		if i == 0 {
 			if mapentries[0].Hostid < offset+size {
-				offset = mapentries[0].Hostid + mapentries[0].Maprange + 1
+				offset = mapentries[0].Hostid + mapentries[0].Maprange
 				continue
 			}
 
@@ -597,15 +597,15 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 		}
 
 		if mapentries[i-1].Hostid+mapentries[i-1].Maprange > offset {
-			offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange + 1
+			offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
 			continue
 		}
 
-		offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange + 1
+		offset = mapentries[i-1].Hostid + mapentries[i-1].Maprange
 		if offset+size < mapentries[i].Hostid {
 			return mkIdmap(offset, size), offset, nil
 		}
-		offset = mapentries[i].Hostid + mapentries[i].Maprange + 1
+		offset = mapentries[i].Hostid + mapentries[i].Maprange
 	}
 
 	if offset+size < daemon.IdmapSet.Idmap[0].Hostid+daemon.IdmapSet.Idmap[0].Maprange {
diff --git a/lxd/container_test.go b/lxd/container_test.go
index da715218a..842eb01be 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -252,13 +252,13 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_isolated() {
 	host := suite.d.IdmapSet.Idmap[0]
 
 	for i := 0; i < 2; i++ {
-		suite.Req.Equal(host.Hostid+65536+1, map1.Idmap[i].Hostid, "hostids don't match %d", i)
+		suite.Req.Equal(host.Hostid+65536, map1.Idmap[i].Hostid, "hostids don't match %d", i)
 		suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
 		suite.Req.Equal(65536, map1.Idmap[i].Maprange, "incorrect maprange")
 	}
 
 	for i := 0; i < 2; i++ {
-		suite.Req.Equal(host.Hostid+(65536+1)*2, map2.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(host.Hostid+65536*2, map2.Idmap[i].Hostid, "hostids don't match")
 		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
 		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
 	}
@@ -299,7 +299,7 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
 	}
 
 	for i := 0; i < 2; i++ {
-		suite.Req.Equal(host.Hostid+65536+1, map2.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(host.Hostid+65536, map2.Idmap[i].Hostid, "hostids don't match")
 		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
 		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
 	}

From d8a75ca6e67fcc14d0253efb8d49712c93aa5a0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Nov 2016 18:28:57 -0500
Subject: [PATCH 0485/1193] idmapset: Properly render "b" (uid/gid) entries
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 6851ce0fc..3831a6663 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -24,11 +24,19 @@ type IdmapEntry struct {
 	Maprange int
 }
 
-func (e *IdmapEntry) ToLxcString() string {
+func (e *IdmapEntry) ToLxcString() []string {
+	if e.Isuid && e.Isgid {
+		return []string{
+			fmt.Sprintf("u %d %d %d", e.Nsid, e.Hostid, e.Maprange),
+			fmt.Sprintf("g %d %d %d", e.Nsid, e.Hostid, e.Maprange),
+		}
+	}
+
 	if e.Isuid {
-		return fmt.Sprintf("u %d %d %d", e.Nsid, e.Hostid, e.Maprange)
+		return []string{fmt.Sprintf("u %d %d %d", e.Nsid, e.Hostid, e.Maprange)}
 	}
-	return fmt.Sprintf("g %d %d %d", e.Nsid, e.Hostid, e.Maprange)
+
+	return []string{fmt.Sprintf("g %d %d %d", e.Nsid, e.Hostid, e.Maprange)}
 }
 
 func is_between(x, low, high int) bool {
@@ -250,7 +258,11 @@ func (m *IdmapSet) AddSafe(i IdmapEntry) error {
 func (m IdmapSet) ToLxcString() []string {
 	var lines []string
 	for _, e := range m.Idmap {
-		lines = append(lines, e.ToLxcString()+"\n")
+		for _, l := range e.ToLxcString() {
+			if !StringInSlice(l+"\n", lines) {
+				lines = append(lines, l+"\n")
+			}
+		}
 	}
 	return lines
 }

From 35fcaed2d55752d8804788b2587dad82a2a554a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Nov 2016 18:29:51 -0500
Subject: [PATCH 0486/1193] idmapset: Properly detect overlaps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The existing code was considering a map of range "1" to be made of 2 IDs
rather than just one.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 3831a6663..7db1a0383 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -63,21 +63,21 @@ func (e *IdmapEntry) HostidsIntersect(i IdmapEntry) bool {
 func (e *IdmapEntry) Intersects(i IdmapEntry) bool {
 	if (e.Isuid && i.Isuid) || (e.Isgid && i.Isgid) {
 		switch {
-		case is_between(e.Hostid, i.Hostid, i.Hostid+i.Maprange):
+		case is_between(e.Hostid, i.Hostid, i.Hostid+i.Maprange-1):
 			return true
-		case is_between(i.Hostid, e.Hostid, e.Hostid+e.Maprange):
+		case is_between(i.Hostid, e.Hostid, e.Hostid+e.Maprange-1):
 			return true
-		case is_between(e.Hostid+e.Maprange, i.Hostid, i.Hostid+i.Maprange):
+		case is_between(e.Hostid+e.Maprange-1, i.Hostid, i.Hostid+i.Maprange-1):
 			return true
-		case is_between(i.Hostid+i.Maprange, e.Hostid, e.Hostid+e.Maprange):
+		case is_between(i.Hostid+i.Maprange-1, e.Hostid, e.Hostid+e.Maprange-1):
 			return true
-		case is_between(e.Nsid, i.Nsid, i.Nsid+i.Maprange):
+		case is_between(e.Nsid, i.Nsid, i.Nsid+i.Maprange-1):
 			return true
-		case is_between(i.Nsid, e.Nsid, e.Nsid+e.Maprange):
+		case is_between(i.Nsid, e.Nsid, e.Nsid+e.Maprange-1):
 			return true
-		case is_between(e.Nsid+e.Maprange, i.Nsid, i.Nsid+i.Maprange):
+		case is_between(e.Nsid+e.Maprange-1, i.Nsid, i.Nsid+i.Maprange-1):
 			return true
-		case is_between(i.Nsid+i.Maprange, e.Nsid, e.Nsid+e.Maprange):
+		case is_between(i.Nsid+i.Maprange-1, e.Nsid, e.Nsid+e.Maprange-1):
 			return true
 		}
 	}
@@ -217,7 +217,7 @@ func (m *IdmapSet) AddSafe(i IdmapEntry) error {
 		}
 
 		if e.HostidsIntersect(i) {
-			return fmt.Errorf("can't map the same host UID twice")
+			return fmt.Errorf("can't map the same host ID twice")
 		}
 
 		added = true

From a576854521bb88cabdc3f178b0d5f1f2fe3a2f25 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Nov 2016 18:30:42 -0500
Subject: [PATCH 0487/1193] idmap: Properly handle "both" entries
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

And also don't ignore errors coming from idmapset.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fcd2e4f1a..8667d378d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -485,19 +485,23 @@ func parseRawIdmap(value string) ([]shared.IdmapEntry, error) {
 		switch entries[0] {
 		case "both":
 			entry.Isuid = true
-			ret.AddSafe(entry)
-			ret.AddSafe(shared.IdmapEntry{
-				Isgid:    true,
-				Hostid:   entry.Hostid,
-				Nsid:     entry.Nsid,
-				Maprange: entry.Maprange,
-			})
+			entry.Isgid = true
+			err := ret.AddSafe(entry)
+			if err != nil {
+				return nil, err
+			}
 		case "uid":
 			entry.Isuid = true
-			ret.AddSafe(entry)
+			err := ret.AddSafe(entry)
+			if err != nil {
+				return nil, err
+			}
 		case "gid":
 			entry.Isgid = true
-			ret.AddSafe(entry)
+			err := ret.AddSafe(entry)
+			if err != nil {
+				return nil, err
+			}
 		default:
 			return nil, fmt.Errorf("invalid raw.idmap type %s", line)
 		}

From 7df1e0bbe5308e958b9d314437f1ff3df34a208d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Nov 2016 18:38:35 -0500
Subject: [PATCH 0488/1193] idmap: User provided ranges are inclusive
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8667d378d..0b5f9c246 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -445,6 +445,7 @@ func parseRawIdmap(value string) ([]shared.IdmapEntry, error) {
 			}
 
 			size -= base
+			size += 1
 		}
 
 		return base, size, nil

From 44703d01dc7410a28887df5d5c89f6df184cdf3e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Nov 2016 19:00:24 -0500
Subject: [PATCH 0489/1193] test: Test the idmap feature
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh             |   4 +
 test/suites/filemanip.sh |   1 +
 test/suites/idmap.sh     | 214 +++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 219 insertions(+)
 create mode 100644 test/suites/idmap.sh

diff --git a/test/main.sh b/test/main.sh
index 6052a370c..8d5a11266 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -454,6 +454,10 @@ echo "==> TEST: filemanip"
 TEST_CURRENT=test_filemanip
 test_filemanip
 
+echo "==> TEST: idmap"
+TEST_CURRENT=test_idmap
+test_idmap
+
 echo "==> TEST: devlxd"
 TEST_CURRENT=test_devlxd
 test_devlxd
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index 3318d9969..bca4138f2 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -22,5 +22,6 @@ test_filemanip() {
   if [ "${LXD_BACKEND}" != "lvm" ]; then
     lxc launch testimage idmap -c "raw.idmap=\"both 0 0\""
     [ "$(stat -c %u "${LXD_DIR}/containers/idmap/rootfs")" = "0" ]
+    lxc delete idmap --force
   fi
 }
diff --git a/test/suites/idmap.sh b/test/suites/idmap.sh
new file mode 100644
index 000000000..1cf6dfb16
--- /dev/null
+++ b/test/suites/idmap.sh
@@ -0,0 +1,214 @@
+#!/bin/sh
+
+test_idmap() {
+  # Check that we have a big enough range for this test
+  if [ ! -e /etc/subuid ] && [ ! -e /etc/subgid ]; then
+    UIDs=1000000000
+    GIDs=1000000000
+    UID_BASE=1000000
+    GID_BASE=1000000
+  else
+    UIDs=0
+    GIDs=0
+    UID_BASE=0
+    GID_BASE=0
+    LARGEST_UIDs=0
+    LARGEST_GIDs=0
+
+    # shellcheck disable=SC2013
+    for entry in $(grep ^root: /etc/subuid); do
+      COUNT=$(echo "${entry}" | cut -d: -f3)
+      UIDs=$((UIDs+COUNT))
+
+      if [ "${COUNT}" -gt "${LARGEST_UIDs}" ]; then
+        LARGEST_UIDs=${COUNT}
+        UID_BASE=$(echo "${entry}" | cut -d: -f2)
+      fi
+    done
+
+    # shellcheck disable=SC2013
+    for entry in $(grep ^root: /etc/subgid); do
+      COUNT=$(echo "${entry}" | cut -d: -f3)
+      GIDs=$((GIDs+COUNT))
+
+      if [ "${COUNT}" -gt "${LARGEST_GIDs}" ]; then
+        LARGEST_GIDs=${COUNT}
+        GID_BASE=$(echo "${entry}" | cut -d: -f2)
+      fi
+    done
+  fi
+
+  if [ "${UIDs}" -lt 500000 ] || [ "${GIDs}" -lt 500000 ]; then
+    echo "==> SKIP: The idmap test requires at least 500000 uids and gids"
+    return
+  fi
+
+  # Setup daemon
+  ensure_import_testimage
+
+  # Check a normal, non-isolated container (full LXD id range)
+  lxc launch testimage idmap
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $2}')" = "${UID_BASE}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $2}')" = "${GID_BASE}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $3}')" = "${UIDs}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $3}')" = "${GIDs}" ]
+
+  # Convert container to isolated and confirm it's not using the first range
+  lxc config set idmap security.idmap.isolated true
+  lxc restart idmap --force
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $2}')" = "$((UID_BASE+65536))" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $2}')" = "$((GID_BASE+65536))" ]
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $3}')" = "65536" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $3}')" = "65536" ]
+
+  # Bump allocation size
+  lxc config set idmap security.idmap.size 100000
+  lxc restart idmap --force
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $2}')" != "${UID_BASE}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $2}')" != "${GID_BASE}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $3}')" = "100000" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $3}')" = "100000" ]
+
+  # Switch back to full LXD range
+  lxc config unset idmap security.idmap.isolated
+  lxc config unset idmap security.idmap.size
+  lxc restart idmap --force
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $2}')" = "${UID_BASE}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $2}')" = "${GID_BASE}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $3}')" = "${UIDs}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $3}')" = "${GIDs}" ]
+  lxc delete idmap --force
+
+  # Confirm id recycling
+  lxc launch testimage idmap -c security.idmap.isolated=true
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $2}')" = "$((UID_BASE+65536))" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $2}')" = "$((GID_BASE+65536))" ]
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $3}')" = "65536" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $3}')" = "65536" ]
+
+  # Copy and check that the base differs
+  lxc copy idmap idmap1
+  lxc start idmap1
+  [ "$(lxc exec idmap1 -- cat /proc/self/uid_map | awk '{print $2}')" = "$((UID_BASE+131072))" ]
+  [ "$(lxc exec idmap1 -- cat /proc/self/gid_map | awk '{print $2}')" = "$((GID_BASE+131072))" ]
+  [ "$(lxc exec idmap1 -- cat /proc/self/uid_map | awk '{print $3}')" = "65536" ]
+  [ "$(lxc exec idmap1 -- cat /proc/self/gid_map | awk '{print $3}')" = "65536" ]
+
+  # Validate non-overlapping maps
+  lxc exec idmap -- touch /a
+  ! lxc exec idmap -- chown 65536 /a
+  lxc exec idmap -- chown 65535 /a
+  PID_1=$(lxc info idmap | grep ^Pid | awk '{print $2}')
+  UID_1=$(stat -c '%u' "/proc/${PID_1}/root/a")
+
+  lxc exec idmap1 -- touch /a
+  PID_2=$(lxc info idmap1 | grep ^Pid | awk '{print $2}')
+  UID_2=$(stat -c '%u' "/proc/${PID_2}/root/a")
+
+  [ "${UID_1}" != "${UID_2}" ]
+  [ "${UID_2}" = "$((UID_1+1))" ]
+
+  # Check profile inheritance
+  lxc profile create idmap
+  lxc profile set idmap security.idmap.isolated true
+  lxc profile set idmap security.idmap.size 100000
+
+  lxc launch testimage idmap2
+  [ "$(lxc exec idmap2 -- cat /proc/self/uid_map | awk '{print $2}')" = "${UID_BASE}" ]
+  [ "$(lxc exec idmap2 -- cat /proc/self/gid_map | awk '{print $2}')" = "${GID_BASE}" ]
+  [ "$(lxc exec idmap2 -- cat /proc/self/uid_map | awk '{print $3}')" = "${UIDs}" ]
+  [ "$(lxc exec idmap2 -- cat /proc/self/gid_map | awk '{print $3}')" = "${GIDs}" ]
+
+  lxc profile apply idmap default,idmap
+  lxc profile apply idmap1 default,idmap
+  lxc profile apply idmap2 default,idmap
+  lxc restart idmap idmap1 idmap2 --force
+  lxc launch testimage idmap3 -p default -p idmap
+
+  UID_1=$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $2}')
+  GID_1=$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $2}')
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $2}')" != "${UID_BASE}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $2}')" != "${GID_BASE}" ]
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $3}')" = "100000" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $3}')" = "100000" ]
+
+  UID_2=$(lxc exec idmap1 -- cat /proc/self/uid_map | awk '{print $2}')
+  GID_2=$(lxc exec idmap1 -- cat /proc/self/gid_map | awk '{print $2}')
+  [ "$(lxc exec idmap1 -- cat /proc/self/uid_map | awk '{print $2}')" != "${UID_BASE}" ]
+  [ "$(lxc exec idmap1 -- cat /proc/self/gid_map | awk '{print $2}')" != "${GID_BASE}" ]
+  [ "$(lxc exec idmap1 -- cat /proc/self/uid_map | awk '{print $3}')" = "100000" ]
+  [ "$(lxc exec idmap1 -- cat /proc/self/gid_map | awk '{print $3}')" = "100000" ]
+
+  UID_3=$(lxc exec idmap2 -- cat /proc/self/uid_map | awk '{print $2}')
+  GID_3=$(lxc exec idmap2 -- cat /proc/self/gid_map | awk '{print $2}')
+  [ "$(lxc exec idmap2 -- cat /proc/self/uid_map | awk '{print $2}')" != "${UID_BASE}" ]
+  [ "$(lxc exec idmap2 -- cat /proc/self/gid_map | awk '{print $2}')" != "${GID_BASE}" ]
+  [ "$(lxc exec idmap2 -- cat /proc/self/uid_map | awk '{print $3}')" = "100000" ]
+  [ "$(lxc exec idmap2 -- cat /proc/self/gid_map | awk '{print $3}')" = "100000" ]
+
+  UID_4=$(lxc exec idmap3 -- cat /proc/self/uid_map | awk '{print $2}')
+  GID_4=$(lxc exec idmap3 -- cat /proc/self/gid_map | awk '{print $2}')
+  [ "$(lxc exec idmap3 -- cat /proc/self/uid_map | awk '{print $2}')" != "${UID_BASE}" ]
+  [ "$(lxc exec idmap3 -- cat /proc/self/gid_map | awk '{print $2}')" != "${GID_BASE}" ]
+  [ "$(lxc exec idmap3 -- cat /proc/self/uid_map | awk '{print $3}')" = "100000" ]
+  [ "$(lxc exec idmap3 -- cat /proc/self/gid_map | awk '{print $3}')" = "100000" ]
+
+  [ "${UID_1}" != "${UID_2}" ]
+  [ "${UID_1}" != "${UID_3}" ]
+  [ "${UID_1}" != "${UID_4}" ]
+  [ "${UID_2}" != "${UID_3}" ]
+  [ "${UID_2}" != "${UID_4}" ]
+  [ "${UID_3}" != "${UID_4}" ]
+
+  [ "${GID_1}" != "${GID_2}" ]
+  [ "${GID_1}" != "${GID_3}" ]
+  [ "${GID_1}" != "${GID_4}" ]
+  [ "${GID_2}" != "${GID_3}" ]
+  [ "${GID_2}" != "${GID_4}" ]
+  [ "${UID_3}" != "${UID_4}" ]
+
+  lxc delete idmap1 idmap2 idmap3 --force
+
+  # Test running out of ids
+  ! lxc launch testimage idmap1 -c security.idmap.isolated=true -c security.idmap.size=$((UIDs+1))
+
+  # Test raw id maps
+  (
+  cat << EOF
+uid ${UID_BASE} 1000000
+gid $((GID_BASE+1)) 1000000
+both $((UID_BASE+2)) 2000000
+EOF
+  ) | lxc config set idmap raw.idmap -
+  lxc restart idmap --force
+  PID=$(lxc info idmap | grep ^Pid | awk '{print $2}')
+
+  lxc exec idmap -- touch /a
+  lxc exec idmap -- chown 1000000:1000000 /a
+  [ "$(stat -c '%u:%g' "/proc/${PID}/root/a")" = "${UID_BASE}:$((GID_BASE+1))" ]
+
+  lxc exec idmap -- touch /b
+  lxc exec idmap -- chown 2000000:2000000 /b
+  [ "$(stat -c '%u:%g' "/proc/${PID}/root/b")" = "$((UID_BASE+2)):$((GID_BASE+2))" ]
+
+  # Test id ranges
+  (
+  cat << EOF
+uid $((UID_BASE+10))-$((UID_BASE+19)) 3000000-3000009
+gid $((GID_BASE+10))-$((GID_BASE+19)) 3000000-3000009
+both $((GID_BASE+20))-$((GID_BASE+29)) 4000000-4000009
+EOF
+  ) | lxc config set idmap raw.idmap -
+  lxc restart idmap --force
+  PID=$(lxc info idmap | grep ^Pid | awk '{print $2}')
+
+  lxc exec idmap -- touch /c
+  lxc exec idmap -- chown 3000009:3000009 /c
+  [ "$(stat -c '%u:%g' "/proc/${PID}/root/c")" = "$((UID_BASE+19)):$((GID_BASE+19))" ]
+
+  lxc exec idmap -- touch /d
+  lxc exec idmap -- chown 4000009:4000009 /d
+  [ "$(stat -c '%u:%g' "/proc/${PID}/root/d")" = "$((UID_BASE+29)):$((GID_BASE+29))" ]
+
+  lxc delete idmap --force
+}

From 752a1ee73d6d8062228e3f6ba031045238003076 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 23 Nov 2016 14:31:13 -0500
Subject: [PATCH 0490/1193] Release LXD 2.0.6
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/flex.go b/shared/flex.go
index a0f650933..bb9de5d64 100644
--- a/shared/flex.go
+++ b/shared/flex.go
@@ -3,7 +3,7 @@
  */
 package shared
 
-var Version = "2.0.5"
+var Version = "2.0.6"
 var UserAgent = "LXD " + Version
 
 /*

From ca0528a01ee10f346f3d599401b86a05104eef20 Mon Sep 17 00:00:00 2001
From: Aleksei Arsenev <aarseniev at yandex-team.ru>
Date: Wed, 23 Nov 2016 20:33:22 +0300
Subject: [PATCH 0491/1193] More clean filter for container names in bashcompl

Signed-off-by: Aleksei Arsenev <aarseniev at yandex-team.ru>
---
 config/bash/lxd-client | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/config/bash/lxd-client b/config/bash/lxd-client
index e22d1a5d1..f991b9d96 100644
--- a/config/bash/lxd-client
+++ b/config/bash/lxd-client
@@ -5,12 +5,12 @@ _have lxc && {
     {
       local state=$1
       local keys=$2
-      
+
       local cmd="lxc list --fast"
       [ -n "$state" ] && cmd="$cmd | grep -E '$state'"
 
       COMPREPLY=( $( compgen -W \
-        "$( eval $cmd | grep -Ev '(+--|NAME)' | awk '{print $2}' ) $keys" "$cur" )
+        "$( eval $cmd | grep -Ev '(\+--|NAME)' | awk '{print $2}' ) $keys" "$cur" )
       )
     }
 

From e597215b6186035e1236769f55f7be3c224fc009 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 13:08:57 -0500
Subject: [PATCH 0492/1193] Don't double apply templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0b5f9c246..d3f4fcfc9 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3514,6 +3514,8 @@ func (c *containerLXC) TemplateApply(trigger string) error {
 		if err != nil {
 			return err
 		}
+
+		return nil
 	}
 
 	return c.templateApplyNow(trigger)

From 06bbb7c4884db1c8deab64e3d8217ddd6d069eb9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 17:28:23 -0500
Subject: [PATCH 0493/1193] tests: Add tests for file templating
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/deps/import-busybox | 37 ++++++++++++++++++--
 test/main.sh             |  4 +++
 test/suites/template.sh  | 89 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 127 insertions(+), 3 deletions(-)
 create mode 100644 test/suites/template.sh

diff --git a/test/deps/import-busybox b/test/deps/import-busybox
index d78d777a5..bcd44ed14 100755
--- a/test/deps/import-busybox
+++ b/test/deps/import-busybox
@@ -175,7 +175,7 @@ class Busybox(object):
         if self.workdir:
             shutil.rmtree(self.workdir)
 
-    def create_tarball(self, split=False):
+    def create_tarball(self, split=False, template=[]):
         xz = "pxz" if find_on_path("pxz") else "xz"
 
         destination_tar = os.path.join(self.workdir, "busybox.tar")
@@ -239,6 +239,33 @@ class Busybox(object):
                 directory_file.name = "rootfs/%s" % path
                 target_tarball.addfile(directory_file)
 
+        # Deal with templating
+        if template:
+            metadata["templates"] = {
+                "/template": {
+                    "when": template,
+                    "template": "template.tpl"}}
+
+            directory_file = tarfile.TarInfo()
+            directory_file.type = tarfile.DIRTYPE
+            directory_file.name = "templates"
+            target_tarball.addfile(directory_file)
+
+            template = """name: {{ container.name }}
+architecture: {{ container.architecture }}
+privileged: {{ container.privileged }}
+ephemeral: {{ container.ephemeral }}
+trigger: {{ trigger }}
+path: {{ path }}
+user.foo: {{ config_get("user.foo", "_unset_") }}
+"""
+
+            template_file = tarfile.TarInfo()
+            template_file.size = len(template)
+            template_file.name = "templates/template.tpl"
+            target_tarball.addfile(template_file,
+                                   io.BytesIO(template.encode()))
+
         # Add the metadata file
         metadata_yaml = json.dumps(metadata, sort_keys=True,
                                    indent=4, separators=(',', ': '),
@@ -304,7 +331,9 @@ if __name__ == "__main__":
         busybox = Busybox()
 
         if args.split:
-            meta_path, rootfs_path = busybox.create_tarball(split=True)
+            meta_path, rootfs_path = busybox.create_tarball(
+                split=True,
+                template=args.template.split(","))
 
             with open(meta_path, "rb") as meta_fd:
                 with open(rootfs_path, "rb") as rootfs_fd:
@@ -321,7 +350,7 @@ if __name__ == "__main__":
                 r = lxd.images_upload((meta_path, rootfs_path), args.public)
             print("Image imported as: %s" % r['fingerprint'])
         else:
-            path = busybox.create_tarball()
+            path = busybox.create_tarball(template=args.template.split(","))
 
             with open(path, "rb") as fd:
                 fingerprint = hashlib.sha256(fd.read()).hexdigest()
@@ -343,6 +372,8 @@ if __name__ == "__main__":
                         default=False, help="Whether to create a split image")
     parser.add_argument("--filename", action="store_true",
                         default=False, help="Set the split image's filename")
+    parser.add_argument("--template", type=str,
+                        default="", help="Trigger test template")
     parser.set_defaults(func=import_busybox)
 
     # Call the function
diff --git a/test/main.sh b/test/main.sh
index 8d5a11266..40d440b67 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -458,6 +458,10 @@ echo "==> TEST: idmap"
 TEST_CURRENT=test_idmap
 test_idmap
 
+echo "==> TEST: template"
+TEST_CURRENT=test_template
+test_template
+
 echo "==> TEST: devlxd"
 TEST_CURRENT=test_devlxd
 test_devlxd
diff --git a/test/suites/template.sh b/test/suites/template.sh
new file mode 100644
index 000000000..90c452f65
--- /dev/null
+++ b/test/suites/template.sh
@@ -0,0 +1,89 @@
+#!/bin/sh
+
+test_template() {
+  # Import a template which only triggers on create
+  deps/import-busybox --alias template-test --template create
+  lxc init template-test template
+
+  # Confirm that template application is delayed to first start
+  ! lxc file pull template/template -
+
+  # Validate that the template is applied
+  lxc start template
+  lxc file pull template/template - | grep "^name: template$"
+
+  # Confirm it's not applied on copies
+  lxc copy template template1
+  lxc file pull template1/template - | grep "^name: template$"
+
+  # Cleanup
+  lxc image delete template-test
+  lxc delete template template1 --force
+
+
+  # Import a template which only triggers on copy
+  deps/import-busybox --alias template-test --template copy
+  lxc launch template-test template
+
+  # Confirm that the template doesn't trigger on create
+  ! lxc file pull template/template -
+
+  # Copy the container
+  lxc copy template template1
+
+  # Confirm that template application is delayed to first start
+  ! lxc file pull template1/template -
+
+  # Validate that the template is applied
+  lxc start template1
+  lxc file pull template1/template - | grep "^name: template1$"
+
+  # Cleanup
+  lxc image delete template-test
+  lxc delete template template1 --force
+
+
+  # Import a template which only triggers on start
+  deps/import-busybox --alias template-test --template start
+  lxc launch template-test template
+
+  # Validate that the template is applied
+  lxc file pull template/template - | grep "^name: template$"
+  lxc file pull template/template - | grep "^user.foo: _unset_$"
+
+  # Confirm it's re-run at every start
+  lxc config set template user.foo bar
+  lxc restart template --force
+  lxc file pull template/template - | grep "^user.foo: bar$"
+
+  # Cleanup
+  lxc image delete template-test
+  lxc delete template --force
+
+
+  # Import a template which triggers on both create and copy
+  deps/import-busybox --alias template-test --template create,copy
+  lxc init template-test template
+
+  # Confirm that template application is delayed to first start
+  ! lxc file pull template/template -
+
+  # Validate that the template is applied
+  lxc start template
+  lxc file pull template/template - | grep "^name: template$"
+
+  # Confirm it's also applied on copies
+  lxc copy template template1
+  lxc start template1
+  lxc file pull template1/template - | grep "^name: template1$"
+  lxc file pull template1/template - | grep "^user.foo: _unset_$"
+
+  # But doesn't change on restart
+  lxc config set template1 user.foo bar
+  lxc restart template1 --force
+  lxc file pull template1/template - | grep "^user.foo: _unset_$"
+
+  # Cleanup
+  lxc image delete template-test
+  lxc delete template --force
+}

From 5713b868c7898cac51b4bc2d3ac164d7d903d5f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 18:24:23 -0500
Subject: [PATCH 0494/1193] Fix idmap handling of pre-idmap containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d3f4fcfc9..0bd20a041 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1417,7 +1417,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	}
 
 	if !reflect.DeepEqual(idmap, lastIdmap) {
-		shared.LogDebugf("Container idmap changed, remapping")
+		shared.LogDebugf("Container idmap changed, remapping: %s => %s", lastIdmap, idmap)
 
 		err := c.StorageStart()
 		if err != nil {
@@ -5482,8 +5482,15 @@ func (c *containerLXC) idmapsetFromConfig(k string) (*shared.IdmapSet, error) {
 }
 
 func (c *containerLXC) NextIdmapSet() (*shared.IdmapSet, error) {
-	return c.idmapsetFromConfig("volatile.idmap.next")
+	if c.localConfig["volatile.idmap.next"] != "" {
+		return c.idmapsetFromConfig("volatile.idmap.next")
+	} else if c.IsPrivileged() {
+		return nil, nil
+	} else if c.daemon.IdmapSet != nil {
+		return c.daemon.IdmapSet, nil
+	}
 
+	return nil, fmt.Errorf("Unable to determine the idmap")
 }
 
 func (c *containerLXC) LastIdmapSet() (*shared.IdmapSet, error) {

From 36cd552f8e457c863dadb2bbe4fbe538bb9b8b2c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 18:41:28 -0500
Subject: [PATCH 0495/1193] Fix concurrent map iteration+modification
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_exec.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index d2e4cff8d..ba7c7aaaf 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -79,11 +79,15 @@ func (s *execWs) Connect(op *operation, r *http.Request, w http.ResponseWriter)
 				return nil
 			}
 
+			s.connsLock.Lock()
 			for i, c := range s.conns {
 				if i != -1 && c == nil {
+					s.connsLock.Unlock()
 					return nil
 				}
 			}
+			s.connsLock.Unlock()
+
 			s.allConnected <- true
 			return nil
 		}

From a4edb56c8992a89ab5e6e33fee8f4179b7b6eb55 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 19:54:04 -0500
Subject: [PATCH 0496/1193] Don't assign idmaps to privileged containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 55 ++++++++++++++++++++++++++++++++--------------------
 1 file changed, 34 insertions(+), 21 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0bd20a041..8a6ba822b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -232,16 +232,21 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Setup initial idmap config
-	idmap, base, err := findIdmap(
-		d,
-		args.Name,
-		c.expandedConfig["security.idmap.isolated"],
-		c.expandedConfig["security.idmap.size"],
-		c.expandedConfig["raw.idmap"],
-	)
-	if err != nil {
-		c.Delete()
-		return nil, err
+	var idmap *shared.IdmapSet
+	base := 0
+	if !c.IsPrivileged() {
+		idmap, base, err = findIdmap(
+			d,
+			args.Name,
+			c.expandedConfig["security.idmap.isolated"],
+			c.expandedConfig["security.idmap.size"],
+			c.expandedConfig["raw.idmap"],
+		)
+
+		if err != nil {
+			c.Delete()
+			return nil, err
+		}
 	}
 
 	var jsonIdmap string
@@ -559,6 +564,10 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 			return nil, 0, err
 		}
 
+		if container.IsPrivileged() {
+			continue
+		}
+
 		if !shared.IsTrue(container.ExpandedConfig()["security.idmap.isolated"]) {
 			continue
 		}
@@ -2707,17 +2716,21 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 	}
 
-	if shared.StringInSlice("security.idmap.isolated", changedConfig) || shared.StringInSlice("security.idmap.size", changedConfig) || shared.StringInSlice("raw.idmap", changedConfig) {
-		// update the idmap
-		idmap, base, err := findIdmap(
-			c.daemon,
-			c.Name(),
-			c.expandedConfig["security.idmap.isolated"],
-			c.expandedConfig["security.idmap.size"],
-			c.expandedConfig["raw.idmap"],
-		)
-		if err != nil {
-			return err
+	if shared.StringInSlice("security.idmap.isolated", changedConfig) || shared.StringInSlice("security.idmap.size", changedConfig) || shared.StringInSlice("raw.idmap", changedConfig) || shared.StringInSlice("security.privileged", changedConfig) {
+		var idmap *shared.IdmapSet
+		base := 0
+		if !c.IsPrivileged() {
+			// update the idmap
+			idmap, base, err = findIdmap(
+				c.daemon,
+				c.Name(),
+				c.expandedConfig["security.idmap.isolated"],
+				c.expandedConfig["security.idmap.size"],
+				c.expandedConfig["raw.idmap"],
+			)
+			if err != nil {
+				return err
+			}
 		}
 
 		var jsonIdmap string

From 518891e40ba8a49b2482829b92ca57bfbcf012a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 20:15:17 -0500
Subject: [PATCH 0497/1193] Don't break when parsing old containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8a6ba822b..1feae14c1 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -572,9 +572,12 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 			continue
 		}
 
-		cBase, err := strconv.ParseInt(container.ExpandedConfig()["volatile.idmap.base"], 10, 32)
-		if err != nil {
-			return nil, 0, err
+		cBase := int64(0)
+		if container.ExpandedConfig()["volatile.idmap.base"] != "" {
+			cBase, err = strconv.ParseInt(container.ExpandedConfig()["volatile.idmap.base"], 10, 32)
+			if err != nil {
+				return nil, 0, err
+			}
 		}
 
 		cSize, err := idmapSize(daemon, container.ExpandedConfig()["security.idmap.isolated"], container.ExpandedConfig()["security.idmap.size"])

From d15abe997bb3d45984c8e08e067e01bdca0ccf42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 20:53:25 -0500
Subject: [PATCH 0498/1193] Make container copy more robust
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go              | 10 ++++++----
 lxc/copy.go            |  6 +++---
 lxd/containers_post.go |  2 ++
 lxd/migrate.go         | 14 +++++++++++++-
 4 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/client.go b/client.go
index 206e8e164..3efc5377c 100644
--- a/client.go
+++ b/client.go
@@ -330,14 +330,16 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 func (c *Client) Addresses() ([]string, error) {
 	addresses := make([]string, 0)
 
+	serverStatus, err := c.ServerStatus()
+	if err != nil {
+		return nil, err
+	}
+
 	if c.Transport == "unix" {
-		serverStatus, err := c.ServerStatus()
-		if err != nil {
-			return nil, err
-		}
 		addresses = serverStatus.Environment.Addresses
 	} else if c.Transport == "https" {
 		addresses = append(addresses, c.BaseURL[8:])
+		addresses = append(addresses, serverStatus.Environment.Addresses...)
 	} else {
 		return nil, fmt.Errorf("unknown transport type: %s", c.Transport)
 	}
diff --git a/lxc/copy.go b/lxc/copy.go
index 39e1d884c..ca49bc7c7 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -173,11 +173,11 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 			continue
 		}
 
-		if err := source.WaitForSuccess(sourceWSResponse.Operation); err != nil {
-			return err
+		if err = dest.WaitForSuccess(migration.Operation); err != nil {
+			continue
 		}
 
-		if err = dest.WaitForSuccess(migration.Operation); err != nil {
+		if err = source.WaitForSuccess(sourceWSResponse.Operation); err != nil {
 			return err
 		}
 
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 6db76ba2f..208098d71 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -253,11 +253,13 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 	if req.Source.Certificate != "" {
 		certBlock, _ := pem.Decode([]byte(req.Source.Certificate))
 		if certBlock == nil {
+			c.Delete()
 			return InternalError(fmt.Errorf("Invalid certificate"))
 		}
 
 		cert, err = x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
+			c.Delete()
 			return InternalError(err)
 		}
 	}
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 2b510ef0d..79667a23d 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -582,12 +582,14 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 
 	c.src.controlConn, err = c.connectWithSecret(c.src.controlSecret)
 	if err != nil {
+		c.src.container.Delete()
 		return err
 	}
 	defer c.src.disconnect()
 
 	c.src.fsConn, err = c.connectWithSecret(c.src.fsSecret)
 	if err != nil {
+		c.src.container.Delete()
 		c.src.sendControl(err)
 		return err
 	}
@@ -595,6 +597,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 	if c.src.live {
 		c.src.criuConn, err = c.connectWithSecret(c.src.criuSecret)
 		if err != nil {
+			c.src.container.Delete()
 			c.src.sendControl(err)
 			return err
 		}
@@ -602,6 +605,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 
 	header := MigrationHeader{}
 	if err := c.src.recv(&header); err != nil {
+		c.src.container.Delete()
 		c.src.sendControl(err)
 		return err
 	}
@@ -627,6 +631,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 	}
 
 	if err := c.src.send(&resp); err != nil {
+		c.src.container.Delete()
 		c.src.sendControl(err)
 		return err
 	}
@@ -723,14 +728,20 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 		select {
 		case err = <-restore:
 			c.src.sendControl(err)
-			return err
+			if err != nil {
+				c.src.container.Delete()
+				return err
+			}
+			return nil
 		case msg, ok := <-source:
 			if !ok {
 				c.src.disconnect()
+				c.src.container.Delete()
 				return fmt.Errorf("Got error reading source")
 			}
 			if !*msg.Success {
 				c.src.disconnect()
+				c.src.container.Delete()
 				return fmt.Errorf(*msg.Message)
 			} else {
 				// The source can only tell us it failed (e.g. if
@@ -739,6 +750,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 				shared.LogDebugf("Unknown message %v from source", msg)
 				err = c.src.container.TemplateApply("copy")
 				if err != nil {
+					c.src.container.Delete()
 					return err
 				}
 			}

From 99da53e2210f30cdc5f773ec7f1ed88be596cc60 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 22:49:37 -0500
Subject: [PATCH 0499/1193] Release LXD 2.0.7
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/flex.go b/shared/flex.go
index bb9de5d64..f1665c02f 100644
--- a/shared/flex.go
+++ b/shared/flex.go
@@ -3,7 +3,7 @@
  */
 package shared
 
-var Version = "2.0.6"
+var Version = "2.0.7"
 var UserAgent = "LXD " + Version
 
 /*

From 28a8da2fb38e28c1bdb9b2018de183296d452669 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 23:15:05 -0500
Subject: [PATCH 0500/1193] Don't grab addresses from public remotes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)

diff --git a/client.go b/client.go
index 3efc5377c..0aaee2b46 100644
--- a/client.go
+++ b/client.go
@@ -330,16 +330,24 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 func (c *Client) Addresses() ([]string, error) {
 	addresses := make([]string, 0)
 
-	serverStatus, err := c.ServerStatus()
-	if err != nil {
-		return nil, err
-	}
-
 	if c.Transport == "unix" {
+		serverStatus, err := c.ServerStatus()
+		if err != nil {
+			return nil, err
+		}
+
 		addresses = serverStatus.Environment.Addresses
 	} else if c.Transport == "https" {
 		addresses = append(addresses, c.BaseURL[8:])
-		addresses = append(addresses, serverStatus.Environment.Addresses...)
+
+		if !c.Remote.Public {
+			serverStatus, err := c.ServerStatus()
+			if err != nil {
+				return nil, err
+			}
+
+			addresses = append(addresses, serverStatus.Environment.Addresses...)
+		}
 	} else {
 		return nil, fmt.Errorf("unknown transport type: %s", c.Transport)
 	}

From 9008171f4fc190a57017281de72e769de0010279 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Nov 2016 23:53:13 -0500
Subject: [PATCH 0501/1193] Release LXD 2.0.8
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/flex.go b/shared/flex.go
index f1665c02f..0186d2c66 100644
--- a/shared/flex.go
+++ b/shared/flex.go
@@ -3,7 +3,7 @@
  */
 package shared
 
-var Version = "2.0.7"
+var Version = "2.0.8"
 var UserAgent = "LXD " + Version
 
 /*

From 6ab0eda8bc310dcafa5a90b9bccc6b6130c77ffd Mon Sep 17 00:00:00 2001
From: Simon Deziel <simon.deziel at gmail.com>
Date: Fri, 25 Nov 2016 14:34:55 -0500
Subject: [PATCH 0502/1193] DHCP happens over UDP only

Signed-off-by: Simon Deziel <simon.deziel at gmail.com>
---
 lxd-bridge/lxd-bridge | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
index 14d8f4d0b..0fa4ceb2d 100755
--- a/lxd-bridge/lxd-bridge
+++ b/lxd-bridge/lxd-bridge
@@ -138,7 +138,6 @@ start() {
     fi
 
     iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
     iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
     iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
     iptables "${use_iptables_lock}" -I FORWARD -i "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
@@ -184,7 +183,6 @@ stop() {
     if [ -d /sys/class/net/${LXD_BRIDGE} ]; then
         ifdown ${LXD_BRIDGE}
         iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
         iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
         iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
         iptables ${use_iptables_lock} -D FORWARD -i ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"

From 60809c80d23b1c79032b2a782faf86400e43ece8 Mon Sep 17 00:00:00 2001
From: Simon Deziel <simon.deziel at gmail.com>
Date: Fri, 25 Nov 2016 14:43:47 -0500
Subject: [PATCH 0503/1193] Make IPv4 firewalling optional (default is enabled)

Signed-off-by: Simon Deziel <simon.deziel at gmail.com>
---
 lxd-bridge/lxd-bridge | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
index 0fa4ceb2d..609c55171 100755
--- a/lxd-bridge/lxd-bridge
+++ b/lxd-bridge/lxd-bridge
@@ -19,6 +19,7 @@ LXD_IPV4_NETWORK=""
 LXD_IPV4_DHCP_RANGE=""
 LXD_IPV4_DHCP_MAX=""
 LXD_IPV4_NAT="false"
+LXD_IPV4_FIREWALL="true"
 
 # IPv6
 LXD_IPV6_ADDR=""
@@ -140,8 +141,10 @@ start() {
     iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
     iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
     iptables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -I FORWARD -i "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
-    iptables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
+    if [ "${LXD_IPV4_FIREWALL}" = "true" ]; then
+        iptables "${use_iptables_lock}" -I FORWARD -i "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        iptables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
+    fi
     iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
 
     LXD_DOMAIN_ARG=""
@@ -185,8 +188,10 @@ stop() {
         iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
         iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
         iptables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -D FORWARD -i ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
-        iptables ${use_iptables_lock} -D FORWARD -o ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        if [ "${LXD_IPV4_FIREWALL}" = "true" ]; then
+            iptables ${use_iptables_lock} -D FORWARD -i ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
+            iptables ${use_iptables_lock} -D FORWARD -o ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        fi
         iptables ${use_iptables_lock} -t mangle -D POSTROUTING -o ${LXD_BRIDGE} -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
 
         if [ -n "${LXD_IPV4_NETWORK}" ] && [ "${LXD_IPV4_NAT}" = "true" ]; then

From 392f2694f9c603241a618565a1bd9f7a801212db Mon Sep 17 00:00:00 2001
From: Simon Deziel <simon.deziel at gmail.com>
Date: Fri, 25 Nov 2016 14:53:35 -0500
Subject: [PATCH 0504/1193] Add ip6tables filter rules

Signed-off-by: Simon Deziel <simon.deziel at gmail.com>
---
 lxd-bridge/lxd-bridge | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
index 609c55171..219efb54d 100755
--- a/lxd-bridge/lxd-bridge
+++ b/lxd-bridge/lxd-bridge
@@ -26,6 +26,7 @@ LXD_IPV6_ADDR=""
 LXD_IPV6_MASK=""
 LXD_IPV6_NETWORK=""
 LXD_IPV6_NAT="false"
+LXD_IPV6_FIREWALL="true"
 LXD_IPV6_PROXY="true"
 
 # shellcheck disable=SC1090
@@ -147,6 +148,16 @@ start() {
     fi
     iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
 
+    if [ "${HAS_IPV6}" = "true" ]; then
+        ip6tables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        ip6tables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        ip6tables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        if [ "${LXD_IPV6_FIREWALL}" = "true" ]; then
+            ip6tables "${use_iptables_lock}" -I FORWARD -i "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
+            ip6tables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
+        fi
+    fi
+
     LXD_DOMAIN_ARG=""
     if [ -n "${LXD_DOMAIN}" ]; then
         LXD_DOMAIN_ARG="-s ${LXD_DOMAIN} -S /${LXD_DOMAIN}/"
@@ -198,6 +209,16 @@ stop() {
             iptables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV4_NETWORK} ! -d ${LXD_IPV4_NETWORK} -j MASQUERADE -m comment --comment "managed by lxd-bridge"
         fi
 
+        if [ "${HAS_IPV6}" = "true" ]; then
+            ip6tables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+            ip6tables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p udp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+            ip6tables ${use_iptables_lock} -D INPUT -i ${LXD_BRIDGE} -p tcp --dport 53 -j ACCEPT -m comment --comment "managed by lxd-bridge"
+            if [ "${LXD_IPV6_FIREWALL}" = "true" ]; then
+                ip6tables ${use_iptables_lock} -D FORWARD -i ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
+                ip6tables ${use_iptables_lock} -D FORWARD -o ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
+            fi
+        fi
+
         if [ "${HAS_IPV6}" = "true" ] && [ -n "${LXD_IPV6_NETWORK}" ] && [ "${LXD_IPV6_NAT}" = "true" ]; then
             ip6tables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV6_NETWORK} ! -d ${LXD_IPV6_NETWORK} -j MASQUERADE -m comment --comment "managed by lxd-bridge"
         fi

From 109817a0a733f3d7645c023e8d5de425b834f39a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 25 Nov 2016 11:47:41 -0500
Subject: [PATCH 0505/1193] Fix seccomp profile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

LXC attempted to parse blank lines as syscall numbers.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 file.dat       | 1 +
 lxd/seccomp.go | 3 +--
 2 files changed, 2 insertions(+), 2 deletions(-)
 create mode 100644 file.dat

diff --git a/file.dat b/file.dat
new file mode 100644
index 000000000..8baef1b4a
--- /dev/null
+++ b/file.dat
@@ -0,0 +1 @@
+abc
diff --git a/lxd/seccomp.go b/lxd/seccomp.go
index 11817a94e..ce6dc7998 100644
--- a/lxd/seccomp.go
+++ b/lxd/seccomp.go
@@ -8,8 +8,7 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
-const DEFAULT_SECCOMP_POLICY = `
-2
+const DEFAULT_SECCOMP_POLICY = `2
 blacklist
 reject_force_umount  # comment this to allow umount -f;  not recommended
 [all]

From a5dda3ff7f3d9e83e9a33e2b1f42711988224ef3 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 25 Nov 2016 20:30:19 +0100
Subject: [PATCH 0506/1193] blacklist lxc.syslog and lxc.ephemeral

- lxc.syslog: syslog() calls localtime() internally given that LXD is
	      multithreaded disallow it for now.
- lxc.ephemeral: Destroys containers on shutdown via the LXC API but this job
		 should __only__ be done by LXD. If not, then we end up with
		 containers in the database that have been destroyed already.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 1feae14c1..bf6a1cabf 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -137,6 +137,14 @@ func lxcValidConfig(rawLxc string) error {
 			return fmt.Errorf("Setting lxc.logfile is not allowed")
 		}
 
+		if key == "lxc.syslog" {
+			return fmt.Errorf("Setting lxc.syslog is not allowed")
+		}
+
+		if key == "lxc.ephemeral" {
+			return fmt.Errorf("Setting lxc.ephemeral is not allowed")
+		}
+
 		if strings.HasPrefix(key, "lxc.network.") {
 			fields := strings.Split(key, ".")
 			if len(fields) == 4 && shared.StringInSlice(fields[3], []string{"ipv4", "ipv6"}) {

From 3f4a96bf69ba9bd74717fe6809d25ead3b0b97af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 26 Nov 2016 18:48:09 -0500
Subject: [PATCH 0507/1193] Add basic logging to container creation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index bf6a1cabf..145967886 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -179,6 +179,11 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		localDevices: args.Devices,
 	}
 
+	ctxMap := log.Ctx{"name": c.name,
+		"ephemeral": c.ephemeral}
+
+	shared.LogInfo("Creating container", ctxMap)
+
 	// No need to detect storage here, its a new container.
 	c.storage = d.Storage
 
@@ -186,6 +191,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	err := c.init()
 	if err != nil {
 		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
 		return nil, err
 	}
 
@@ -222,6 +228,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		err = c.Update(updateArgs, false)
 		if err != nil {
 			c.Delete()
+			shared.LogError("Failed creating container", ctxMap)
 			return nil, err
 		}
 	}
@@ -230,12 +237,14 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	err = containerValidConfig(c.expandedConfig, false, true)
 	if err != nil {
 		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
 		return nil, err
 	}
 
 	err = containerValidDevices(c.expandedDevices, false, true)
 	if err != nil {
 		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
 		return nil, err
 	}
 
@@ -253,6 +262,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 
 		if err != nil {
 			c.Delete()
+			shared.LogError("Failed creating container", ctxMap)
 			return nil, err
 		}
 	}
@@ -262,6 +272,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		idmapBytes, err := json.Marshal(idmap.Idmap)
 		if err != nil {
 			c.Delete()
+			shared.LogError("Failed creating container", ctxMap)
 			return nil, err
 		}
 		jsonIdmap = string(idmapBytes)
@@ -272,12 +283,14 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	err = c.ConfigKeySet("volatile.idmap.next", jsonIdmap)
 	if err != nil {
 		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
 		return nil, err
 	}
 
 	err = c.ConfigKeySet("volatile.idmap.base", fmt.Sprintf("%v", base))
 	if err != nil {
 		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
 		return nil, err
 	}
 
@@ -286,6 +299,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		err = c.ConfigKeySet("volatile.last_state.idmap", jsonIdmap)
 		if err != nil {
 			c.Delete()
+			shared.LogError("Failed creating container", ctxMap)
 			return nil, err
 		}
 	}
@@ -294,9 +308,12 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	err = c.init()
 	if err != nil {
 		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
 		return nil, err
 	}
 
+	shared.LogInfo("Created container", ctxMap)
+
 	return c, nil
 }
 

From a45da913bdcacc1db8f5611b478ff88c2fd69620 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 26 Nov 2016 19:59:39 -0500
Subject: [PATCH 0508/1193] logging: Introduce our own formatter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This allows us to sort the context keys.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/logging/format.go      | 191 ++++++++++++++++++++++++++++++++++++++++++
 shared/logging/log.go         |  20 +++--
 shared/logging/log_posix.go   |   6 +-
 shared/logging/log_windows.go |   2 +-
 4 files changed, 208 insertions(+), 11 deletions(-)
 create mode 100644 shared/logging/format.go

diff --git a/shared/logging/format.go b/shared/logging/format.go
new file mode 100644
index 000000000..083d40ea4
--- /dev/null
+++ b/shared/logging/format.go
@@ -0,0 +1,191 @@
+package logging
+
+import (
+	"bytes"
+	"fmt"
+	"reflect"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	log "gopkg.in/inconshreveable/log15.v2"
+)
+
+const (
+	timeFormat     = "2006-01-02T15:04:05-0700"
+	floatFormat    = 'f'
+	errorKey       = "LOG15_ERROR"
+	termTimeFormat = "01-02|15:04:05"
+	termMsgJust    = 40
+)
+
+// Imported from the log15 project
+
+// TerminalFormat formats log records optimized for human readability on
+// a terminal with color-coded level output and terser human friendly timestamp.
+// This format should only be used for interactive programs or while developing.
+//
+//     [TIME] [LEVEL] MESAGE key=value key=value ...
+//
+// Example:
+//
+//     [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002
+//
+func TerminalFormat() log.Format {
+	return log.FormatFunc(func(r *log.Record) []byte {
+		var color = 0
+		switch r.Lvl {
+		case log.LvlCrit:
+			color = 35
+		case log.LvlError:
+			color = 31
+		case log.LvlWarn:
+			color = 33
+		case log.LvlInfo:
+			color = 32
+		case log.LvlDebug:
+			color = 36
+		}
+
+		b := &bytes.Buffer{}
+		lvl := strings.ToUpper(r.Lvl.String())
+		if color > 0 {
+			fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg)
+		} else {
+			fmt.Fprintf(b, "[%s] [%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg)
+		}
+
+		// try to justify the log output for short messages
+		if len(r.Ctx) > 0 && len(r.Msg) < termMsgJust {
+			b.Write(bytes.Repeat([]byte{' '}, termMsgJust-len(r.Msg)))
+		}
+
+		// print the keys logfmt style
+		logfmt(b, r.Ctx, color)
+		return b.Bytes()
+	})
+}
+
+func LogfmtFormat() log.Format {
+	return log.FormatFunc(func(r *log.Record) []byte {
+		common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg}
+		buf := &bytes.Buffer{}
+		logfmt(buf, append(common, r.Ctx...), 0)
+		return buf.Bytes()
+	})
+}
+
+func logfmt(buf *bytes.Buffer, ctx []interface{}, color int) {
+	entries := []string{}
+
+	for i := 0; i < len(ctx); i += 2 {
+		k, ok := ctx[i].(string)
+		v := formatLogfmtValue(ctx[i+1])
+		if !ok {
+			k, v = errorKey, formatLogfmtValue(k)
+		}
+
+		// XXX: we should probably check that all of your key bytes aren't invalid
+		if color > 0 {
+			entries = append(entries, fmt.Sprintf("\x1b[%dm%s\x1b[0m=%s", color, k, v))
+		} else {
+			entries = append(entries, fmt.Sprintf("%s=%s", k, v))
+		}
+	}
+
+	sort.Strings(entries)
+
+	for i, v := range entries {
+		if i != 0 {
+			buf.WriteByte(' ')
+		}
+
+		fmt.Fprint(buf, v)
+	}
+
+	buf.WriteByte('\n')
+}
+
+func formatShared(value interface{}) (result interface{}) {
+	defer func() {
+		if err := recover(); err != nil {
+			if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() {
+				result = "nil"
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	switch v := value.(type) {
+	case time.Time:
+		return v.Format(timeFormat)
+
+	case error:
+		return v.Error()
+
+	case fmt.Stringer:
+		return v.String()
+
+	default:
+		return v
+	}
+}
+
+// formatValue formats a value for serialization
+func formatLogfmtValue(value interface{}) string {
+	if value == nil {
+		return "nil"
+	}
+
+	value = formatShared(value)
+	switch v := value.(type) {
+	case bool:
+		return strconv.FormatBool(v)
+	case float32:
+		return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
+	case float64:
+		return strconv.FormatFloat(v, floatFormat, 3, 64)
+	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
+		return fmt.Sprintf("%d", value)
+	case string:
+		return escapeString(v)
+	default:
+		return escapeString(fmt.Sprintf("%+v", value))
+	}
+}
+
+func escapeString(s string) string {
+	needQuotes := false
+	e := bytes.Buffer{}
+	e.WriteByte('"')
+	for _, r := range s {
+		if r <= ' ' || r == '=' || r == '"' {
+			needQuotes = true
+		}
+
+		switch r {
+		case '\\', '"':
+			e.WriteByte('\\')
+			e.WriteByte(byte(r))
+		case '\n':
+			e.WriteByte('\\')
+			e.WriteByte('n')
+		case '\r':
+			e.WriteByte('\\')
+			e.WriteByte('r')
+		case '\t':
+			e.WriteByte('\\')
+			e.WriteByte('t')
+		default:
+			e.WriteRune(r)
+		}
+	}
+	e.WriteByte('"')
+	start, stop := 0, e.Len()
+	if !needQuotes {
+		start, stop = 1, stop-1
+	}
+	return string(e.Bytes()[start:stop])
+}
diff --git a/shared/logging/log.go b/shared/logging/log.go
index 53e2d6a66..f07302859 100644
--- a/shared/logging/log.go
+++ b/shared/logging/log.go
@@ -6,6 +6,7 @@ import (
 	"path/filepath"
 
 	"github.com/lxc/lxd/shared"
+	"gopkg.in/inconshreveable/log15.v2/term"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -15,11 +16,16 @@ func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHa
 	Log := log.New()
 
 	var handlers []log.Handler
-
 	var syshandler log.Handler
 
+	// Format handler
+	format := LogfmtFormat()
+	if term.IsTty(os.Stderr.Fd()) {
+		format = TerminalFormat()
+	}
+
 	// System specific handler
-	syshandler = getSystemHandler(syslog, debug)
+	syshandler = getSystemHandler(syslog, debug, format)
 	if syshandler != nil {
 		handlers = append(handlers, syshandler)
 	}
@@ -35,11 +41,11 @@ func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHa
 				handlers,
 				log.LvlFilterHandler(
 					log.LvlInfo,
-					log.Must.FileHandler(logfile, log.LogfmtFormat()),
+					log.Must.FileHandler(logfile, format),
 				),
 			)
 		} else {
-			handlers = append(handlers, log.Must.FileHandler(logfile, log.LogfmtFormat()))
+			handlers = append(handlers, log.Must.FileHandler(logfile, format))
 		}
 	}
 
@@ -50,18 +56,18 @@ func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHa
 				handlers,
 				log.LvlFilterHandler(
 					log.LvlInfo,
-					log.StderrHandler,
+					log.StreamHandler(os.Stderr, format),
 				),
 			)
 		} else {
-			handlers = append(handlers, log.StderrHandler)
+			handlers = append(handlers, log.StreamHandler(os.Stderr, format))
 		}
 	} else {
 		handlers = append(
 			handlers,
 			log.LvlFilterHandler(
 				log.LvlWarn,
-				log.StderrHandler,
+				log.StreamHandler(os.Stderr, format),
 			),
 		)
 	}
diff --git a/shared/logging/log_posix.go b/shared/logging/log_posix.go
index 28e0dd10c..7cdbfd922 100644
--- a/shared/logging/log_posix.go
+++ b/shared/logging/log_posix.go
@@ -7,16 +7,16 @@ import (
 )
 
 // getSystemHandler on Linux writes messages to syslog.
-func getSystemHandler(syslog string, debug bool) log.Handler {
+func getSystemHandler(syslog string, debug bool, format log.Format) log.Handler {
 	// SyslogHandler
 	if syslog != "" {
 		if !debug {
 			return log.LvlFilterHandler(
 				log.LvlInfo,
-				log.Must.SyslogHandler(syslog, log.LogfmtFormat()),
+				log.Must.SyslogHandler(syslog, format),
 			)
 		} else {
-			return log.Must.SyslogHandler(syslog, log.LogfmtFormat())
+			return log.Must.SyslogHandler(syslog, format)
 		}
 	}
 
diff --git a/shared/logging/log_windows.go b/shared/logging/log_windows.go
index ff8b24ec7..009326e30 100644
--- a/shared/logging/log_windows.go
+++ b/shared/logging/log_windows.go
@@ -7,6 +7,6 @@ import (
 )
 
 // getSystemHandler on Windows does nothing.
-func getSystemHandler(syslog string, debug bool) log.Handler {
+func getSystemHandler(syslog string, debug bool, format log.Format) log.Handler {
 	return nil
 }

From e9f16d2b2971243ba396c682431f1a489004065c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 26 Nov 2016 21:50:53 -0500
Subject: [PATCH 0509/1193] Avoid race condition in network fill function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 72 ++++++++++++++++++++++++++++++----------------------
 lxd/db_containers.go |  9 +++++++
 2 files changed, 51 insertions(+), 30 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 145967886..43469fc95 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4799,6 +4799,26 @@ func (c *containerLXC) fillNetworkDevice(name string, m shared.Device) (shared.D
 		}
 	}
 
+	updateKey := func(key string, value string) error {
+		tx, err := dbBegin(c.daemon.db)
+		if err != nil {
+			return err
+		}
+
+		err = dbContainerConfigInsert(tx, c.id, map[string]string{key: value})
+		if err != nil {
+			tx.Rollback()
+			return err
+		}
+
+		err = txCommit(tx)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
 	// Fill in the MAC address
 	if m["nictype"] != "physical" && m["hwaddr"] == "" {
 		configKey := fmt.Sprintf("volatile.%s.hwaddr", name)
@@ -4810,24 +4830,20 @@ func (c *containerLXC) fillNetworkDevice(name string, m shared.Device) (shared.D
 				return nil, err
 			}
 
-			c.localConfig[configKey] = volatileHwaddr
-			c.expandedConfig[configKey] = volatileHwaddr
-
 			// Update the database
-			tx, err := dbBegin(c.daemon.db)
+			err = updateKey(configKey, volatileHwaddr)
 			if err != nil {
-				return nil, err
-			}
-
-			err = dbContainerConfigInsert(tx, c.id, map[string]string{configKey: volatileHwaddr})
-			if err != nil {
-				tx.Rollback()
-				return nil, err
-			}
+				// Check if something else filled it in behind our back
+				value, err1 := dbContainerConfigGet(c.daemon.db, c.id, configKey)
+				if err1 != nil || value == "" {
+					return nil, err
+				}
 
-			err = txCommit(tx)
-			if err != nil {
-				return nil, err
+				c.localConfig[configKey] = value
+				c.expandedConfig[configKey] = value
+			} else {
+				c.localConfig[configKey] = volatileHwaddr
+				c.expandedConfig[configKey] = volatileHwaddr
 			}
 		}
 		newDevice["hwaddr"] = volatileHwaddr
@@ -4844,24 +4860,20 @@ func (c *containerLXC) fillNetworkDevice(name string, m shared.Device) (shared.D
 				return nil, err
 			}
 
-			c.localConfig[configKey] = volatileName
-			c.expandedConfig[configKey] = volatileName
-
 			// Update the database
-			tx, err := dbBegin(c.daemon.db)
-			if err != nil {
-				return nil, err
-			}
-
-			err = dbContainerConfigInsert(tx, c.id, map[string]string{configKey: volatileName})
+			err = updateKey(configKey, volatileName)
 			if err != nil {
-				tx.Rollback()
-				return nil, err
-			}
+				// Check if something else filled it in behind our back
+				value, err1 := dbContainerConfigGet(c.daemon.db, c.id, configKey)
+				if err1 != nil || value == "" {
+					return nil, err
+				}
 
-			err = txCommit(tx)
-			if err != nil {
-				return nil, err
+				c.localConfig[configKey] = value
+				c.expandedConfig[configKey] = value
+			} else {
+				c.localConfig[configKey] = volatileName
+				c.expandedConfig[configKey] = volatileName
 			}
 		}
 		newDevice["name"] = volatileName
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index b51c75776..d865a52a8 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -215,6 +215,15 @@ func dbContainerConfigInsert(tx *sql.Tx, id int, config map[string]string) error
 	return nil
 }
 
+func dbContainerConfigGet(db *sql.DB, id int, key string) (string, error) {
+	q := "SELECT value FROM containers_config WHERE container_id=? AND key=?"
+	value := ""
+	arg1 := []interface{}{id, key}
+	arg2 := []interface{}{&value}
+	err := dbQueryRowScan(db, q, arg1, arg2)
+	return value, err
+}
+
 func dbContainerConfigRemove(db *sql.DB, id int, name string) error {
 	_, err := dbExec(db, "DELETE FROM containers_config WHERE key=? AND container_id=?", name, id)
 	return err

From ade01a5ce64000d6ca3572fdb10387fb9d815a1f Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 28 Nov 2016 09:04:23 -0700
Subject: [PATCH 0510/1193] Change ContainerStart to take the name and path to
 start

Instead of taking the container and computing the name and path, let's just
take the name and path themselves. We'll use this in the next patch to
start the storage for a container that we haven't created in memory or in
the database yet, when we're reading its slurp file.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go |  4 ++--
 lxd/storage.go       | 16 ++++++++--------
 lxd/storage_btrfs.go |  4 ++--
 lxd/storage_dir.go   |  4 ++--
 lxd/storage_lvm.go   | 20 ++++++++++----------
 lxd/storage_test.go  |  4 ++--
 lxd/storage_zfs.go   |  6 +++---
 7 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 43469fc95..f3bd91cb0 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4255,7 +4255,7 @@ func (c *containerLXC) StorageStart() error {
 		return c.storage.ContainerSnapshotStart(c)
 	}
 
-	return c.storage.ContainerStart(c)
+	return c.storage.ContainerStart(c.Name(), c.Path())
 }
 
 func (c *containerLXC) StorageStop() error {
@@ -4263,7 +4263,7 @@ func (c *containerLXC) StorageStop() error {
 		return c.storage.ContainerSnapshotStop(c)
 	}
 
-	return c.storage.ContainerStop(c)
+	return c.storage.ContainerStop(c.Name(), c.Path())
 }
 
 // Mount handling
diff --git a/lxd/storage.go b/lxd/storage.go
index 5bb94dc09..b315dc3a5 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -148,8 +148,8 @@ type storage interface {
 	ContainerCanRestore(container container, sourceContainer container) error
 	ContainerDelete(container container) error
 	ContainerCopy(container container, sourceContainer container) error
-	ContainerStart(container container) error
-	ContainerStop(container container) error
+	ContainerStart(name string, path string) error
+	ContainerStop(name string, path string) error
 	ContainerRename(container container, newName string) error
 	ContainerRestore(container container, sourceContainer container) error
 	ContainerSetQuota(container container, size int64) error
@@ -430,14 +430,14 @@ func (lw *storageLogWrapper) ContainerCopy(
 	return lw.w.ContainerCopy(container, sourceContainer)
 }
 
-func (lw *storageLogWrapper) ContainerStart(container container) error {
-	lw.log.Debug("ContainerStart", log.Ctx{"container": container.Name()})
-	return lw.w.ContainerStart(container)
+func (lw *storageLogWrapper) ContainerStart(name string, path string) error {
+	lw.log.Debug("ContainerStart", log.Ctx{"container": name})
+	return lw.w.ContainerStart(name, path)
 }
 
-func (lw *storageLogWrapper) ContainerStop(container container) error {
-	lw.log.Debug("ContainerStop", log.Ctx{"container": container.Name()})
-	return lw.w.ContainerStop(container)
+func (lw *storageLogWrapper) ContainerStop(name string, path string) error {
+	lw.log.Debug("ContainerStop", log.Ctx{"container": name})
+	return lw.w.ContainerStop(name, path)
 }
 
 func (lw *storageLogWrapper) ContainerRename(
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 639c85f22..845fd7222 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -171,11 +171,11 @@ func (s *storageBtrfs) ContainerCopy(container container, sourceContainer contai
 	return container.TemplateApply("copy")
 }
 
-func (s *storageBtrfs) ContainerStart(container container) error {
+func (s *storageBtrfs) ContainerStart(name string, path string) error {
 	return nil
 }
 
-func (s *storageBtrfs) ContainerStop(container container) error {
+func (s *storageBtrfs) ContainerStop(name string, path string) error {
 	return nil
 }
 
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 172948327..c8dac4d54 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -123,11 +123,11 @@ func (s *storageDir) ContainerCopy(
 	return container.TemplateApply("copy")
 }
 
-func (s *storageDir) ContainerStart(container container) error {
+func (s *storageDir) ContainerStart(name string, path string) error {
 	return nil
 }
 
-func (s *storageDir) ContainerStop(container container) error {
+func (s *storageDir) ContainerStop(name string, path string) error {
 	return nil
 }
 
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index ed73211b0..ac4f4b2f2 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -403,7 +403,7 @@ func (s *storageLvm) ContainerCopy(container container, sourceContainer containe
 			return err
 		}
 
-		if err := s.ContainerStart(container); err != nil {
+		if err := s.ContainerStart(container.Name(), container.Path()); err != nil {
 			s.log.Error("Error starting/mounting container", log.Ctx{"err": err, "container": container.Name()})
 			s.ContainerDelete(container)
 			return err
@@ -418,35 +418,35 @@ func (s *storageLvm) ContainerCopy(container container, sourceContainer containe
 			return fmt.Errorf("rsync failed: %s", string(output))
 		}
 
-		if err := s.ContainerStop(container); err != nil {
+		if err := s.ContainerStop(container.Name(), container.Path()); err != nil {
 			return err
 		}
 	}
 	return container.TemplateApply("copy")
 }
 
-func (s *storageLvm) ContainerStart(container container) error {
-	lvName := containerNameToLVName(container.Name())
+func (s *storageLvm) ContainerStart(name string, path string) error {
+	lvName := containerNameToLVName(name)
 	lvpath := fmt.Sprintf("/dev/%s/%s", s.vgName, lvName)
 	fstype := daemonConfig["storage.lvm_fstype"].Get()
 
-	err := tryMount(lvpath, container.Path(), fstype, 0, "discard")
+	err := tryMount(lvpath, path, fstype, 0, "discard")
 	if err != nil {
 		return fmt.Errorf(
 			"Error mounting snapshot LV path='%s': %v",
-			container.Path(),
+			path,
 			err)
 	}
 
 	return nil
 }
 
-func (s *storageLvm) ContainerStop(container container) error {
-	err := tryUnmount(container.Path(), 0)
+func (s *storageLvm) ContainerStop(name string, path string) error {
+	err := tryUnmount(path, 0)
 	if err != nil {
 		return fmt.Errorf(
 			"failed to unmount container path '%s'.\nError: %v",
-			container.Path(),
+			path,
 			err)
 	}
 
@@ -687,7 +687,7 @@ func (s *storageLvm) ContainerSnapshotStart(container container) error {
 }
 
 func (s *storageLvm) ContainerSnapshotStop(container container) error {
-	err := s.ContainerStop(container)
+	err := s.ContainerStop(container.Name(), container.Path())
 	if err != nil {
 		return err
 	}
diff --git a/lxd/storage_test.go b/lxd/storage_test.go
index c31db24b8..def3028cd 100644
--- a/lxd/storage_test.go
+++ b/lxd/storage_test.go
@@ -61,11 +61,11 @@ func (s *storageMock) ContainerCopy(
 	return nil
 }
 
-func (s *storageMock) ContainerStart(container container) error {
+func (s *storageMock) ContainerStart(name string, path string) error {
 	return nil
 }
 
-func (s *storageMock) ContainerStop(container container) error {
+func (s *storageMock) ContainerStop(name string, path string) error {
 	return nil
 }
 
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index bc0fd430f..36bd0e86d 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -91,8 +91,8 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 }
 
 // Things we don't need to care about
-func (s *storageZfs) ContainerStart(container container) error {
-	fs := fmt.Sprintf("containers/%s", container.Name())
+func (s *storageZfs) ContainerStart(name string, path string) error {
+	fs := fmt.Sprintf("containers/%s", name)
 
 	// Just in case the container filesystem got unmounted
 	if !shared.IsMountPoint(shared.VarPath(fs)) {
@@ -102,7 +102,7 @@ func (s *storageZfs) ContainerStart(container container) error {
 	return nil
 }
 
-func (s *storageZfs) ContainerStop(container container) error {
+func (s *storageZfs) ContainerStop(name string, path string) error {
 	return nil
 }
 

From bf028e82c0bfa21caaeb78facd7b252910ef2487 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Mon, 28 Nov 2016 18:40:45 +0000
Subject: [PATCH 0511/1193] rework EEXISTS detection on create

In particular: let's use the database and not stat the filesystem, so that
if we are doing a `lxd import`, it doesn't fail.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container.go | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 0da9be95b..995140afc 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -695,22 +695,22 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 		}
 	}
 
-	path := containerPath(args.Name, args.Ctype == cTypeSnapshot)
-	if shared.PathExists(path) {
-		if shared.IsSnapshot(args.Name) {
-			return nil, fmt.Errorf("Snapshot '%s' already exists", args.Name)
+	// Create the container entry
+	id, err := dbContainerCreate(d.db, args)
+	if err != nil {
+		if err == DbErrAlreadyDefined {
+			thing := "Container"
+			if shared.IsSnapshot(args.Name) {
+				thing = "Snapshot"
+			}
+			return nil, fmt.Errorf("%s '%s' already exists", thing, args.Name)
 		}
-		return nil, fmt.Errorf("The container already exists")
+		return nil, err
 	}
 
 	// Wipe any existing log for this container name
 	os.RemoveAll(shared.LogPath(args.Name))
 
-	// Create the container entry
-	id, err := dbContainerCreate(d.db, args)
-	if err != nil {
-		return nil, err
-	}
 	args.Id = id
 
 	// Read the timestamp from the database

From 443bfbff93826f98f03d9a617f4ff3afa04b7f73 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 28 Nov 2016 17:52:34 -0500
Subject: [PATCH 0512/1193] lxc-to-lxd: Show nicer error on missing python3-lxc
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 516d12b23..30541921f 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -2,12 +2,17 @@
 import argparse
 import http.client
 import json
-import lxc
 import os
 import socket
 import subprocess
 import sys
 
+try:
+    import lxc
+except ImportError:
+    print("You must have python3-lxc installed for this script to work.")
+    sys.exit(1)
+
 
 # Unix connection to LXD
 class UnixHTTPConnection(http.client.HTTPConnection):

From 9e13777df37cf1f38d4c4d0f30b1f8960edc15d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 28 Nov 2016 17:57:06 -0500
Subject: [PATCH 0513/1193] lxc-to-lxd: All properties must be strings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2663

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 30541921f..e2044b550 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -233,7 +233,7 @@ def convert_container(lxd_socket, container_name, args):
             device['parent'] = dev.link
 
         if dev.mtu:
-            device['mtu'] = int(dev.mtu)
+            device['mtu'] = dev.mtu
 
         if dev.name:
             device['name'] = dev.name

From 6eaacceeeb4e995f28d7f67fa8011d679b92817d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 30 Nov 2016 22:58:04 -0500
Subject: [PATCH 0514/1193] Fix file push error handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f3bd91cb0..3102a49e5 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3775,7 +3775,6 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 	uid := -1
 	gid := -1
 	mode := -1
-
 	var errStr string
 
 	// Process forkgetfile response
@@ -3795,6 +3794,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 			if errno == "2" {
 				return -1, -1, 0, os.ErrNotExist
 			}
+
 			return -1, -1, 0, fmt.Errorf(errStr)
 		}
 
@@ -3858,6 +3858,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error {
 	var rootUid = 0
 	var rootGid = 0
+	var errStr string
 
 	// Map uid and gid if needed
 	if !c.IsRunning() {
@@ -3904,14 +3905,25 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 		}
 	}
 
-	// Process forkputfile response
-	if string(out) != "" {
-		if strings.HasPrefix(string(out), "error:") {
-			return fmt.Errorf(strings.TrimPrefix(strings.TrimSuffix(string(out), "\n"), "error: "))
+	// Process forkgetfile response
+	for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+		if line == "" {
+			continue
 		}
 
-		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.LogDebugf("forkgetfile: %s", line)
+		// Extract errors
+		if strings.HasPrefix(line, "error: ") {
+			errStr = strings.TrimPrefix(line, "error: ")
+			continue
+		}
+
+		if strings.HasPrefix(line, "errno: ") {
+			errno := strings.TrimPrefix(line, "errno: ")
+			if errno == "2" {
+				return os.ErrNotExist
+			}
+
+			return fmt.Errorf(errStr)
 		}
 	}
 

From 0c3fe8a65f26b978a44a4b6bea045e871d31f85f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 30 Nov 2016 23:14:31 -0500
Subject: [PATCH 0515/1193] Fix logging for file_manip commands
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 3102a49e5..7fd2863f4 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3736,9 +3736,10 @@ func (c *containerLXC) FileExists(path string) error {
 
 	if err != nil {
 		return fmt.Errorf(
-			"Error calling 'lxd forkcheckfile %s %d': err='%v'",
-			path,
+			"Error calling 'lxd forkcheckfile %s %d %s': err='%v'",
+			c.RootfsPath(),
 			c.InitPID(),
+			path,
 			err)
 	}
 
@@ -3833,9 +3834,10 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 
 	if err != nil {
 		return -1, -1, 0, fmt.Errorf(
-			"Error calling 'lxd forkgetfile %s %d %s': err='%v'",
-			dstpath,
+			"Error calling 'lxd forkgetfile %s %d %s %s': err='%v'",
+			c.RootfsPath(),
 			c.InitPID(),
+			dstpath,
 			srcpath,
 			err)
 	}
@@ -3929,13 +3931,17 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 
 	if err != nil {
 		return fmt.Errorf(
-			"Error calling 'lxd forkputfile %s %d %s %d %d %d': err='%v'",
-			srcpath,
+			"Error calling 'lxd forkputfile %s %d %s %s %d %d %d %d %d %d': err='%v'",
+			c.RootfsPath(),
 			c.InitPID(),
+			srcpath,
 			dstpath,
 			uid,
 			gid,
 			mode,
+			rootUid,
+			rootGid,
+			int(os.FileMode(0640)&os.ModePerm),
 			err)
 	}
 
@@ -3981,9 +3987,10 @@ func (c *containerLXC) FileRemove(path string) error {
 
 	if err != nil {
 		return fmt.Errorf(
-			"Error calling 'lxd forkremovefile %s %d': err='%v'",
-			path,
+			"Error calling 'lxd forkremovefile %s %d %s': err='%v'",
+			c.RootfsPath(),
 			c.InitPID(),
+			path,
 			err)
 	}
 

From d7a373763fcc9b5cddb9ce2a6b2af99b0ac0bfaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 30 Nov 2016 23:14:59 -0500
Subject: [PATCH 0516/1193] Return a clear error when replacing a directory
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2668

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/nsexec.go | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/lxd/nsexec.go b/lxd/nsexec.go
index 018c31f5a..e100d5c7c 100644
--- a/lxd/nsexec.go
+++ b/lxd/nsexec.go
@@ -52,6 +52,12 @@ void error(char *msg)
 {
 	int old_errno = errno;
 
+	if (old_errno == 0) {
+		fprintf(stderr, "%s\n", msg);
+		fprintf(stderr, "errno: 0\n");
+		return;
+	}
+
 	perror(msg);
 	fprintf(stderr, "errno: %d\n", old_errno);
 }
@@ -200,6 +206,11 @@ int manip_file_in_ns(char *rootfs, int pid, char *host, char *container, bool is
 	if (is_put && stat(container, &st) < 0)
 		exists = 0;
 
+	if (is_put && exists && S_ISDIR(st.st_mode)) {
+		error("error: Path already exists as a directory");
+		goto close_host;
+	}
+
 	umask(0);
 	container_fd = open(container, container_open_flags, 0);
 	if (container_fd < 0) {

From fca14693aaaf4634a98a34e491344adbd4816571 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 1 Dec 2016 21:29:46 +0100
Subject: [PATCH 0517/1193] lxc-to-lxd: add more unsupported config keys

- lxc.syslog
- lxc.ephemeral
- lxc.logfile

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 scripts/lxc-to-lxd | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index e2044b550..d7472a6e2 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -346,6 +346,27 @@ def convert_container(lxd_socket, container_name, args):
         print("Custom capabilities aren't supported, skipping...")
         return False
 
+    # Skip ephemeral
+    print("Processing container ephemeral configuration")
+    value = config_get(lxc_config, "lxc.ephemeral")
+    if value:
+        print("Setting lxc.ephemeral is not supported, skipping...")
+        return False
+
+    # Skip syslog
+    print("Processing container syslog configuration")
+    value = config_get(lxc_config, "lxc.syslog")
+    if value:
+        print("Setting lxc.syslog is not supported, skipping...")
+        return False
+
+    # Skip logfile
+    print("Processing container syslog configuration")
+    value = config_get(lxc_config, "lxc.logfile")
+    if value:
+        print("Setting lxc.logfile is not supported, skipping...")
+        return False
+
     # Setup the container creation request
     new = {'name': container_name,
            'source': {'type': 'none'},

From ed6d6a80b807646c862966746c316c1e82e95d28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 2 Dec 2016 18:03:20 -0500
Subject: [PATCH 0518/1193] Log daemon version
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 23d2a3887..1b75e6d85 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -566,13 +566,13 @@ func (d *Daemon) Init() error {
 
 	/* Print welcome message */
 	if d.MockMode {
-		shared.LogInfo("LXD is starting in mock mode",
+		shared.LogInfo(fmt.Sprintf("LXD %s is starting in mock mode", shared.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	} else if d.SetupMode {
-		shared.LogInfo("LXD is starting in setup mode",
+		shared.LogInfo(fmt.Sprintf("LXD %s is starting in setup mode", shared.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	} else {
-		shared.LogInfo("LXD is starting in normal mode",
+		shared.LogInfo(fmt.Sprintf("LXD %s is starting in normal mode", shared.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	}
 

From ae8b65bc627512962ff537e1e4b2f67e67487a7a Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 2 Dec 2016 19:44:45 +0000
Subject: [PATCH 0519/1193] Don't set InsecureSkipVerify on daemon's tls config

We don't need this, and it's scary, so let's not set it.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/daemon.go | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 1b75e6d85..7a76080c7 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -857,11 +857,10 @@ func (d *Daemon) Init() error {
 		}
 
 		tlsConfig := &tls.Config{
-			InsecureSkipVerify: true,
-			ClientAuth:         tls.RequestClientCert,
-			Certificates:       []tls.Certificate{cert},
-			MinVersion:         tls.VersionTLS12,
-			MaxVersion:         tls.VersionTLS12,
+			ClientAuth:   tls.RequestClientCert,
+			Certificates: []tls.Certificate{cert},
+			MinVersion:   tls.VersionTLS12,
+			MaxVersion:   tls.VersionTLS12,
 			CipherSuites: []uint16{
 				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},

From f40fe64d8e6689a27245256babbad476e5d5a615 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 5 Dec 2016 11:09:44 +0100
Subject: [PATCH 0520/1193] Fix container state recording
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2686

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/containers.go | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/lxd/containers.go b/lxd/containers.go
index 145888f20..c657f62df 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -165,10 +165,7 @@ func containersShutdown(d *Daemon) error {
 		}
 
 		// Record the current state
-		err = c.ConfigKeySet("volatile.last_state.power", c.State())
-		if err != nil {
-			return err
-		}
+		lastState := c.State()
 
 		// Stop the container
 		if c.IsRunning() {
@@ -176,8 +173,12 @@ func containersShutdown(d *Daemon) error {
 			go func() {
 				c.Shutdown(time.Second * 30)
 				c.Stop(false)
+				c.ConfigKeySet("volatile.last_state.power", lastState)
+
 				wg.Done()
 			}()
+		} else {
+			c.ConfigKeySet("volatile.last_state.power", lastState)
 		}
 	}
 	wg.Wait()

From 87e82dde090a642e641fe40f7dff745e236ea50f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 5 Dec 2016 11:25:45 +0100
Subject: [PATCH 0521/1193] tests: Test lxd shutdown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/basic.sh | 25 +++++++++++++++++++++++--
 1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index c2a799d1a..c85679a6a 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -187,8 +187,9 @@ test_basic_usage() {
   [ "$(my_curl "https://${LXD_ADDR}/1.0/containers/configtest" | jq -r .metadata.config[\"raw.lxc\"])" = "lxc.hook.clone=/bin/true" ]
   lxc delete configtest
 
-  # Test socket activation
+  # Test activateifneeded/shutdown
   LXD_ACTIVATION_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD_ACTIVATION_DIR}"
   spawn_lxd "${LXD_ACTIVATION_DIR}"
   (
     set -e
@@ -202,7 +203,27 @@ test_basic_usage() {
     lxd activateifneeded --debug 2>&1 | grep -q -v "activating..."
     lxc config set autostart boot.autostart true --force-local
     lxd activateifneeded --debug 2>&1 | grep -q "Daemon has auto-started containers, activating..."
-    lxc delete autostart --force-local
+
+    lxc config unset autostart boot.autostart --force-local
+    lxd activateifneeded --debug 2>&1 | grep -q -v "activating..."
+
+    lxc start autostart --force-local
+    PID=$(lxc info autostart --force-local | grep ^Pid | awk '{print $2}')
+    lxd shutdown
+    [ -d "/proc/${PID}" ] && false
+
+    lxd activateifneeded --debug 2>&1 | grep -q "Daemon has auto-started containers, activating..."
+
+    # shellcheck disable=SC2086
+    lxd --logfile "${LXD_DIR}/lxd.log" ${DEBUG-} "$@" 2>&1 &
+    LXD_PID=$!
+    echo "${LXD_PID}" > "${LXD_DIR}/lxd.pid"
+    echo "${LXD_DIR}" >> "${TEST_DIR}/daemons"
+    lxd waitready --timeout=300
+
+    lxc list --force-local autostart | grep -q RUNNING
+
+    lxc delete autostart --force --force-local
   )
   # shellcheck disable=SC2031
   LXD_DIR=${LXD_DIR}

From 935eb7b387dc488e1d6b654dd4896bba45786298 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 5 Dec 2016 11:45:47 +0100
Subject: [PATCH 0522/1193] Only mark ready once containers are up
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 7a76080c7..8ad991964 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1061,7 +1061,7 @@ func (d *Daemon) Ready() error {
 	}()
 
 	/* Restore containers */
-	go containersRestart(d)
+	containersRestart(d)
 
 	/* Re-balance in case things changed while LXD was down */
 	deviceTaskBalance(d)

From 8da852581944b9cb5a9747cce103da8733308f1f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 2 Dec 2016 00:02:07 +0100
Subject: [PATCH 0523/1193] lxc-to-lxd: switch to using whitelist

We keep a whitelist for supported configuration keys. We then perform a check
whether the container sets any unsupported configuration keys. We report the
first unsupported configuration key we found back to the user and the error out.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 scripts/lxc-to-lxd | 177 +++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 145 insertions(+), 32 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index d7472a6e2..b053f71f4 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -14,6 +14,86 @@ except ImportError:
     sys.exit(1)
 
 
+# Whitelist of keys we either need to check or allow setting in LXD. The latter
+# is strictly only true for 'lxc.aa_profile'.
+keys_to_check = [
+    'lxc.pts',
+    # 'lxc.tty',
+    # 'lxc.devttydir',
+    # 'lxc.kmsg',
+    'lxc.aa_profile',
+    # 'lxc.cgroup.',
+    'lxc.loglevel',
+    # 'lxc.logfile',
+    'lxc.mount.auto',
+    'lxc.mount',
+    # 'lxc.rootfs.mount',
+    # 'lxc.rootfs.options',
+    # 'lxc.pivotdir',
+    # 'lxc.hook.pre-start',
+    # 'lxc.hook.pre-mount',
+    # 'lxc.hook.mount',
+    # 'lxc.hook.autodev',
+    # 'lxc.hook.start',
+    # 'lxc.hook.stop',
+    # 'lxc.hook.post-stop',
+    # 'lxc.hook.clone',
+    # 'lxc.hook.destroy',
+    # 'lxc.hook',
+    'lxc.network.type',
+    'lxc.network.flags',
+    'lxc.network.link',
+    'lxc.network.name',
+    'lxc.network.macvlan.mode',
+    'lxc.network.veth.pair',
+    # 'lxc.network.script.up',
+    # 'lxc.network.script.down',
+    'lxc.network.hwaddr',
+    'lxc.network.mtu',
+    # 'lxc.network.vlan.id',
+    # 'lxc.network.ipv4.gateway',
+    # 'lxc.network.ipv4',
+    # 'lxc.network.ipv6.gateway',
+    # 'lxc.network.ipv6',
+    # 'lxc.network.',
+    # 'lxc.network',
+    # 'lxc.console.logfile',
+    # 'lxc.console',
+    'lxc.include',
+    'lxc.start.auto',
+    'lxc.start.delay',
+    'lxc.start.order',
+    # 'lxc.monitor.unshare',
+    # 'lxc.group',
+    'lxc.environment',
+    # 'lxc.init_cmd',
+    # 'lxc.init_uid',
+    # 'lxc.init_gid',
+    # 'lxc.ephemeral',
+    # 'lxc.syslog',
+    # 'lxc.no_new_privs',
+
+    # Additional keys that are either set by this script or are used to report
+    # helpful errors to users.
+    'lxc.arch',
+    'lxc.id_map',
+    'lxd.migrated',
+    'lxc.rootfs.backend',
+    'lxc.rootfs',
+    'lxc.utsname',
+    'lxc.aa_allow_incomplete',
+    'lxc.autodev',
+    'lxc.haltsignal',
+    'lxc.rebootsignal',
+    'lxc.stopsignal',
+    'lxc.mount.entry',
+    'lxc.cap.drop'
+    # 'lxc.cap.keep',
+    # 'lxc.seccomp',
+    # 'lxc.se_context',
+    ]
+
+
 # Unix connection to LXD
 class UnixHTTPConnection(http.client.HTTPConnection):
     def __init__(self, path):
@@ -40,6 +120,17 @@ def config_get(config, key, default=None):
         return result
 
 
+def config_keys(config):
+    keys = []
+    for line in config:
+        fields = line.split("=", 1)
+        cur = fields[0].strip()
+        if cur and not cur.startswith("#") and cur.startswith("lxc."):
+            keys.append(cur)
+
+    return keys
+
+
 # Parse a LXC configuration file, called recursively for includes
 def config_parse(path):
     config = []
@@ -77,12 +168,13 @@ def config_parse(path):
                 with open(value, "r") as fd:
                     for line in fd:
                         line = line.strip()
-                        if line and not line.startswith("#"):
+                        if (line and not line.startswith("#") and
+                                line.startswith("lxc.")):
                             config.append("lxc.mount.entry = %s" % line)
                 continue
 
             # Proces normal configuration keys
-            if line and not line.startswith("#"):
+            if line and not line.strip().startswith("#"):
                 config.append(line)
 
     return config
@@ -133,6 +225,17 @@ def convert_container(lxd_socket, container_name, args):
     # As some keys can't be queried over the API, parse the config ourselves
     print("Parsing LXC configuration")
     lxc_config = config_parse(container.config_file_name)
+    found_keys = config_keys(lxc_config)
+
+    # Generic check for any invalid LXC configuration keys.
+    print("Checking for unsupported LXC configuration keys")
+    diff = list(set(found_keys) - set(keys_to_check))
+    for d in diff:
+        if (not d.startswith('lxc.network.') and not
+                d.startswith('lxc.cgroup.')):
+            print("Found at least one unsupported config key %s: " % d)
+            print("Not importing this container, skipping...")
+            return False
 
     if args.debug:
         print("Container configuration:")
@@ -140,6 +243,8 @@ def convert_container(lxd_socket, container_name, args):
         print("\n ".join(lxc_config))
         print("")
 
+    # Check for keys that have values differing from the LXD defaults.
+    print("Checking whether container has already been migrated")
     if config_get(lxc_config, "lxd.migrated"):
         print("Container has already been migrated, skipping...")
         return False
@@ -150,6 +255,12 @@ def convert_container(lxd_socket, container_name, args):
         print("Container already exists, skipping...")
         return False
 
+    # Validating lxc.id_map: must be unset.
+    print("Validating container mode")
+    if config_get(lxc_config, "lxc.id_map"):
+        print("Unprivileged containers aren't supported, skipping...")
+        return False
+
     # Validate lxc.utsname
     print("Validating container name")
     value = config_get(lxc_config, "lxc.utsname")
@@ -157,17 +268,40 @@ def convert_container(lxd_socket, container_name, args):
         print("Container name doesn't match lxc.utsname, skipping...")
         return False
 
-    # Detect privileged containers
-    print("Validating container mode")
-    if config_get(lxc_config, "lxc.id_map"):
-        print("Unprivileged containers aren't supported, skipping...")
+    # Validate lxc.aa_allow_incomplete: must be set to 0 or unset.
+    print("Validating whether incomplete AppArmor support is enabled")
+    value = config_get(lxc_config, "lxc.aa_allow_incomplete")
+    if value and int(value[0]) != 0:
+        print("Container allows incomplete AppArmor support, skipping...")
         return False
 
-    # Detect hooks in config
-    for line in lxc_config:
-        if line.startswith("lxc.hook."):
-            print("Hooks aren't supported, skipping...")
-            return False
+    # Validate lxc.autodev: must be set to 1 or unset.
+    print("Validating whether mounting a minimal /dev is enabled")
+    value = config_get(lxc_config, "lxc.autodev")
+    if value and int(value[0]) != 1:
+        print("Container doesn't mount a minimal /dev filesystem, skipping...")
+        return False
+
+    # Validate lxc.haltsignal: must be unset.
+    print("Validating that no custom haltsignal is set")
+    value = config_get(lxc_config, "lxc.haltsignal")
+    if value:
+        print("Container sets custom halt signal, skipping...")
+        return False
+
+    # Validate lxc.rebootsignal: must be unset.
+    print("Validating that no custom rebootsignal is set")
+    value = config_get(lxc_config, "lxc.rebootsignal")
+    if value:
+        print("Container sets custom reboot signal, skipping...")
+        return False
+
+    # Validate lxc.stopsignal: must be unset.
+    print("Validating that no custom stopsignal is set")
+    value = config_get(lxc_config, "lxc.stopsignal")
+    if value:
+        print("Container sets custom stop signal, skipping...")
+        return False
 
     # Extract and valid rootfs key
     print("Validating container rootfs")
@@ -346,27 +480,6 @@ def convert_container(lxd_socket, container_name, args):
         print("Custom capabilities aren't supported, skipping...")
         return False
 
-    # Skip ephemeral
-    print("Processing container ephemeral configuration")
-    value = config_get(lxc_config, "lxc.ephemeral")
-    if value:
-        print("Setting lxc.ephemeral is not supported, skipping...")
-        return False
-
-    # Skip syslog
-    print("Processing container syslog configuration")
-    value = config_get(lxc_config, "lxc.syslog")
-    if value:
-        print("Setting lxc.syslog is not supported, skipping...")
-        return False
-
-    # Skip logfile
-    print("Processing container syslog configuration")
-    value = config_get(lxc_config, "lxc.logfile")
-    if value:
-        print("Setting lxc.logfile is not supported, skipping...")
-        return False
-
     # Setup the container creation request
     new = {'name': container_name,
            'source': {'type': 'none'},

From 54b1713d3b12c2bf4127616a53d8a1f1ca9c11c6 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 2 Dec 2016 19:33:24 +0100
Subject: [PATCH 0524/1193] lxc-to-lxd: copy rootfs by default, do not move

This was hitting me by surprise when using this script. I'd argue that users
much rather expect a copy than a move. It also is more safe: If something goes
wrong users will still have their old container in place to recover.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 scripts/lxc-to-lxd | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index b053f71f4..a5421321e 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -533,7 +533,16 @@ def convert_container(lxd_socket, container_name, args):
     lxd_rootfs = os.path.join(args.lxdpath, "containers",
                               container_name, "rootfs")
 
-    if args.copy_rootfs:
+    if args.move_rootfs:
+        if os.path.exists(lxd_rootfs):
+            os.rmdir(lxd_rootfs)
+
+        if subprocess.call(["mv", rootfs, lxd_rootfs]) != 0:
+            print("Failed to move the container rootfs, skipping...")
+            return False
+
+        os.mkdir(rootfs)
+    else:
         print("Copying container rootfs")
         if not os.path.exists(lxd_rootfs):
             os.mkdir(lxd_rootfs)
@@ -543,15 +552,6 @@ def convert_container(lxd_socket, container_name, args):
                             "%s/" % rootfs, "%s/" % lxd_rootfs]) != 0:
             print("Failed to transfer the container rootfs, skipping...")
             return False
-    else:
-        if os.path.exists(lxd_rootfs):
-            os.rmdir(lxd_rootfs)
-
-        if subprocess.call(["mv", rootfs, lxd_rootfs]) != 0:
-            print("Failed to move the container rootfs, skipping...")
-            return False
-
-        os.mkdir(rootfs)
 
     # Delete the source
     if args.delete:
@@ -575,7 +575,7 @@ parser.add_argument("--all", action="store_true", default=False,
                     help="Import all containers")
 parser.add_argument("--delete", action="store_true", default=False,
                     help="Delete the source container")
-parser.add_argument("--copy-rootfs", action="store_true", default=False,
+parser.add_argument("--move-rootfs", action="store_true", default=False,
                     help="Copy the container rootfs rather than moving it")
 parser.add_argument("--lxcpath", type=str, default=False,
                     help="Alternate LXC path")

From d603f93d509f013b4be2c4c18d69f244c2f25c53 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Dec 2016 14:00:07 +0100
Subject: [PATCH 0525/1193] tests: Simplify testsuite spawn code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh              | 153 ++++++++++++----------------------------------
 test/suites/concurrent.sh |   5 ++
 test/suites/exec.sh       |   5 ++
 test/suites/fuidshift.sh  |   5 ++
 4 files changed, 54 insertions(+), 114 deletions(-)

diff --git a/test/main.sh b/test/main.sh
index 40d440b67..55cbb9c85 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -287,10 +287,10 @@ cleanup() {
 
   # Allow for inspection
   if [ -n "${LXD_INSPECT:-}" ]; then
-    echo "==> Test result: ${TEST_RESULT}"
     if [ "${TEST_RESULT}" != "success" ]; then
-      echo "failed test: ${TEST_CURRENT}"
+      echo "==> TEST DONE: ${TEST_DESCRIPTION}"
     fi
+    echo "==> Test result: ${TEST_RESULT}"
 
     # shellcheck disable=SC2086
     printf "To poke around, use:\n LXD_DIR=%s LXD_CONF=%s sudo -E %s/bin/lxc COMMAND\n" "${LXD_DIR}" "${LXD_CONF}" ${GOPATH:-}
@@ -312,10 +312,10 @@ cleanup() {
 
   echo ""
   echo ""
-  echo "==> Test result: ${TEST_RESULT}"
   if [ "${TEST_RESULT}" != "success" ]; then
-    echo "failed test: ${TEST_CURRENT}"
+    echo "==> TEST DONE: ${TEST_DESCRIPTION}"
   fi
+  echo "==> Test result: ${TEST_RESULT}"
 }
 
 wipe() {
@@ -381,121 +381,46 @@ spawn_lxd "${LXD2_DIR}"
 LXD2_ADDR=$(cat "${LXD2_DIR}/lxd.addr")
 export LXD2_ADDR
 
+
+run_test() {
+  TEST_CURRENT=${1}
+  TEST_CURRENT_DESCRIPTION=${2:-${1}}
+
+  echo "==> TEST BEGIN: ${TEST_CURRENT_DESCRIPTION}"
+  ${TEST_CURRENT}
+  echo "==> TEST DONE: ${TEST_CURRENT_DESCRIPTION}"
+}
+
 # allow for running a specific set of tests
 if [ "$#" -gt 0 ]; then
-  "test_${1}"
+  run_test "test_${1}"
   TEST_RESULT=success
   exit
 fi
 
-echo "==> TEST: doing static analysis of commits"
-TEST_CURRENT=test_static_analysis
-test_static_analysis
-
-echo "==> TEST: checking dependencies"
-TEST_CURRENT=test_check_deps
-test_check_deps
-
-echo "==> TEST: Database schema update"
-TEST_CURRENT=test_database_update
-test_database_update
-
-echo "==> TEST: lxc remote url"
-TEST_CURRENT=test_remote_url
-test_remote_url
-
-echo "==> TEST: lxc remote administration"
-TEST_CURRENT=test_remote_admin
-test_remote_admin
-
-echo "==> TEST: basic usage"
-TEST_CURRENT=test_basic_usage
-test_basic_usage
-
-echo "==> TEST: security"
-TEST_CURRENT=test_security
-test_security
-
-echo "==> TEST: images (and cached image expiry)"
-TEST_CURRENT=test_image_expiry
-test_image_expiry
-
-if [ -n "${LXD_CONCURRENT:-}" ]; then
-  echo "==> TEST: concurrent exec"
-  TEST_CURRENT=test_concurrent_exec
-  test_concurrent_exec
-
-  echo "==> TEST: concurrent startup"
-  TEST_CURRENT=test_concurrent
-  test_concurrent
-fi
-
-echo "==> TEST: lxc remote usage"
-TEST_CURRENT=test_remote_usage
-test_remote_usage
-
-echo "==> TEST: snapshots"
-TEST_CURRENT=test_snapshots
-test_snapshots
-
-echo "==> TEST: snapshot restore"
-TEST_CURRENT=test_snap_restore
-test_snap_restore
-
-echo "==> TEST: profiles, devices and configuration"
-TEST_CURRENT=test_config_profiles
-test_config_profiles
-
-echo "==> TEST: server config"
-TEST_CURRENT=test_server_config
-test_server_config
-
-echo "==> TEST: filemanip"
-TEST_CURRENT=test_filemanip
-test_filemanip
-
-echo "==> TEST: idmap"
-TEST_CURRENT=test_idmap
-test_idmap
-
-echo "==> TEST: template"
-TEST_CURRENT=test_template
-test_template
-
-echo "==> TEST: devlxd"
-TEST_CURRENT=test_devlxd
-test_devlxd
-
-if which fuidshift >/dev/null 2>&1; then
-  echo "==> TEST: uidshift"
-  TEST_CURRENT=test_fuidshift
-  test_fuidshift
-else
-  echo "==> SKIP: fuidshift (binary missing)"
-fi
-
-echo "==> TEST: migration"
-TEST_CURRENT=test_migration
-test_migration
-
-curversion=$(dpkg -s lxc | awk '/^Version/ { print $2 }')
-if dpkg --compare-versions "${curversion}" gt 1.1.2-0ubuntu3; then
-  echo "==> TEST: fdleak"
-  TEST_CURRENT=test_fdleak
-  test_fdleak
-else
-  # We temporarily skip the fdleak test because a bug in lxc is
-  # known to make it # fail without lxc commit
-  # 858377e: # logs: introduce a thread-local 'current' lxc_config (v2)
-  echo "==> SKIPPING TEST: fdleak"
-fi
-
-echo "==> TEST: cpu profiling"
-TEST_CURRENT=test_cpu_profiling
-test_cpu_profiling
-
-echo "==> TEST: memory profiling"
-TEST_CURRENT=test_mem_profiling
-test_mem_profiling
+run_test test_check_deps "checking dependencies"
+run_test test_static_analysis "static analysis"
+run_test test_database_update "database schema updates"
+run_test test_remote_url "remote  url handling"
+run_test test_remote_admin "remote administration"
+run_test test_remote_usage "remote usage"
+run_test test_basic_usage "basic usage"
+run_test test_security "security features"
+run_test test_image_expiry "image expiry"
+run_test test_concurrent_exec "concurrent exec"
+run_test test_concurrent "concurrent startup"
+run_test test_snapshots "container snapshots"
+run_test test_snap_restore "snapshot restores"
+run_test test_config_profiles "profiles, devices and configuration"
+run_test test_server_config "server configuration"
+run_test test_filemanip "file manipulations"
+run_test test_idmap "id mapping"
+run_test test_template "file templating"
+run_test test_devlxd "/dev/lxd"
+run_test test_fuidshift "fuidshift"
+run_test test_migration "migration"
+run_test test_fdleak "fd leak"
+run_test test_cpu_profiling "CPU profiling"
+run_test test_mem_profiling "memory profiling"
 
 TEST_RESULT=success
diff --git a/test/suites/concurrent.sh b/test/suites/concurrent.sh
index a4b3dd71b..33362bcd7 100644
--- a/test/suites/concurrent.sh
+++ b/test/suites/concurrent.sh
@@ -1,6 +1,11 @@
 #!/bin/sh
 
 test_concurrent() {
+  if [ -z "${LXD_CONCURRENT:-}" ]; then
+    echo "==> SKIP: LXD_CONCURRENT isn't set"
+    return
+  fi
+
   ensure_import_testimage
 
   spawn_container() {
diff --git a/test/suites/exec.sh b/test/suites/exec.sh
index 815892d07..8adb7f321 100644
--- a/test/suites/exec.sh
+++ b/test/suites/exec.sh
@@ -1,6 +1,11 @@
 #!/bin/sh
 
 test_concurrent_exec() {
+  if [ -z "${LXD_CONCURRENT:-}" ]; then
+    echo "==> SKIP: LXD_CONCURRENT isn't set"
+    return
+  fi
+
   ensure_import_testimage
 
   name=x1
diff --git a/test/suites/fuidshift.sh b/test/suites/fuidshift.sh
index 1a631bcb3..c4a58a84a 100644
--- a/test/suites/fuidshift.sh
+++ b/test/suites/fuidshift.sh
@@ -48,6 +48,11 @@ test_root_fuidshift() {
 }
 
 test_fuidshift() {
+  if ! which fuidshift >/dev/null 2>&1; then
+    echo "==> SKIP: No fuidshift binary could be found"
+    return
+  fi
+
   if [ "$(id -u)" -ne 0 ]; then
     test_nonroot_fuidshift
   else

From 5f070e8ba394bf29e3a609e07bf77e05e26c4f46 Mon Sep 17 00:00:00 2001
From: Rene Fragoso <ctrlrsf at gmail.com>
Date: Wed, 1 Jun 2016 10:00:05 -0400
Subject: [PATCH 0526/1193] Allow deleting multiple images at a time

lxc image delete subcommand was modified to allow more than
one image name as arguments.

When more than one image is specified, we will iterate and
delete each one.

All images have to be from same remote.

Signed-off-by: Rene Fragoso <ctrlrsf at gmail.com>
---
 lxc/image.go | 26 ++++++++++++++++------
 po/lxd.pot   | 70 ++++++++++++++++++++++++++++++------------------------------
 2 files changed, 55 insertions(+), 41 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 68aedf035..3ada00841 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -117,8 +117,8 @@ lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--
     The auto-update flag instructs the server to keep this image up to
     date. It requires the source to be an alias and for it to be public.
 
-lxc image delete [remote:]<image>
-    Delete an image from the LXD image store.
+lxc image delete [remote:]<image> [<image>...]
+    Delete one or more images from the LXD image store.
 
 lxc image export [remote:]<image> [target]
     Export an image from the LXD image store into a distributable tarball.
@@ -282,7 +282,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		return err
 
 	case "delete":
-		/* delete [<remote>:]<image> */
+		/* delete [<remote>:]<image> [<image>...] */
 		if len(args) < 2 {
 			return errArgs
 		}
@@ -292,14 +292,28 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			inName = "default"
 		}
 
+		imageNames := []string{inName}
+
+		if len(args) > 2 {
+			for _, extraImage := range args[2:] {
+				imageNames = append(imageNames, extraImage)
+			}
+		}
+
 		d, err := lxd.NewClient(config, remote)
 		if err != nil {
 			return err
 		}
 
-		image := c.dereferenceAlias(d, inName)
-		err = d.DeleteImage(image)
-		return err
+		for _, imageName := range imageNames {
+			image := c.dereferenceAlias(d, imageName)
+			err = d.DeleteImage(image)
+			if err != nil {
+				return err
+			}
+		}
+
+		return nil
 
 	case "info":
 		if len(args) < 2 {
diff --git a/po/lxd.pot b/po/lxd.pot
index 86fef584f..43f3fedb5 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-11-22 23:13-0500\n"
+        "POT-Creation-Date: 2016-12-06 17:32+0100\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -77,7 +77,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:595
+#: lxc/image.go:609
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -90,11 +90,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:616 lxc/image.go:645
+#: lxc/image.go:630 lxc/image.go:659
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:620
+#: lxc/image.go:634
 msgid   "ARCH"
 msgstr  ""
 
@@ -111,7 +111,7 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:354
+#: lxc/image.go:368
 msgid   "Aliases:"
 msgstr  ""
 
@@ -119,12 +119,12 @@ msgstr  ""
 msgid   "An environment variable of the form HOME=/home/foo"
 msgstr  ""
 
-#: lxc/image.go:337 lxc/info.go:93
+#: lxc/image.go:351 lxc/info.go:93
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:358
+#: lxc/image.go:372
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
@@ -193,7 +193,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:699 lxc/profile.go:190
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:713 lxc/profile.go:190
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -251,7 +251,7 @@ msgid   "Create a read-only snapshot of a container.\n"
         "lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:342 lxc/info.go:95
+#: lxc/image.go:356 lxc/info.go:95
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -265,7 +265,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:619 lxc/image.go:647
+#: lxc/image.go:633 lxc/image.go:661
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -323,16 +323,16 @@ msgid   "Execute the specified command in a container.\n"
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
 
-#: lxc/image.go:346
+#: lxc/image.go:360
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:348
+#: lxc/image.go:362
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:272 lxc/image.go:617 lxc/image.go:646
+#: lxc/config.go:272 lxc/image.go:631 lxc/image.go:660
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -340,7 +340,7 @@ msgstr  ""
 msgid   "Fast mode (same as --columns=nsacPt"
 msgstr  ""
 
-#: lxc/image.go:335
+#: lxc/image.go:349
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -393,12 +393,12 @@ msgstr  ""
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:413 lxc/image.go:425
+#: lxc/image.go:427 lxc/image.go:439
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:410
+#: lxc/image.go:424
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
@@ -643,8 +643,8 @@ msgid   "Manipulate container images.\n"
         "    The auto-update flag instructs the server to keep this image up to\n"
         "    date. It requires the source to be an alias and for it to be public.\n"
         "\n"
-        "lxc image delete [remote:]<image>\n"
-        "    Delete an image from the LXD image store.\n"
+        "lxc image delete [remote:]<image> [<image>...]\n"
+        "    Delete one or more images from the LXD image store.\n"
         "\n"
         "lxc image export [remote:]<image> [target]\n"
         "    Export an image from the LXD image store into a distributable tarball.\n"
@@ -757,7 +757,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:416
+#: lxc/image.go:430
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -765,7 +765,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:518
+#: lxc/image.go:532
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -790,7 +790,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:618 lxc/remote.go:355
+#: lxc/image.go:632 lxc/remote.go:355
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -829,7 +829,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:700
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:714
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -880,7 +880,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:350
+#: lxc/image.go:364
 msgid   "Properties:"
 msgstr  ""
 
@@ -888,7 +888,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:338
+#: lxc/image.go:352
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -926,7 +926,7 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:621
+#: lxc/image.go:635
 msgid   "SIZE"
 msgstr  ""
 
@@ -991,7 +991,7 @@ msgstr  ""
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/image.go:336
+#: lxc/image.go:350
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -1000,7 +1000,7 @@ msgstr  ""
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/image.go:360
+#: lxc/image.go:374
 msgid   "Source:"
 msgstr  ""
 
@@ -1067,7 +1067,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it."
 msgstr  ""
 
-#: lxc/image.go:339
+#: lxc/image.go:353
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -1075,7 +1075,7 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:418
+#: lxc/image.go:432
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
@@ -1093,7 +1093,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:622
+#: lxc/image.go:636
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1105,7 +1105,7 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:344
+#: lxc/image.go:358
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1167,11 +1167,11 @@ msgstr  ""
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:330
+#: lxc/image.go:344
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:332
+#: lxc/image.go:346
 msgid   "enabled"
 msgstr  ""
 
@@ -1189,7 +1189,7 @@ msgstr  ""
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:325 lxc/image.go:598
+#: lxc/image.go:339 lxc/image.go:612
 msgid   "no"
 msgstr  ""
 
@@ -1247,7 +1247,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:327 lxc/image.go:602
+#: lxc/delete.go:45 lxc/image.go:341 lxc/image.go:616
 msgid   "yes"
 msgstr  ""
 

From 6cab7340029744985318cfec209d49ccf93fed58 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Dec 2016 17:43:35 +0100
Subject: [PATCH 0527/1193] tests: Fix bad variable name
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/main.sh b/test/main.sh
index 55cbb9c85..67f8fca8a 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -288,7 +288,7 @@ cleanup() {
   # Allow for inspection
   if [ -n "${LXD_INSPECT:-}" ]; then
     if [ "${TEST_RESULT}" != "success" ]; then
-      echo "==> TEST DONE: ${TEST_DESCRIPTION}"
+      echo "==> TEST DONE: ${TEST_CURRENT_DESCRIPTION}"
     fi
     echo "==> Test result: ${TEST_RESULT}"
 
@@ -313,7 +313,7 @@ cleanup() {
   echo ""
   echo ""
   if [ "${TEST_RESULT}" != "success" ]; then
-    echo "==> TEST DONE: ${TEST_DESCRIPTION}"
+    echo "==> TEST DONE: ${TEST_CURRENT_DESCRIPTION}"
   fi
   echo "==> Test result: ${TEST_RESULT}"
 }

From d223754ee1ecb574de997af4ed10a445b775e28f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Dec 2016 17:44:06 +0100
Subject: [PATCH 0528/1193] tests: Shorten test name to fit on Jenkins
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/main.sh b/test/main.sh
index 67f8fca8a..ac1dd7fec 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -411,7 +411,7 @@ run_test test_concurrent_exec "concurrent exec"
 run_test test_concurrent "concurrent startup"
 run_test test_snapshots "container snapshots"
 run_test test_snap_restore "snapshot restores"
-run_test test_config_profiles "profiles, devices and configuration"
+run_test test_config_profiles "profiles and configuration"
 run_test test_server_config "server configuration"
 run_test test_filemanip "file manipulations"
 run_test test_idmap "id mapping"

From 6c9a2d6288ef95a6e1492a011ae9653ab74b8907 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 6 Dec 2016 17:51:13 +0100
Subject: [PATCH 0529/1193] tests: Fix standalone remote test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/remote.sh | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index 327ef91bf..869b8fee7 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -80,6 +80,9 @@ test_remote_admin() {
 }
 
 test_remote_usage() {
+  ensure_import_testimage
+  ensure_has_localhost_remote "${LXD_ADDR}"
+
   lxc_remote remote add lxd2 "${LXD2_ADDR}" --accept-certificate --password foo
 
   # we need a public image on localhost

From c9587e3b86a5e9aa1b38f40b2e5bd4e992f6a7a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 7 Dec 2016 01:29:17 +0100
Subject: [PATCH 0530/1193] Properly validate daemon keys on unset
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2698

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_config.go | 11 ++++++++++-
 lxd/storage_zfs.go   | 18 +++++++++---------
 2 files changed, 19 insertions(+), 10 deletions(-)

diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 719d139c9..1870b52c4 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -49,8 +49,17 @@ func (k *daemonConfigKey) name() string {
 }
 
 func (k *daemonConfigKey) Validate(d *Daemon, value string) error {
-	// No need to validate when unsetting
+	// Handle unsetting
 	if value == "" {
+		value = k.defaultValue
+
+		if k.validator != nil {
+			err := k.validator(d, k.name(), value)
+			if err != nil {
+				return err
+			}
+		}
+
 		return nil
 	}
 
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 36bd0e86d..bff396fd7 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -1184,17 +1184,17 @@ func storageZFSValidatePoolName(d *Daemon, key string, value string) error {
 		if err != nil {
 			return fmt.Errorf("Invalid ZFS pool: %v", err)
 		}
-	}
 
-	// Confirm that the new pool is empty
-	s.zfsPool = value
-	subvols, err := s.zfsListSubvolumes("")
-	if err != nil {
-		return err
-	}
+		// Confirm that the new pool is empty
+		s.zfsPool = value
+		subvols, err := s.zfsListSubvolumes("")
+		if err != nil {
+			return err
+		}
 
-	if len(subvols) > 0 {
-		return fmt.Errorf("Provided ZFS pool (or dataset) isn't empty")
+		if len(subvols) > 0 {
+			return fmt.Errorf("Provided ZFS pool (or dataset) isn't empty")
+		}
 	}
 
 	// Confirm the old pool isn't in use anymore

From 10f46fac99c8ee999f58dde81e80c70725f2761a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 8 Dec 2016 11:31:07 +0100
Subject: [PATCH 0531/1193] Don't record last_state.power twice
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Saves us from a full Update() run for every container at every daemon startup.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/containers.go | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/lxd/containers.go b/lxd/containers.go
index c657f62df..f840c1d9a 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -126,19 +126,6 @@ func containersRestart(d *Daemon) error {
 		}
 	}
 
-	// Reset the recorded state (to ensure it's up to date)
-	_, err = dbExec(d.db, "DELETE FROM containers_config WHERE key='volatile.last_state.power'")
-	if err != nil {
-		return err
-	}
-
-	for _, c := range containers {
-		err = c.ConfigKeySet("volatile.last_state.power", c.State())
-		if err != nil {
-			return err
-		}
-	}
-
 	return nil
 }
 

From cc792ae6a0302a0e8567dc1debd600d7520e7a84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 9 Dec 2016 15:19:09 +0100
Subject: [PATCH 0532/1193] Fix device hotplug with major/minor set
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7fd2863f4..5c636d123 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4544,8 +4544,10 @@ func (c *containerLXC) insertUnixDevice(name string, m shared.Device) error {
 	}
 
 	dType := ""
-	if m["type"] != "" {
-		dType = m["type"]
+	if m["type"] == "unix-char" {
+		dType = "c"
+	} else if m["type"] == "unix-block" {
+		dType = "b"
 	}
 
 	if dType == "" || dMajor < 0 || dMinor < 0 {

From 43c5c71967c56904fbe397cab2a22717bed0fe47 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 1 Dec 2016 09:03:26 -0700
Subject: [PATCH 0533/1193] client: commonize update methods and add PATCH

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go | 64 ++++++++++++++-------------------------------------------------
 1 file changed, 14 insertions(+), 50 deletions(-)

diff --git a/client.go b/client.go
index 0aaee2b46..ace03c3bd 100644
--- a/client.go
+++ b/client.go
@@ -381,7 +381,7 @@ func (c *Client) baseGet(getUrl string) (*Response, error) {
 	return HoistResponse(resp, Sync)
 }
 
-func (c *Client) put(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) doUpdateMethod(method string, base string, args interface{}, rtype ResponseType) (*Response, error) {
 	uri := c.url(shared.APIVersion, base)
 
 	buf := bytes.Buffer{}
@@ -390,9 +390,9 @@ func (c *Client) put(base string, args interface{}, rtype ResponseType) (*Respon
 		return nil, err
 	}
 
-	shared.LogDebugf("Putting %s to %s", buf.String(), uri)
+	shared.LogDebugf("%s %s to %s", method, buf.String(), uri)
 
-	req, err := http.NewRequest("PUT", uri, &buf)
+	req, err := http.NewRequest(method, uri, &buf)
 	if err != nil {
 		return nil, err
 	}
@@ -407,30 +407,20 @@ func (c *Client) put(base string, args interface{}, rtype ResponseType) (*Respon
 	return HoistResponse(resp, rtype)
 }
 
-func (c *Client) post(base string, args interface{}, rtype ResponseType) (*Response, error) {
-	uri := c.url(shared.APIVersion, base)
-
-	buf := bytes.Buffer{}
-	err := json.NewEncoder(&buf).Encode(args)
-	if err != nil {
-		return nil, err
-	}
-
-	shared.LogDebugf("Posting %s to %s", buf.String(), uri)
+func (c *Client) put(base string, args interface{}, rtype ResponseType) (*Response, error) {
+	return c.doUpdateMethod("PUT", base, args, rtype)
+}
 
-	req, err := http.NewRequest("POST", uri, &buf)
-	if err != nil {
-		return nil, err
-	}
-	req.Header.Set("User-Agent", shared.UserAgent)
-	req.Header.Set("Content-Type", "application/json")
+func (c *Client) patch(base string, args interface{}, rtype ResponseType) (*Response, error) {
+	return c.doUpdateMethod("PATCH", base, args, rtype)
+}
 
-	resp, err := c.Http.Do(req)
-	if err != nil {
-		return nil, err
-	}
+func (c *Client) post(base string, args interface{}, rtype ResponseType) (*Response, error) {
+	return c.doUpdateMethod("POST", base, args, rtype)
+}
 
-	return HoistResponse(resp, rtype)
+func (c *Client) delete(base string, args interface{}, rtype ResponseType) (*Response, error) {
+	return c.doUpdateMethod("DELETE", base, args, rtype)
 }
 
 func (c *Client) getRaw(uri string) (*http.Response, error) {
@@ -457,32 +447,6 @@ func (c *Client) getRaw(uri string) (*http.Response, error) {
 	return raw, nil
 }
 
-func (c *Client) delete(base string, args interface{}, rtype ResponseType) (*Response, error) {
-	uri := c.url(shared.APIVersion, base)
-
-	buf := bytes.Buffer{}
-	err := json.NewEncoder(&buf).Encode(args)
-	if err != nil {
-		return nil, err
-	}
-
-	shared.LogDebugf("Deleting %s to %s", buf.String(), uri)
-
-	req, err := http.NewRequest("DELETE", uri, &buf)
-	if err != nil {
-		return nil, err
-	}
-	req.Header.Set("User-Agent", shared.UserAgent)
-	req.Header.Set("Content-Type", "application/json")
-
-	resp, err := c.Http.Do(req)
-	if err != nil {
-		return nil, err
-	}
-
-	return HoistResponse(resp, rtype)
-}
-
 func (c *Client) Websocket(operation string, secret string) (*websocket.Conn, error) {
 	query := url.Values{"secret": []string{secret}}
 	url := c.BaseWSURL + path.Join(operation, "websocket") + "?" + query.Encode()

From ff2f05691b53d215f47458add142d34582958a6b Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 7 Dec 2016 16:15:53 +0100
Subject: [PATCH 0534/1193] shared: make PrintStack print at the Error level

This is used for debugging, and --debug is very verbose and sometimes I
don't want to have to wade through it. So let's use Error instead :)

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/log.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/log.go b/shared/log.go
index 792ccf685..f696febff 100644
--- a/shared/log.go
+++ b/shared/log.go
@@ -93,5 +93,5 @@ func LogCritf(format string, args ...interface{}) {
 func PrintStack() {
 	buf := make([]byte, 1<<16)
 	runtime.Stack(buf, true)
-	LogDebugf("%s", buf)
+	LogErrorf("%s", buf)
 }

From 716567e20b75eaa637fe2cd298231394be5782bc Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 8 Dec 2016 17:26:24 +0100
Subject: [PATCH 0535/1193] make a helper to compute cert fingerprint

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go      |  9 ++++-----
 shared/cert.go | 11 +++++++++++
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/client.go b/client.go
index ace03c3bd..b78a58325 100644
--- a/client.go
+++ b/client.go
@@ -2,7 +2,6 @@ package lxd
 
 import (
 	"bytes"
-	"crypto/sha256"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/json"
@@ -1638,10 +1637,10 @@ func (c *Client) ServerStatus() (*shared.ServerState, error) {
 
 	// Fill in certificate fingerprint if not provided
 	if ss.Environment.CertificateFingerprint == "" && ss.Environment.Certificate != "" {
-		pemCertificate, _ := pem.Decode([]byte(ss.Environment.Certificate))
-		if pemCertificate != nil {
-			digest := sha256.Sum256(pemCertificate.Bytes)
-			ss.Environment.CertificateFingerprint = fmt.Sprintf("%x", digest)
+		var err error
+		ss.Environment.CertificateFingerprint, err = shared.CertFingerprint(ss.Environment.Certificate)
+		if err != nil {
+			return nil, err
 		}
 	}
 
diff --git a/shared/cert.go b/shared/cert.go
index ebd5604cf..91be1531a 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -8,6 +8,7 @@ package shared
 import (
 	"crypto/rand"
 	"crypto/rsa"
+	"crypto/sha256"
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/pem"
@@ -211,3 +212,13 @@ func ReadCert(fpath string) (*x509.Certificate, error) {
 
 	return x509.ParseCertificate(certBlock.Bytes)
 }
+
+func CertFingerprint(c string) (string, error) {
+	pemCertificate, _ := pem.Decode([]byte(c))
+	if pemCertificate != nil {
+		digest := sha256.Sum256(pemCertificate.Bytes)
+		return fmt.Sprintf("%x", digest), nil
+	}
+
+	return "", fmt.Errorf("invalid certificate")
+}

From ed7106af9fc8cf3ffece4faa1639475a960d733b Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 14 Dec 2016 09:20:39 -0700
Subject: [PATCH 0536/1193] make LXD_DIR with +x for group and everyone

I think the systemd unit does this chmod for us, but when we mkdir it on
our own for testing people can't even see $LXD_DIR/unix.socket.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/daemon.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 8ad991964..0d84b4353 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -733,7 +733,7 @@ func (d *Daemon) Init() error {
 	d.lxcpath = shared.VarPath("containers")
 
 	/* Make sure all our directories are available */
-	if err := os.MkdirAll(shared.CachePath(), 0700); err != nil {
+	if err := os.MkdirAll(shared.CachePath(), 0711); err != nil {
 		return err
 	}
 	if err := os.MkdirAll(shared.VarPath("containers"), 0711); err != nil {

From 215009fa1ba5f8258097f14c5801723872249366 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 14 Dec 2016 17:16:38 +0000
Subject: [PATCH 0537/1193] do mounts in the right order

In particular, if there are two paths to be mounted:

/mnt
/mnt/foo

We should always mount /mnt before we mount /mnt/foo, otherwise, the /mnt
mount will overmount /mnt/foo, which is almost certainly not what people
want.

Closes #2717

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/container_lxc.go  | 60 +++++++++++++++++++++++++++++++++++++++++++--------
 test/suites/config.sh | 20 +++++++++++++++++
 2 files changed, 71 insertions(+), 9 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5c636d123..a07d90036 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1520,6 +1520,8 @@ func (c *containerLXC) startCommon() (string, error) {
 	c.removeUnixDevices()
 	c.removeDiskDevices()
 
+	diskDevices := map[string]shared.Device{}
+
 	// Create the devices
 	for _, k := range c.expandedDevices.DeviceNames() {
 		m := c.expandedDevices[k]
@@ -1543,16 +1545,20 @@ func (c *containerLXC) startCommon() (string, error) {
 				}
 			}
 		} else if m["type"] == "disk" {
-			// Disk device
 			if m["path"] != "/" {
-				_, err := c.createDiskDevice(k, m)
-				if err != nil {
-					return "", err
-				}
+				diskDevices[k] = m
 			}
 		}
 	}
 
+	err = c.addDiskDevices(diskDevices, func(name string, d shared.Device) error {
+		_, err := c.createDiskDevice(name, d)
+		return err
+	})
+	if err != nil {
+		return "", err
+	}
+
 	// Create any missing directory
 	err = os.MkdirAll(c.LogPath(), 0700)
 	if err != nil {
@@ -3043,6 +3049,8 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			}
 		}
 
+		diskDevices := map[string]shared.Device{}
+
 		for k, m := range addDevices {
 			if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
 				err = c.insertUnixDevice(k, m)
@@ -3050,10 +3058,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 					return err
 				}
 			} else if m["type"] == "disk" && m["path"] != "/" {
-				err = c.insertDiskDevice(k, m)
-				if err != nil {
-					return err
-				}
+				diskDevices[k] = m
 			} else if m["type"] == "nic" {
 				err = c.insertNetworkDevice(k, m)
 				if err != nil {
@@ -3062,6 +3067,11 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			}
 		}
 
+		err = c.addDiskDevices(diskDevices, c.insertDiskDevice)
+		if err != nil {
+			return err
+		}
+
 		updateDiskLimit := false
 		for k, m := range updateDevices {
 			if m["type"] == "disk" {
@@ -5077,6 +5087,38 @@ func (c *containerLXC) insertDiskDevice(name string, m shared.Device) error {
 	return nil
 }
 
+type byPath []shared.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]shared.Device, handler func(string, shared.Device) error) error {
+	ordered := byPath{}
+
+	for _, d := range devices {
+		ordered = append(ordered, d)
+	}
+
+	sort.Sort(ordered)
+	for _, d := range ordered {
+		err := handler(d["path"], d)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
 func (c *containerLXC) removeDiskDevice(name string, m shared.Device) error {
 	// Check that the container is running
 	pid := c.InitPID()
diff --git a/test/suites/config.sh b/test/suites/config.sh
index 6613727ca..18e158c89 100644
--- a/test/suites/config.sh
+++ b/test/suites/config.sh
@@ -79,6 +79,24 @@ testloopmounts() {
   sed -i "\|^${lpath}|d" "${TEST_DIR}/loops"
 }
 
+test_mount_order() {
+  mkdir -p "${TEST_DIR}/order/empty"
+  mkdir -p "${TEST_DIR}/order/full"
+  touch "${TEST_DIR}/order/full/filler"
+
+  # The idea here is that sometimes (depending on how golang randomizes the
+  # config) the empty dir will have the contents of full in it, but sometimes
+  # it won't depending on whether the devices below are processed in order or
+  # not. This should not be racy, and they should *always* be processed in path
+  # order, so the filler file should always be there.
+  lxc config device add foo order disk source="${TEST_DIR}/order" path=/mnt
+  lxc config device add foo orderFull disk source="${TEST_DIR}/order/full" path=/mnt/empty
+
+  lxc start foo
+  lxc exec foo -- cat /mnt/empty/filler
+  lxc stop foo --force
+}
+
 test_config_profiles() {
   ensure_import_testimage
 
@@ -170,6 +188,8 @@ test_config_profiles() {
 
   testloopmounts
 
+  test_mount_order
+
   lxc delete foo
 
   lxc init testimage foo

From c3fb2efa9f49a6d6a8cf21dd6dc073435d191bc0 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 14 Dec 2016 18:55:44 +0000
Subject: [PATCH 0538/1193] centralize all cert fingerprint generation

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go           |  2 +-
 lxc/remote.go       |  3 +--
 lxd/certificates.go | 13 ++++---------
 shared/cert.go      | 18 +++++++++++++-----
 4 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/client.go b/client.go
index b78a58325..e37163572 100644
--- a/client.go
+++ b/client.go
@@ -1638,7 +1638,7 @@ func (c *Client) ServerStatus() (*shared.ServerState, error) {
 	// Fill in certificate fingerprint if not provided
 	if ss.Environment.CertificateFingerprint == "" && ss.Environment.Certificate != "" {
 		var err error
-		ss.Environment.CertificateFingerprint, err = shared.CertFingerprint(ss.Environment.Certificate)
+		ss.Environment.CertificateFingerprint, err = shared.CertFingerprintStr(ss.Environment.Certificate)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxc/remote.go b/lxc/remote.go
index 83a82a797..c23febd28 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"crypto/sha256"
 	"crypto/x509"
 	"encoding/pem"
 	"fmt"
@@ -190,7 +189,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 
 	if certificate != nil {
 		if !acceptCert {
-			digest := sha256.Sum256(certificate.Raw)
+			digest := shared.CertFingerprint(certificate)
 
 			fmt.Printf(i18n.G("Certificate fingerprint: %x")+"\n", digest)
 			fmt.Printf(i18n.G("ok (y/n)?") + " ")
diff --git a/lxd/certificates.go b/lxd/certificates.go
index 585ba2184..e1e5c3ab1 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"crypto/sha256"
 	"crypto/x509"
 	"encoding/base64"
 	"encoding/pem"
@@ -14,10 +13,6 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
-func certGenerateFingerprint(cert *x509.Certificate) string {
-	return fmt.Sprintf("%x", sha256.Sum256(cert.Raw))
-}
-
 func certificatesGet(d *Daemon, r *http.Request) Response {
 	recursion := d.isRecursionRequest(r)
 
@@ -44,7 +39,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 
 	body := []string{}
 	for _, cert := range d.clientCerts {
-		fingerprint := fmt.Sprintf("/%s/certificates/%s", shared.APIVersion, certGenerateFingerprint(&cert))
+		fingerprint := fmt.Sprintf("/%s/certificates/%s", shared.APIVersion, shared.CertFingerprint(&cert))
 		body = append(body, fingerprint)
 	}
 
@@ -85,7 +80,7 @@ func readSavedClientCAList(d *Daemon) {
 
 func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
 	baseCert := new(dbCertInfo)
-	baseCert.Fingerprint = certGenerateFingerprint(cert)
+	baseCert.Fingerprint = shared.CertFingerprint(cert)
 	baseCert.Type = 1
 	baseCert.Name = host
 	baseCert.Certificate = string(
@@ -141,9 +136,9 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("Can't use TLS data on non-TLS link"))
 	}
 
-	fingerprint := certGenerateFingerprint(cert)
+	fingerprint := shared.CertFingerprint(cert)
 	for _, existingCert := range d.clientCerts {
-		if fingerprint == certGenerateFingerprint(&existingCert) {
+		if fingerprint == shared.CertFingerprint(&existingCert) {
 			return BadRequest(fmt.Errorf("Certificate already in trust store"))
 		}
 	}
diff --git a/shared/cert.go b/shared/cert.go
index 91be1531a..d1225e5d5 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -213,12 +213,20 @@ func ReadCert(fpath string) (*x509.Certificate, error) {
 	return x509.ParseCertificate(certBlock.Bytes)
 }
 
-func CertFingerprint(c string) (string, error) {
+func CertFingerprint(cert *x509.Certificate) string {
+	return fmt.Sprintf("%x", sha256.Sum256(cert.Raw))
+}
+
+func CertFingerprintStr(c string) (string, error) {
 	pemCertificate, _ := pem.Decode([]byte(c))
-	if pemCertificate != nil {
-		digest := sha256.Sum256(pemCertificate.Bytes)
-		return fmt.Sprintf("%x", digest), nil
+	if pemCertificate == nil {
+		return "", fmt.Errorf("invalid certificate")
+	}
+
+	cert, err := x509.ParseCertificate(pemCertificate.Bytes)
+	if err != nil {
+		return "", err
 	}
 
-	return "", fmt.Errorf("invalid certificate")
+	return CertFingerprint(cert), nil
 }

From ec441d9a6e71f84be176c3965969214985c97789 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 14 Dec 2016 18:57:06 +0000
Subject: [PATCH 0539/1193] make directories with stricter permissions

$LXD_DIR is really the only thing that needs 711, and CachePath doesn't.
Let's be explicit about this distinction.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/daemon.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 0d84b4353..1f91fc127 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -733,7 +733,10 @@ func (d *Daemon) Init() error {
 	d.lxcpath = shared.VarPath("containers")
 
 	/* Make sure all our directories are available */
-	if err := os.MkdirAll(shared.CachePath(), 0711); err != nil {
+	if err := os.MkdirAll(shared.VarPath(), 0711); err != nil {
+		return err
+	}
+	if err := os.MkdirAll(shared.CachePath(), 0700); err != nil {
 		return err
 	}
 	if err := os.MkdirAll(shared.VarPath("containers"), 0711); err != nil {

From 9f04fb75f131e978aad47666518334c45c7d3d68 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 14:58:08 -0500
Subject: [PATCH 0540/1193] main: Move activateifneeded to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go                  | 70 ---------------------------------------
 lxd/main_activateifneeded.go | 78 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 78 insertions(+), 70 deletions(-)
 create mode 100644 lxd/main_activateifneeded.go

diff --git a/lxd/main.go b/lxd/main.go
index 0fa3b944b..f945b40e4 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -472,76 +472,6 @@ func cmdShutdown() error {
 	return nil
 }
 
-func cmdActivateIfNeeded() error {
-	// Don't start a full daemon, we just need DB access
-	d := &Daemon{
-		imagesDownloading:     map[string]chan bool{},
-		imagesDownloadingLock: sync.RWMutex{},
-		lxcpath:               shared.VarPath("containers"),
-	}
-
-	if !shared.PathExists(shared.VarPath("lxd.db")) {
-		shared.LogDebugf("No DB, so no need to start the daemon now.")
-		return nil
-	}
-
-	err := initializeDbObject(d, shared.VarPath("lxd.db"))
-	if err != nil {
-		return err
-	}
-
-	/* Load all config values from the database */
-	err = daemonConfigInit(d.db)
-	if err != nil {
-		return err
-	}
-
-	// Look for network socket
-	value := daemonConfig["core.https_address"].Get()
-	if value != "" {
-		shared.LogDebugf("Daemon has core.https_address set, activating...")
-		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-		return err
-	}
-
-	// Look for auto-started or previously started containers
-	d.IdmapSet, err = shared.DefaultIdmapSet()
-	if err != nil {
-		return err
-	}
-
-	result, err := dbContainersList(d.db, cTypeRegular)
-	if err != nil {
-		return err
-	}
-
-	for _, name := range result {
-		c, err := containerLoadByName(d, name)
-		if err != nil {
-			return err
-		}
-
-		config := c.ExpandedConfig()
-		lastState := config["volatile.last_state.power"]
-		autoStart := config["boot.autostart"]
-
-		if c.IsRunning() {
-			shared.LogDebugf("Daemon has running containers, activating...")
-			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-			return err
-		}
-
-		if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) {
-			shared.LogDebugf("Daemon has auto-started containers, activating...")
-			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-			return err
-		}
-	}
-
-	shared.LogDebugf("No need to start the daemon now.")
-	return nil
-}
-
 func cmdWaitReady() error {
 	var timeout int
 
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
new file mode 100644
index 000000000..50cce63dc
--- /dev/null
+++ b/lxd/main_activateifneeded.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+	"sync"
+
+	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared"
+)
+
+func cmdActivateIfNeeded() error {
+	// Don't start a full daemon, we just need DB access
+	d := &Daemon{
+		imagesDownloading:     map[string]chan bool{},
+		imagesDownloadingLock: sync.RWMutex{},
+		lxcpath:               shared.VarPath("containers"),
+	}
+
+	if !shared.PathExists(shared.VarPath("lxd.db")) {
+		shared.LogDebugf("No DB, so no need to start the daemon now.")
+		return nil
+	}
+
+	err := initializeDbObject(d, shared.VarPath("lxd.db"))
+	if err != nil {
+		return err
+	}
+
+	/* Load all config values from the database */
+	err = daemonConfigInit(d.db)
+	if err != nil {
+		return err
+	}
+
+	// Look for network socket
+	value := daemonConfig["core.https_address"].Get()
+	if value != "" {
+		shared.LogDebugf("Daemon has core.https_address set, activating...")
+		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+		return err
+	}
+
+	// Look for auto-started or previously started containers
+	d.IdmapSet, err = shared.DefaultIdmapSet()
+	if err != nil {
+		return err
+	}
+
+	result, err := dbContainersList(d.db, cTypeRegular)
+	if err != nil {
+		return err
+	}
+
+	for _, name := range result {
+		c, err := containerLoadByName(d, name)
+		if err != nil {
+			return err
+		}
+
+		config := c.ExpandedConfig()
+		lastState := config["volatile.last_state.power"]
+		autoStart := config["boot.autostart"]
+
+		if c.IsRunning() {
+			shared.LogDebugf("Daemon has running containers, activating...")
+			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			return err
+		}
+
+		if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) {
+			shared.LogDebugf("Daemon has auto-started containers, activating...")
+			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			return err
+		}
+	}
+
+	shared.LogDebugf("No need to start the daemon now.")
+	return nil
+}

From 677c8dcdb3285930c0cd83810405df1c0282a2e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 14:59:18 -0500
Subject: [PATCH 0541/1193] main: Move callhook to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go          | 69 ---------------------------------------------
 lxd/main_callhook.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 69 deletions(-)
 create mode 100644 lxd/main_callhook.go

diff --git a/lxd/main.go b/lxd/main.go
index f945b40e4..b4bf7290a 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -253,75 +253,6 @@ func run() error {
 	return cmdDaemon()
 }
 
-func cmdCallHook(args []string) error {
-	if len(args) < 4 {
-		return fmt.Errorf("Invalid arguments")
-	}
-
-	path := args[1]
-	id := args[2]
-	state := args[3]
-	target := ""
-
-	err := os.Setenv("LXD_DIR", path)
-	if err != nil {
-		return err
-	}
-
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	url := fmt.Sprintf("%s/internal/containers/%s/on%s", c.BaseURL, id, state)
-
-	if state == "stop" {
-		target = os.Getenv("LXC_TARGET")
-		if target == "" {
-			target = "unknown"
-		}
-		url = fmt.Sprintf("%s?target=%s", url, target)
-	}
-
-	req, err := http.NewRequest("GET", url, nil)
-	if err != nil {
-		return err
-	}
-
-	hook := make(chan error, 1)
-	go func() {
-		raw, err := c.Http.Do(req)
-		if err != nil {
-			hook <- err
-			return
-		}
-
-		_, err = lxd.HoistResponse(raw, lxd.Sync)
-		if err != nil {
-			hook <- err
-			return
-		}
-
-		hook <- nil
-	}()
-
-	select {
-	case err := <-hook:
-		if err != nil {
-			return err
-		}
-		break
-	case <-time.After(30 * time.Second):
-		return fmt.Errorf("Hook didn't finish within 30s")
-	}
-
-	if target == "reboot" {
-		return fmt.Errorf("Reboot must be handled by LXD.")
-	}
-
-	return nil
-}
-
 func cmdDaemon() error {
 	if *argCPUProfile != "" {
 		f, err := os.Create(*argCPUProfile)
diff --git a/lxd/main_callhook.go b/lxd/main_callhook.go
new file mode 100644
index 000000000..b8e273702
--- /dev/null
+++ b/lxd/main_callhook.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdCallHook(args []string) error {
+	if len(args) < 4 {
+		return fmt.Errorf("Invalid arguments")
+	}
+
+	path := args[1]
+	id := args[2]
+	state := args[3]
+	target := ""
+
+	err := os.Setenv("LXD_DIR", path)
+	if err != nil {
+		return err
+	}
+
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	url := fmt.Sprintf("%s/internal/containers/%s/on%s", c.BaseURL, id, state)
+
+	if state == "stop" {
+		target = os.Getenv("LXC_TARGET")
+		if target == "" {
+			target = "unknown"
+		}
+		url = fmt.Sprintf("%s?target=%s", url, target)
+	}
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return err
+	}
+
+	hook := make(chan error, 1)
+	go func() {
+		raw, err := c.Http.Do(req)
+		if err != nil {
+			hook <- err
+			return
+		}
+
+		_, err = lxd.HoistResponse(raw, lxd.Sync)
+		if err != nil {
+			hook <- err
+			return
+		}
+
+		hook <- nil
+	}()
+
+	select {
+	case err := <-hook:
+		if err != nil {
+			return err
+		}
+		break
+	case <-time.After(30 * time.Second):
+		return fmt.Errorf("Hook didn't finish within 30s")
+	}
+
+	if target == "reboot" {
+		return fmt.Errorf("Reboot must be handled by LXD.")
+	}
+
+	return nil
+}

From 1d22672ab8888560de4ec329518c90f4d00c4d4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:08:10 -0500
Subject: [PATCH 0542/1193] main: Move forkgetnet to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go            | 138 +---------------------------------------------
 lxd/main_forkgetnet.go | 146 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 147 insertions(+), 137 deletions(-)
 create mode 100644 lxd/main_forkgetnet.go

diff --git a/lxd/main.go b/lxd/main.go
index b4bf7290a..1092a209b 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -2,9 +2,7 @@ package main
 
 import (
 	"bufio"
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"math/rand"
 	"net"
 	"net/http"
@@ -226,7 +224,7 @@ func run() error {
 
 		// Internal commands
 		case "forkgetnet":
-			return printnet()
+			return cmdForkGetNet()
 		case "forkmigrate":
 			return MigrateContainer(os.Args[1:])
 		case "forkstart":
@@ -848,140 +846,6 @@ they otherwise would.
 	return nil
 }
 
-func printnet() error {
-	networks := map[string]shared.ContainerStateNetwork{}
-
-	interfaces, err := net.Interfaces()
-	if err != nil {
-		return err
-	}
-
-	stats := map[string][]int64{}
-
-	content, err := ioutil.ReadFile("/proc/net/dev")
-	if err == nil {
-		for _, line := range strings.Split(string(content), "\n") {
-			fields := strings.Fields(line)
-
-			if len(fields) != 17 {
-				continue
-			}
-
-			rxBytes, err := strconv.ParseInt(fields[1], 10, 64)
-			if err != nil {
-				continue
-			}
-
-			rxPackets, err := strconv.ParseInt(fields[2], 10, 64)
-			if err != nil {
-				continue
-			}
-
-			txBytes, err := strconv.ParseInt(fields[9], 10, 64)
-			if err != nil {
-				continue
-			}
-
-			txPackets, err := strconv.ParseInt(fields[10], 10, 64)
-			if err != nil {
-				continue
-			}
-
-			intName := strings.TrimSuffix(fields[0], ":")
-			stats[intName] = []int64{rxBytes, rxPackets, txBytes, txPackets}
-		}
-	}
-
-	for _, netIf := range interfaces {
-		netState := "down"
-		netType := "unknown"
-
-		if netIf.Flags&net.FlagBroadcast > 0 {
-			netType = "broadcast"
-		}
-
-		if netIf.Flags&net.FlagPointToPoint > 0 {
-			netType = "point-to-point"
-		}
-
-		if netIf.Flags&net.FlagLoopback > 0 {
-			netType = "loopback"
-		}
-
-		if netIf.Flags&net.FlagUp > 0 {
-			netState = "up"
-		}
-
-		network := shared.ContainerStateNetwork{
-			Addresses: []shared.ContainerStateNetworkAddress{},
-			Counters:  shared.ContainerStateNetworkCounters{},
-			Hwaddr:    netIf.HardwareAddr.String(),
-			Mtu:       netIf.MTU,
-			State:     netState,
-			Type:      netType,
-		}
-
-		addrs, err := netIf.Addrs()
-		if err == nil {
-			for _, addr := range addrs {
-				fields := strings.SplitN(addr.String(), "/", 2)
-				if len(fields) != 2 {
-					continue
-				}
-
-				family := "inet"
-				if strings.Contains(fields[0], ":") {
-					family = "inet6"
-				}
-
-				scope := "global"
-				if strings.HasPrefix(fields[0], "127") {
-					scope = "local"
-				}
-
-				if fields[0] == "::1" {
-					scope = "local"
-				}
-
-				if strings.HasPrefix(fields[0], "169.254") {
-					scope = "link"
-				}
-
-				if strings.HasPrefix(fields[0], "fe80:") {
-					scope = "link"
-				}
-
-				address := shared.ContainerStateNetworkAddress{}
-				address.Family = family
-				address.Address = fields[0]
-				address.Netmask = fields[1]
-				address.Scope = scope
-
-				network.Addresses = append(network.Addresses, address)
-			}
-		}
-
-		counters, ok := stats[netIf.Name]
-		if ok {
-			network.Counters.BytesReceived = counters[0]
-			network.Counters.PacketsReceived = counters[1]
-			network.Counters.BytesSent = counters[2]
-			network.Counters.PacketsSent = counters[3]
-		}
-
-		networks[netIf.Name] = network
-	}
-
-	buf, err := json.Marshal(networks)
-	if err != nil {
-		return err
-	}
-
-	fmt.Printf("%s\n", buf)
-
-	return nil
-}
-
 func cmdMigrateDumpSuccess(args []string) error {
 	if len(args) != 3 {
 		return fmt.Errorf("bad migrate dump success args %s", args)
diff --git a/lxd/main_forkgetnet.go b/lxd/main_forkgetnet.go
new file mode 100644
index 000000000..7e016f389
--- /dev/null
+++ b/lxd/main_forkgetnet.go
@@ -0,0 +1,146 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net"
+	"strconv"
+	"strings"
+
+	"github.com/lxc/lxd/shared"
+)
+
+func cmdForkGetNet() error {
+	networks := map[string]shared.ContainerStateNetwork{}
+
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		return err
+	}
+
+	stats := map[string][]int64{}
+
+	content, err := ioutil.ReadFile("/proc/net/dev")
+	if err == nil {
+		for _, line := range strings.Split(string(content), "\n") {
+			fields := strings.Fields(line)
+
+			if len(fields) != 17 {
+				continue
+			}
+
+			rxBytes, err := strconv.ParseInt(fields[1], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			rxPackets, err := strconv.ParseInt(fields[2], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			txBytes, err := strconv.ParseInt(fields[9], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			txPackets, err := strconv.ParseInt(fields[10], 10, 64)
+			if err != nil {
+				continue
+			}
+
+			intName := strings.TrimSuffix(fields[0], ":")
+			stats[intName] = []int64{rxBytes, rxPackets, txBytes, txPackets}
+		}
+	}
+
+	for _, netIf := range interfaces {
+		netState := "down"
+		netType := "unknown"
+
+		if netIf.Flags&net.FlagBroadcast > 0 {
+			netType = "broadcast"
+		}
+
+		if netIf.Flags&net.FlagPointToPoint > 0 {
+			netType = "point-to-point"
+		}
+
+		if netIf.Flags&net.FlagLoopback > 0 {
+			netType = "loopback"
+		}
+
+		if netIf.Flags&net.FlagUp > 0 {
+			netState = "up"
+		}
+
+		network := shared.ContainerStateNetwork{
+			Addresses: []shared.ContainerStateNetworkAddress{},
+			Counters:  shared.ContainerStateNetworkCounters{},
+			Hwaddr:    netIf.HardwareAddr.String(),
+			Mtu:       netIf.MTU,
+			State:     netState,
+			Type:      netType,
+		}
+
+		addrs, err := netIf.Addrs()
+		if err == nil {
+			for _, addr := range addrs {
+				fields := strings.SplitN(addr.String(), "/", 2)
+				if len(fields) != 2 {
+					continue
+				}
+
+				family := "inet"
+				if strings.Contains(fields[0], ":") {
+					family = "inet6"
+				}
+
+				scope := "global"
+				if strings.HasPrefix(fields[0], "127") {
+					scope = "local"
+				}
+
+				if fields[0] == "::1" {
+					scope = "local"
+				}
+
+				if strings.HasPrefix(fields[0], "169.254") {
+					scope = "link"
+				}
+
+				if strings.HasPrefix(fields[0], "fe80:") {
+					scope = "link"
+				}
+
+				address := shared.ContainerStateNetworkAddress{}
+				address.Family = family
+				address.Address = fields[0]
+				address.Netmask = fields[1]
+				address.Scope = scope
+
+				network.Addresses = append(network.Addresses, address)
+			}
+		}
+
+		counters, ok := stats[netIf.Name]
+		if ok {
+			network.Counters.BytesReceived = counters[0]
+			network.Counters.PacketsReceived = counters[1]
+			network.Counters.BytesSent = counters[2]
+			network.Counters.PacketsSent = counters[3]
+		}
+
+		networks[netIf.Name] = network
+	}
+
+	buf, err := json.Marshal(networks)
+	if err != nil {
+		return err
+	}
+
+	fmt.Printf("%s\n", buf)
+
+	return nil
+}

From 0e8ebd4d8377de43f0548bc474d20fd6c1a257c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:09:21 -0500
Subject: [PATCH 0543/1193] main: Move forkmigrate to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go             |  2 +-
 lxd/main_forkmigrate.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/migrate.go          | 44 -----------------------------------------
 3 files changed, 53 insertions(+), 45 deletions(-)
 create mode 100644 lxd/main_forkmigrate.go

diff --git a/lxd/main.go b/lxd/main.go
index 1092a209b..7c6a544f2 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -226,7 +226,7 @@ func run() error {
 		case "forkgetnet":
 			return cmdForkGetNet()
 		case "forkmigrate":
-			return MigrateContainer(os.Args[1:])
+			return cmdForkMigrate(os.Args[1:])
 		case "forkstart":
 			return startContainer(os.Args[1:])
 		case "forkexec":
diff --git a/lxd/main_forkmigrate.go b/lxd/main_forkmigrate.go
new file mode 100644
index 000000000..45e437aae
--- /dev/null
+++ b/lxd/main_forkmigrate.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"strconv"
+
+	"gopkg.in/lxc/go-lxc.v2"
+)
+
+/*
+ * Similar to forkstart, this is called when lxd is invoked as:
+ *
+ *    lxd forkmigrate <container> <lxcpath> <path_to_config> <path_to_criu_images> <preserves_inodes>
+ *
+ * liblxc's restore() sets up the processes in such a way that the monitor ends
+ * up being a child of the process that calls it, in our case lxd. However, we
+ * really want the monitor to be daemonized, so we fork again. Additionally, we
+ * want to fork for the same reasons we do forkstart (i.e. reduced memory
+ * footprint when we fork tasks that will never free golang's memory, etc.)
+ */
+func cmdForkMigrate(args []string) error {
+	if len(args) != 6 {
+		return fmt.Errorf("Bad arguments %q", args)
+	}
+
+	name := args[1]
+	lxcpath := args[2]
+	configPath := args[3]
+	imagesDir := args[4]
+	preservesInodes, err := strconv.ParseBool(args[5])
+
+	c, err := lxc.NewContainer(name, lxcpath)
+	if err != nil {
+		return err
+	}
+
+	if err := c.LoadConfigFile(configPath); err != nil {
+		return err
+	}
+
+	/* see https://github.com/golang/go/issues/13155, startContainer, and dc3a229 */
+	os.Stdin.Close()
+	os.Stdout.Close()
+	os.Stderr.Close()
+
+	return c.Migrate(lxc.MIGRATE_RESTORE, lxc.MigrateOptions{
+		Directory:       imagesDir,
+		Verbose:         true,
+		PreservesInodes: preservesInodes,
+	})
+}
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 79667a23d..d5f3f43fd 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -12,7 +12,6 @@ import (
 	"net/url"
 	"os"
 	"path/filepath"
-	"strconv"
 	"strings"
 	"sync"
 
@@ -757,46 +756,3 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 		}
 	}
 }
-
-/*
- * Similar to forkstart, this is called when lxd is invoked as:
- *
- *    lxd forkmigrate <container> <lxcpath> <path_to_config> <path_to_criu_images> <preserves_inodes>
- *
- * liblxc's restore() sets up the processes in such a way that the monitor ends
- * up being a child of the process that calls it, in our case lxd. However, we
- * really want the monitor to be daemonized, so we fork again. Additionally, we
- * want to fork for the same reasons we do forkstart (i.e. reduced memory
- * footprint when we fork tasks that will never free golang's memory, etc.)
- */
-func MigrateContainer(args []string) error {
-	if len(args) != 6 {
-		return fmt.Errorf("Bad arguments %q", args)
-	}
-
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
-	imagesDir := args[4]
-	preservesInodes, err := strconv.ParseBool(args[5])
-
-	c, err := lxc.NewContainer(name, lxcpath)
-	if err != nil {
-		return err
-	}
-
-	if err := c.LoadConfigFile(configPath); err != nil {
-		return err
-	}
-
-	/* see https://github.com/golang/go/issues/13155, startContainer, and dc3a229 */
-	os.Stdin.Close()
-	os.Stdout.Close()
-	os.Stderr.Close()
-
-	return c.Migrate(lxc.MIGRATE_RESTORE, lxc.MigrateOptions{
-		Directory:       imagesDir,
-		Verbose:         true,
-		PreservesInodes: preservesInodes,
-	})
-}

From 709460ea3cf88d60d892f729c2aeb701cecd1009 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:17:42 -0500
Subject: [PATCH 0544/1193] main: Move netcat to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go        |  2 +-
 lxd/main_netcat.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/rsync.go       | 41 -----------------------------------------
 3 files changed, 50 insertions(+), 42 deletions(-)
 create mode 100644 lxd/main_netcat.go

diff --git a/lxd/main.go b/lxd/main.go
index 7c6a544f2..998846787 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -236,7 +236,7 @@ func run() error {
 			}
 			os.Exit(ret)
 		case "netcat":
-			return Netcat(os.Args[1:])
+			return cmdNetcat(os.Args[1:])
 		case "migratedumpsuccess":
 			return cmdMigrateDumpSuccess(os.Args[1:])
 		}
diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go
new file mode 100644
index 000000000..f5be1b3c4
--- /dev/null
+++ b/lxd/main_netcat.go
@@ -0,0 +1,49 @@
+package main
+
+import (
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"sync"
+)
+
+// Netcat is called with:
+//
+//    lxd netcat /path/to/unix/socket
+//
+// and does unbuffered netcatting of to socket to stdin/stdout. Any arguments
+// after the path to the unix socket are ignored, so that this can be passed
+// directly to rsync as the sync command.
+func cmdNetcat(args []string) error {
+	if len(args) < 2 {
+		return fmt.Errorf("Bad arguments %q", args)
+	}
+
+	uAddr, err := net.ResolveUnixAddr("unix", args[1])
+	if err != nil {
+		return err
+	}
+
+	conn, err := net.DialUnix("unix", nil, uAddr)
+	if err != nil {
+		return err
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	go func() {
+		io.Copy(os.Stdout, conn)
+		conn.Close()
+		wg.Done()
+	}()
+
+	go func() {
+		io.Copy(conn, os.Stdin)
+	}()
+
+	wg.Wait()
+
+	return nil
+}
diff --git a/lxd/rsync.go b/lxd/rsync.go
index 49554b962..ee58feefe 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -7,7 +7,6 @@ import (
 	"net"
 	"os"
 	"os/exec"
-	"sync"
 
 	"github.com/gorilla/websocket"
 
@@ -164,43 +163,3 @@ func RsyncRecv(path string, conn *websocket.Conn) error {
 
 	return err
 }
-
-// Netcat is called with:
-//
-//    lxd netcat /path/to/unix/socket
-//
-// and does unbuffered netcatting of to socket to stdin/stdout. Any arguments
-// after the path to the unix socket are ignored, so that this can be passed
-// directly to rsync as the sync command.
-func Netcat(args []string) error {
-	if len(args) < 2 {
-		return fmt.Errorf("Bad arguments %q", args)
-	}
-
-	uAddr, err := net.ResolveUnixAddr("unix", args[1])
-	if err != nil {
-		return err
-	}
-
-	conn, err := net.DialUnix("unix", nil, uAddr)
-	if err != nil {
-		return err
-	}
-
-	wg := sync.WaitGroup{}
-	wg.Add(1)
-
-	go func() {
-		io.Copy(os.Stdout, conn)
-		conn.Close()
-		wg.Done()
-	}()
-
-	go func() {
-		io.Copy(conn, os.Stdin)
-	}()
-
-	wg.Wait()
-
-	return nil
-}

From 5ed34c8b8bf882ee2ad9ac143331c2aded40a45b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:22:53 -0500
Subject: [PATCH 0545/1193] main: Rename nsexec.go to main_nsexec.go
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/{nsexec.go => main_nsexec.go} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename lxd/{nsexec.go => main_nsexec.go} (100%)

diff --git a/lxd/nsexec.go b/lxd/main_nsexec.go
similarity index 100%
rename from lxd/nsexec.go
rename to lxd/main_nsexec.go

From 9443dca096b6a249c676fe6627a3a862605c0e61 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:00:21 -0500
Subject: [PATCH 0546/1193] main: Move daemon to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go        |  90 -----------------------------------------------
 lxd/main_daemon.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 101 insertions(+), 90 deletions(-)
 create mode 100644 lxd/main_daemon.go

diff --git a/lxd/main.go b/lxd/main.go
index 998846787..8f739f3f7 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -8,11 +8,8 @@ import (
 	"net/http"
 	"os"
 	"os/exec"
-	"os/signal"
-	"runtime/pprof"
 	"strconv"
 	"strings"
-	"sync"
 	"syscall"
 	"time"
 
@@ -251,93 +248,6 @@ func run() error {
 	return cmdDaemon()
 }
 
-func cmdDaemon() error {
-	if *argCPUProfile != "" {
-		f, err := os.Create(*argCPUProfile)
-		if err != nil {
-			fmt.Printf("Error opening cpu profile file: %s\n", err)
-			return nil
-		}
-		pprof.StartCPUProfile(f)
-		defer pprof.StopCPUProfile()
-	}
-
-	if *argMemProfile != "" {
-		go memProfiler(*argMemProfile)
-	}
-
-	neededPrograms := []string{"setfacl", "rsync", "tar", "unsquashfs", "xz"}
-	for _, p := range neededPrograms {
-		_, err := exec.LookPath(p)
-		if err != nil {
-			return err
-		}
-	}
-
-	if *argPrintGoroutinesEvery > 0 {
-		go func() {
-			for {
-				time.Sleep(time.Duration(*argPrintGoroutinesEvery) * time.Second)
-				shared.PrintStack()
-			}
-		}()
-	}
-
-	d := &Daemon{
-		group:     *argGroup,
-		SetupMode: shared.PathExists(shared.VarPath(".setup_mode"))}
-	err := d.Init()
-	if err != nil {
-		if d != nil && d.db != nil {
-			d.db.Close()
-		}
-		return err
-	}
-
-	var ret error
-	var wg sync.WaitGroup
-	wg.Add(1)
-
-	go func() {
-		ch := make(chan os.Signal)
-		signal.Notify(ch, syscall.SIGPWR)
-		sig := <-ch
-
-		shared.LogInfof("Received '%s signal', shutting down containers.", sig)
-
-		containersShutdown(d)
-
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	go func() {
-		<-d.shutdownChan
-
-		shared.LogInfof("Asked to shutdown by API, shutting down containers.")
-
-		containersShutdown(d)
-
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	go func() {
-		ch := make(chan os.Signal)
-		signal.Notify(ch, syscall.SIGINT)
-		signal.Notify(ch, syscall.SIGQUIT)
-		signal.Notify(ch, syscall.SIGTERM)
-		sig := <-ch
-
-		shared.LogInfof("Received '%s signal', exiting.", sig)
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	wg.Wait()
-	return ret
-}
-
 func cmdReady() error {
 	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
 	if err != nil {
diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
new file mode 100644
index 000000000..a257b7769
--- /dev/null
+++ b/lxd/main_daemon.go
@@ -0,0 +1,101 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"os/signal"
+	"runtime/pprof"
+	"sync"
+	"syscall"
+	"time"
+
+	"github.com/lxc/lxd/shared"
+)
+
+func cmdDaemon() error {
+	if *argCPUProfile != "" {
+		f, err := os.Create(*argCPUProfile)
+		if err != nil {
+			fmt.Printf("Error opening cpu profile file: %s\n", err)
+			return nil
+		}
+		pprof.StartCPUProfile(f)
+		defer pprof.StopCPUProfile()
+	}
+
+	if *argMemProfile != "" {
+		go memProfiler(*argMemProfile)
+	}
+
+	neededPrograms := []string{"setfacl", "rsync", "tar", "unsquashfs", "xz"}
+	for _, p := range neededPrograms {
+		_, err := exec.LookPath(p)
+		if err != nil {
+			return err
+		}
+	}
+
+	if *argPrintGoroutinesEvery > 0 {
+		go func() {
+			for {
+				time.Sleep(time.Duration(*argPrintGoroutinesEvery) * time.Second)
+				shared.PrintStack()
+			}
+		}()
+	}
+
+	d := &Daemon{
+		group:     *argGroup,
+		SetupMode: shared.PathExists(shared.VarPath(".setup_mode"))}
+	err := d.Init()
+	if err != nil {
+		if d != nil && d.db != nil {
+			d.db.Close()
+		}
+		return err
+	}
+
+	var ret error
+	var wg sync.WaitGroup
+	wg.Add(1)
+
+	go func() {
+		ch := make(chan os.Signal)
+		signal.Notify(ch, syscall.SIGPWR)
+		sig := <-ch
+
+		shared.LogInfof("Received '%s signal', shutting down containers.", sig)
+
+		containersShutdown(d)
+
+		ret = d.Stop()
+		wg.Done()
+	}()
+
+	go func() {
+		<-d.shutdownChan
+
+		shared.LogInfof("Asked to shutdown by API, shutting down containers.")
+
+		containersShutdown(d)
+
+		ret = d.Stop()
+		wg.Done()
+	}()
+
+	go func() {
+		ch := make(chan os.Signal)
+		signal.Notify(ch, syscall.SIGINT)
+		signal.Notify(ch, syscall.SIGQUIT)
+		signal.Notify(ch, syscall.SIGTERM)
+		sig := <-ch
+
+		shared.LogInfof("Received '%s signal', exiting.", sig)
+		ret = d.Stop()
+		wg.Done()
+	}()
+
+	wg.Wait()
+	return ret
+}

From a3d9a07baafa40222daa47c20132a35e8f3697ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:01:29 -0500
Subject: [PATCH 0547/1193] main: Move forkexec to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/containers.go    | 87 ----------------------------------------------
 lxd/main.go          |  2 +-
 lxd/main_forkexec.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 99 insertions(+), 88 deletions(-)
 create mode 100644 lxd/main_forkexec.go

diff --git a/lxd/containers.go b/lxd/containers.go
index f840c1d9a..da9be8060 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -5,7 +5,6 @@ import (
 	"os"
 	"sort"
 	"strconv"
-	"strings"
 	"sync"
 	"syscall"
 	"time"
@@ -249,89 +248,3 @@ func startContainer(args []string) error {
 
 	return c.Start()
 }
-
-/*
- * This is called by lxd when called as "lxd forkexec <container>"
- */
-func execContainer(args []string) (int, error) {
-	if len(args) < 6 {
-		return -1, fmt.Errorf("Bad arguments: %q", args)
-	}
-
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
-
-	c, err := lxc.NewContainer(name, lxcpath)
-	if err != nil {
-		return -1, fmt.Errorf("Error initializing container for start: %q", err)
-	}
-
-	err = c.LoadConfigFile(configPath)
-	if err != nil {
-		return -1, fmt.Errorf("Error opening startup config file: %q", err)
-	}
-
-	syscall.Dup3(int(os.Stdin.Fd()), 200, 0)
-	syscall.Dup3(int(os.Stdout.Fd()), 201, 0)
-	syscall.Dup3(int(os.Stderr.Fd()), 202, 0)
-
-	syscall.Close(int(os.Stdin.Fd()))
-	syscall.Close(int(os.Stdout.Fd()))
-	syscall.Close(int(os.Stderr.Fd()))
-
-	opts := lxc.DefaultAttachOptions
-	opts.ClearEnv = true
-	opts.StdinFd = 200
-	opts.StdoutFd = 201
-	opts.StderrFd = 202
-
-	logPath := shared.LogPath(name, "forkexec.log")
-	if shared.PathExists(logPath) {
-		os.Remove(logPath)
-	}
-
-	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
-	if err == nil {
-		syscall.Dup3(int(logFile.Fd()), 1, 0)
-		syscall.Dup3(int(logFile.Fd()), 2, 0)
-	}
-
-	env := []string{}
-	cmd := []string{}
-
-	section := ""
-	for _, arg := range args[5:len(args)] {
-		// The "cmd" section must come last as it may contain a --
-		if arg == "--" && section != "cmd" {
-			section = ""
-			continue
-		}
-
-		if section == "" {
-			section = arg
-			continue
-		}
-
-		if section == "env" {
-			fields := strings.SplitN(arg, "=", 2)
-			if len(fields) == 2 && fields[0] == "HOME" {
-				opts.Cwd = fields[1]
-			}
-			env = append(env, arg)
-		} else if section == "cmd" {
-			cmd = append(cmd, arg)
-		} else {
-			return -1, fmt.Errorf("Invalid exec section: %s", section)
-		}
-	}
-
-	opts.Env = env
-
-	status, err := c.RunCommandStatus(cmd, opts)
-	if err != nil {
-		return -1, fmt.Errorf("Failed running command: %q", err)
-	}
-
-	return status >> 8, nil
-}
diff --git a/lxd/main.go b/lxd/main.go
index 8f739f3f7..a72fedee7 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -227,7 +227,7 @@ func run() error {
 		case "forkstart":
 			return startContainer(os.Args[1:])
 		case "forkexec":
-			ret, err := execContainer(os.Args[1:])
+			ret, err := cmdForkExec(os.Args[1:])
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "error: %v\n", err)
 			}
diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
new file mode 100644
index 000000000..e3ebcc6b6
--- /dev/null
+++ b/lxd/main_forkexec.go
@@ -0,0 +1,98 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"syscall"
+
+	"gopkg.in/lxc/go-lxc.v2"
+
+	"github.com/lxc/lxd/shared"
+)
+
+/*
+ * This is called by lxd when called as "lxd forkexec <container>"
+ */
+func cmdForkExec(args []string) (int, error) {
+	if len(args) < 6 {
+		return -1, fmt.Errorf("Bad arguments: %q", args)
+	}
+
+	name := args[1]
+	lxcpath := args[2]
+	configPath := args[3]
+
+	c, err := lxc.NewContainer(name, lxcpath)
+	if err != nil {
+		return -1, fmt.Errorf("Error initializing container for start: %q", err)
+	}
+
+	err = c.LoadConfigFile(configPath)
+	if err != nil {
+		return -1, fmt.Errorf("Error opening startup config file: %q", err)
+	}
+
+	syscall.Dup3(int(os.Stdin.Fd()), 200, 0)
+	syscall.Dup3(int(os.Stdout.Fd()), 201, 0)
+	syscall.Dup3(int(os.Stderr.Fd()), 202, 0)
+
+	syscall.Close(int(os.Stdin.Fd()))
+	syscall.Close(int(os.Stdout.Fd()))
+	syscall.Close(int(os.Stderr.Fd()))
+
+	opts := lxc.DefaultAttachOptions
+	opts.ClearEnv = true
+	opts.StdinFd = 200
+	opts.StdoutFd = 201
+	opts.StderrFd = 202
+
+	logPath := shared.LogPath(name, "forkexec.log")
+	if shared.PathExists(logPath) {
+		os.Remove(logPath)
+	}
+
+	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
+	if err == nil {
+		syscall.Dup3(int(logFile.Fd()), 1, 0)
+		syscall.Dup3(int(logFile.Fd()), 2, 0)
+	}
+
+	env := []string{}
+	cmd := []string{}
+
+	section := ""
+	for _, arg := range args[5:len(args)] {
+		// The "cmd" section must come last as it may contain a --
+		if arg == "--" && section != "cmd" {
+			section = ""
+			continue
+		}
+
+		if section == "" {
+			section = arg
+			continue
+		}
+
+		if section == "env" {
+			fields := strings.SplitN(arg, "=", 2)
+			if len(fields) == 2 && fields[0] == "HOME" {
+				opts.Cwd = fields[1]
+			}
+			env = append(env, arg)
+		} else if section == "cmd" {
+			cmd = append(cmd, arg)
+		} else {
+			return -1, fmt.Errorf("Invalid exec section: %s", section)
+		}
+	}
+
+	opts.Env = env
+
+	status, err := c.RunCommandStatus(cmd, opts)
+	if err != nil {
+		return -1, fmt.Errorf("Failed running command: %q", err)
+	}
+
+	return status >> 8, nil
+}

From 9df562c165784a2c1d87eb881f6acfbbb578b591 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:11:54 -0500
Subject: [PATCH 0548/1193] main: Move forkstart to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/containers.go     | 53 ---------------------------------------------
 lxd/main.go           |  2 +-
 lxd/main_forkstart.go | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 54 deletions(-)
 create mode 100644 lxd/main_forkstart.go

diff --git a/lxd/containers.go b/lxd/containers.go
index da9be8060..63f31429d 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -1,16 +1,11 @@
 package main
 
 import (
-	"fmt"
-	"os"
 	"sort"
 	"strconv"
 	"sync"
-	"syscall"
 	"time"
 
-	"gopkg.in/lxc/go-lxc.v2"
-
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -200,51 +195,3 @@ func containerDeleteSnapshots(d *Daemon, cname string) error {
 
 	return nil
 }
-
-/*
- * This is called by lxd when called as "lxd forkstart <container>"
- * 'forkstart' is used instead of just 'start' in the hopes that people
- * do not accidentally type 'lxd start' instead of 'lxc start'
- */
-func startContainer(args []string) error {
-	if len(args) != 4 {
-		return fmt.Errorf("Bad arguments: %q", args)
-	}
-
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
-
-	c, err := lxc.NewContainer(name, lxcpath)
-	if err != nil {
-		return fmt.Errorf("Error initializing container for start: %q", err)
-	}
-
-	err = c.LoadConfigFile(configPath)
-	if err != nil {
-		return fmt.Errorf("Error opening startup config file: %q", err)
-	}
-
-	/* due to https://github.com/golang/go/issues/13155 and the
-	 * CollectOutput call we make for the forkstart process, we need to
-	 * close our stdin/stdout/stderr here. Collecting some of the logs is
-	 * better than collecting no logs, though.
-	 */
-	os.Stdin.Close()
-	os.Stderr.Close()
-	os.Stdout.Close()
-
-	// Redirect stdout and stderr to a log file
-	logPath := shared.LogPath(name, "forkstart.log")
-	if shared.PathExists(logPath) {
-		os.Remove(logPath)
-	}
-
-	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
-	if err == nil {
-		syscall.Dup3(int(logFile.Fd()), 1, 0)
-		syscall.Dup3(int(logFile.Fd()), 2, 0)
-	}
-
-	return c.Start()
-}
diff --git a/lxd/main.go b/lxd/main.go
index a72fedee7..2dc494c70 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -225,7 +225,7 @@ func run() error {
 		case "forkmigrate":
 			return cmdForkMigrate(os.Args[1:])
 		case "forkstart":
-			return startContainer(os.Args[1:])
+			return cmdForkStart(os.Args[1:])
 		case "forkexec":
 			ret, err := cmdForkExec(os.Args[1:])
 			if err != nil {
diff --git a/lxd/main_forkstart.go b/lxd/main_forkstart.go
new file mode 100644
index 000000000..cc3d2ed1b
--- /dev/null
+++ b/lxd/main_forkstart.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"syscall"
+
+	"gopkg.in/lxc/go-lxc.v2"
+
+	"github.com/lxc/lxd/shared"
+)
+
+/*
+ * This is called by lxd when called as "lxd forkstart <container>"
+ * 'forkstart' is used instead of just 'start' in the hopes that people
+ * do not accidentally type 'lxd start' instead of 'lxc start'
+ */
+func cmdForkStart(args []string) error {
+	if len(args) != 4 {
+		return fmt.Errorf("Bad arguments: %q", args)
+	}
+
+	name := args[1]
+	lxcpath := args[2]
+	configPath := args[3]
+
+	c, err := lxc.NewContainer(name, lxcpath)
+	if err != nil {
+		return fmt.Errorf("Error initializing container for start: %q", err)
+	}
+
+	err = c.LoadConfigFile(configPath)
+	if err != nil {
+		return fmt.Errorf("Error opening startup config file: %q", err)
+	}
+
+	/* due to https://github.com/golang/go/issues/13155 and the
+	 * CollectOutput call we make for the forkstart process, we need to
+	 * close our stdin/stdout/stderr here. Collecting some of the logs is
+	 * better than collecting no logs, though.
+	 */
+	os.Stdin.Close()
+	os.Stderr.Close()
+	os.Stdout.Close()
+
+	// Redirect stdout and stderr to a log file
+	logPath := shared.LogPath(name, "forkstart.log")
+	if shared.PathExists(logPath) {
+		os.Remove(logPath)
+	}
+
+	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
+	if err == nil {
+		syscall.Dup3(int(logFile.Fd()), 1, 0)
+		syscall.Dup3(int(logFile.Fd()), 2, 0)
+	}
+
+	return c.Start()
+}

From abd22e57641826924ec59f8f579c8ac019922a94 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:14:52 -0500
Subject: [PATCH 0549/1193] main: Move init to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go      | 402 -----------------------------------------------------
 lxd/main_init.go | 411 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 411 insertions(+), 402 deletions(-)
 create mode 100644 lxd/main_init.go

diff --git a/lxd/main.go b/lxd/main.go
index 2dc494c70..b12977e76 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -1,20 +1,12 @@
 package main
 
 import (
-	"bufio"
 	"fmt"
 	"math/rand"
-	"net"
 	"net/http"
 	"os"
-	"os/exec"
-	"strconv"
-	"strings"
-	"syscall"
 	"time"
 
-	"golang.org/x/crypto/ssh/terminal"
-
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -362,400 +354,6 @@ func cmdWaitReady() error {
 	return nil
 }
 
-func cmdInit() error {
-	var defaultPrivileged int // controls whether we set security.privileged=true
-	var storageBackend string // dir or zfs
-	var storageMode string    // existing, loop or device
-	var storageLoopSize int64 // Size in GB
-	var storageDevice string  // Path
-	var storagePool string    // pool name
-	var networkAddress string // Address
-	var networkPort int64     // Port
-	var trustPassword string  // Trust password
-
-	// Detect userns
-	defaultPrivileged = -1
-	runningInUserns = shared.RunningInUserNS()
-
-	// Only root should run this
-	if os.Geteuid() != 0 {
-		return fmt.Errorf("This must be run as root")
-	}
-
-	backendsAvailable := []string{"dir"}
-	backendsSupported := []string{"dir", "zfs"}
-
-	// Detect zfs
-	out, err := exec.LookPath("zfs")
-	if err == nil && len(out) != 0 && !runningInUserns {
-		_ = loadModule("zfs")
-
-		err := shared.RunCommand("zpool", "list")
-		if err == nil {
-			backendsAvailable = append(backendsAvailable, "zfs")
-		}
-	}
-
-	reader := bufio.NewReader(os.Stdin)
-
-	askBool := func(question string, default_ string) bool {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y"}) {
-				return true
-			} else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n"}) {
-				return false
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askChoice := func(question string, choices []string, default_ string) string {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if shared.StringInSlice(input, choices) {
-				return input
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askInt := func(question string, min int64, max int64, default_ string) int64 {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			intInput, err := strconv.ParseInt(input, 10, 64)
-
-			if err == nil && (min == -1 || intInput >= min) && (max == -1 || intInput <= max) {
-				return intInput
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askString := func(question string, default_ string, validate func(string) error) string {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if validate != nil {
-				result := validate(input)
-				if result != nil {
-					fmt.Printf("Invalid input: %s\n\n", result)
-					continue
-				}
-			}
-			if len(input) != 0 {
-				return input
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askPassword := func(question string) string {
-		for {
-			fmt.Printf(question)
-			pwd, _ := terminal.ReadPassword(0)
-			fmt.Printf("\n")
-			inFirst := string(pwd)
-			inFirst = strings.TrimSuffix(inFirst, "\n")
-
-			fmt.Printf("Again: ")
-			pwd, _ = terminal.ReadPassword(0)
-			fmt.Printf("\n")
-			inSecond := string(pwd)
-			inSecond = strings.TrimSuffix(inSecond, "\n")
-
-			if inFirst == inSecond {
-				return inFirst
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	// Confirm that LXD is online
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return fmt.Errorf("Unable to talk to LXD: %s", err)
-	}
-
-	// Check that we have no containers or images in the store
-	containers, err := c.ListContainers()
-	if err != nil {
-		return fmt.Errorf("Unable to list the LXD containers: %s", err)
-	}
-
-	images, err := c.ListImages()
-	if err != nil {
-		return fmt.Errorf("Unable to list the LXD images: %s", err)
-	}
-
-	if len(containers) > 0 || len(images) > 0 {
-		return fmt.Errorf("You have existing containers or images. lxd init requires an empty LXD.")
-	}
-
-	if *argAuto {
-		if *argStorageBackend == "" {
-			*argStorageBackend = "dir"
-		}
-
-		// Do a bunch of sanity checks
-		if !shared.StringInSlice(*argStorageBackend, backendsSupported) {
-			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", *argStorageBackend)
-		}
-
-		if !shared.StringInSlice(*argStorageBackend, backendsAvailable) {
-			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", *argStorageBackend)
-		}
-
-		if *argStorageBackend == "dir" {
-			if *argStorageCreateLoop != -1 || *argStorageCreateDevice != "" || *argStoragePool != "" {
-				return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
-			}
-		}
-
-		if *argStorageBackend == "zfs" {
-			if *argStorageCreateLoop != -1 && *argStorageCreateDevice != "" {
-				return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified with the 'zfs' backend.")
-			}
-
-			if *argStoragePool == "" {
-				return fmt.Errorf("--storage-pool must be specified with the 'zfs' backend.")
-			}
-		}
-
-		if *argNetworkAddress == "" {
-			if *argNetworkPort != -1 {
-				return fmt.Errorf("--network-port cannot be used without --network-address.")
-			}
-			if *argTrustPassword != "" {
-				return fmt.Errorf("--trust-password cannot be used without --network-address.")
-			}
-		}
-
-		// Set the local variables
-		if *argStorageCreateDevice != "" {
-			storageMode = "device"
-		} else if *argStorageCreateLoop != -1 {
-			storageMode = "loop"
-		} else {
-			storageMode = "existing"
-		}
-
-		storageBackend = *argStorageBackend
-		storageLoopSize = *argStorageCreateLoop
-		storageDevice = *argStorageCreateDevice
-		storagePool = *argStoragePool
-		networkAddress = *argNetworkAddress
-		networkPort = *argNetworkPort
-		trustPassword = *argTrustPassword
-	} else {
-		if *argStorageBackend != "" || *argStorageCreateDevice != "" || *argStorageCreateLoop != -1 || *argStoragePool != "" || *argNetworkAddress != "" || *argNetworkPort != -1 || *argTrustPassword != "" {
-			return fmt.Errorf("Init configuration is only valid with --auto")
-		}
-
-		defaultStorage := "dir"
-		if shared.StringInSlice("zfs", backendsAvailable) {
-			defaultStorage = "zfs"
-		}
-
-		storageBackend = askChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), backendsSupported, defaultStorage)
-
-		if !shared.StringInSlice(storageBackend, backendsSupported) {
-			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)
-		}
-
-		if !shared.StringInSlice(storageBackend, backendsAvailable) {
-			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", storageBackend)
-		}
-
-		if storageBackend == "zfs" {
-			if askBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
-				storagePool = askString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
-				if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
-					deviceExists := func(path string) error {
-						if !shared.IsBlockdevPath(path) {
-							return fmt.Errorf("'%s' is not a block device", path)
-						}
-						return nil
-					}
-					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
-					storageMode = "device"
-				} else {
-					st := syscall.Statfs_t{}
-					err := syscall.Statfs(shared.VarPath(), &st)
-					if err != nil {
-						return fmt.Errorf("couldn't statfs %s: %s", shared.VarPath(), err)
-					}
-
-					/* choose 15 GB < x < 100GB, where x is 20% of the disk size */
-					def := uint64(st.Frsize) * st.Blocks / (1024 * 1024 * 1024) / 5
-					if def > 100 {
-						def = 100
-					}
-					if def < 15 {
-						def = 15
-					}
-
-					q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%d]: ", def)
-					storageLoopSize = askInt(q, 1, -1, fmt.Sprintf("%d", def))
-					storageMode = "loop"
-				}
-			} else {
-				storagePool = askString("Name of the existing ZFS pool or dataset: ", "", nil)
-				storageMode = "existing"
-			}
-		}
-
-		if runningInUserns {
-			fmt.Printf(`
-We detected that you are running inside an unprivileged container.
-This means that unless you manually configured your host otherwise,
-you will not have enough uid and gid to allocate to your containers.
-
-LXD can re-use your container's own allocation to avoid the problem.
-Doing so makes your nested containers slightly less safe as they could
-in theory attack their parent container and gain more privileges than
-they otherwise would.
-
-`)
-			if askBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
-				defaultPrivileged = 1
-			} else {
-				defaultPrivileged = 0
-			}
-		}
-
-		if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
-			isIPAddress := func(s string) error {
-				if s != "all" && net.ParseIP(s) == nil {
-					return fmt.Errorf("'%s' is not an IP address", s)
-				}
-				return nil
-			}
-
-			networkAddress = askString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
-			if networkAddress == "all" {
-				networkAddress = "::"
-			}
-
-			if net.ParseIP(networkAddress).To4() == nil {
-				networkAddress = fmt.Sprintf("[%s]", networkAddress)
-			}
-			networkPort = askInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
-			trustPassword = askPassword("Trust password for new clients: ")
-		}
-	}
-
-	if !shared.StringInSlice(storageBackend, []string{"dir", "zfs"}) {
-		return fmt.Errorf("Invalid storage backend: %s", storageBackend)
-	}
-
-	// Unset all storage keys, core.https_address and core.trust_password
-	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
-		_, err = c.SetServerConfig(key, "")
-		if err != nil {
-			return err
-		}
-	}
-
-	// Destroy any existing loop device
-	for _, file := range []string{"zfs.img"} {
-		os.Remove(shared.VarPath(file))
-	}
-
-	if storageBackend == "zfs" {
-		if storageMode == "loop" {
-			storageDevice = shared.VarPath("zfs.img")
-			f, err := os.Create(storageDevice)
-			if err != nil {
-				return fmt.Errorf("Failed to open %s: %s", storageDevice, err)
-			}
-
-			err = f.Chmod(0600)
-			if err != nil {
-				return fmt.Errorf("Failed to chmod %s: %s", storageDevice, err)
-			}
-
-			err = f.Truncate(int64(storageLoopSize * 1024 * 1024 * 1024))
-			if err != nil {
-				return fmt.Errorf("Failed to create sparse file %s: %s", storageDevice, err)
-			}
-
-			err = f.Close()
-			if err != nil {
-				return fmt.Errorf("Failed to close %s: %s", storageDevice, err)
-			}
-		}
-
-		if shared.StringInSlice(storageMode, []string{"loop", "device"}) {
-			output, err := exec.Command(
-				"zpool",
-				"create", storagePool, storageDevice,
-				"-f", "-m", "none", "-O", "compression=on").CombinedOutput()
-			if err != nil {
-				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
-			}
-		}
-
-		// Configure LXD to use the pool
-		_, err = c.SetServerConfig("storage.zfs_pool_name", storagePool)
-		if err != nil {
-			return err
-		}
-	}
-
-	if defaultPrivileged == 0 {
-		err = c.SetProfileConfigItem("default", "security.privileged", "")
-		if err != nil {
-			return err
-		}
-	} else if defaultPrivileged == 1 {
-		err = c.SetProfileConfigItem("default", "security.privileged", "true")
-		if err != nil {
-		}
-	}
-
-	if networkAddress != "" {
-		_, err = c.SetServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
-		if err != nil {
-			return err
-		}
-
-		if trustPassword != "" {
-			_, err = c.SetServerConfig("core.trust_password", trustPassword)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	fmt.Printf("LXD has been successfully configured.\n")
-	return nil
-}
-
 func cmdMigrateDumpSuccess(args []string) error {
 	if len(args) != 3 {
 		return fmt.Errorf("bad migrate dump success args %s", args)
diff --git a/lxd/main_init.go b/lxd/main_init.go
new file mode 100644
index 000000000..76ddb1fd2
--- /dev/null
+++ b/lxd/main_init.go
@@ -0,0 +1,411 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"net"
+	"os"
+	"os/exec"
+	"strconv"
+	"strings"
+	"syscall"
+
+	"golang.org/x/crypto/ssh/terminal"
+
+	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared"
+)
+
+func cmdInit() error {
+	var defaultPrivileged int // controls whether we set security.privileged=true
+	var storageBackend string // dir or zfs
+	var storageMode string    // existing, loop or device
+	var storageLoopSize int64 // Size in GB
+	var storageDevice string  // Path
+	var storagePool string    // pool name
+	var networkAddress string // Address
+	var networkPort int64     // Port
+	var trustPassword string  // Trust password
+
+	// Detect userns
+	defaultPrivileged = -1
+	runningInUserns = shared.RunningInUserNS()
+
+	// Only root should run this
+	if os.Geteuid() != 0 {
+		return fmt.Errorf("This must be run as root")
+	}
+
+	backendsAvailable := []string{"dir"}
+	backendsSupported := []string{"dir", "zfs"}
+
+	// Detect zfs
+	out, err := exec.LookPath("zfs")
+	if err == nil && len(out) != 0 && !runningInUserns {
+		_ = loadModule("zfs")
+
+		err := shared.RunCommand("zpool", "list")
+		if err == nil {
+			backendsAvailable = append(backendsAvailable, "zfs")
+		}
+	}
+
+	reader := bufio.NewReader(os.Stdin)
+
+	askBool := func(question string, default_ string) bool {
+		for {
+			fmt.Printf(question)
+			input, _ := reader.ReadString('\n')
+			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y"}) {
+				return true
+			} else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n"}) {
+				return false
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	askChoice := func(question string, choices []string, default_ string) string {
+		for {
+			fmt.Printf(question)
+			input, _ := reader.ReadString('\n')
+			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			if shared.StringInSlice(input, choices) {
+				return input
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	askInt := func(question string, min int64, max int64, default_ string) int64 {
+		for {
+			fmt.Printf(question)
+			input, _ := reader.ReadString('\n')
+			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			intInput, err := strconv.ParseInt(input, 10, 64)
+
+			if err == nil && (min == -1 || intInput >= min) && (max == -1 || intInput <= max) {
+				return intInput
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	askString := func(question string, default_ string, validate func(string) error) string {
+		for {
+			fmt.Printf(question)
+			input, _ := reader.ReadString('\n')
+			input = strings.TrimSuffix(input, "\n")
+			if input == "" {
+				input = default_
+			}
+			if validate != nil {
+				result := validate(input)
+				if result != nil {
+					fmt.Printf("Invalid input: %s\n\n", result)
+					continue
+				}
+			}
+			if len(input) != 0 {
+				return input
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	askPassword := func(question string) string {
+		for {
+			fmt.Printf(question)
+			pwd, _ := terminal.ReadPassword(0)
+			fmt.Printf("\n")
+			inFirst := string(pwd)
+			inFirst = strings.TrimSuffix(inFirst, "\n")
+
+			fmt.Printf("Again: ")
+			pwd, _ = terminal.ReadPassword(0)
+			fmt.Printf("\n")
+			inSecond := string(pwd)
+			inSecond = strings.TrimSuffix(inSecond, "\n")
+
+			if inFirst == inSecond {
+				return inFirst
+			}
+
+			fmt.Printf("Invalid input, try again.\n\n")
+		}
+	}
+
+	// Confirm that LXD is online
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return fmt.Errorf("Unable to talk to LXD: %s", err)
+	}
+
+	// Check that we have no containers or images in the store
+	containers, err := c.ListContainers()
+	if err != nil {
+		return fmt.Errorf("Unable to list the LXD containers: %s", err)
+	}
+
+	images, err := c.ListImages()
+	if err != nil {
+		return fmt.Errorf("Unable to list the LXD images: %s", err)
+	}
+
+	if len(containers) > 0 || len(images) > 0 {
+		return fmt.Errorf("You have existing containers or images. lxd init requires an empty LXD.")
+	}
+
+	if *argAuto {
+		if *argStorageBackend == "" {
+			*argStorageBackend = "dir"
+		}
+
+		// Do a bunch of sanity checks
+		if !shared.StringInSlice(*argStorageBackend, backendsSupported) {
+			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", *argStorageBackend)
+		}
+
+		if !shared.StringInSlice(*argStorageBackend, backendsAvailable) {
+			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", *argStorageBackend)
+		}
+
+		if *argStorageBackend == "dir" {
+			if *argStorageCreateLoop != -1 || *argStorageCreateDevice != "" || *argStoragePool != "" {
+				return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
+			}
+		}
+
+		if *argStorageBackend == "zfs" {
+			if *argStorageCreateLoop != -1 && *argStorageCreateDevice != "" {
+				return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified with the 'zfs' backend.")
+			}
+
+			if *argStoragePool == "" {
+				return fmt.Errorf("--storage-pool must be specified with the 'zfs' backend.")
+			}
+		}
+
+		if *argNetworkAddress == "" {
+			if *argNetworkPort != -1 {
+				return fmt.Errorf("--network-port cannot be used without --network-address.")
+			}
+			if *argTrustPassword != "" {
+				return fmt.Errorf("--trust-password cannot be used without --network-address.")
+			}
+		}
+
+		// Set the local variables
+		if *argStorageCreateDevice != "" {
+			storageMode = "device"
+		} else if *argStorageCreateLoop != -1 {
+			storageMode = "loop"
+		} else {
+			storageMode = "existing"
+		}
+
+		storageBackend = *argStorageBackend
+		storageLoopSize = *argStorageCreateLoop
+		storageDevice = *argStorageCreateDevice
+		storagePool = *argStoragePool
+		networkAddress = *argNetworkAddress
+		networkPort = *argNetworkPort
+		trustPassword = *argTrustPassword
+	} else {
+		if *argStorageBackend != "" || *argStorageCreateDevice != "" || *argStorageCreateLoop != -1 || *argStoragePool != "" || *argNetworkAddress != "" || *argNetworkPort != -1 || *argTrustPassword != "" {
+			return fmt.Errorf("Init configuration is only valid with --auto")
+		}
+
+		defaultStorage := "dir"
+		if shared.StringInSlice("zfs", backendsAvailable) {
+			defaultStorage = "zfs"
+		}
+
+		storageBackend = askChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), backendsSupported, defaultStorage)
+
+		if !shared.StringInSlice(storageBackend, backendsSupported) {
+			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)
+		}
+
+		if !shared.StringInSlice(storageBackend, backendsAvailable) {
+			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", storageBackend)
+		}
+
+		if storageBackend == "zfs" {
+			if askBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
+				storagePool = askString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
+				if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
+					deviceExists := func(path string) error {
+						if !shared.IsBlockdevPath(path) {
+							return fmt.Errorf("'%s' is not a block device", path)
+						}
+						return nil
+					}
+					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
+					storageMode = "device"
+				} else {
+					st := syscall.Statfs_t{}
+					err := syscall.Statfs(shared.VarPath(), &st)
+					if err != nil {
+						return fmt.Errorf("couldn't statfs %s: %s", shared.VarPath(), err)
+					}
+
+					/* choose 15 GB < x < 100GB, where x is 20% of the disk size */
+					def := uint64(st.Frsize) * st.Blocks / (1024 * 1024 * 1024) / 5
+					if def > 100 {
+						def = 100
+					}
+					if def < 15 {
+						def = 15
+					}
+
+					q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%d]: ", def)
+					storageLoopSize = askInt(q, 1, -1, fmt.Sprintf("%d", def))
+					storageMode = "loop"
+				}
+			} else {
+				storagePool = askString("Name of the existing ZFS pool or dataset: ", "", nil)
+				storageMode = "existing"
+			}
+		}
+
+		if runningInUserns {
+			fmt.Printf(`
+We detected that you are running inside an unprivileged container.
+This means that unless you manually configured your host otherwise,
+you will not have enough uid and gid to allocate to your containers.
+
+LXD can re-use your container's own allocation to avoid the problem.
+Doing so makes your nested containers slightly less safe as they could
+in theory attack their parent container and gain more privileges than
+they otherwise would.
+
+`)
+			if askBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
+				defaultPrivileged = 1
+			} else {
+				defaultPrivileged = 0
+			}
+		}
+
+		if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
+			isIPAddress := func(s string) error {
+				if s != "all" && net.ParseIP(s) == nil {
+					return fmt.Errorf("'%s' is not an IP address", s)
+				}
+				return nil
+			}
+
+			networkAddress = askString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
+			if networkAddress == "all" {
+				networkAddress = "::"
+			}
+
+			if net.ParseIP(networkAddress).To4() == nil {
+				networkAddress = fmt.Sprintf("[%s]", networkAddress)
+			}
+			networkPort = askInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
+			trustPassword = askPassword("Trust password for new clients: ")
+		}
+	}
+
+	if !shared.StringInSlice(storageBackend, []string{"dir", "zfs"}) {
+		return fmt.Errorf("Invalid storage backend: %s", storageBackend)
+	}
+
+	// Unset all storage keys, core.https_address and core.trust_password
+	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
+		_, err = c.SetServerConfig(key, "")
+		if err != nil {
+			return err
+		}
+	}
+
+	// Destroy any existing loop device
+	for _, file := range []string{"zfs.img"} {
+		os.Remove(shared.VarPath(file))
+	}
+
+	if storageBackend == "zfs" {
+		if storageMode == "loop" {
+			storageDevice = shared.VarPath("zfs.img")
+			f, err := os.Create(storageDevice)
+			if err != nil {
+				return fmt.Errorf("Failed to open %s: %s", storageDevice, err)
+			}
+
+			err = f.Chmod(0600)
+			if err != nil {
+				return fmt.Errorf("Failed to chmod %s: %s", storageDevice, err)
+			}
+
+			err = f.Truncate(int64(storageLoopSize * 1024 * 1024 * 1024))
+			if err != nil {
+				return fmt.Errorf("Failed to create sparse file %s: %s", storageDevice, err)
+			}
+
+			err = f.Close()
+			if err != nil {
+				return fmt.Errorf("Failed to close %s: %s", storageDevice, err)
+			}
+		}
+
+		if shared.StringInSlice(storageMode, []string{"loop", "device"}) {
+			output, err := exec.Command(
+				"zpool",
+				"create", storagePool, storageDevice,
+				"-f", "-m", "none", "-O", "compression=on").CombinedOutput()
+			if err != nil {
+				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
+			}
+		}
+
+		// Configure LXD to use the pool
+		_, err = c.SetServerConfig("storage.zfs_pool_name", storagePool)
+		if err != nil {
+			return err
+		}
+	}
+
+	if defaultPrivileged == 0 {
+		err = c.SetProfileConfigItem("default", "security.privileged", "")
+		if err != nil {
+			return err
+		}
+	} else if defaultPrivileged == 1 {
+		err = c.SetProfileConfigItem("default", "security.privileged", "true")
+		if err != nil {
+		}
+	}
+
+	if networkAddress != "" {
+		_, err = c.SetServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
+		if err != nil {
+			return err
+		}
+
+		if trustPassword != "" {
+			_, err = c.SetServerConfig("core.trust_password", trustPassword)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	fmt.Printf("LXD has been successfully configured.\n")
+	return nil
+}

From bf14ee961865117691f14a497486adf761db1b3c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:15:27 -0500
Subject: [PATCH 0550/1193] main: Move migratedumpsuccess to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go                    | 19 -------------------
 lxd/main_migratedumpsuccess.go | 26 ++++++++++++++++++++++++++
 2 files changed, 26 insertions(+), 19 deletions(-)
 create mode 100644 lxd/main_migratedumpsuccess.go

diff --git a/lxd/main.go b/lxd/main.go
index b12977e76..591058831 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -353,22 +353,3 @@ func cmdWaitReady() error {
 
 	return nil
 }
-
-func cmdMigrateDumpSuccess(args []string) error {
-	if len(args) != 3 {
-		return fmt.Errorf("bad migrate dump success args %s", args)
-	}
-
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	conn, err := c.Websocket(args[1], args[2])
-	if err != nil {
-		return err
-	}
-	conn.Close()
-
-	return c.WaitForSuccess(args[1])
-}
diff --git a/lxd/main_migratedumpsuccess.go b/lxd/main_migratedumpsuccess.go
new file mode 100644
index 000000000..c4c8fb150
--- /dev/null
+++ b/lxd/main_migratedumpsuccess.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+	"fmt"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdMigrateDumpSuccess(args []string) error {
+	if len(args) != 3 {
+		return fmt.Errorf("bad migrate dump success args %s", args)
+	}
+
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	conn, err := c.Websocket(args[1], args[2])
+	if err != nil {
+		return err
+	}
+	conn.Close()
+
+	return c.WaitForSuccess(args[1])
+}

From e95c4a89cad0c7e6be060745a3db75ff57c3f087 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:18:29 -0500
Subject: [PATCH 0551/1193] main: Move ready to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go       | 24 ------------------------
 lxd/main_ready.go | 31 +++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 24 deletions(-)
 create mode 100644 lxd/main_ready.go

diff --git a/lxd/main.go b/lxd/main.go
index 591058831..5d9f368c6 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -240,30 +240,6 @@ func run() error {
 	return cmdDaemon()
 }
 
-func cmdReady() error {
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/ready", nil)
-	if err != nil {
-		return err
-	}
-
-	raw, err := c.Http.Do(req)
-	if err != nil {
-		return err
-	}
-
-	_, err = lxd.HoistResponse(raw, lxd.Sync)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
 func cmdShutdown() error {
 	var timeout int
 
diff --git a/lxd/main_ready.go b/lxd/main_ready.go
new file mode 100644
index 000000000..4e4ebfbd1
--- /dev/null
+++ b/lxd/main_ready.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdReady() error {
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/ready", nil)
+	if err != nil {
+		return err
+	}
+
+	raw, err := c.Http.Do(req)
+	if err != nil {
+		return err
+	}
+
+	_, err = lxd.HoistResponse(raw, lxd.Sync)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

From 7db416bd1b7da736a045da1dcd3790178b5f8d63 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:19:09 -0500
Subject: [PATCH 0552/1193] main: Move shutdown to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go          | 39 ---------------------------------------
 lxd/main_shutdown.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 39 deletions(-)
 create mode 100644 lxd/main_shutdown.go

diff --git a/lxd/main.go b/lxd/main.go
index 5d9f368c6..e3dee9d70 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -240,45 +240,6 @@ func run() error {
 	return cmdDaemon()
 }
 
-func cmdShutdown() error {
-	var timeout int
-
-	if *argTimeout == -1 {
-		timeout = 60
-	} else {
-		timeout = *argTimeout
-	}
-
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/shutdown", nil)
-	if err != nil {
-		return err
-	}
-
-	_, err = c.Http.Do(req)
-	if err != nil {
-		return err
-	}
-
-	monitor := make(chan error, 1)
-	go func() {
-		monitor <- c.Monitor(nil, func(m interface{}) {})
-	}()
-
-	select {
-	case <-monitor:
-		break
-	case <-time.After(time.Second * time.Duration(timeout)):
-		return fmt.Errorf("LXD still running after %ds timeout.", timeout)
-	}
-
-	return nil
-}
-
 func cmdWaitReady() error {
 	var timeout int
 
diff --git a/lxd/main_shutdown.go b/lxd/main_shutdown.go
new file mode 100644
index 000000000..9110ed501
--- /dev/null
+++ b/lxd/main_shutdown.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdShutdown() error {
+	var timeout int
+
+	if *argTimeout == -1 {
+		timeout = 60
+	} else {
+		timeout = *argTimeout
+	}
+
+	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/shutdown", nil)
+	if err != nil {
+		return err
+	}
+
+	_, err = c.Http.Do(req)
+	if err != nil {
+		return err
+	}
+
+	monitor := make(chan error, 1)
+	go func() {
+		monitor <- c.Monitor(nil, func(m interface{}) {})
+	}()
+
+	select {
+	case <-monitor:
+		break
+	case <-time.After(time.Second * time.Duration(timeout)):
+		return fmt.Errorf("LXD still running after %ds timeout.", timeout)
+	}
+
+	return nil
+}

From 99063893c8b03ad7db8bad3d9491c6038aaf6edd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 14 Dec 2016 15:19:54 -0500
Subject: [PATCH 0553/1193] main: Move waitready to own file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main.go           | 53 ---------------------------------------------
 lxd/main_waitready.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 53 deletions(-)
 create mode 100644 lxd/main_waitready.go

diff --git a/lxd/main.go b/lxd/main.go
index e3dee9d70..60e30f2c2 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -3,11 +3,9 @@ package main
 import (
 	"fmt"
 	"math/rand"
-	"net/http"
 	"os"
 	"time"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/logging"
@@ -239,54 +237,3 @@ func run() error {
 
 	return cmdDaemon()
 }
-
-func cmdWaitReady() error {
-	var timeout int
-
-	if *argTimeout == -1 {
-		timeout = 15
-	} else {
-		timeout = *argTimeout
-	}
-
-	finger := make(chan error, 1)
-	go func() {
-		for {
-			c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			req, err := http.NewRequest("GET", c.BaseURL+"/internal/ready", nil)
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			raw, err := c.Http.Do(req)
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			_, err = lxd.HoistResponse(raw, lxd.Sync)
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			finger <- nil
-			return
-		}
-	}()
-
-	select {
-	case <-finger:
-		break
-	case <-time.After(time.Second * time.Duration(timeout)):
-		return fmt.Errorf("LXD still not running after %ds timeout.", timeout)
-	}
-
-	return nil
-}
diff --git a/lxd/main_waitready.go b/lxd/main_waitready.go
new file mode 100644
index 000000000..7e2b4cfd4
--- /dev/null
+++ b/lxd/main_waitready.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/lxc/lxd"
+)
+
+func cmdWaitReady() error {
+	var timeout int
+
+	if *argTimeout == -1 {
+		timeout = 15
+	} else {
+		timeout = *argTimeout
+	}
+
+	finger := make(chan error, 1)
+	go func() {
+		for {
+			c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			if err != nil {
+				time.Sleep(500 * time.Millisecond)
+				continue
+			}
+
+			req, err := http.NewRequest("GET", c.BaseURL+"/internal/ready", nil)
+			if err != nil {
+				time.Sleep(500 * time.Millisecond)
+				continue
+			}
+
+			raw, err := c.Http.Do(req)
+			if err != nil {
+				time.Sleep(500 * time.Millisecond)
+				continue
+			}
+
+			_, err = lxd.HoistResponse(raw, lxd.Sync)
+			if err != nil {
+				time.Sleep(500 * time.Millisecond)
+				continue
+			}
+
+			finger <- nil
+			return
+		}
+	}()
+
+	select {
+	case <-finger:
+		break
+	case <-time.After(time.Second * time.Duration(timeout)):
+		return fmt.Errorf("LXD still not running after %ds timeout.", timeout)
+	}
+
+	return nil
+}

From 31e96e210113881f1a6ca725d62821df9b4e71be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 11:03:18 -0500
Subject: [PATCH 0554/1193] nsexec: Also call setgroups
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This should make some linters happier.

Closes #2724

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_nsexec.go | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
index e100d5c7c..527b1b916 100644
--- a/lxd/main_nsexec.go
+++ b/lxd/main_nsexec.go
@@ -37,6 +37,7 @@ package main
 #include <alloca.h>
 #include <libgen.h>
 #include <ifaddrs.h>
+#include <grp.h>
 
 // This expects:
 //  ./lxd forkputfile /source/path <pid> /target/path
@@ -154,8 +155,8 @@ void attach_userns(int pid) {
 				_exit(1);
 			}
 
-			if (setuid(0) < 0) {
-				fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
+			if (setgroups(0, NULL) < 0) {
+				fprintf(stderr, "Failed setgroups to container root groups: %s\n", strerror(errno));
 				_exit(1);
 			}
 
@@ -163,6 +164,12 @@ void attach_userns(int pid) {
 				fprintf(stderr, "Failed setgid to container root group: %s\n", strerror(errno));
 				_exit(1);
 			}
+
+			if (setuid(0) < 0) {
+				fprintf(stderr, "Failed setuid to container root user: %s\n", strerror(errno));
+				_exit(1);
+			}
+
 		}
 	}
 }

From ea7c6cf39b3e0f1061ce30c7efdc6bc1ad494ce3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0555/1193] action: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/action.go | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/lxc/action.go b/lxc/action.go
index 8fece381f..15b356924 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -31,19 +31,19 @@ func (c *actionCmd) usage() string {
 	}
 
 	return fmt.Sprintf(i18n.G(
-		`Changes state of one or more containers to %s.
+		`Change state of one or more containers to %s.
 
-lxc %s <name> [<name>...]%s`), c.name, c.name, c.additionalHelp)
+lxc %s [<remote>:]<container> [[<remote>:]<container>...]%s`), c.name, c.name, c.additionalHelp)
 }
 
 func (c *actionCmd) flags() {
 	if c.hasTimeout {
-		gnuflag.IntVar(&c.timeout, "timeout", -1, i18n.G("Time to wait for the container before killing it."))
-		gnuflag.BoolVar(&c.force, "f", false, i18n.G("Force the container to shutdown."))
-		gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the container to shutdown."))
+		gnuflag.IntVar(&c.timeout, "timeout", -1, i18n.G("Time to wait for the container before killing it"))
+		gnuflag.BoolVar(&c.force, "f", false, i18n.G("Force the container to shutdown"))
+		gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the container to shutdown"))
 	}
-	gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Store the container state (only for stop)."))
-	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only for start)."))
+	gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Store the container state (only for stop)"))
+	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only for start)"))
 }
 
 func (c *actionCmd) run(config *lxd.Config, args []string) error {

From 5b65fba43c23236193b3c5034cc6a993f19cedbf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0556/1193] config: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config.go | 40 ++++++++++++++++++++--------------------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index 631589a0a..73091a4a8 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -29,7 +29,7 @@ func (c *configCmd) showByDefault() bool {
 }
 
 func (c *configCmd) flags() {
-	gnuflag.BoolVar(&c.expanded, "expanded", false, i18n.G("Whether to show the expanded configuration"))
+	gnuflag.BoolVar(&c.expanded, "expanded", false, i18n.G("Show the expanded configuration"))
 }
 
 func (c *configCmd) configEditHelp() string {
@@ -57,33 +57,33 @@ func (c *configCmd) usage() string {
 	return i18n.G(
 		`Manage configuration.
 
-lxc config device add <[remote:]container> <name> <type> [key=value]...     Add a device to a container.
-lxc config device get <[remote:]container> <name> <key>                     Get a device property.
-lxc config device set <[remote:]container> <name> <key> <value>             Set a device property.
-lxc config device unset <[remote:]container> <name> <key>                   Unset a device property.
-lxc config device list <[remote:]container>                                 List devices for container.
-lxc config device show <[remote:]container>                                 Show full device details for container.
-lxc config device remove <[remote:]container> <name>                        Remove device from container.
-
-lxc config get [remote:][container] <key>                                   Get container or server configuration key.
-lxc config set [remote:][container] <key> <value>                           Set container or server configuration key.
-lxc config unset [remote:][container] <key>                                 Unset container or server configuration key.
-lxc config show [remote:][container] [--expanded]                           Show container or server configuration.
-lxc config edit [remote:][container]                                        Edit container or server configuration in external editor.
+lxc config device add [<remote>:]<container> <device> <type> [key=value...]   Add a device to a container.
+lxc config device get [<remote>:]<container> <device> <key>                   Get a device property.
+lxc config device set [<remote>:]<container> <device> <key> <value>           Set a device property.
+lxc config device unset [<remote>:]<container> <device> <key>                 Unset a device property.
+lxc config device list [<remote>:]<container>                                 List devices for container.
+lxc config device show [<remote>:]<container>                                 Show full device details for container.
+lxc config device remove [<remote>:]<container> <name>                        Remove device from container.
+
+lxc config get [<remote>:][container] <key>                                   Get container or server configuration key.
+lxc config set [<remote>:][container] <key> <value>                           Set container or server configuration key.
+lxc config unset [<remote>:][container] <key>                                 Unset container or server configuration key.
+lxc config show [<remote>:][container] [--expanded]                           Show container or server configuration.
+lxc config edit [<remote>:][container]                                        Edit container or server configuration in external editor.
     Edit configuration, either by launching external editor or reading STDIN.
     Example: lxc config edit <container> # launch editor
-             cat config.yaml | lxc config edit <config> # read from config.yaml
+             cat config.yaml | lxc config edit <container> # read from config.yaml
 
-lxc config trust list [remote]                                              List all trusted certs.
-lxc config trust add [remote] <certfile.crt>                                Add certfile.crt to trusted hosts.
-lxc config trust remove [remote] [hostname|fingerprint]                     Remove the cert from trusted hosts.
+lxc config trust list [<remote>:]                                             List all trusted certs.
+lxc config trust add [<remote>:] <certfile.crt>                               Add certfile.crt to trusted hosts.
+lxc config trust remove [<remote>:] [hostname|fingerprint]                    Remove the cert from trusted hosts.
 
 Examples:
 To mount host's /share/c1 onto /opt in the container:
-    lxc config device add [remote:]container1 <device-name> disk source=/share/c1 path=opt
+    lxc config device add [<remote>:]container1 <device-name> disk source=/share/c1 path=opt
 
 To set an lxc config value:
-    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete = 1'
+    lxc config set [<remote>:]<container> raw.lxc 'lxc.aa_allow_incomplete = 1'
 
 To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the default):
     lxc config set core.https_address [::]:8443

From 6929bb3466e4eab437c28a13fff2e0e4427b75b4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0557/1193] copy: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/copy.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/copy.go b/lxc/copy.go
index ca49bc7c7..27a5bf13a 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -20,9 +20,9 @@ func (c *copyCmd) showByDefault() bool {
 
 func (c *copyCmd) usage() string {
 	return i18n.G(
-		`Copy containers within or in between lxd instances.
+		`Copy containers within or in between LXD instances.
 
-lxc copy [remote:]<source container> [remote:]<destination container> [--ephemeral|e]`)
+lxc copy [<remote>:]<source>[/<snapshot>] [<remote>:]<destination> [--ephemeral|e]`)
 }
 
 func (c *copyCmd) flags() {

From 9ad88b399dfc6d36b90d32fc4bbfa4a934886ea0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0558/1193] delete: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/delete.go | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxc/delete.go b/lxc/delete.go
index 660f983b4..866e5a385 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -25,16 +25,16 @@ func (c *deleteCmd) usage() string {
 	return i18n.G(
 		`Delete containers or snapshots.
 
-lxc delete [remote:]<container>[/<snapshot>] [remote:][<container>[/<snapshot>]...]
+lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]
 
 Destroy containers or snapshots with any attached data (configuration, snapshots, ...).`)
 }
 
 func (c *deleteCmd) flags() {
-	gnuflag.BoolVar(&c.force, "f", false, i18n.G("Force the removal of stopped containers."))
-	gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the removal of stopped containers."))
-	gnuflag.BoolVar(&c.interactive, "i", false, i18n.G("Require user confirmation."))
-	gnuflag.BoolVar(&c.interactive, "interactive", false, i18n.G("Require user confirmation."))
+	gnuflag.BoolVar(&c.force, "f", false, i18n.G("Force the removal of stopped containers"))
+	gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the removal of stopped containers"))
+	gnuflag.BoolVar(&c.interactive, "i", false, i18n.G("Require user confirmation"))
+	gnuflag.BoolVar(&c.interactive, "interactive", false, i18n.G("Require user confirmation"))
 }
 
 func (c *deleteCmd) promptDelete(name string) error {

From 0df562fa6ab08625180aea48b49330dd89c8c900 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0559/1193] exec: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/exec.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/exec.go b/lxc/exec.go
index 925db5f47..f142bba74 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -45,13 +45,13 @@ func (c *execCmd) usage() string {
 	return i18n.G(
 		`Execute the specified command in a container.
 
-lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env EDITOR=/usr/bin/vim]... [--] <command line>
+lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>
 
 Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored).`)
 }
 
 func (c *execCmd) flags() {
-	gnuflag.Var(&c.envArgs, "env", i18n.G("An environment variable of the form HOME=/home/foo"))
+	gnuflag.Var(&c.envArgs, "env", i18n.G("Environment variable to set (e.g. HOME=/home/foo)"))
 	gnuflag.StringVar(&c.modeFlag, "mode", "auto", i18n.G("Override the terminal mode (auto, interactive or non-interactive)"))
 }
 

From 2d5d7534b3bc51635ab69a70f1ddc11754249868 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0560/1193] file: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/file.go | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/lxc/file.go b/lxc/file.go
index 40d5994d1..c1d121490 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -30,13 +30,20 @@ func (c *fileCmd) showByDefault() bool {
 
 func (c *fileCmd) usage() string {
 	return i18n.G(
-		`Manage files on a container.
+		`Manage files in a container.
 
-lxc file pull <source> [<source>...] <target>
-lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] <target>
-lxc file edit <file>
+lxc file pull [<remote>:]<container> [[<remote>:]<container>...] <target path>
+lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source path>...] [<remote>:]<container>
+lxc file edit [<remote>:]<container>/<path>
 
-<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>`)
+<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>
+
+Examples:
+To push /etc/hosts into the container foo:
+    lxc file push /etc/hosts foo/etc/hosts
+
+To pull /etc/hosts from the container:
+    lxc file pull foo/etc/hosts .`)
 }
 
 func (c *fileCmd) flags() {

From e66860dd142a7a7258021ef5bd5f6d72bc816688 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0561/1193] finger: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/finger.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/finger.go b/lxc/finger.go
index d500e18c4..af1e28bac 100644
--- a/lxc/finger.go
+++ b/lxc/finger.go
@@ -15,7 +15,7 @@ func (c *fingerCmd) usage() string {
 	return i18n.G(
 		`Check if the LXD instance is up.
 
-lxc finger <remote>`)
+lxc finger [<remote>:]`)
 }
 
 func (c *fingerCmd) flags() {}

From bebf2f4de93649b4484a696347c6e589278a4935 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0562/1193] help: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/help.go | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/lxc/help.go b/lxc/help.go
index b01732a75..bba74f3e4 100644
--- a/lxc/help.go
+++ b/lxc/help.go
@@ -23,9 +23,9 @@ func (c *helpCmd) showByDefault() bool {
 
 func (c *helpCmd) usage() string {
 	return i18n.G(
-		`Presents details on how to use LXD.
+		`Help page for the LXD client.
 
-lxd help [--all]`)
+lxc help [--all]`)
 }
 
 func (c *helpCmd) flags() {
@@ -45,7 +45,7 @@ func (c *helpCmd) run(_ *lxd.Config, args []string) error {
 		return nil
 	}
 
-	fmt.Println(i18n.G("Usage: lxc [subcommand] [options]"))
+	fmt.Println(i18n.G("Usage: lxc <command> [options]"))
 	fmt.Println(i18n.G("Available commands:"))
 	var names []string
 	for name := range commands {
@@ -61,14 +61,14 @@ func (c *helpCmd) run(_ *lxd.Config, args []string) error {
 	if !c.showAll {
 		fmt.Println()
 		fmt.Println(i18n.G("Options:"))
-		fmt.Println("  --all              " + i18n.G("Print less common commands."))
-		fmt.Println("  --debug            " + i18n.G("Print debug information."))
-		fmt.Println("  --verbose          " + i18n.G("Print verbose information."))
-		fmt.Println("  --version          " + i18n.G("Show client version."))
+		fmt.Println("  --all              " + i18n.G("Print less common commands"))
+		fmt.Println("  --debug            " + i18n.G("Print debug information"))
+		fmt.Println("  --verbose          " + i18n.G("Print verbose information"))
+		fmt.Println("  --version          " + i18n.G("Show client version"))
 		fmt.Println()
 		fmt.Println(i18n.G("Environment:"))
-		fmt.Println("  LXD_CONF           " + i18n.G("Path to an alternate client configuration directory."))
-		fmt.Println("  LXD_DIR            " + i18n.G("Path to an alternate server directory."))
+		fmt.Println("  LXD_CONF           " + i18n.G("Path to an alternate client configuration directory"))
+		fmt.Println("  LXD_DIR            " + i18n.G("Path to an alternate server directory"))
 	}
 	return nil
 }

From 62810933d2cc5ec484a4b68e987faa8235fb0f2b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0563/1193] image: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 3ada00841..3f8ef1fa1 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -108,19 +108,19 @@ Images can be referenced by their full hash, shortest unique partial
 hash or alias name (if one is set).
 
 
-lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS].. [prop=value]
+lxc image import <tarball> [<rootfs tarball>|<URL>] [<remote>:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS...] [prop=value]
     Import an image tarball (or tarballs) into the LXD image store.
 
-lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update]
+lxc image copy [<remote>:]<image> <remote>: [--alias=ALIAS...] [--copy-aliases] [--public] [--auto-update]
     Copy an image from one LXD daemon to another over the network.
 
     The auto-update flag instructs the server to keep this image up to
     date. It requires the source to be an alias and for it to be public.
 
-lxc image delete [remote:]<image> [<image>...]
+lxc image delete [<remote>:]<image> [[<remote>:]<image>...]
     Delete one or more images from the LXD image store.
 
-lxc image export [remote:]<image> [target]
+lxc image export [<remote>:]<image> [target]
     Export an image from the LXD image store into a distributable tarball.
 
     The output target is optional and defaults to the working directory.
@@ -132,31 +132,30 @@ lxc image export [remote:]<image> [target]
     the appropriate extension will be appended to the provided file name
     based on the algorithm used to compress the image. 
 
-lxc image info [remote:]<image>
+lxc image info [<remote>:]<image>
     Print everything LXD knows about a given image.
 
-lxc image list [remote:] [filter]
+lxc image list [<remote>:] [filter]
     List images in the LXD image store. Filters may be of the
     <key>=<value> form for property based filtering, or part of the image
     hash or part of the image alias name.
 
-lxc image show [remote:]<image>
+lxc image show [<remote>:]<image>
     Yaml output of the user modifiable properties of an image.
 
-lxc image edit [remote:]<image>
+lxc image edit [<remote>:]<image>
     Edit image, either by launching external editor or reading STDIN.
     Example: lxc image edit <image> # launch editor
              cat image.yaml | lxc image edit <image> # read from image.yaml
 
-lxc image alias create [remote:]<alias> <fingerprint>
+lxc image alias create [<remote>:]<alias> <fingerprint>
     Create a new alias for an existing image.
 
-lxc image alias delete [remote:]<alias>
+lxc image alias delete [<remote>:]<alias>
     Delete an alias.
 
-lxc image alias list [remote:] [filter]
-    List the aliases. Filters may be part of the image hash or part of the image alias name.
-`)
+lxc image alias list [<remote>:] [filter]
+    List the aliases. Filters may be part of the image hash or part of the image alias name.`)
 }
 
 func (c *imageCmd) flags() {

From a9a878ba790192cf29774dae1734af6136998399 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0564/1193] info: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/info.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/info.go b/lxc/info.go
index a7480f82b..8fdbe21e5 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -26,10 +26,10 @@ func (c *infoCmd) usage() string {
 		`List information on LXD servers and containers.
 
 For a container:
- lxc info [<remote>:]container [--show-log]
+    lxc info [<remote:>]<container> [--show-log]
 
 For a server:
- lxc info [<remote>:]`)
+    lxc info [<remote:>]`)
 }
 
 func (c *infoCmd) flags() {

From 13b437033806eaef358c39760e60cb42e72e9e8f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0565/1193] init: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/init.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index 6209d854f..5f59fc48b 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -73,7 +73,7 @@ func (c *initCmd) usage() string {
 	return i18n.G(
 		`Initialize a container from a particular image.
 
-lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
+lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
 
 Initializes a container using the specified image and name.
 
@@ -81,7 +81,7 @@ Not specifying -p will result in the default profile.
 Specifying "-p" with no argument will result in no profile.
 
 Example:
-lxc init ubuntu u1`)
+    lxc init ubuntu u1`)
 }
 
 func (c *initCmd) is_ephem(s string) bool {

From 4757bdb08e336e5eb5475f030f4bc785390fab45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0566/1193] launch: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/launch.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/launch.go b/lxc/launch.go
index 8d751ed68..70ada54a8 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -22,7 +22,7 @@ func (c *launchCmd) usage() string {
 	return i18n.G(
 		`Launch a container from a particular image.
 
-lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
+lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
 
 Launches a container using the specified image and name.
 
@@ -30,7 +30,7 @@ Not specifying -p will result in the default profile.
 Specifying "-p" with no argument will result in no profile.
 
 Example:
-lxc launch ubuntu:16.04 u1`)
+    lxc launch ubuntu:16.04 u1`)
 }
 
 func (c *launchCmd) flags() {

From d2ef28708c02e4ec590f3f3770d34d06b8ca9edf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0567/1193] list: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index cd70398a1..063a8bf14 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -65,9 +65,9 @@ func (c *listCmd) showByDefault() bool {
 
 func (c *listCmd) usage() string {
 	return i18n.G(
-		`Lists the available resources.
+		`Lists the containers.
 
-lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]
+lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]
 
 The filters are:
 * A single keyword like "web" which will list any container with a name starting by "web".

From f3da4857e529e3b6f0c94291167f0b13e6ee2651 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0568/1193] main: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index 41fd6ead8..43bd4992b 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -37,10 +37,10 @@ func main() {
 }
 
 func run() error {
-	verbose := gnuflag.Bool("verbose", false, i18n.G("Enables verbose mode."))
-	debug := gnuflag.Bool("debug", false, i18n.G("Enables debug mode."))
-	forceLocal := gnuflag.Bool("force-local", false, i18n.G("Force using the local unix socket."))
-	noAlias := gnuflag.Bool("no-alias", false, i18n.G("Ignore aliases when determining what command to run."))
+	verbose := gnuflag.Bool("verbose", false, i18n.G("Enable verbose mode"))
+	debug := gnuflag.Bool("debug", false, i18n.G("Enable debug mode"))
+	forceLocal := gnuflag.Bool("force-local", false, i18n.G("Force using the local unix socket"))
+	noAlias := gnuflag.Bool("no-alias", false, i18n.G("Ignore aliases when determining what command to run"))
 
 	configDir := "$HOME/.config/lxc"
 	if os.Getenv("LXD_CONF") != "" {

From a68387ef7ee87c4875e8b7b740befec17043bdd9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0569/1193] monitor: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/monitor.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/monitor.go b/lxc/monitor.go
index dedb7de3e..877a1f9e6 100644
--- a/lxc/monitor.go
+++ b/lxc/monitor.go
@@ -41,7 +41,7 @@ func (c *monitorCmd) usage() string {
 	return i18n.G(
 		`Monitor activity on the LXD server.
 
-lxc monitor [remote:] [--type=TYPE...]
+lxc monitor [<remote>:] [--type=TYPE...]
 
 Connects to the monitoring interface of the specified LXD server.
 
@@ -49,7 +49,7 @@ By default will listen to all message types.
 Specific types to listen to can be specified with --type.
 
 Example:
-lxc monitor --type=logging`)
+    lxc monitor --type=logging`)
 }
 
 func (c *monitorCmd) flags() {

From d25001124dc0dd08811e56d82ecbbddc30d95a5b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0570/1193] move: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/move.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lxc/move.go b/lxc/move.go
index c99779080..d740db569 100644
--- a/lxc/move.go
+++ b/lxc/move.go
@@ -16,12 +16,14 @@ func (c *moveCmd) usage() string {
 	return i18n.G(
 		`Move containers within or in between lxd instances.
 
-lxc move [remote:]<source container> [remote:]<destination container>
+lxc move [<remote>:]<source container> [<remote>:][<destination container>]
     Move a container between two hosts, renaming it if destination name differs.
 
 lxc move <old name> <new name>
     Rename a local container.
-`)
+
+lxc move <container>/<old snapshot name> <container>/<new snapshot name>
+    Rename a snapshot.`)
 }
 
 func (c *moveCmd) flags() {}

From 3d5fb7a892fd7e75e37e9259a292be238c179826 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0571/1193] profile: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2719

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/profile.go | 37 ++++++++++++++++++-------------------
 1 file changed, 18 insertions(+), 19 deletions(-)

diff --git a/lxc/profile.go b/lxc/profile.go
index 9a3575084..8b797c9e3 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -47,19 +47,19 @@ func (c *profileCmd) usage() string {
 	return i18n.G(
 		`Manage configuration profiles.
 
-lxc profile list [filters]                     List available profiles.
-lxc profile show <profile>                     Show details of a profile.
-lxc profile create <profile>                   Create a profile.
-lxc profile copy <profile> <remote>            Copy the profile to the specified remote.
-lxc profile get <profile> <key>                Get profile configuration.
-lxc profile set <profile> <key> <value>        Set profile configuration.
-lxc profile unset <profile> <key>              Unset profile configuration.
-lxc profile delete <profile>                   Delete a profile.
-lxc profile edit <profile>
+lxc profile list [<remote>:]                                    List available profiles.
+lxc profile show [<remote>:]<profile>                           Show details of a profile.
+lxc profile create [<remote>:]<profile>                         Create a profile.
+lxc profile copy [<remote>:]<profile> [<remote>:]<profile>      Copy the profile.
+lxc profile get [<remote>:]<profile> <key>                      Get profile configuration.
+lxc profile set [<remote>:]<profile> <key> <value>              Set profile configuration.
+lxc profile unset [<remote>:]<profile> <key>                    Unset profile configuration.
+lxc profile delete [<remote>:]<profile>                         Delete a profile.
+lxc profile edit [<remote>:]<profile>
     Edit profile, either by launching external editor or reading STDIN.
     Example: lxc profile edit <profile> # launch editor
              cat profile.yaml | lxc profile edit <profile> # read from profile.yaml
-lxc profile apply <container> <profiles>
+lxc profile apply [<remote>:]<container> <profiles>
     Apply a comma-separated list of profiles to a container, in order.
     All profiles passed in this call (and only those) will be applied
     to the specified container.
@@ -69,15 +69,14 @@ lxc profile apply <container> <profiles>
              lxc profile apply bar,default # Apply default second now
 
 Devices:
-lxc profile device list <profile>                                   List devices in the given profile.
-lxc profile device show <profile>                                   Show full device details in the given profile.
-lxc profile device remove <profile> <name>                          Remove a device from a profile.
-lxc profile device get <[remote:]profile> <name> <key>              Get a device property.
-lxc profile device set <[remote:]profile> <name> <key> <value>      Set a device property.
-lxc profile device unset <[remote:]profile> <name> <key>            Unset a device property.
-lxc profile device add <profile name> <device name> <device type> [key=value]...
-    Add a profile device, such as a disk or a nic, to the containers
-    using the specified profile.`)
+lxc profile device list [<remote>:]<profile>                                List devices in the given profile.
+lxc profile device show [<remote>:]<profile>                                Show full device details in the given profile.
+lxc profile device remove [<remote>:]<profile> <name>                       Remove a device from a profile.
+lxc profile device get [<remote>:]<profile> <name> <key>                    Get a device property.
+lxc profile device set [<remote>:]<profile> <name> <key> <value>            Set a device property.
+lxc profile device unset [<remote>:]<profile> <name> <key>                  Unset a device property.
+lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]
+    Add a profile device, such as a disk or a nic, to the containers using the specified profile.`)
 }
 
 func (c *profileCmd) flags() {}

From f3b3f8f6fd5ea038ff0929da378466efaa0e7eaa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0572/1193] publish: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/publish.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/publish.go b/lxc/publish.go
index 331797586..51bc2645c 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -25,7 +25,7 @@ func (c *publishCmd) usage() string {
 	return i18n.G(
 		`Publish containers as images.
 
-lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-value]...`)
+lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]`)
 }
 
 func (c *publishCmd) flags() {

From f0767ddb9d97f938063e32f4ed98a570cdb1f517 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0573/1193] remote: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/remote.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index c23febd28..b31cec1d6 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -37,13 +37,13 @@ func (c *remoteCmd) usage() string {
 	return i18n.G(
 		`Manage remote LXD servers.
 
-lxc remote add <name> <url> [--accept-certificate] [--password=PASSWORD]
-                            [--public] [--protocol=PROTOCOL]                Add the remote <name> at <url>.
-lxc remote remove <name>                                                    Remove the remote <name>.
+lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD]
+                                      [--public] [--protocol=PROTOCOL]      Add the remote <name> at <url>.
+lxc remote remove <remote>                                                  Remove the remote <name>.
 lxc remote list                                                             List all remotes.
-lxc remote rename <old> <new>                                               Rename remote <old> to <new>.
-lxc remote set-url <name> <url>                                             Update <name>'s url to <url>.
-lxc remote set-default <name>                                               Set the default remote.
+lxc remote rename <old name> <new name>                                     Rename remote <old> to <new>.
+lxc remote set-url <remote> <url>                                           Update <name>'s url to <url>.
+lxc remote set-default <remote>                                             Set the default remote.
 lxc remote get-default                                                      Print the default remote.`)
 }
 

From c9dec7597d5ead02890560cdcc7829857601e76b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0574/1193] restore: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/restore.go | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/lxc/restore.go b/lxc/restore.go
index 3e4f8d52e..b37b3eff0 100644
--- a/lxc/restore.go
+++ b/lxc/restore.go
@@ -19,16 +19,19 @@ func (c *restoreCmd) showByDefault() bool {
 
 func (c *restoreCmd) usage() string {
 	return i18n.G(
-		`Set the current state of a container back to a snapshot.
+		`Restore a container's state to a previous snapshot.
 
-lxc restore [remote:]<container> <snapshot name> [--stateful]
+lxc restore [<remote>:]<container> <snapshot> [--stateful]
 
 Restores a container from a snapshot (optionally with running state, see
 snapshot help for details).
 
-For example:
-lxc snapshot u1 snap0 # create the snapshot
-lxc restore u1 snap0 # restore the snapshot`)
+Examples:
+Create the snapshot:
+    lxc snapshot u1 snap0
+
+Restore the snapshot:
+    lxc restore u1 snap0`)
 }
 
 func (c *restoreCmd) flags() {

From 8d2680de312bc6b06efee79eb53ad0734cf60aad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0575/1193] snapshot: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/snapshot.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/snapshot.go b/lxc/snapshot.go
index 7dad2941c..50a01ad2c 100644
--- a/lxc/snapshot.go
+++ b/lxc/snapshot.go
@@ -21,7 +21,7 @@ func (c *snapshotCmd) usage() string {
 	return i18n.G(
 		`Create a read-only snapshot of a container.
 
-lxc snapshot [remote:]<source> <snapshot name> [--stateful]
+lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]
 
 Creates a snapshot of the container (optionally with the container's memory
 state). When --stateful is used, LXD attempts to checkpoint the container's
@@ -31,7 +31,7 @@ TCP connections after the TCP timeout window has expired, may not be restored
 successfully).
 
 Example:
-lxc snapshot u1 snap0`)
+    lxc snapshot u1 snap0`)
 }
 
 func (c *snapshotCmd) flags() {

From 2b5f7535d486f5d379f5b10cb80d3b2b1314b854 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:34 -0500
Subject: [PATCH 0576/1193] Update PO file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/lxd.pot | 422 +++++++++++++++++++++++++++++++------------------------------
 1 file changed, 217 insertions(+), 205 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index 43f3fedb5..f7de03612 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-12-06 17:32+0100\n"
+        "POT-Creation-Date: 2016-12-15 14:19-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -77,7 +77,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:609
+#: lxc/image.go:608
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -86,15 +86,15 @@ msgstr  ""
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/profile.go:226
+#: lxc/profile.go:225
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:630 lxc/image.go:659
+#: lxc/image.go:629 lxc/image.go:658
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:634
+#: lxc/image.go:633
 msgid   "ARCH"
 msgstr  ""
 
@@ -102,29 +102,25 @@ msgstr  ""
 msgid   "ARCHITECTURE"
 msgstr  ""
 
-#: lxc/remote.go:52
+#: lxc/remote.go:51
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:245
+#: lxc/remote.go:244
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:368
+#: lxc/image.go:367
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/exec.go:54
-msgid   "An environment variable of the form HOME=/home/foo"
-msgstr  ""
-
-#: lxc/image.go:351 lxc/info.go:93
+#: lxc/image.go:350 lxc/info.go:93
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:372
+#: lxc/image.go:371
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
@@ -159,29 +155,29 @@ msgstr  ""
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:343
+#: lxc/profile.go:342
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:195
+#: lxc/remote.go:194
 #, c-format
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
 
 #: lxc/action.go:33
 #, c-format
-msgid   "Changes state of one or more containers to %s.\n"
+msgid   "Change state of one or more containers to %s.\n"
         "\n"
-        "lxc %s <name> [<name>...]%s"
+        "lxc %s [<remote>:]<container> [[<remote>:]<container>...]%s"
 msgstr  ""
 
 #: lxc/finger.go:15
 msgid   "Check if the LXD instance is up.\n"
         "\n"
-        "lxc finger <remote>"
+        "lxc finger [<remote>:]"
 msgstr  ""
 
-#: lxc/remote.go:268
+#: lxc/remote.go:267
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
@@ -193,7 +189,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:713 lxc/profile.go:190
+#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:712 lxc/profile.go:189
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -216,29 +212,29 @@ msgstr  ""
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:164
+#: lxc/image.go:163
 msgid   "Copy aliases from source"
 msgstr  ""
 
 #: lxc/copy.go:22
-msgid   "Copy containers within or in between lxd instances.\n"
+msgid   "Copy containers within or in between LXD instances.\n"
         "\n"
-        "lxc copy [remote:]<source container> [remote:]<destination container> [--ephemeral|e]"
+        "lxc copy [<remote>:]<source>[/<snapshot>] [<remote>:]<destination> [--ephemeral|e]"
 msgstr  ""
 
-#: lxc/image.go:276
+#: lxc/image.go:275
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:210
+#: lxc/remote.go:209
 msgid   "Could not create server cert dir"
 msgstr  ""
 
 #: lxc/snapshot.go:21
 msgid   "Create a read-only snapshot of a container.\n"
         "\n"
-        "lxc snapshot [remote:]<source> <snapshot name> [--stateful]\n"
+        "lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
         "\n"
         "Creates a snapshot of the container (optionally with the container's memory\n"
         "state). When --stateful is used, LXD attempts to checkpoint the container's\n"
@@ -248,10 +244,10 @@ msgid   "Create a read-only snapshot of a container.\n"
         "successfully).\n"
         "\n"
         "Example:\n"
-        "lxc snapshot u1 snap0"
+        "    lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:356 lxc/info.go:95
+#: lxc/image.go:355 lxc/info.go:95
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -265,14 +261,14 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:633 lxc/image.go:661
+#: lxc/image.go:632 lxc/image.go:660
 msgid   "DESCRIPTION"
 msgstr  ""
 
 #: lxc/delete.go:25
 msgid   "Delete containers or snapshots.\n"
         "\n"
-        "lxc delete [remote:]<container>[/<snapshot>] [remote:][<container>[/<snapshot>]...]\n"
+        "lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]\n"
         "\n"
         "Destroy containers or snapshots with any attached data (configuration, snapshots, ...)."
 msgstr  ""
@@ -296,11 +292,15 @@ msgid   "EXPIRY DATE"
 msgstr  ""
 
 #: lxc/main.go:41
-msgid   "Enables debug mode."
+msgid   "Enable debug mode"
 msgstr  ""
 
 #: lxc/main.go:40
-msgid   "Enables verbose mode."
+msgid   "Enable verbose mode"
+msgstr  ""
+
+#: lxc/exec.go:54
+msgid   "Environment variable to set (e.g. HOME=/home/foo)"
 msgstr  ""
 
 #: lxc/help.go:69
@@ -318,21 +318,21 @@ msgstr  ""
 #: lxc/exec.go:45
 msgid   "Execute the specified command in a container.\n"
         "\n"
-        "lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env EDITOR=/usr/bin/vim]... [--] <command line>\n"
+        "lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>\n"
         "\n"
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
 
-#: lxc/image.go:360
+#: lxc/image.go:359
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:362
+#: lxc/image.go:361
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:272 lxc/image.go:631 lxc/image.go:660
+#: lxc/config.go:272 lxc/image.go:630 lxc/image.go:659
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -340,21 +340,21 @@ msgstr  ""
 msgid   "Fast mode (same as --columns=nsacPt"
 msgstr  ""
 
-#: lxc/image.go:349
+#: lxc/image.go:348
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
 
 #: lxc/action.go:42 lxc/action.go:43
-msgid   "Force the container to shutdown."
+msgid   "Force the container to shutdown"
 msgstr  ""
 
 #: lxc/delete.go:34 lxc/delete.go:35
-msgid   "Force the removal of stopped containers."
+msgid   "Force the removal of stopped containers"
 msgstr  ""
 
 #: lxc/main.go:42
-msgid   "Force using the local unix socket."
+msgid   "Force using the local unix socket"
 msgstr  ""
 
 #: lxc/list.go:101
@@ -365,6 +365,12 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
+#: lxc/help.go:25
+msgid   "Help page for the LXD client.\n"
+        "\n"
+        "lxc help [--all]"
+msgstr  ""
+
 #: lxc/list.go:392
 msgid   "IPV4"
 msgstr  ""
@@ -382,23 +388,23 @@ msgid   "If this is your first time using LXD, you should also run: sudo lxd ini
 msgstr  ""
 
 #: lxc/main.go:43
-msgid   "Ignore aliases when determining what command to run."
+msgid   "Ignore aliases when determining what command to run"
 msgstr  ""
 
 #: lxc/action.go:46
-msgid   "Ignore the container state (only for start)."
+msgid   "Ignore the container state (only for start)"
 msgstr  ""
 
-#: lxc/image.go:279
+#: lxc/image.go:278
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:427 lxc/image.go:439
+#: lxc/image.go:426 lxc/image.go:438
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:424
+#: lxc/image.go:423
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
@@ -406,7 +412,7 @@ msgstr  ""
 #: lxc/init.go:73
 msgid   "Initialize a container from a particular image.\n"
         "\n"
-        "lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+        "lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
         "\n"
         "Initializes a container using the specified image and name.\n"
         "\n"
@@ -414,10 +420,10 @@ msgid   "Initialize a container from a particular image.\n"
         "Specifying \"-p\" with no argument will result in no profile.\n"
         "\n"
         "Example:\n"
-        "lxc init ubuntu u1"
+        "    lxc init ubuntu u1"
 msgstr  ""
 
-#: lxc/remote.go:121
+#: lxc/remote.go:120
 #, c-format
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
@@ -430,12 +436,12 @@ msgstr  ""
 msgid   "Invalid configuration key"
 msgstr  ""
 
-#: lxc/file.go:195
+#: lxc/file.go:202
 #, c-format
 msgid   "Invalid source %s"
 msgstr  ""
 
-#: lxc/file.go:57
+#: lxc/file.go:64
 #, c-format
 msgid   "Invalid target %s"
 msgstr  ""
@@ -444,7 +450,7 @@ msgstr  ""
 msgid   "Ips:"
 msgstr  ""
 
-#: lxc/image.go:165
+#: lxc/image.go:164
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
@@ -455,7 +461,7 @@ msgstr  ""
 #: lxc/launch.go:22
 msgid   "Launch a container from a particular image.\n"
         "\n"
-        "lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+        "lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
         "\n"
         "Launches a container using the specified image and name.\n"
         "\n"
@@ -463,23 +469,23 @@ msgid   "Launch a container from a particular image.\n"
         "Specifying \"-p\" with no argument will result in no profile.\n"
         "\n"
         "Example:\n"
-        "lxc launch ubuntu:16.04 u1"
+        "    lxc launch ubuntu:16.04 u1"
 msgstr  ""
 
 #: lxc/info.go:25
 msgid   "List information on LXD servers and containers.\n"
         "\n"
         "For a container:\n"
-        " lxc info [<remote>:]container [--show-log]\n"
+        "    lxc info [<remote:>]<container> [--show-log]\n"
         "\n"
         "For a server:\n"
-        " lxc info [<remote>:]"
+        "    lxc info [<remote:>]"
 msgstr  ""
 
 #: lxc/list.go:67
-msgid   "Lists the available resources.\n"
+msgid   "Lists the containers.\n"
         "\n"
-        "lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
+        "lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]\n"
         "\n"
         "The filters are:\n"
         "* A single keyword like \"web\" which will list any container with a name starting by \"web\".\n"
@@ -511,7 +517,7 @@ msgstr  ""
 msgid   "Log:"
 msgstr  ""
 
-#: lxc/image.go:163
+#: lxc/image.go:162
 msgid   "Make image public"
 msgstr  ""
 
@@ -522,19 +528,19 @@ msgstr  ""
 #: lxc/profile.go:47
 msgid   "Manage configuration profiles.\n"
         "\n"
-        "lxc profile list [filters]                     List available profiles.\n"
-        "lxc profile show <profile>                     Show details of a profile.\n"
-        "lxc profile create <profile>                   Create a profile.\n"
-        "lxc profile copy <profile> <remote>            Copy the profile to the specified remote.\n"
-        "lxc profile get <profile> <key>                Get profile configuration.\n"
-        "lxc profile set <profile> <key> <value>        Set profile configuration.\n"
-        "lxc profile unset <profile> <key>              Unset profile configuration.\n"
-        "lxc profile delete <profile>                   Delete a profile.\n"
-        "lxc profile edit <profile>\n"
+        "lxc profile list [<remote>:]                                    List available profiles.\n"
+        "lxc profile show [<remote>:]<profile>                           Show details of a profile.\n"
+        "lxc profile create [<remote>:]<profile>                         Create a profile.\n"
+        "lxc profile copy [<remote>:]<profile> [<remote>:]<profile>      Copy the profile.\n"
+        "lxc profile get [<remote>:]<profile> <key>                      Get profile configuration.\n"
+        "lxc profile set [<remote>:]<profile> <key> <value>              Set profile configuration.\n"
+        "lxc profile unset [<remote>:]<profile> <key>                    Unset profile configuration.\n"
+        "lxc profile delete [<remote>:]<profile>                         Delete a profile.\n"
+        "lxc profile edit [<remote>:]<profile>\n"
         "    Edit profile, either by launching external editor or reading STDIN.\n"
         "    Example: lxc profile edit <profile> # launch editor\n"
         "             cat profile.yaml | lxc profile edit <profile> # read from profile.yaml\n"
-        "lxc profile apply <container> <profiles>\n"
+        "lxc profile apply [<remote>:]<container> <profiles>\n"
         "    Apply a comma-separated list of profiles to a container, in order.\n"
         "    All profiles passed in this call (and only those) will be applied\n"
         "    to the specified container.\n"
@@ -544,47 +550,46 @@ msgid   "Manage configuration profiles.\n"
         "             lxc profile apply bar,default # Apply default second now\n"
         "\n"
         "Devices:\n"
-        "lxc profile device list <profile>                                   List devices in the given profile.\n"
-        "lxc profile device show <profile>                                   Show full device details in the given profile.\n"
-        "lxc profile device remove <profile> <name>                          Remove a device from a profile.\n"
-        "lxc profile device get <[remote:]profile> <name> <key>              Get a device property.\n"
-        "lxc profile device set <[remote:]profile> <name> <key> <value>      Set a device property.\n"
-        "lxc profile device unset <[remote:]profile> <name> <key>            Unset a device property.\n"
-        "lxc profile device add <profile name> <device name> <device type> [key=value]...\n"
-        "    Add a profile device, such as a disk or a nic, to the containers\n"
-        "    using the specified profile."
+        "lxc profile device list [<remote>:]<profile>                                List devices in the given profile.\n"
+        "lxc profile device show [<remote>:]<profile>                                Show full device details in the given profile.\n"
+        "lxc profile device remove [<remote>:]<profile> <name>                       Remove a device from a profile.\n"
+        "lxc profile device get [<remote>:]<profile> <name> <key>                    Get a device property.\n"
+        "lxc profile device set [<remote>:]<profile> <name> <key> <value>            Set a device property.\n"
+        "lxc profile device unset [<remote>:]<profile> <name> <key>                  Unset a device property.\n"
+        "lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]\n"
+        "    Add a profile device, such as a disk or a nic, to the containers using the specified profile."
 msgstr  ""
 
 #: lxc/config.go:57
 msgid   "Manage configuration.\n"
         "\n"
-        "lxc config device add <[remote:]container> <name> <type> [key=value]...     Add a device to a container.\n"
-        "lxc config device get <[remote:]container> <name> <key>                     Get a device property.\n"
-        "lxc config device set <[remote:]container> <name> <key> <value>             Set a device property.\n"
-        "lxc config device unset <[remote:]container> <name> <key>                   Unset a device property.\n"
-        "lxc config device list <[remote:]container>                                 List devices for container.\n"
-        "lxc config device show <[remote:]container>                                 Show full device details for container.\n"
-        "lxc config device remove <[remote:]container> <name>                        Remove device from container.\n"
-        "\n"
-        "lxc config get [remote:][container] <key>                                   Get container or server configuration key.\n"
-        "lxc config set [remote:][container] <key> <value>                           Set container or server configuration key.\n"
-        "lxc config unset [remote:][container] <key>                                 Unset container or server configuration key.\n"
-        "lxc config show [remote:][container] [--expanded]                           Show container or server configuration.\n"
-        "lxc config edit [remote:][container]                                        Edit container or server configuration in external editor.\n"
+        "lxc config device add [<remote>:]<container> <device> <type> [key=value...]   Add a device to a container.\n"
+        "lxc config device get [<remote>:]<container> <device> <key>                   Get a device property.\n"
+        "lxc config device set [<remote>:]<container> <device> <key> <value>           Set a device property.\n"
+        "lxc config device unset [<remote>:]<container> <device> <key>                 Unset a device property.\n"
+        "lxc config device list [<remote>:]<container>                                 List devices for container.\n"
+        "lxc config device show [<remote>:]<container>                                 Show full device details for container.\n"
+        "lxc config device remove [<remote>:]<container> <name>                        Remove device from container.\n"
+        "\n"
+        "lxc config get [<remote>:][container] <key>                                   Get container or server configuration key.\n"
+        "lxc config set [<remote>:][container] <key> <value>                           Set container or server configuration key.\n"
+        "lxc config unset [<remote>:][container] <key>                                 Unset container or server configuration key.\n"
+        "lxc config show [<remote>:][container] [--expanded]                           Show container or server configuration.\n"
+        "lxc config edit [<remote>:][container]                                        Edit container or server configuration in external editor.\n"
         "    Edit configuration, either by launching external editor or reading STDIN.\n"
         "    Example: lxc config edit <container> # launch editor\n"
-        "             cat config.yaml | lxc config edit <config> # read from config.yaml\n"
+        "             cat config.yaml | lxc config edit <container> # read from config.yaml\n"
         "\n"
-        "lxc config trust list [remote]                                              List all trusted certs.\n"
-        "lxc config trust add [remote] <certfile.crt>                                Add certfile.crt to trusted hosts.\n"
-        "lxc config trust remove [remote] [hostname|fingerprint]                     Remove the cert from trusted hosts.\n"
+        "lxc config trust list [<remote>:]                                             List all trusted certs.\n"
+        "lxc config trust add [<remote>:] <certfile.crt>                               Add certfile.crt to trusted hosts.\n"
+        "lxc config trust remove [<remote>:] [hostname|fingerprint]                    Remove the cert from trusted hosts.\n"
         "\n"
         "Examples:\n"
         "To mount host's /share/c1 onto /opt in the container:\n"
-        "    lxc config device add [remote:]container1 <device-name> disk source=/share/c1 path=opt\n"
+        "    lxc config device add [<remote>:]container1 <device-name> disk source=/share/c1 path=opt\n"
         "\n"
         "To set an lxc config value:\n"
-        "    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete = 1'\n"
+        "    lxc config set [<remote>:]<container> raw.lxc 'lxc.aa_allow_incomplete = 1'\n"
         "\n"
         "To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the default):\n"
         "    lxc config set core.https_address [::]:8443\n"
@@ -594,25 +599,32 @@ msgid   "Manage configuration.\n"
 msgstr  ""
 
 #: lxc/file.go:32
-msgid   "Manage files on a container.\n"
+msgid   "Manage files in a container.\n"
+        "\n"
+        "lxc file pull [<remote>:]<container> [[<remote>:]<container>...] <target path>\n"
+        "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source path>...] [<remote>:]<container>\n"
+        "lxc file edit [<remote>:]<container>/<path>\n"
+        "\n"
+        "<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>\n"
         "\n"
-        "lxc file pull <source> [<source>...] <target>\n"
-        "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] <target>\n"
-        "lxc file edit <file>\n"
+        "Examples:\n"
+        "To push /etc/hosts into the container foo:\n"
+        "    lxc file push /etc/hosts foo/etc/hosts\n"
         "\n"
-        "<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>"
+        "To pull /etc/hosts from the container:\n"
+        "    lxc file pull foo/etc/hosts ."
 msgstr  ""
 
-#: lxc/remote.go:38
+#: lxc/remote.go:37
 msgid   "Manage remote LXD servers.\n"
         "\n"
-        "lxc remote add <name> <url> [--accept-certificate] [--password=PASSWORD]\n"
-        "                            [--public] [--protocol=PROTOCOL]                Add the remote <name> at <url>.\n"
-        "lxc remote remove <name>                                                    Remove the remote <name>.\n"
+        "lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD]\n"
+        "                                      [--public] [--protocol=PROTOCOL]      Add the remote <name> at <url>.\n"
+        "lxc remote remove <remote>                                                  Remove the remote <name>.\n"
         "lxc remote list                                                             List all remotes.\n"
-        "lxc remote rename <old> <new>                                               Rename remote <old> to <new>.\n"
-        "lxc remote set-url <name> <url>                                             Update <name>'s url to <url>.\n"
-        "lxc remote set-default <name>                                               Set the default remote.\n"
+        "lxc remote rename <old name> <new name>                                     Rename remote <old> to <new>.\n"
+        "lxc remote set-url <remote> <url>                                           Update <name>'s url to <url>.\n"
+        "lxc remote set-default <remote>                                             Set the default remote.\n"
         "lxc remote get-default                                                      Print the default remote."
 msgstr  ""
 
@@ -634,19 +646,19 @@ msgid   "Manipulate container images.\n"
         "hash or alias name (if one is set).\n"
         "\n"
         "\n"
-        "lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS].. [prop=value]\n"
+        "lxc image import <tarball> [<rootfs tarball>|<URL>] [<remote>:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS...] [prop=value]\n"
         "    Import an image tarball (or tarballs) into the LXD image store.\n"
         "\n"
-        "lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update]\n"
+        "lxc image copy [<remote>:]<image> <remote>: [--alias=ALIAS...] [--copy-aliases] [--public] [--auto-update]\n"
         "    Copy an image from one LXD daemon to another over the network.\n"
         "\n"
         "    The auto-update flag instructs the server to keep this image up to\n"
         "    date. It requires the source to be an alias and for it to be public.\n"
         "\n"
-        "lxc image delete [remote:]<image> [<image>...]\n"
+        "lxc image delete [<remote>:]<image> [[<remote>:]<image>...]\n"
         "    Delete one or more images from the LXD image store.\n"
         "\n"
-        "lxc image export [remote:]<image> [target]\n"
+        "lxc image export [<remote>:]<image> [target]\n"
         "    Export an image from the LXD image store into a distributable tarball.\n"
         "\n"
         "    The output target is optional and defaults to the working directory.\n"
@@ -658,30 +670,30 @@ msgid   "Manipulate container images.\n"
         "    the appropriate extension will be appended to the provided file name\n"
         "    based on the algorithm used to compress the image. \n"
         "\n"
-        "lxc image info [remote:]<image>\n"
+        "lxc image info [<remote>:]<image>\n"
         "    Print everything LXD knows about a given image.\n"
         "\n"
-        "lxc image list [remote:] [filter]\n"
+        "lxc image list [<remote>:] [filter]\n"
         "    List images in the LXD image store. Filters may be of the\n"
         "    <key>=<value> form for property based filtering, or part of the image\n"
         "    hash or part of the image alias name.\n"
         "\n"
-        "lxc image show [remote:]<image>\n"
+        "lxc image show [<remote>:]<image>\n"
         "    Yaml output of the user modifiable properties of an image.\n"
         "\n"
-        "lxc image edit [remote:]<image>\n"
+        "lxc image edit [<remote>:]<image>\n"
         "    Edit image, either by launching external editor or reading STDIN.\n"
         "    Example: lxc image edit <image> # launch editor\n"
         "             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
         "\n"
-        "lxc image alias create [remote:]<alias> <fingerprint>\n"
+        "lxc image alias create [<remote>:]<alias> <fingerprint>\n"
         "    Create a new alias for an existing image.\n"
         "\n"
-        "lxc image alias delete [remote:]<alias>\n"
+        "lxc image alias delete [<remote>:]<alias>\n"
         "    Delete an alias.\n"
         "\n"
-        "lxc image alias list [remote:] [filter]\n"
-        "    List the aliases. Filters may be part of the image hash or part of the image alias name.\n"
+        "lxc image alias list [<remote>:] [filter]\n"
+        "    List the aliases. Filters may be part of the image hash or part of the image alias name."
 msgstr  ""
 
 #: lxc/info.go:150
@@ -699,7 +711,7 @@ msgstr  ""
 #: lxc/monitor.go:41
 msgid   "Monitor activity on the LXD server.\n"
         "\n"
-        "lxc monitor [remote:] [--type=TYPE...]\n"
+        "lxc monitor [<remote>:] [--type=TYPE...]\n"
         "\n"
         "Connects to the monitoring interface of the specified LXD server.\n"
         "\n"
@@ -707,32 +719,35 @@ msgid   "Monitor activity on the LXD server.\n"
         "Specific types to listen to can be specified with --type.\n"
         "\n"
         "Example:\n"
-        "lxc monitor --type=logging"
+        "    lxc monitor --type=logging"
 msgstr  ""
 
-#: lxc/file.go:183
+#: lxc/file.go:190
 msgid   "More than one file to download, but target is not a directory"
 msgstr  ""
 
 #: lxc/move.go:16
 msgid   "Move containers within or in between lxd instances.\n"
         "\n"
-        "lxc move [remote:]<source container> [remote:]<destination container>\n"
+        "lxc move [<remote>:]<source container> [<remote>:][<destination container>]\n"
         "    Move a container between two hosts, renaming it if destination name differs.\n"
         "\n"
         "lxc move <old name> <new name>\n"
         "    Rename a local container.\n"
+        "\n"
+        "lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
+        "    Rename a snapshot."
 msgstr  ""
 
 #: lxc/action.go:69
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:396 lxc/remote.go:352
+#: lxc/list.go:396 lxc/remote.go:351
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/remote.go:326 lxc/remote.go:331
+#: lxc/remote.go:325 lxc/remote.go:330
 msgid   "NO"
 msgstr  ""
 
@@ -741,7 +756,7 @@ msgstr  ""
 msgid   "Name: %s"
 msgstr  ""
 
-#: lxc/image.go:166 lxc/publish.go:33
+#: lxc/image.go:165 lxc/publish.go:33
 msgid   "New alias to define at target"
 msgstr  ""
 
@@ -753,11 +768,11 @@ msgstr  ""
 msgid   "No fingerprint specified."
 msgstr  ""
 
-#: lxc/remote.go:106
+#: lxc/remote.go:105
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:430
+#: lxc/image.go:429
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -765,7 +780,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:532
+#: lxc/image.go:531
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -786,11 +801,11 @@ msgstr  ""
 msgid   "PROFILES"
 msgstr  ""
 
-#: lxc/remote.go:354
+#: lxc/remote.go:353
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:632 lxc/remote.go:355
+#: lxc/image.go:631 lxc/remote.go:354
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -803,11 +818,11 @@ msgid   "Packets sent"
 msgstr  ""
 
 #: lxc/help.go:70
-msgid   "Path to an alternate client configuration directory."
+msgid   "Path to an alternate client configuration directory"
 msgstr  ""
 
 #: lxc/help.go:71
-msgid   "Path to an alternate server directory."
+msgid   "Path to an alternate server directory"
 msgstr  ""
 
 #: lxc/main.go:31
@@ -819,30 +834,24 @@ msgstr  ""
 msgid   "Pid: %d"
 msgstr  ""
 
-#: lxc/help.go:25
-msgid   "Presents details on how to use LXD.\n"
-        "\n"
-        "lxd help [--all]"
-msgstr  ""
-
-#: lxc/profile.go:191
+#: lxc/profile.go:190
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:714
+#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:713
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
 #: lxc/help.go:65
-msgid   "Print debug information."
+msgid   "Print debug information"
 msgstr  ""
 
 #: lxc/help.go:64
-msgid   "Print less common commands."
+msgid   "Print less common commands"
 msgstr  ""
 
 #: lxc/help.go:66
-msgid   "Print verbose information."
+msgid   "Print verbose information"
 msgstr  ""
 
 #: lxc/version.go:18
@@ -856,17 +865,17 @@ msgstr  ""
 msgid   "Processes: %d"
 msgstr  ""
 
-#: lxc/profile.go:228
+#: lxc/profile.go:227
 #, c-format
 msgid   "Profile %s applied to %s"
 msgstr  ""
 
-#: lxc/profile.go:142
+#: lxc/profile.go:141
 #, c-format
 msgid   "Profile %s created"
 msgstr  ""
 
-#: lxc/profile.go:212
+#: lxc/profile.go:211
 #, c-format
 msgid   "Profile %s deleted"
 msgstr  ""
@@ -880,15 +889,15 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:364
+#: lxc/image.go:363
 msgid   "Properties:"
 msgstr  ""
 
-#: lxc/remote.go:55
+#: lxc/remote.go:54
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:352
+#: lxc/image.go:351
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -896,10 +905,10 @@ msgstr  ""
 #: lxc/publish.go:25
 msgid   "Publish containers as images.\n"
         "\n"
-        "lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-value]..."
+        "lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]"
 msgstr  ""
 
-#: lxc/remote.go:53
+#: lxc/remote.go:52
 msgid   "Remote admin password"
 msgstr  ""
 
@@ -914,19 +923,35 @@ msgid   "Remove %s (yes/no): "
 msgstr  ""
 
 #: lxc/delete.go:36 lxc/delete.go:37
-msgid   "Require user confirmation."
+msgid   "Require user confirmation"
 msgstr  ""
 
 #: lxc/info.go:127
 msgid   "Resources:"
 msgstr  ""
 
+#: lxc/restore.go:21
+msgid   "Restore a container's state to a previous snapshot.\n"
+        "\n"
+        "lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
+        "\n"
+        "Restores a container from a snapshot (optionally with running state, see\n"
+        "snapshot help for details).\n"
+        "\n"
+        "Examples:\n"
+        "Create the snapshot:\n"
+        "    lxc snapshot u1 snap0\n"
+        "\n"
+        "Restore the snapshot:\n"
+        "    lxc restore u1 snap0"
+msgstr  ""
+
 #: lxc/init.go:219
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:635
+#: lxc/image.go:634
 msgid   "SIZE"
 msgstr  ""
 
@@ -938,44 +963,31 @@ msgstr  ""
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:356
+#: lxc/remote.go:355
 msgid   "STATIC"
 msgstr  ""
 
-#: lxc/remote.go:203
+#: lxc/remote.go:202
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:265
+#: lxc/remote.go:264
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
-#: lxc/remote.go:54
+#: lxc/remote.go:53
 msgid   "Server protocol (lxd or simplestreams)"
 msgstr  ""
 
-#: lxc/restore.go:21
-msgid   "Set the current state of a container back to a snapshot.\n"
-        "\n"
-        "lxc restore [remote:]<container> <snapshot name> [--stateful]\n"
-        "\n"
-        "Restores a container from a snapshot (optionally with running state, see\n"
-        "snapshot help for details).\n"
-        "\n"
-        "For example:\n"
-        "lxc snapshot u1 snap0 # create the snapshot\n"
-        "lxc restore u1 snap0 # restore the snapshot"
-msgstr  ""
-
-#: lxc/file.go:44
+#: lxc/file.go:51
 msgid   "Set the file's gid on push"
 msgstr  ""
 
-#: lxc/file.go:45
+#: lxc/file.go:52
 msgid   "Set the file's perms on push"
 msgstr  ""
 
-#: lxc/file.go:43
+#: lxc/file.go:50
 msgid   "Set the file's uid on push"
 msgstr  ""
 
@@ -984,14 +996,18 @@ msgid   "Show all commands (not just interesting ones)"
 msgstr  ""
 
 #: lxc/help.go:67
-msgid   "Show client version."
+msgid   "Show client version"
 msgstr  ""
 
 #: lxc/info.go:36
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/image.go:350
+#: lxc/config.go:32
+msgid   "Show the expanded configuration"
+msgstr  ""
+
+#: lxc/image.go:349
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -1000,7 +1016,7 @@ msgstr  ""
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/image.go:374
+#: lxc/image.go:373
 msgid   "Source:"
 msgstr  ""
 
@@ -1023,7 +1039,7 @@ msgid   "Stopping container failed!"
 msgstr  ""
 
 #: lxc/action.go:45
-msgid   "Store the container state (only for stop)."
+msgid   "Store the container state (only for stop)"
 msgstr  ""
 
 #: lxc/info.go:158
@@ -1064,10 +1080,10 @@ msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
 
 #: lxc/action.go:41
-msgid   "Time to wait for the container before killing it."
+msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:353
+#: lxc/image.go:352
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -1075,7 +1091,7 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:432
+#: lxc/image.go:431
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
@@ -1093,19 +1109,19 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:636
+#: lxc/image.go:635
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:353
+#: lxc/remote.go:352
 msgid   "URL"
 msgstr  ""
 
-#: lxc/remote.go:81
+#: lxc/remote.go:80
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:358
+#: lxc/image.go:357
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1116,14 +1132,14 @@ msgid   "Usage: %s"
 msgstr  ""
 
 #: lxc/help.go:48
-msgid   "Usage: lxc [subcommand] [options]"
+msgid   "Usage: lxc <command> [options]"
 msgstr  ""
 
 #: lxc/delete.go:46
 msgid   "User aborted delete operation."
 msgstr  ""
 
-#: lxc/restore.go:35
+#: lxc/restore.go:38
 msgid   "Whether or not to restore the container's running state from snapshot (if available)"
 msgstr  ""
 
@@ -1131,11 +1147,7 @@ msgstr  ""
 msgid   "Whether or not to snapshot the container's running state"
 msgstr  ""
 
-#: lxc/config.go:32
-msgid   "Whether to show the expanded configuration"
-msgstr  ""
-
-#: lxc/remote.go:328 lxc/remote.go:333
+#: lxc/remote.go:327 lxc/remote.go:332
 msgid   "YES"
 msgstr  ""
 
@@ -1155,11 +1167,11 @@ msgstr  ""
 msgid   "can't copy to the same container name"
 msgstr  ""
 
-#: lxc/remote.go:316
+#: lxc/remote.go:315
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:342
+#: lxc/remote.go:341
 msgid   "default"
 msgstr  ""
 
@@ -1167,11 +1179,11 @@ msgstr  ""
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:344
+#: lxc/image.go:343
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:346
+#: lxc/image.go:345
 msgid   "enabled"
 msgstr  ""
 
@@ -1189,7 +1201,7 @@ msgstr  ""
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:339 lxc/image.go:612
+#: lxc/image.go:338 lxc/image.go:611
 msgid   "no"
 msgstr  ""
 
@@ -1197,7 +1209,7 @@ msgstr  ""
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
-#: lxc/remote.go:196
+#: lxc/remote.go:195
 msgid   "ok (y/n)?"
 msgstr  ""
 
@@ -1206,22 +1218,22 @@ msgstr  ""
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:378
+#: lxc/remote.go:377
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:308 lxc/remote.go:370 lxc/remote.go:405 lxc/remote.go:421
+#: lxc/remote.go:307 lxc/remote.go:369 lxc/remote.go:404 lxc/remote.go:420
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:291
+#: lxc/remote.go:290
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
-#: lxc/remote.go:312 lxc/remote.go:374 lxc/remote.go:409
+#: lxc/remote.go:311 lxc/remote.go:373 lxc/remote.go:408
 #, c-format
 msgid   "remote %s is static and cannot be modified"
 msgstr  ""
@@ -1247,7 +1259,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:341 lxc/image.go:616
+#: lxc/delete.go:45 lxc/image.go:340 lxc/image.go:615
 msgid   "yes"
 msgstr  ""
 

From 5c90a418798d42bf9417ad6ef237b2725a20a084 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 15 Dec 2016 23:55:23 +0100
Subject: [PATCH 0577/1193] exec: Exec() return attached PID && take bool arg

Give Exec() an explicit boolean argument "wait" which the user can use to tell
the function whether he wants it to wait on the command or not and return the
attached PID as an additional return value.
It's the callers responsibility to wait on the command. (Note. The PID returned
by Exec() can not be waited upon since it's a child of the lxd forkexec command.
It can however be used to e.g. forward signals.)

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container.go      | 15 ++++++++--
 lxd/container_exec.go | 81 ++++++++++++++++++++++++++++++++++++++++-----------
 lxd/container_lxc.go  | 39 ++++++++++++++++++-------
 lxd/main_forkexec.go  | 41 ++++++++++++++++++++++++--
 4 files changed, 145 insertions(+), 31 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 995140afc..8d4a9af03 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -442,8 +442,19 @@ type container interface {
 	FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error
 	FileRemove(path string) error
 
-	// Command execution
-	Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error)
+	/* Command execution:
+		 * 1. passing in false for wait
+		 *    - equivalent to calling cmd.Run()
+		 * 2. passing in true for wait
+	         *    - start the command and return its PID in the first return
+	         *      argument and the PID of the attached process in the second
+	         *      argument. It's the callers responsibility to wait on the
+	         *      command. (Note. The returned PID of the attached process can not
+	         *      be waited upon since it's a child of the lxd forkexec command
+	         *      (the PID returned in the first return argument). It can however
+	         *      be used to e.g. forward signals.)
+	*/
+	Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, wait bool) (int, int, error)
 
 	// Status
 	Render() (interface{}, error)
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index ba7c7aaaf..0b3e44039 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -9,6 +9,7 @@ import (
 	"strconv"
 	"strings"
 	"sync"
+	"syscall"
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/websocket"
@@ -137,11 +138,14 @@ func (s *execWs) Do(op *operation) error {
 	}
 
 	controlExit := make(chan bool)
+	attachedChildIsBorn := make(chan int)
+	attachedChildIsDead := make(chan bool, 1)
 	var wgEOF sync.WaitGroup
 
 	if s.interactive {
 		wgEOF.Add(1)
 		go func() {
+			<-attachedChildIsBorn
 			select {
 			case <-s.controlConnected:
 				break
@@ -195,6 +199,7 @@ func (s *execWs) Do(op *operation) error {
 				}
 			}
 		}()
+
 		go func() {
 			readDone, writeDone := shared.WebsocketMirror(s.conns[0], ptys[0], ptys[0])
 			<-readDone
@@ -202,6 +207,7 @@ func (s *execWs) Do(op *operation) error {
 			s.conns[0].Close()
 			wgEOF.Done()
 		}()
+
 	} else {
 		wgEOF.Add(len(ttys) - 1)
 		for i := 0; i < len(ttys); i++ {
@@ -218,33 +224,74 @@ func (s *execWs) Do(op *operation) error {
 		}
 	}
 
-	cmdResult, cmdErr := s.container.Exec(s.command, s.env, stdin, stdout, stderr)
+	finisher := func(cmdResult int, cmdErr error) error {
+		for _, tty := range ttys {
+			tty.Close()
+		}
 
-	for _, tty := range ttys {
-		tty.Close()
-	}
+		if s.conns[-1] == nil {
+			if s.interactive {
+				controlExit <- true
+			}
+		} else {
+			s.conns[-1].Close()
+		}
+
+		attachedChildIsDead <- true
 
-	if s.conns[-1] == nil {
-		if s.interactive {
-			controlExit <- true
+		wgEOF.Wait()
+
+		for _, pty := range ptys {
+			pty.Close()
 		}
-	} else {
-		s.conns[-1].Close()
-	}
 
-	wgEOF.Wait()
+		metadata := shared.Jmap{"return": cmdResult}
+		err = op.UpdateMetadata(metadata)
+		if err != nil {
+			return err
+		}
 
-	for _, pty := range ptys {
-		pty.Close()
+		return cmdErr
 	}
 
-	metadata := shared.Jmap{"return": cmdResult}
-	err = op.UpdateMetadata(metadata)
+	pid, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false)
 	if err != nil {
 		return err
 	}
 
-	return cmdErr
+	if s.interactive {
+		attachedChildIsBorn <- attachedPid
+	}
+
+	proc, err := os.FindProcess(pid)
+	if err != nil {
+		return finisher(-1, fmt.Errorf("Failed finding process: %q", err))
+	}
+
+	procState, err := proc.Wait()
+	if err != nil {
+		return finisher(-1, fmt.Errorf("Failed waiting on process %d: %q", pid, err))
+	}
+
+	if procState.Success() {
+		return finisher(0, nil)
+	}
+
+	status, ok := procState.Sys().(syscall.WaitStatus)
+	if ok {
+		if status.Exited() {
+			return finisher(status.ExitStatus(), nil)
+		}
+		// Backwards compatible behavior. Report success when we exited
+		// due to a signal. Otherwise this may break Jenkins, e.g. when
+		// lxc exec foo reboot receives SIGTERM and status.Exitstats()
+		// would report -1.
+		if status.Signaled() {
+			return finisher(0, nil)
+		}
+	}
+
+	return finisher(-1, nil)
 }
 
 func containerExecPost(d *Daemon, r *http.Request) Response {
@@ -337,7 +384,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	}
 
 	run := func(op *operation) error {
-		cmdResult, cmdErr := c.Exec(post.Command, env, nil, nil, nil)
+		cmdResult, _, cmdErr := c.Exec(post.Command, env, nil, nil, nil, true)
 		metadata := shared.Jmap{"return": cmdResult}
 		err = op.UpdateMetadata(metadata)
 		if err != nil {
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index a07d90036..fcbc20a53 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4007,7 +4007,7 @@ func (c *containerLXC) FileRemove(path string) error {
 	return nil
 }
 
-func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File) (int, error) {
+func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, wait bool) (int, int, error) {
 	envSlice := []string{}
 
 	for k, v := range env {
@@ -4031,25 +4031,44 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	cmd.Stdout = stdout
 	cmd.Stderr = stderr
 
-	shared.LogInfo("Executing command", log.Ctx{"environment": envSlice, "args": args})
+	r, w, err := shared.Pipe()
+	defer r.Close()
+	if err != nil {
+		shared.LogErrorf("s", err)
+		return -1, -1, err
+	}
 
-	err := cmd.Run()
+	cmd.ExtraFiles = []*os.File{w}
+	err = cmd.Start()
+	if err != nil {
+		w.Close()
+		return -1, -1, err
+	}
+	w.Close()
+	attachedPid := -1
+	if err := json.NewDecoder(r).Decode(&attachedPid); err != nil {
+		shared.LogErrorf("Failed to retrieve PID of executing child process: %s", err)
+		return -1, -1, err
+	}
+
+	// It's the callers responsibility to wait or not wait.
+	if !wait {
+		return cmd.Process.Pid, attachedPid, nil
+	}
+
+	err = cmd.Wait()
 	if err != nil {
 		exitErr, ok := err.(*exec.ExitError)
 		if ok {
 			status, ok := exitErr.Sys().(syscall.WaitStatus)
 			if ok {
-				shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": args, "exit_status": status.ExitStatus()})
-				return status.ExitStatus(), nil
+				return status.ExitStatus(), attachedPid, nil
 			}
 		}
-
-		shared.LogInfo("Failed executing command", log.Ctx{"environment": envSlice, "args": args, "err": err})
-		return -1, err
+		return -1, -1, err
 	}
 
-	shared.LogInfo("Executed command", log.Ctx{"environment": envSlice, "args": args})
-	return 0, nil
+	return 0, attachedPid, nil
 }
 
 func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk {
diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
index e3ebcc6b6..8ebb0a694 100644
--- a/lxd/main_forkexec.go
+++ b/lxd/main_forkexec.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 	"os"
 	"strings"
@@ -89,10 +90,46 @@ func cmdForkExec(args []string) (int, error) {
 
 	opts.Env = env
 
-	status, err := c.RunCommandStatus(cmd, opts)
+	status, err := c.RunCommandNoWait(cmd, opts)
 	if err != nil {
 		return -1, fmt.Errorf("Failed running command: %q", err)
 	}
+	// Send the PID of the executing process.
+	w := os.NewFile(uintptr(3), "attachedPid")
+	defer w.Close()
 
-	return status >> 8, nil
+	err = json.NewEncoder(w).Encode(status)
+	if err != nil {
+		return -1, fmt.Errorf("Failed sending PID of executing command: %q", err)
+	}
+
+	proc, err := os.FindProcess(status)
+	if err != nil {
+		return -1, fmt.Errorf("Failed finding process: %q", err)
+	}
+
+	procState, err := proc.Wait()
+	if err != nil {
+		return -1, fmt.Errorf("Failed waiting on process %d: %q", status, err)
+	}
+
+	if procState.Success() {
+		return 0, nil
+	}
+
+	exCode, ok := procState.Sys().(syscall.WaitStatus)
+	if ok {
+		if exCode.Exited() {
+			return exCode.ExitStatus(), nil
+		}
+		// Backwards compatible behavior. Report success when we exited
+		// due to a signal. Otherwise this may break Jenkins, e.g. when
+		// lxc exec foo reboot receives SIGTERM and exCode.Exitstats()
+		// would report -1.
+		if exCode.Signaled() {
+			return 0, nil
+		}
+	}
+
+	return -1, fmt.Errorf("Command failed")
 }

From aef1c5a6962d662278b445c2a35d459537946be9 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 8 Dec 2016 14:34:50 +0100
Subject: [PATCH 0578/1193] shared/util_linux: add GetPollRevents()

It's a wrapper around a C function that polls on a single file descriptor and
returns the status and the detected revents.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util_linux.go | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 51e0e438b..0f83573e2 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -25,6 +25,7 @@ import (
 #include <errno.h>
 #include <fcntl.h>
 #include <limits.h>
+#include <poll.h>
 #include <string.h>
 #include <stdio.h>
 
@@ -181,9 +182,50 @@ int shiftowner(char *basepath, char *path, int uid, int gid) {
 	close(fd);
 	return 0;
 }
+
+int get_poll_revents(int lfd, int timeout, int flags, int *revents, int *saved_errno)
+{
+	int ret;
+	struct pollfd pfd = {lfd, flags, 0};
+
+again:
+	ret = poll(&pfd, 1, timeout);
+	if (ret < 0) {
+		if (errno == EINTR)
+			goto again;
+
+		*saved_errno = errno;
+		fprintf(stderr, "Failed to poll() on file descriptor.\n");
+		return -1;
+	}
+
+	*revents = pfd.revents;
+
+	return ret;
+}
 */
 import "C"
 
+const POLLIN int = C.POLLIN
+const POLLPRI int = C.POLLPRI
+const POLLNVAL int = C.POLLNVAL
+const POLLERR int = C.POLLERR
+const POLLHUP int = C.POLLHUP
+const POLLRDHUP int = C.POLLRDHUP
+
+func GetPollRevents(fd int, timeout int, flags int) (int, int, error) {
+	var err error
+	revents := C.int(0)
+	saved_errno := C.int(0)
+
+	ret := C.get_poll_revents(C.int(fd), C.int(timeout), C.int(flags), &revents, &saved_errno)
+	if int(ret) < 0 {
+		err = syscall.Errno(saved_errno)
+	}
+
+	return int(ret), int(revents), err
+}
+
 func ShiftOwner(basepath string, path string, uid int, gid int) error {
 	cbasepath := C.CString(basepath)
 	defer C.free(unsafe.Pointer(cbasepath))

From b1b32b01e46913b0f27fef274c57c66e81201446 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sat, 10 Dec 2016 12:41:13 +0100
Subject: [PATCH 0579/1193] exec: detect background tasks to allow clean exit

This adds the function ExecReaderToChannel() to our linux specific utility
functions. It is the workhorse that reads from the master side of a pty file
descriptor. Background tasks are identified correctly. That means you can run:

	chb at conventiont|~/source/go/bin
	> ./lxc exec xen1 -- bash
	root at xen1:~# sleep infinity &

or

	chb at conventiont|~/source/go/bin
	> ./lxc exec xen1 -- bash
	root at xen1:~# yes &
	[1] 290
	root at xen1:~# y
	root at xen1:~# y
	root at xen1:~# y
	.
	.
	.

and still cleanly exit via "Ctrl+D" or "exit". The function is explained in
detail directly in the code.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util_linux.go | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 181 insertions(+)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 0f83573e2..77e4b23b3 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -6,9 +6,12 @@ package shared
 import (
 	"errors"
 	"fmt"
+	"io"
 	"os"
 	"os/exec"
 	"strings"
+	"sync"
+	"sync/atomic"
 	"syscall"
 	"unsafe"
 )
@@ -524,3 +527,181 @@ func GetAllXattr(path string) (xattrs map[string]string, err error) {
 
 	return xattrs, nil
 }
+
+// Extensively commented directly in the code. Please leave the comments!
+// Looking at this in a couple of months noone will know why and how this works
+// anymore.
+func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int) <-chan []byte {
+	if bufferSize <= (128 * 1024) {
+		bufferSize = (128 * 1024)
+	}
+
+	ch := make(chan ([]byte))
+	mutex := &sync.Mutex{}
+	chanClosed := false
+	onReturn := func() {
+		mutex.Lock()
+		if !chanClosed {
+			chanClosed = true
+			close(ch)
+		}
+		mutex.Unlock()
+	}
+
+	// COMMENT(brauner):
+	// [1]: This function has just one job: Dealing with the case where we
+	// are running an interactive shell session where we put a process in
+	// the background that does hold stdin/stdout open, but does not
+	// generate any output at all. This case cannot be dealt with in the
+	// following function call. Here's why: Assume the above case, now the
+	// attached child (the shell in this example) exits. This will not
+	// generate any poll() event: We won't get POLLHUP because the
+	// background process is holding stdin/stdout open and noone is writing
+	// to it. So we effectively block on GetPollRevents() in the function
+	// below. Hence, we use another go routine here who's only job is to
+	// handle that case: When we detect that the child has exited we check
+	// whether a POLLIN or POLLHUP event has been generated. If not, we know
+	// that there's nothing buffered on stdout and exit.
+	var attachedChildIsDead int32 = 0
+	go func() {
+		<-exited
+
+		atomic.StoreInt32(&attachedChildIsDead, 1)
+
+		ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP))
+		if ret < 0 {
+			LogErrorf("Failed to poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) on file descriptor: %s.", err)
+		} else if ret > 0 {
+			if (revents & POLLERR) > 0 {
+				LogWarnf("Detected poll(POLLERR) event.")
+			}
+		} else if ret == 0 {
+			LogDebugf("No data in stdout: exiting.")
+			onReturn()
+			return
+		}
+	}()
+
+	go func() {
+		readSize := (128 * 1024)
+		offset := 0
+		buf := make([]byte, bufferSize)
+		avoidAtomicLoad := false
+
+		defer onReturn()
+		for {
+			nr := 0
+			var err error
+
+			ret, revents, err := GetPollRevents(fd, -1, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP))
+			if ret < 0 {
+				// COMMENT(brauner):
+				// This condition is only reached in cases where we are massively f*cked since we even handle
+				// EINTR in the underlying C wrapper around poll(). So let's exit here.
+				LogErrorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
+				return
+			}
+
+			// COMMENT(brauner):
+			// [2]: If the process exits before all its data has been read by us and no other process holds stdin or
+			// stdout open, then we will observe a (POLLHUP | POLLRDHUP | POLLIN) event. This means, we need to
+			// keep on reading from the pty file descriptor until we get a simple POLLHUP back.
+			both := ((revents & (POLLIN | POLLPRI)) > 0) && ((revents & (POLLHUP | POLLRDHUP)) > 0)
+			if both {
+				LogDebugf("Detected poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) event.")
+				read := buf[offset : offset+readSize]
+				nr, err = r.Read(read)
+			}
+
+			if (revents & POLLERR) > 0 {
+				LogWarnf("Detected poll(POLLERR) event: exiting.")
+				return
+			}
+
+			if ((revents & (POLLIN | POLLPRI)) > 0) && !both {
+				// COMMENT(brauner):
+				// This might appear unintuitive at first but is actually a nice trick: Assume we are running
+				// a shell session in a container and put a process in the background that is writing to
+				// stdout. Now assume the attached process (aka the shell in this example) exits because we
+				// used Ctrl+D to send EOF or something. If no other process would be holding stdout open we
+				// would expect to observe either a (POLLHUP | POLLRDHUP | POLLIN | POLLPRI) event if there
+				// is still data buffered from the previous process or a simple (POLLHUP | POLLRDHUP) if
+				// no data is buffered. The fact that we only observe a (POLLIN | POLLPRI) event means that
+				// another process is holding stdout open and is writing to it.
+				// One counter argument that can be leveraged is (brauner looks at tycho :))
+				// "Hey, you need to write at least one additional tty buffer to make sure that
+				// everything that the attached child has written is actually shown."
+				// The answer to that is:
+				// "This case can only happen if the process has exited and has left data in stdout which
+				// would generate a (POLLIN | POLLPRI | POLLHUP | POLLRDHUP) event and this case is already
+				// handled and triggers another codepath. (See [2].)"
+				if avoidAtomicLoad || atomic.LoadInt32(&attachedChildIsDead) == 1 {
+					avoidAtomicLoad = true
+					// COMMENT(brauner):
+					// Handle race between atomic.StorInt32() in the go routine
+					// explained in [1] and atomic.LoadInt32() in the go routine
+					// here:
+					// We need to check for (POLLHUP | POLLRDHUP) here again since we might
+					// still be handling a pure POLLIN event from a write prior to the childs
+					// exit. But the child might have exited right before and performed
+					// atomic.StoreInt32() to update attachedChildIsDead before we
+					// performed our atomic.LoadInt32(). This means we accidently hit this
+					// codepath and are misinformed about the available poll() events. So we
+					// need to perform a non-blocking poll() again to exclude that case:
+					//
+					// - If we detect no (POLLHUP | POLLRDHUP) event we know the child
+					//   has already exited but someone else is holding stdin/stdout open and
+					//   writing to it.
+					//   Note that his case should only ever be triggered in situations like
+					//   running a shell and doing stuff like:
+					//    > ./lxc exec xen1 -- bash
+					//   root at xen1:~# yes &
+					//   .
+					//   .
+					//   .
+					//   now send Ctrl+D or type "exit". By the time the Ctrl+D/exit event is
+					//   triggered, we will have read all of the childs data it has written to
+					//   stdout and so we can assume that anything that comes now belongs to
+					//   the process that is holding stdin/stdout open.
+					//
+					// - If we detect a (POLLHUP | POLLRDHUP) event we know that we've
+					//   hit this codepath on accident caused by the race between
+					//   atomic.StoreInt32() in the go routine explained in [1] and
+					//   atomic.LoadInt32() in this go routine. So the next call to
+					//   GetPollRevents() will either return
+					//   (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP)
+					//   or (POLLHUP | POLLRDHUP). Both will trigger another codepath (See [2].)
+					//   that takes care that all data of the child that is buffered in
+					//   stdout is written out.
+					ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP))
+					if ret < 0 {
+						LogErrorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
+						return
+					} else if (revents & (POLLHUP | POLLRDHUP)) == 0 {
+						LogDebugf("Exiting but background processes are still running.")
+						return
+					}
+				}
+				read := buf[offset : offset+readSize]
+				nr, err = r.Read(read)
+			}
+
+			// COMMENT(brauner):
+			// The attached process has exited and we have read all data that may have
+			// been buffered.
+			if ((revents & (POLLHUP | POLLRDHUP)) > 0) && !both {
+				LogDebugf("Detected poll(POLLHUP) event: exiting.")
+				return
+			}
+
+			offset += nr
+			if offset > 0 && (offset+readSize >= bufferSize || err != nil) {
+				ch <- buf[0:offset]
+				offset = 0
+				buf = make([]byte, bufferSize)
+			}
+		}
+	}()
+
+	return ch
+}

From bf619338e221a3b58f1704003db7f21ebd1e9729 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 8 Dec 2016 14:46:10 +0100
Subject: [PATCH 0580/1193] shared/network_linux: ad WebsocketExecMirror()

This function specifically deals with running commands attached to a pty in the
container. We need to put it in a separate file as it is Linux specific.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_exec.go   |   2 +-
 lxd/rsync.go            |   4 +-
 shared/network.go       | 153 +++++++++++++++++++++++++++---------------------
 shared/network_linux.go |  48 +++++++++++++++
 4 files changed, 136 insertions(+), 71 deletions(-)
 create mode 100644 shared/network_linux.go

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 0b3e44039..61017575e 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -201,7 +201,7 @@ func (s *execWs) Do(op *operation) error {
 		}()
 
 		go func() {
-			readDone, writeDone := shared.WebsocketMirror(s.conns[0], ptys[0], ptys[0])
+			readDone, writeDone := shared.WebsocketExecMirror(s.conns[0], ptys[0], ptys[0], attachedChildIsDead, int(ptys[0].Fd()))
 			<-readDone
 			<-writeDone
 			s.conns[0].Close()
diff --git a/lxd/rsync.go b/lxd/rsync.go
index ee58feefe..ba646ee35 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -96,7 +96,7 @@ func RsyncSend(path string, conn *websocket.Conn) error {
 		return err
 	}
 
-	readDone, writeDone := shared.WebsocketMirror(conn, dataSocket, dataSocket)
+	readDone, writeDone := shared.WebsocketMirror(conn, dataSocket, dataSocket, nil, nil)
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
@@ -146,7 +146,7 @@ func RsyncRecv(path string, conn *websocket.Conn) error {
 		return err
 	}
 
-	readDone, writeDone := shared.WebsocketMirror(conn, stdin, stdout)
+	readDone, writeDone := shared.WebsocketMirror(conn, stdin, stdout, nil, nil)
 	data, err2 := ioutil.ReadAll(stderr)
 	if err2 != nil {
 		shared.LogDebugf("error reading rsync stderr: %s", err2)
diff --git a/shared/network.go b/shared/network.go
index 39a7f589e..20b14c054 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -204,84 +204,101 @@ func WebsocketRecvStream(w io.Writer, conn *websocket.Conn) chan bool {
 	return ch
 }
 
+func defaultReader(conn *websocket.Conn, r io.ReadCloser, readDone chan<- bool) {
+	/* For now, we don't need to adjust buffer sizes in
+	* WebsocketMirror, since it's used for interactive things like
+	* exec.
+	 */
+	in := ReaderToChannel(r, -1)
+	for {
+		buf, ok := <-in
+		if !ok {
+			r.Close()
+			LogDebugf("sending write barrier")
+			conn.WriteMessage(websocket.TextMessage, []byte{})
+			readDone <- true
+			return
+		}
+		w, err := conn.NextWriter(websocket.BinaryMessage)
+		if err != nil {
+			LogDebugf("Got error getting next writer %s", err)
+			break
+		}
+
+		_, err = w.Write(buf)
+		w.Close()
+		if err != nil {
+			LogDebugf("Got err writing %s", err)
+			break
+		}
+	}
+	closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
+	conn.WriteMessage(websocket.CloseMessage, closeMsg)
+	readDone <- true
+	r.Close()
+}
+
+func defaultWriter(conn *websocket.Conn, w io.WriteCloser, writeDone chan<- bool) {
+	for {
+		mt, r, err := conn.NextReader()
+		if err != nil {
+			LogDebugf("Got error getting next reader %s, %s", err, w)
+			break
+		}
+
+		if mt == websocket.CloseMessage {
+			LogDebugf("Got close message for reader")
+			break
+		}
+
+		if mt == websocket.TextMessage {
+			LogDebugf("Got message barrier, resetting stream")
+			break
+		}
+
+		buf, err := ioutil.ReadAll(r)
+		if err != nil {
+			LogDebugf("Got error writing to writer %s", err)
+			break
+		}
+		i, err := w.Write(buf)
+		if i != len(buf) {
+			LogDebugf("Didn't write all of buf")
+			break
+		}
+		if err != nil {
+			LogDebugf("Error writing buf %s", err)
+			break
+		}
+	}
+	writeDone <- true
+	w.Close()
+}
+
 // WebsocketMirror allows mirroring a reader to a websocket and taking the
 // result and writing it to a writer. This function allows for multiple
 // mirrorings and correctly negotiates stream endings. However, it means any
 // websocket.Conns passed to it are live when it returns, and must be closed
 // explicitly.
-func WebsocketMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser) (chan bool, chan bool) {
+type WebSocketMirrorReader func(conn *websocket.Conn, r io.ReadCloser, readDone chan<- bool)
+type WebSocketMirrorWriter func(conn *websocket.Conn, w io.WriteCloser, writeDone chan<- bool)
+
+func WebsocketMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser, Reader WebSocketMirrorReader, Writer WebSocketMirrorWriter) (chan bool, chan bool) {
 	readDone := make(chan bool, 1)
 	writeDone := make(chan bool, 1)
-	go func(conn *websocket.Conn, w io.WriteCloser) {
-		for {
-			mt, r, err := conn.NextReader()
-			if err != nil {
-				LogDebugf("Got error getting next reader %s, %s", err, w)
-				break
-			}
 
-			if mt == websocket.CloseMessage {
-				LogDebugf("Got close message for reader")
-				break
-			}
-
-			if mt == websocket.TextMessage {
-				LogDebugf("Got message barrier, resetting stream")
-				break
-			}
+	ReadFunc := Reader
+	if ReadFunc == nil {
+		ReadFunc = defaultReader
+	}
 
-			buf, err := ioutil.ReadAll(r)
-			if err != nil {
-				LogDebugf("Got error writing to writer %s", err)
-				break
-			}
-			i, err := w.Write(buf)
-			if i != len(buf) {
-				LogDebugf("Didn't write all of buf")
-				break
-			}
-			if err != nil {
-				LogDebugf("Error writing buf %s", err)
-				break
-			}
-		}
-		writeDone <- true
-		w.Close()
-	}(conn, w)
-
-	go func(conn *websocket.Conn, r io.ReadCloser) {
-		/* For now, we don't need to adjust buffer sizes in
-		 * WebsocketMirror, since it's used for interactive things like
-		 * exec.
-		 */
-		in := ReaderToChannel(r, -1)
-		for {
-			buf, ok := <-in
-			if !ok {
-				r.Close()
-				LogDebugf("sending write barrier")
-				conn.WriteMessage(websocket.TextMessage, []byte{})
-				readDone <- true
-				return
-			}
-			w, err := conn.NextWriter(websocket.BinaryMessage)
-			if err != nil {
-				LogDebugf("Got error getting next writer %s", err)
-				break
-			}
+	WriteFunc := Writer
+	if WriteFunc == nil {
+		WriteFunc = defaultWriter
+	}
 
-			_, err = w.Write(buf)
-			w.Close()
-			if err != nil {
-				LogDebugf("Got err writing %s", err)
-				break
-			}
-		}
-		closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
-		conn.WriteMessage(websocket.CloseMessage, closeMsg)
-		readDone <- true
-		r.Close()
-	}(conn, r)
+	go ReadFunc(conn, r, readDone)
+	go WriteFunc(conn, w, writeDone)
 
 	return readDone, writeDone
 }
diff --git a/shared/network_linux.go b/shared/network_linux.go
new file mode 100644
index 000000000..3bee79274
--- /dev/null
+++ b/shared/network_linux.go
@@ -0,0 +1,48 @@
+// +build linux
+
+package shared
+
+import (
+	"io"
+
+	"github.com/gorilla/websocket"
+)
+
+func WebsocketExecMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser, exited chan bool, fd int) (chan bool, chan bool) {
+	readDone := make(chan bool, 1)
+	writeDone := make(chan bool, 1)
+
+	go defaultWriter(conn, w, writeDone)
+
+	go func(conn *websocket.Conn, r io.ReadCloser) {
+		in := ExecReaderToChannel(r, -1, exited, fd)
+		for {
+			buf, ok := <-in
+			if !ok {
+				r.Close()
+				LogDebugf("sending write barrier")
+				conn.WriteMessage(websocket.TextMessage, []byte{})
+				readDone <- true
+				return
+			}
+			w, err := conn.NextWriter(websocket.BinaryMessage)
+			if err != nil {
+				LogDebugf("Got error getting next writer %s", err)
+				break
+			}
+
+			_, err = w.Write(buf)
+			w.Close()
+			if err != nil {
+				LogDebugf("Got err writing %s", err)
+				break
+			}
+		}
+		closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
+		conn.WriteMessage(websocket.CloseMessage, closeMsg)
+		readDone <- true
+		r.Close()
+	}(conn, r)
+
+	return readDone, writeDone
+}

From 91b39c3ec77ebbb4bc2c7ac4d33de5062838e637 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 13 Dec 2016 02:35:23 +0100
Subject: [PATCH 0581/1193] util_linux: ExecReaderToChannel() use sync.Once

Takes care that the closeChannel() function is exactly executed once. This
allows us to avoid using a mutex.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util_linux.go | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 77e4b23b3..8ef5b82a1 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -537,15 +537,12 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 	}
 
 	ch := make(chan ([]byte))
-	mutex := &sync.Mutex{}
-	chanClosed := false
-	onReturn := func() {
-		mutex.Lock()
-		if !chanClosed {
-			chanClosed = true
-			close(ch)
-		}
-		mutex.Unlock()
+
+	// Takes care that the closeChannel() function is exactly executed once.
+	// This allows us to avoid using a mutex.
+	var once sync.Once
+	closeChannel := func() {
+		close(ch)
 	}
 
 	// COMMENT(brauner):
@@ -577,7 +574,7 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 			}
 		} else if ret == 0 {
 			LogDebugf("No data in stdout: exiting.")
-			onReturn()
+			once.Do(closeChannel)
 			return
 		}
 	}()
@@ -588,7 +585,7 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 		buf := make([]byte, bufferSize)
 		avoidAtomicLoad := false
 
-		defer onReturn()
+		defer once.Do(closeChannel)
 		for {
 			nr := 0
 			var err error

From 6402d6d4e4bc82f9265fc34139cf5edf737d0368 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 18:20:08 -0500
Subject: [PATCH 0582/1193] daemon: Common codepath for http client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 40 ++++++++++++++--------------------------
 lxd/images.go | 14 ++------------
 2 files changed, 16 insertions(+), 38 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 1f91fc127..8602fffc0 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -105,10 +105,10 @@ type Command struct {
 	delete        func(d *Daemon, r *http.Request) Response
 }
 
-func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) {
+func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
 	var err error
-
 	var cert *x509.Certificate
+
 	if certificate != "" {
 		certBlock, _ := pem.Decode([]byte(certificate))
 		if certBlock == nil {
@@ -137,6 +137,12 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 		Transport: tr,
 	}
 
+	return &myhttp, nil
+}
+
+func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) {
+	var err error
+
 	req, err := http.NewRequest("GET", url, nil)
 	if err != nil {
 		return nil, err
@@ -144,6 +150,11 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 
 	req.Header.Set("User-Agent", shared.UserAgent)
 
+	myhttp, err := d.httpClient(certificate)
+	if err != nil {
+		return nil, err
+	}
+
 	r, err := myhttp.Do(req)
 	if err != nil {
 		return nil, err
@@ -164,34 +175,11 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, error) {
 	var err error
 
-	var cert *x509.Certificate
-	if certificate != "" {
-		certBlock, _ := pem.Decode([]byte(certificate))
-		if certBlock == nil {
-			return nil, fmt.Errorf("Invalid certificate")
-		}
-
-		cert, err = x509.ParseCertificate(certBlock.Bytes)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	tlsConfig, err := shared.GetTLSConfig("", "", cert)
+	myhttp, err := d.httpClient(certificate)
 	if err != nil {
 		return nil, err
 	}
 
-	tr := &http.Transport{
-		TLSClientConfig:   tlsConfig,
-		Dial:              shared.RFC3493Dialer,
-		Proxy:             d.proxy,
-		DisableKeepAlives: true,
-	}
-	myhttp := http.Client{
-		Transport: tr,
-	}
-
 	req, err := http.NewRequest("GET", url, nil)
 	if err != nil {
 		return nil, err
diff --git a/lxd/images.go b/lxd/images.go
index db138de75..c0a6bb944 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -367,22 +367,12 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 		return fmt.Errorf("Missing URL")
 	}
 
-	// Resolve the image URL
-	tlsConfig, err := shared.GetTLSConfig("", "", nil)
+	myhttp, err := d.httpClient("")
 	if err != nil {
 		return err
 	}
 
-	tr := &http.Transport{
-		TLSClientConfig: tlsConfig,
-		Dial:            shared.RFC3493Dialer,
-		Proxy:           d.proxy,
-	}
-
-	myhttp := http.Client{
-		Transport: tr,
-	}
-
+	// Resolve the image URL
 	head, err := http.NewRequest("HEAD", req.Source["url"], nil)
 	if err != nil {
 		return err

From eee6d2ff09f9784be76952a12c3b1b7c87750820 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 15 Nov 2016 20:39:41 -0500
Subject: [PATCH 0583/1193] Track speed during network transfers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go               |  2 +-
 lxc/image.go            |  4 ++--
 lxd/daemon_images.go    |  4 ++--
 shared/simplestreams.go |  4 ++--
 shared/util.go          | 22 +++++++++++++++++++---
 5 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/client.go b/client.go
index e37163572..18d1eae5b 100644
--- a/client.go
+++ b/client.go
@@ -939,7 +939,7 @@ func (c *Client) PostImageURL(imageFile string, properties []string, public bool
 	return fingerprint, nil
 }
 
-func (c *Client) PostImage(imageFile string, rootfsFile string, properties []string, public bool, aliases []string, progressHandler func(percent int)) (string, error) {
+func (c *Client) PostImage(imageFile string, rootfsFile string, properties []string, public bool, aliases []string, progressHandler func(int, int)) (string, error) {
 	if c.Remote.Public {
 		return "", fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxc/image.go b/lxc/image.go
index 3f8ef1fa1..914a8193f 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -429,8 +429,8 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("Only https:// is supported for remote image import."))
 		} else {
 			progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")}
-			handler := func(percent int) {
-				progress.Update(fmt.Sprintf("%d%%", percent))
+			handler := func(percent int, speed int) {
+				progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(int64(speed))))
 			}
 
 			fingerprint, err = d.PostImage(imageFile, rootfsFile, properties, c.publicImage, c.addAliases, handler)
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 876bd7ab0..5007d1307 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -247,7 +247,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		d.Storage.ImageDelete(fp)
 	}
 
-	progress := func(progressInt int) {
+	progress := func(progressInt int, speedInt int) {
 		if op == nil {
 			return
 		}
@@ -257,7 +257,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			meta = make(map[string]interface{})
 		}
 
-		progress := fmt.Sprintf("%d%%", progressInt)
+		progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(int64(speedInt)))
 
 		if meta["download_progress"] != progress {
 			meta["download_progress"] = progress
diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index ab9c7da34..3f933e573 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -511,7 +511,7 @@ func (s *SimpleStreams) getPaths(fingerprint string) ([][]string, error) {
 	return nil, fmt.Errorf("Couldn't find the requested image")
 }
 
-func (s *SimpleStreams) downloadFile(path string, hash string, target string, progress func(int)) error {
+func (s *SimpleStreams) downloadFile(path string, hash string, target string, progress func(int, int)) error {
 	download := func(url string, hash string, target string) error {
 		out, err := os.Create(target)
 		if err != nil {
@@ -640,7 +640,7 @@ func (s *SimpleStreams) ExportImage(image string, target string) (string, error)
 	return target, nil
 }
 
-func (s *SimpleStreams) Download(image string, file string, target string, progress func(int)) error {
+func (s *SimpleStreams) Download(image string, file string, target string, progress func(int, int)) error {
 	paths, err := s.getPaths(image)
 	if err != nil {
 		return err
diff --git a/shared/util.go b/shared/util.go
index 72f6bf987..37fd867f8 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -19,6 +19,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"time"
 )
 
 const SnapshotDelimiter = "/"
@@ -722,8 +723,10 @@ type TransferProgress struct {
 	percentage float64
 	total      int64
 
+	start *time.Time
+
 	Length  int64
-	Handler func(int)
+	Handler func(int, int)
 }
 
 func (pt *TransferProgress) Read(p []byte) (int, error) {
@@ -733,19 +736,32 @@ func (pt *TransferProgress) Read(p []byte) (int, error) {
 		return n, err
 	}
 
+	if pt.start == nil {
+		cur := time.Now()
+		pt.start = &cur
+	}
+
 	if n > 0 {
 		pt.total += int64(n)
 		percentage := float64(pt.total) / float64(pt.Length) * float64(100)
 
 		if percentage-pt.percentage > 0.9 {
+			// Determine percentage
 			pt.percentage = percentage
-
 			progressInt := 1 - (int(percentage) % 1) + int(percentage)
 			if progressInt > 100 {
 				progressInt = 100
 			}
 
-			pt.Handler(progressInt)
+			// Determine speed
+			speedInt := int(0)
+			duration := time.Since(*pt.start).Seconds()
+			if duration > 0 {
+				speed := float64(pt.total) / duration
+				speedInt = int(speed)
+			}
+
+			pt.Handler(progressInt, speedInt)
 		}
 	}
 

From 559be79cb3e6e809d686dccd38646dd8fb5ef97d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 16 Nov 2016 15:13:16 -0500
Subject: [PATCH 0584/1193] Support absolute file transfer tracking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

For when we don't know the total length.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go               |  2 +-
 lxc/image.go            |  4 +--
 lxd/daemon_images.go    |  4 +--
 shared/simplestreams.go |  4 +--
 shared/util.go          | 67 +++++++++++++++++++++++++++++++++++--------------
 5 files changed, 55 insertions(+), 26 deletions(-)

diff --git a/client.go b/client.go
index 18d1eae5b..5b6a8c04e 100644
--- a/client.go
+++ b/client.go
@@ -939,7 +939,7 @@ func (c *Client) PostImageURL(imageFile string, properties []string, public bool
 	return fingerprint, nil
 }
 
-func (c *Client) PostImage(imageFile string, rootfsFile string, properties []string, public bool, aliases []string, progressHandler func(int, int)) (string, error) {
+func (c *Client) PostImage(imageFile string, rootfsFile string, properties []string, public bool, aliases []string, progressHandler func(int64, int64)) (string, error) {
 	if c.Remote.Public {
 		return "", fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxc/image.go b/lxc/image.go
index 914a8193f..b9e118c4b 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -429,8 +429,8 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("Only https:// is supported for remote image import."))
 		} else {
 			progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")}
-			handler := func(percent int, speed int) {
-				progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(int64(speed))))
+			handler := func(percent int64, speed int64) {
+				progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed)))
 			}
 
 			fingerprint, err = d.PostImage(imageFile, rootfsFile, properties, c.publicImage, c.addAliases, handler)
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 5007d1307..fe1941319 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -247,7 +247,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		d.Storage.ImageDelete(fp)
 	}
 
-	progress := func(progressInt int, speedInt int) {
+	progress := func(progressInt int64, speedInt int64) {
 		if op == nil {
 			return
 		}
@@ -257,7 +257,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			meta = make(map[string]interface{})
 		}
 
-		progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(int64(speedInt)))
+		progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(speedInt))
 
 		if meta["download_progress"] != progress {
 			meta["download_progress"] = progress
diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index 3f933e573..9ad089fdf 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -511,7 +511,7 @@ func (s *SimpleStreams) getPaths(fingerprint string) ([][]string, error) {
 	return nil, fmt.Errorf("Couldn't find the requested image")
 }
 
-func (s *SimpleStreams) downloadFile(path string, hash string, target string, progress func(int, int)) error {
+func (s *SimpleStreams) downloadFile(path string, hash string, target string, progress func(int64, int64)) error {
 	download := func(url string, hash string, target string) error {
 		out, err := os.Create(target)
 		if err != nil {
@@ -640,7 +640,7 @@ func (s *SimpleStreams) ExportImage(image string, target string) (string, error)
 	return target, nil
 }
 
-func (s *SimpleStreams) Download(image string, file string, target string, progress func(int, int)) error {
+func (s *SimpleStreams) Download(image string, file string, target string, progress func(int64, int64)) error {
 	paths, err := s.getPaths(image)
 	if err != nil {
 		return err
diff --git a/shared/util.go b/shared/util.go
index 37fd867f8..8fb0be540 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -724,47 +724,76 @@ type TransferProgress struct {
 	total      int64
 
 	start *time.Time
+	last  *time.Time
 
 	Length  int64
-	Handler func(int, int)
+	Handler func(int64, int64)
 }
 
 func (pt *TransferProgress) Read(p []byte) (int, error) {
+	// Do normal reader tasks
 	n, err := pt.Reader.Read(p)
+	pt.total += int64(n)
 
+	// Skip the rest if no handler attached
 	if pt.Handler == nil {
 		return n, err
 	}
 
+	// Initialize start time if needed
 	if pt.start == nil {
 		cur := time.Now()
 		pt.start = &cur
+		pt.last = pt.start
 	}
 
-	if n > 0 {
-		pt.total += int64(n)
-		percentage := float64(pt.total) / float64(pt.Length) * float64(100)
+	// Skip if no data to count
+	if n <= 0 {
+		return n, err
+	}
 
-		if percentage-pt.percentage > 0.9 {
-			// Determine percentage
-			pt.percentage = percentage
-			progressInt := 1 - (int(percentage) % 1) + int(percentage)
-			if progressInt > 100 {
-				progressInt = 100
-			}
+	// Update interval handling
+	var percentage float64
+	if pt.Length > 0 {
+		// If running in relative mode, check that we increased by at least 1%
+		percentage = float64(pt.total) / float64(pt.Length) * float64(100)
+		if percentage-pt.percentage < 0.9 {
+			return n, err
+		}
+	} else {
+		// If running in absolute mode, check that at least a second elapsed
+		interval := time.Since(*pt.last).Seconds()
+		if interval < 1 {
+			return n, err
+		}
+	}
 
-			// Determine speed
-			speedInt := int(0)
-			duration := time.Since(*pt.start).Seconds()
-			if duration > 0 {
-				speed := float64(pt.total) / duration
-				speedInt = int(speed)
-			}
+	// Determine speed
+	speedInt := int64(0)
+	duration := time.Since(*pt.start).Seconds()
+	if duration > 0 {
+		speed := float64(pt.total) / duration
+		speedInt = int64(speed)
+	}
 
-			pt.Handler(progressInt, speedInt)
+	// Determine progress
+	progressInt := int64(0)
+	if pt.Length > 0 {
+		pt.percentage = percentage
+		progressInt = int64(1 - (int(percentage) % 1) + int(percentage))
+		if progressInt > 100 {
+			progressInt = 100
 		}
+	} else {
+		progressInt = pt.total
+
+		// Update timestamp
+		cur := time.Now()
+		pt.last = &cur
 	}
 
+	pt.Handler(progressInt, speedInt)
+
 	return n, err
 }
 

From 834f574549f630dc76f3b85b0c89dc722ed2307f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 16 Nov 2016 15:22:59 -0500
Subject: [PATCH 0585/1193] Convert TransferProgress to ReadCloser
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go               | 4 ++--
 lxd/daemon_images.go    | 2 +-
 shared/simplestreams.go | 2 +-
 shared/util.go          | 4 ++--
 4 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/client.go b/client.go
index 5b6a8c04e..6004e0e1c 100644
--- a/client.go
+++ b/client.go
@@ -1006,7 +1006,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 			return "", err
 		}
 
-		progress := &shared.TransferProgress{Reader: body, Length: size, Handler: progressHandler}
+		progress := &shared.TransferProgress{ReadCloser: body, Length: size, Handler: progressHandler}
 
 		req, err = http.NewRequest("POST", uri, progress)
 		req.Header.Set("Content-Type", w.FormDataContentType())
@@ -1022,7 +1022,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 			return "", err
 		}
 
-		progress := &shared.TransferProgress{Reader: fImage, Length: stat.Size(), Handler: progressHandler}
+		progress := &shared.TransferProgress{ReadCloser: fImage, Length: stat.Size(), Handler: progressHandler}
 
 		req, err = http.NewRequest("POST", uri, progress)
 		req.Header.Set("X-LXD-filename", filepath.Base(imageFile))
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index fe1941319..7c364b654 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -359,7 +359,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		ctype = "application/octet-stream"
 	}
 
-	body := &shared.TransferProgress{Reader: raw.Body, Length: raw.ContentLength, Handler: progress}
+	body := &shared.TransferProgress{ReadCloser: raw.Body, Length: raw.ContentLength, Handler: progress}
 
 	if ctype == "multipart/form-data" {
 		// Parse the POST data
diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index 9ad089fdf..094a65823 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -535,7 +535,7 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 			return fmt.Errorf("invalid simplestreams source: got %d looking for %s", resp.StatusCode, path)
 		}
 
-		body := &TransferProgress{Reader: resp.Body, Length: resp.ContentLength, Handler: progress}
+		body := &TransferProgress{ReadCloser: resp.Body, Length: resp.ContentLength, Handler: progress}
 
 		sha256 := sha256.New()
 		_, err = io.Copy(io.MultiWriter(out, sha256), body)
diff --git a/shared/util.go b/shared/util.go
index 8fb0be540..a8f898756 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -719,7 +719,7 @@ func GetByteSizeString(input int64) string {
 }
 
 type TransferProgress struct {
-	io.Reader
+	io.ReadCloser
 	percentage float64
 	total      int64
 
@@ -732,7 +732,7 @@ type TransferProgress struct {
 
 func (pt *TransferProgress) Read(p []byte) (int, error) {
 	// Do normal reader tasks
-	n, err := pt.Reader.Read(p)
+	n, err := pt.ReadCloser.Read(p)
 	pt.total += int64(n)
 
 	// Skip the rest if no handler attached

From 41fc0fdf78f8f127b3a6d2fafd1ce5bd462d5cb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 16 Nov 2016 21:03:11 -0500
Subject: [PATCH 0586/1193] Implement write tracking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go               | 16 +++++++++++--
 lxd/daemon_images.go    |  8 ++++++-
 shared/simplestreams.go |  8 ++++++-
 shared/util.go          | 62 +++++++++++++++++++++++++++++++++++--------------
 4 files changed, 73 insertions(+), 21 deletions(-)

diff --git a/client.go b/client.go
index 6004e0e1c..74f0bba47 100644
--- a/client.go
+++ b/client.go
@@ -1006,7 +1006,13 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 			return "", err
 		}
 
-		progress := &shared.TransferProgress{ReadCloser: body, Length: size, Handler: progressHandler}
+		progress := &shared.ProgressReader{
+			ReadCloser: body,
+			Tracker: &shared.ProgressTracker{
+				Length:  size,
+				Handler: progressHandler,
+			},
+		}
 
 		req, err = http.NewRequest("POST", uri, progress)
 		req.Header.Set("Content-Type", w.FormDataContentType())
@@ -1022,7 +1028,13 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 			return "", err
 		}
 
-		progress := &shared.TransferProgress{ReadCloser: fImage, Length: stat.Size(), Handler: progressHandler}
+		progress := &shared.ProgressReader{
+			ReadCloser: fImage,
+			Tracker: &shared.ProgressTracker{
+				Length:  stat.Size(),
+				Handler: progressHandler,
+			},
+		}
 
 		req, err = http.NewRequest("POST", uri, progress)
 		req.Header.Set("X-LXD-filename", filepath.Base(imageFile))
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 7c364b654..f841a6da8 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -359,7 +359,13 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		ctype = "application/octet-stream"
 	}
 
-	body := &shared.TransferProgress{ReadCloser: raw.Body, Length: raw.ContentLength, Handler: progress}
+	body := &shared.ProgressReader{
+		ReadCloser: raw.Body,
+		Tracker: &shared.ProgressTracker{
+			Length:  raw.ContentLength,
+			Handler: progress,
+		},
+	}
 
 	if ctype == "multipart/form-data" {
 		// Parse the POST data
diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index 094a65823..7cd4bd2b2 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -535,7 +535,13 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 			return fmt.Errorf("invalid simplestreams source: got %d looking for %s", resp.StatusCode, path)
 		}
 
-		body := &TransferProgress{ReadCloser: resp.Body, Length: resp.ContentLength, Handler: progress}
+		body := &ProgressReader{
+			ReadCloser: resp.Body,
+			Tracker: &ProgressTracker{
+				Length:  resp.ContentLength,
+				Handler: progress,
+			},
+		}
 
 		sha256 := sha256.New()
 		_, err = io.Copy(io.MultiWriter(out, sha256), body)
diff --git a/shared/util.go b/shared/util.go
index a8f898756..a6d86d6cf 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -718,26 +718,20 @@ func GetByteSizeString(input int64) string {
 	return fmt.Sprintf("%.2fEB", value)
 }
 
-type TransferProgress struct {
-	io.ReadCloser
-	percentage float64
-	total      int64
-
-	start *time.Time
-	last  *time.Time
-
+type ProgressTracker struct {
 	Length  int64
 	Handler func(int64, int64)
-}
 
-func (pt *TransferProgress) Read(p []byte) (int, error) {
-	// Do normal reader tasks
-	n, err := pt.ReadCloser.Read(p)
-	pt.total += int64(n)
+	percentage float64
+	total      int64
+	start      *time.Time
+	last       *time.Time
+}
 
+func (pt *ProgressTracker) Update(n int) {
 	// Skip the rest if no handler attached
 	if pt.Handler == nil {
-		return n, err
+		return
 	}
 
 	// Initialize start time if needed
@@ -749,7 +743,7 @@ func (pt *TransferProgress) Read(p []byte) (int, error) {
 
 	// Skip if no data to count
 	if n <= 0 {
-		return n, err
+		return
 	}
 
 	// Update interval handling
@@ -758,13 +752,13 @@ func (pt *TransferProgress) Read(p []byte) (int, error) {
 		// If running in relative mode, check that we increased by at least 1%
 		percentage = float64(pt.total) / float64(pt.Length) * float64(100)
 		if percentage-pt.percentage < 0.9 {
-			return n, err
+			return
 		}
 	} else {
 		// If running in absolute mode, check that at least a second elapsed
 		interval := time.Since(*pt.last).Seconds()
 		if interval < 1 {
-			return n, err
+			return
 		}
 	}
 
@@ -793,6 +787,40 @@ func (pt *TransferProgress) Read(p []byte) (int, error) {
 	}
 
 	pt.Handler(progressInt, speedInt)
+}
+
+type ProgressReader struct {
+	io.ReadCloser
+	Tracker *ProgressTracker
+}
+
+func (pt *ProgressReader) Read(p []byte) (int, error) {
+	// Do normal reader tasks
+	n, err := pt.ReadCloser.Read(p)
+
+	// Do the actual progress tracking
+	if pt.Tracker != nil {
+		pt.Tracker.total += int64(n)
+		pt.Tracker.Update(n)
+	}
+
+	return n, err
+}
+
+type ProgressWriter struct {
+	io.WriteCloser
+	Tracker *ProgressTracker
+}
+
+func (pt *ProgressWriter) Write(p []byte) (int, error) {
+	// Do normal writer tasks
+	n, err := pt.WriteCloser.Write(p)
+
+	// Do the actual progress tracking
+	if pt.Tracker != nil {
+		pt.Tracker.total += int64(n)
+		pt.Tracker.Update(n)
+	}
 
 	return n, err
 }

From 6b9a34d4ad2c6ecf61123d89e97836a27bbf8854 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 17:18:44 -0500
Subject: [PATCH 0587/1193] shared: Give IO progress tracker its own package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                    |   9 ++--
 lxd/daemon_images.go         |   5 +-
 shared/ioprogress/reader.go  |  23 +++++++++
 shared/ioprogress/tracker.go |  76 ++++++++++++++++++++++++++++++
 shared/ioprogress/writer.go  |  23 +++++++++
 shared/simplestreams.go      |   6 ++-
 shared/util.go               | 108 -------------------------------------------
 7 files changed, 134 insertions(+), 116 deletions(-)
 create mode 100644 shared/ioprogress/reader.go
 create mode 100644 shared/ioprogress/tracker.go
 create mode 100644 shared/ioprogress/writer.go

diff --git a/client.go b/client.go
index 74f0bba47..c4aad0663 100644
--- a/client.go
+++ b/client.go
@@ -24,6 +24,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/ioprogress"
 )
 
 // Client can talk to a LXD daemon.
@@ -1006,9 +1007,9 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 			return "", err
 		}
 
-		progress := &shared.ProgressReader{
+		progress := &ioprogress.ProgressReader{
 			ReadCloser: body,
-			Tracker: &shared.ProgressTracker{
+			Tracker: &ioprogress.ProgressTracker{
 				Length:  size,
 				Handler: progressHandler,
 			},
@@ -1028,9 +1029,9 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 			return "", err
 		}
 
-		progress := &shared.ProgressReader{
+		progress := &ioprogress.ProgressReader{
 			ReadCloser: fImage,
-			Tracker: &shared.ProgressTracker{
+			Tracker: &ioprogress.ProgressTracker{
 				Length:  stat.Size(),
 				Handler: progressHandler,
 			},
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index f841a6da8..6ce67990b 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -16,6 +16,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/ioprogress"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -359,9 +360,9 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		ctype = "application/octet-stream"
 	}
 
-	body := &shared.ProgressReader{
+	body := &ioprogress.ProgressReader{
 		ReadCloser: raw.Body,
-		Tracker: &shared.ProgressTracker{
+		Tracker: &ioprogress.ProgressTracker{
 			Length:  raw.ContentLength,
 			Handler: progress,
 		},
diff --git a/shared/ioprogress/reader.go b/shared/ioprogress/reader.go
new file mode 100644
index 000000000..262aa40a0
--- /dev/null
+++ b/shared/ioprogress/reader.go
@@ -0,0 +1,23 @@
+package ioprogress
+
+import (
+	"io"
+)
+
+type ProgressReader struct {
+	io.ReadCloser
+	Tracker *ProgressTracker
+}
+
+func (pt *ProgressReader) Read(p []byte) (int, error) {
+	// Do normal reader tasks
+	n, err := pt.ReadCloser.Read(p)
+
+	// Do the actual progress tracking
+	if pt.Tracker != nil {
+		pt.Tracker.total += int64(n)
+		pt.Tracker.Update(n)
+	}
+
+	return n, err
+}
diff --git a/shared/ioprogress/tracker.go b/shared/ioprogress/tracker.go
new file mode 100644
index 000000000..78c730b2d
--- /dev/null
+++ b/shared/ioprogress/tracker.go
@@ -0,0 +1,76 @@
+package ioprogress
+
+import (
+	"time"
+)
+
+type ProgressTracker struct {
+	Length  int64
+	Handler func(int64, int64)
+
+	percentage float64
+	total      int64
+	start      *time.Time
+	last       *time.Time
+}
+
+func (pt *ProgressTracker) Update(n int) {
+	// Skip the rest if no handler attached
+	if pt.Handler == nil {
+		return
+	}
+
+	// Initialize start time if needed
+	if pt.start == nil {
+		cur := time.Now()
+		pt.start = &cur
+		pt.last = pt.start
+	}
+
+	// Skip if no data to count
+	if n <= 0 {
+		return
+	}
+
+	// Update interval handling
+	var percentage float64
+	if pt.Length > 0 {
+		// If running in relative mode, check that we increased by at least 1%
+		percentage = float64(pt.total) / float64(pt.Length) * float64(100)
+		if percentage-pt.percentage < 0.9 {
+			return
+		}
+	} else {
+		// If running in absolute mode, check that at least a second elapsed
+		interval := time.Since(*pt.last).Seconds()
+		if interval < 1 {
+			return
+		}
+	}
+
+	// Determine speed
+	speedInt := int64(0)
+	duration := time.Since(*pt.start).Seconds()
+	if duration > 0 {
+		speed := float64(pt.total) / duration
+		speedInt = int64(speed)
+	}
+
+	// Determine progress
+	progressInt := int64(0)
+	if pt.Length > 0 {
+		pt.percentage = percentage
+		progressInt = int64(1 - (int(percentage) % 1) + int(percentage))
+		if progressInt > 100 {
+			progressInt = 100
+		}
+	} else {
+		progressInt = pt.total
+
+		// Update timestamp
+		cur := time.Now()
+		pt.last = &cur
+	}
+
+	pt.Handler(progressInt, speedInt)
+}
diff --git a/shared/ioprogress/writer.go b/shared/ioprogress/writer.go
new file mode 100644
index 000000000..708911b28
--- /dev/null
+++ b/shared/ioprogress/writer.go
@@ -0,0 +1,23 @@
+package ioprogress
+
+import (
+	"io"
+)
+
+type ProgressWriter struct {
+	io.WriteCloser
+	Tracker *ProgressTracker
+}
+
+func (pt *ProgressWriter) Write(p []byte) (int, error) {
+	// Do normal writer tasks
+	n, err := pt.WriteCloser.Write(p)
+
+	// Do the actual progress tracking
+	if pt.Tracker != nil {
+		pt.Tracker.total += int64(n)
+		pt.Tracker.Update(n)
+	}
+
+	return n, err
+}
diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index 7cd4bd2b2..10ab656a7 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -13,6 +13,8 @@ import (
 	"sort"
 	"strings"
 	"time"
+
+	"github.com/lxc/lxd/shared/ioprogress"
 )
 
 type ssSortImage []ImageInfo
@@ -535,9 +537,9 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 			return fmt.Errorf("invalid simplestreams source: got %d looking for %s", resp.StatusCode, path)
 		}
 
-		body := &ProgressReader{
+		body := &ioprogress.ProgressReader{
 			ReadCloser: resp.Body,
-			Tracker: &ProgressTracker{
+			Tracker: &ioprogress.ProgressTracker{
 				Length:  resp.ContentLength,
 				Handler: progress,
 			},
diff --git a/shared/util.go b/shared/util.go
index a6d86d6cf..a221c650e 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -19,7 +19,6 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
-	"time"
 )
 
 const SnapshotDelimiter = "/"
@@ -718,113 +717,6 @@ func GetByteSizeString(input int64) string {
 	return fmt.Sprintf("%.2fEB", value)
 }
 
-type ProgressTracker struct {
-	Length  int64
-	Handler func(int64, int64)
-
-	percentage float64
-	total      int64
-	start      *time.Time
-	last       *time.Time
-}
-
-func (pt *ProgressTracker) Update(n int) {
-	// Skip the rest if no handler attached
-	if pt.Handler == nil {
-		return
-	}
-
-	// Initialize start time if needed
-	if pt.start == nil {
-		cur := time.Now()
-		pt.start = &cur
-		pt.last = pt.start
-	}
-
-	// Skip if no data to count
-	if n <= 0 {
-		return
-	}
-
-	// Update interval handling
-	var percentage float64
-	if pt.Length > 0 {
-		// If running in relative mode, check that we increased by at least 1%
-		percentage = float64(pt.total) / float64(pt.Length) * float64(100)
-		if percentage-pt.percentage < 0.9 {
-			return
-		}
-	} else {
-		// If running in absolute mode, check that at least a second elapsed
-		interval := time.Since(*pt.last).Seconds()
-		if interval < 1 {
-			return
-		}
-	}
-
-	// Determine speed
-	speedInt := int64(0)
-	duration := time.Since(*pt.start).Seconds()
-	if duration > 0 {
-		speed := float64(pt.total) / duration
-		speedInt = int64(speed)
-	}
-
-	// Determine progress
-	progressInt := int64(0)
-	if pt.Length > 0 {
-		pt.percentage = percentage
-		progressInt = int64(1 - (int(percentage) % 1) + int(percentage))
-		if progressInt > 100 {
-			progressInt = 100
-		}
-	} else {
-		progressInt = pt.total
-
-		// Update timestamp
-		cur := time.Now()
-		pt.last = &cur
-	}
-
-	pt.Handler(progressInt, speedInt)
-}
-
-type ProgressReader struct {
-	io.ReadCloser
-	Tracker *ProgressTracker
-}
-
-func (pt *ProgressReader) Read(p []byte) (int, error) {
-	// Do normal reader tasks
-	n, err := pt.ReadCloser.Read(p)
-
-	// Do the actual progress tracking
-	if pt.Tracker != nil {
-		pt.Tracker.total += int64(n)
-		pt.Tracker.Update(n)
-	}
-
-	return n, err
-}
-
-type ProgressWriter struct {
-	io.WriteCloser
-	Tracker *ProgressTracker
-}
-
-func (pt *ProgressWriter) Write(p []byte) (int, error) {
-	// Do normal writer tasks
-	n, err := pt.WriteCloser.Write(p)
-
-	// Do the actual progress tracking
-	if pt.Tracker != nil {
-		pt.Tracker.total += int64(n)
-		pt.Tracker.Update(n)
-	}
-
-	return n, err
-}
-
 func RunCommand(name string, arg ...string) error {
 	output, err := exec.Command(name, arg...).CombinedOutput()
 	if err != nil {

From e63d45b0574b1666ecf9af75cda483aed666f5e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 17:19:29 -0500
Subject: [PATCH 0588/1193] shared: Give simplestreams client its own package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                                   |  5 ++-
 lxd/daemon_images.go                        |  9 +++--
 shared/{ => simplestreams}/simplestreams.go | 63 +++++++++++++++--------------
 3 files changed, 40 insertions(+), 37 deletions(-)
 rename shared/{ => simplestreams}/simplestreams.go (89%)

diff --git a/client.go b/client.go
index c4aad0663..1fc776115 100644
--- a/client.go
+++ b/client.go
@@ -25,6 +25,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
+	"github.com/lxc/lxd/shared/simplestreams"
 )
 
 // Client can talk to a LXD daemon.
@@ -39,7 +40,7 @@ type Client struct {
 
 	Http            http.Client
 	websocketDialer websocket.Dialer
-	simplestreams   *shared.SimpleStreams
+	simplestreams   *simplestreams.SimpleStreams
 }
 
 type ResponseType string
@@ -316,7 +317,7 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 	}
 
 	if info.RemoteConfig.Protocol == "simplestreams" {
-		ss, err := shared.SimpleStreamsClient(c.Remote.Addr, shared.ProxyFromEnvironment)
+		ss, err := simplestreams.SimpleStreamsClient(c.Remote.Addr, shared.ProxyFromEnvironment)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 6ce67990b..592823310 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -17,6 +17,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
+	"github.com/lxc/lxd/shared/simplestreams"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -26,7 +27,7 @@ type imageStreamCacheEntry struct {
 	Aliases      shared.ImageAliases `yaml:"aliases"`
 	Fingerprints []string            `yaml:"fingerprints"`
 	expiry       time.Time
-	ss           *shared.SimpleStreams
+	ss           *simplestreams.SimpleStreams
 }
 
 var imageStreamCache = map[string]*imageStreamCacheEntry{}
@@ -66,7 +67,7 @@ func imageLoadStreamCache(d *Daemon) error {
 
 	for url, entry := range imageStreamCache {
 		if entry.ss == nil {
-			ss, err := shared.SimpleStreamsClient(url, d.proxy)
+			ss, err := simplestreams.SimpleStreamsClient(url, d.proxy)
 			if err != nil {
 				return err
 			}
@@ -82,7 +83,7 @@ func imageLoadStreamCache(d *Daemon) error {
 // downloads the image from a remote server.
 func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) {
 	var err error
-	var ss *shared.SimpleStreams
+	var ss *simplestreams.SimpleStreams
 	var ctxMap log.Ctx
 
 	if protocol == "" {
@@ -98,7 +99,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		if entry == nil || entry.expiry.Before(time.Now()) {
 			refresh := func() (*imageStreamCacheEntry, error) {
 				// Setup simplestreams client
-				ss, err = shared.SimpleStreamsClient(server, d.proxy)
+				ss, err = simplestreams.SimpleStreamsClient(server, d.proxy)
 				if err != nil {
 					return nil, err
 				}
diff --git a/shared/simplestreams.go b/shared/simplestreams/simplestreams.go
similarity index 89%
rename from shared/simplestreams.go
rename to shared/simplestreams/simplestreams.go
index 10ab656a7..5b977ceab 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -1,4 +1,4 @@
-package shared
+package simplestreams
 
 import (
 	"crypto/sha256"
@@ -14,10 +14,11 @@ import (
 	"strings"
 	"time"
 
+	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
 )
 
-type ssSortImage []ImageInfo
+type ssSortImage []shared.ImageInfo
 
 func (a ssSortImage) Len() int {
 	return len(a)
@@ -75,21 +76,21 @@ type SimpleStreamsManifest struct {
 	Products map[string]SimpleStreamsManifestProduct `json:"products"`
 }
 
-func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
+func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]string) {
 	downloads := map[string][][]string{}
 
-	images := []ImageInfo{}
+	images := []shared.ImageInfo{}
 	nameLayout := "20060102"
 	eolLayout := "2006-01-02"
 
 	for _, product := range s.Products {
 		// Skip unsupported architectures
-		architecture, err := ArchitectureId(product.Architecture)
+		architecture, err := shared.ArchitectureId(product.Architecture)
 		if err != nil {
 			continue
 		}
 
-		architectureName, err := ArchitectureName(architecture)
+		architectureName, err := shared.ArchitectureName(architecture)
 		if err != nil {
 			continue
 		}
@@ -117,7 +118,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 			found := 0
 			for _, item := range version.Items {
 				// Skip the files we don't care about
-				if !StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz", "squashfs"}) {
+				if !shared.StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz", "squashfs"}) {
 					continue
 				}
 				found += 1
@@ -168,7 +169,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 			}
 			description = fmt.Sprintf("%s (%s)", description, name)
 
-			image := ImageInfo{}
+			image := shared.ImageInfo{}
 			image.Architecture = architectureName
 			image.Public = true
 			image.Size = size
@@ -188,9 +189,9 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 
 			// Add the provided aliases
 			if product.Aliases != "" {
-				image.Aliases = []ImageAlias{}
+				image.Aliases = []shared.ImageAlias{}
 				for _, entry := range strings.Split(product.Aliases, ",") {
-					image.Aliases = append(image.Aliases, ImageAlias{Name: entry})
+					image.Aliases = append(image.Aliases, shared.ImageAlias{Name: entry})
 				}
 			}
 
@@ -263,14 +264,14 @@ type SimpleStreamsIndexStream struct {
 
 func SimpleStreamsClient(url string, proxy func(*http.Request) (*url.URL, error)) (*SimpleStreams, error) {
 	// Setup a http client
-	tlsConfig, err := GetTLSConfig("", "", nil)
+	tlsConfig, err := shared.GetTLSConfig("", "", nil)
 	if err != nil {
 		return nil, err
 	}
 
 	tr := &http.Transport{
 		TLSClientConfig: tlsConfig,
-		Dial:            RFC3493Dialer,
+		Dial:            shared.RFC3493Dialer,
 		Proxy:           proxy,
 	}
 
@@ -290,8 +291,8 @@ type SimpleStreams struct {
 
 	cachedIndex    *SimpleStreamsIndex
 	cachedManifest map[string]*SimpleStreamsManifest
-	cachedImages   []ImageInfo
-	cachedAliases  map[string]*ImageAliasesEntry
+	cachedImages   []shared.ImageInfo
+	cachedAliases  map[string]*shared.ImageAliasesEntry
 }
 
 func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) {
@@ -303,7 +304,7 @@ func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) {
 	if err != nil {
 		return nil, err
 	}
-	req.Header.Set("User-Agent", UserAgent)
+	req.Header.Set("User-Agent", shared.UserAgent)
 
 	r, err := s.http.Do(req)
 	if err != nil {
@@ -337,7 +338,7 @@ func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, erro
 	if err != nil {
 		return nil, err
 	}
-	req.Header.Set("User-Agent", UserAgent)
+	req.Header.Set("User-Agent", shared.UserAgent)
 
 	r, err := s.http.Do(req)
 	if err != nil {
@@ -362,8 +363,8 @@ func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, erro
 	return &ssManifest, nil
 }
 
-func (s *SimpleStreams) applyAliases(images []ImageInfo) ([]ImageInfo, map[string]*ImageAliasesEntry, error) {
-	aliases := map[string]*ImageAliasesEntry{}
+func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) {
+	aliases := map[string]*shared.ImageAliasesEntry{}
 
 	sort.Sort(ssSortImage(images))
 
@@ -375,7 +376,7 @@ func (s *SimpleStreams) applyAliases(images []ImageInfo) ([]ImageInfo, map[strin
 		}
 	}
 
-	addAlias := func(name string, fingerprint string) *ImageAlias {
+	addAlias := func(name string, fingerprint string) *shared.ImageAlias {
 		if defaultOS != "" {
 			name = strings.TrimPrefix(name, fmt.Sprintf("%s/", defaultOS))
 		}
@@ -384,17 +385,17 @@ func (s *SimpleStreams) applyAliases(images []ImageInfo) ([]ImageInfo, map[strin
 			return nil
 		}
 
-		alias := ImageAliasesEntry{}
+		alias := shared.ImageAliasesEntry{}
 		alias.Name = name
 		alias.Target = fingerprint
 		aliases[name] = &alias
 
-		return &ImageAlias{Name: name}
+		return &shared.ImageAlias{Name: name}
 	}
 
-	architectureName, _ := ArchitectureGetLocal()
+	architectureName, _ := shared.ArchitectureGetLocal()
 
-	newImages := []ImageInfo{}
+	newImages := []shared.ImageInfo{}
 	for _, image := range images {
 		if image.Aliases != nil {
 			// Build a new list of aliases from the provided ones
@@ -424,12 +425,12 @@ func (s *SimpleStreams) applyAliases(images []ImageInfo) ([]ImageInfo, map[strin
 	return newImages, aliases, nil
 }
 
-func (s *SimpleStreams) getImages() ([]ImageInfo, map[string]*ImageAliasesEntry, error) {
+func (s *SimpleStreams) getImages() ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) {
 	if s.cachedImages != nil && s.cachedAliases != nil {
 		return s.cachedImages, s.cachedAliases, nil
 	}
 
-	images := []ImageInfo{}
+	images := []shared.ImageInfo{}
 
 	// Load the main index
 	ssIndex, err := s.parseIndex()
@@ -525,7 +526,7 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 		if err != nil {
 			return err
 		}
-		req.Header.Set("User-Agent", UserAgent)
+		req.Header.Set("User-Agent", shared.UserAgent)
 
 		resp, err := s.http.Do(req)
 		if err != nil {
@@ -576,13 +577,13 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 	return nil
 }
 
-func (s *SimpleStreams) ListAliases() (ImageAliases, error) {
+func (s *SimpleStreams) ListAliases() (shared.ImageAliases, error) {
 	_, aliasesMap, err := s.getImages()
 	if err != nil {
 		return nil, err
 	}
 
-	aliases := ImageAliases{}
+	aliases := shared.ImageAliases{}
 
 	for _, alias := range aliasesMap {
 		aliases = append(aliases, *alias)
@@ -591,7 +592,7 @@ func (s *SimpleStreams) ListAliases() (ImageAliases, error) {
 	return aliases, nil
 }
 
-func (s *SimpleStreams) ListImages() ([]ImageInfo, error) {
+func (s *SimpleStreams) ListImages() ([]shared.ImageInfo, error) {
 	images, _, err := s.getImages()
 	return images, err
 }
@@ -610,7 +611,7 @@ func (s *SimpleStreams) GetAlias(name string) string {
 	return alias.Target
 }
 
-func (s *SimpleStreams) GetImageInfo(fingerprint string) (*ImageInfo, error) {
+func (s *SimpleStreams) GetImageInfo(fingerprint string) (*shared.ImageInfo, error) {
 	images, _, err := s.getImages()
 	if err != nil {
 		return nil, err
@@ -626,7 +627,7 @@ func (s *SimpleStreams) GetImageInfo(fingerprint string) (*ImageInfo, error) {
 }
 
 func (s *SimpleStreams) ExportImage(image string, target string) (string, error) {
-	if !IsDir(target) {
+	if !shared.IsDir(target) {
 		return "", fmt.Errorf("Split images can only be written to a directory.")
 	}
 

From 86afc897125e9ceef8bb5a17e491f5241bf04cbe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 17:54:53 -0500
Subject: [PATCH 0589/1193] shared: Give Architecture handling its own package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/api_1.0.go                              |  3 ++-
 lxd/container.go                            |  3 ++-
 lxd/container_lxc.go                        | 19 ++++++++++---------
 lxd/container_put.go                        |  4 +++-
 lxd/containers_post.go                      |  8 +++++---
 lxd/daemon.go                               |  7 ++++---
 lxd/db_images.go                            |  7 ++++---
 lxd/images.go                               |  5 +++--
 shared/{ => osarch}/architectures.go        |  2 +-
 shared/{ => osarch}/architectures_linux.go  |  2 +-
 shared/{ => osarch}/architectures_others.go |  2 +-
 shared/simplestreams/simplestreams.go       |  7 ++++---
 12 files changed, 40 insertions(+), 29 deletions(-)
 rename shared/{ => osarch}/architectures.go (99%)
 rename shared/{ => osarch}/architectures_linux.go (96%)
 rename shared/{ => osarch}/architectures_others.go (86%)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 521786f2c..92c355bd7 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -11,6 +11,7 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/osarch"
 )
 
 var api10 = []Command{
@@ -114,7 +115,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		architectures := []string{}
 
 		for _, architecture := range d.architectures {
-			architectureName, err := shared.ArchitectureName(architecture)
+			architectureName, err := osarch.ArchitectureName(architecture)
 			if err != nil {
 				return InternalError(err)
 			}
diff --git a/lxd/container.go b/lxd/container.go
index 8d4a9af03..0afbf2192 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -11,6 +11,7 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/osarch"
 )
 
 // Helper functions
@@ -689,7 +690,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Validate architecture
-	_, err = shared.ArchitectureName(args.Architecture)
+	_, err = osarch.ArchitectureName(args.Architecture)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fcbc20a53..02b6e5d06 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -25,6 +25,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -864,9 +865,9 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup architecture
-	personality, err := shared.ArchitecturePersonality(c.architecture)
+	personality, err := osarch.ArchitecturePersonality(c.architecture)
 	if err != nil {
-		personality, err = shared.ArchitecturePersonality(c.daemon.architectures[0])
+		personality, err = osarch.ArchitecturePersonality(c.daemon.architectures[0])
 		if err != nil {
 			return err
 		}
@@ -2189,7 +2190,7 @@ func (c *containerLXC) Render() (interface{}, error) {
 	}
 
 	// Ignore err as the arch string on error is correct (unknown)
-	architectureName, _ := shared.ArchitectureName(c.architecture)
+	architectureName, _ := osarch.ArchitectureName(c.architecture)
 
 	if c.IsSnapshot() {
 		return &shared.SnapshotInfo{
@@ -2600,7 +2601,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 
 	// Validate the new architecture
 	if args.Architecture != 0 {
-		_, err = shared.ArchitectureName(args.Architecture)
+		_, err = osarch.ArchitectureName(args.Architecture)
 		if err != nil {
 			return fmt.Errorf("Invalid architecture id: %s", err)
 		}
@@ -3237,13 +3238,13 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 				return err
 			}
 
-			arch, _ = shared.ArchitectureName(parent.Architecture())
+			arch, _ = osarch.ArchitectureName(parent.Architecture())
 		} else {
-			arch, _ = shared.ArchitectureName(c.architecture)
+			arch, _ = osarch.ArchitectureName(c.architecture)
 		}
 
 		if arch == "" {
-			arch, err = shared.ArchitectureName(c.daemon.architectures[0])
+			arch, err = osarch.ArchitectureName(c.daemon.architectures[0])
 			if err != nil {
 				shared.LogError("Failed exporting container", ctxMap)
 				return err
@@ -3660,9 +3661,9 @@ func (c *containerLXC) templateApplyNow(trigger string) error {
 		}
 
 		// Figure out the architecture
-		arch, err := shared.ArchitectureName(c.architecture)
+		arch, err := osarch.ArchitectureName(c.architecture)
 		if err != nil {
-			arch, err = shared.ArchitectureName(c.daemon.architectures[0])
+			arch, err = osarch.ArchitectureName(c.daemon.architectures[0])
 			if err != nil {
 				return err
 			}
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 530b74268..4dc73fd50 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -7,7 +7,9 @@ import (
 	"net/http"
 
 	"github.com/gorilla/mux"
+
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -37,7 +39,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
-	architecture, err := shared.ArchitectureId(configRaw.Architecture)
+	architecture, err := osarch.ArchitectureId(configRaw.Architecture)
 	if err != nil {
 		architecture = 0
 	}
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 208098d71..adf01930d 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -10,7 +10,9 @@ import (
 
 	"github.com/dustinkirkland/golang-petname"
 	"github.com/gorilla/websocket"
+
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -137,7 +139,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 
 		hash = imgInfo.Fingerprint
 
-		architecture, err := shared.ArchitectureId(imgInfo.Architecture)
+		architecture, err := osarch.ArchitectureId(imgInfo.Architecture)
 		if err != nil {
 			architecture = 0
 		}
@@ -169,7 +171,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 }
 
 func createFromNone(d *Daemon, req *containerPostReq) Response {
-	architecture, err := shared.ArchitectureId(req.Architecture)
+	architecture, err := osarch.ArchitectureId(req.Architecture)
 	if err != nil {
 		architecture = 0
 	}
@@ -205,7 +207,7 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 		return NotImplemented
 	}
 
-	architecture, err := shared.ArchitectureId(req.Architecture)
+	architecture, err := osarch.ArchitectureId(req.Architecture)
 	if err != nil {
 		architecture = 0
 	}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 8602fffc0..0c7c96282 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -32,6 +32,7 @@ import (
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logging"
+	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -697,18 +698,18 @@ func (d *Daemon) Init() error {
 	/* Get the list of supported architectures */
 	var architectures = []int{}
 
-	architectureName, err := shared.ArchitectureGetLocal()
+	architectureName, err := osarch.ArchitectureGetLocal()
 	if err != nil {
 		return err
 	}
 
-	architecture, err := shared.ArchitectureId(architectureName)
+	architecture, err := osarch.ArchitectureId(architectureName)
 	if err != nil {
 		return err
 	}
 	architectures = append(architectures, architecture)
 
-	personalities, err := shared.ArchitecturePersonalities(architecture)
+	personalities, err := osarch.ArchitecturePersonalities(architecture)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/db_images.go b/lxd/db_images.go
index 76ce7bb81..ac515a3d0 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -8,6 +8,7 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/osarch"
 )
 
 var dbImageSourceProtocol = map[int]string{
@@ -175,7 +176,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 		image.LastUsedDate = time.Time{}
 	}
 
-	image.Architecture, _ = shared.ArchitectureName(arch)
+	image.Architecture, _ = osarch.ArchitectureName(arch)
 
 	// The upload date is enforced by NOT NULL in the schema, so it can never be nil.
 	image.UploadDate = *upload
@@ -312,7 +313,7 @@ func dbImageLastAccessInit(db *sql.DB, fingerprint string) error {
 }
 
 func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error {
-	arch, err := shared.ArchitectureId(architecture)
+	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
 	}
@@ -369,7 +370,7 @@ func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, auto
 }
 
 func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error {
-	arch, err := shared.ArchitectureId(architecture)
+	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
 	}
diff --git a/lxd/images.go b/lxd/images.go
index c0a6bb944..43340ac8f 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -24,6 +24,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logging"
+	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -311,7 +312,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq,
 		return info, err
 	}
 
-	info.Architecture, _ = shared.ArchitectureName(c.Architecture())
+	info.Architecture, _ = osarch.ArchitectureName(c.Architecture())
 	info.Properties = req.Properties
 
 	return info, nil
@@ -796,7 +797,7 @@ func getImageMetadata(fname string) (*imageMetadata, error) {
 		return nil, fmt.Errorf("Could not parse %s: %v", metadataName, err)
 	}
 
-	_, err = shared.ArchitectureId(metadata.Architecture)
+	_, err = osarch.ArchitectureId(metadata.Architecture)
 	if err != nil {
 		return nil, err
 	}
diff --git a/shared/architectures.go b/shared/osarch/architectures.go
similarity index 99%
rename from shared/architectures.go
rename to shared/osarch/architectures.go
index 0f4f9ba83..728098cc3 100644
--- a/shared/architectures.go
+++ b/shared/osarch/architectures.go
@@ -1,4 +1,4 @@
-package shared
+package osarch
 
 import (
 	"fmt"
diff --git a/shared/architectures_linux.go b/shared/osarch/architectures_linux.go
similarity index 96%
rename from shared/architectures_linux.go
rename to shared/osarch/architectures_linux.go
index 0cc82ff36..c95b58a73 100644
--- a/shared/architectures_linux.go
+++ b/shared/osarch/architectures_linux.go
@@ -1,6 +1,6 @@
 // +build linux
 
-package shared
+package osarch
 
 import (
 	"syscall"
diff --git a/shared/architectures_others.go b/shared/osarch/architectures_others.go
similarity index 86%
rename from shared/architectures_others.go
rename to shared/osarch/architectures_others.go
index 5609316bb..22bd1eaf7 100644
--- a/shared/architectures_others.go
+++ b/shared/osarch/architectures_others.go
@@ -1,6 +1,6 @@
 // +build !linux
 
-package shared
+package osarch
 
 func ArchitectureGetLocal() (string, error) {
 	return ArchitectureDefault, nil
diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index 5b977ceab..18b17acb2 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
+	"github.com/lxc/lxd/shared/osarch"
 )
 
 type ssSortImage []shared.ImageInfo
@@ -85,12 +86,12 @@ func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]stri
 
 	for _, product := range s.Products {
 		// Skip unsupported architectures
-		architecture, err := shared.ArchitectureId(product.Architecture)
+		architecture, err := osarch.ArchitectureId(product.Architecture)
 		if err != nil {
 			continue
 		}
 
-		architectureName, err := shared.ArchitectureName(architecture)
+		architectureName, err := osarch.ArchitectureName(architecture)
 		if err != nil {
 			continue
 		}
@@ -393,7 +394,7 @@ func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageI
 		return &shared.ImageAlias{Name: name}
 	}
 
-	architectureName, _ := shared.ArchitectureGetLocal()
+	architectureName, _ := osarch.ArchitectureGetLocal()
 
 	newImages := []shared.ImageInfo{}
 	for _, image := range images {

From 1fcc133f46f9693933d0c8dcca726cec67364ba1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 18:23:48 -0500
Subject: [PATCH 0590/1193] simplestreams: Don't depend on custom http handler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                             | 15 ++++++++++++++-
 lxd/daemon_images.go                  | 14 ++++++++++++--
 shared/simplestreams/simplestreams.go | 21 ++-------------------
 3 files changed, 28 insertions(+), 22 deletions(-)

diff --git a/client.go b/client.go
index 1fc776115..58a6cc613 100644
--- a/client.go
+++ b/client.go
@@ -317,7 +317,20 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 	}
 
 	if info.RemoteConfig.Protocol == "simplestreams" {
-		ss, err := simplestreams.SimpleStreamsClient(c.Remote.Addr, shared.ProxyFromEnvironment)
+		tlsconfig, err := shared.GetTLSConfig("", "", nil)
+		if err != nil {
+			return nil, err
+		}
+
+		tr := &http.Transport{
+			TLSClientConfig:   tlsconfig,
+			Dial:              shared.RFC3493Dialer,
+			Proxy:             shared.ProxyFromEnvironment,
+			DisableKeepAlives: true,
+		}
+		c.Http.Transport = tr
+
+		ss, err := simplestreams.NewClient(c.Remote.Addr, c.Http)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 592823310..c2dd4f6f2 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -67,7 +67,12 @@ func imageLoadStreamCache(d *Daemon) error {
 
 	for url, entry := range imageStreamCache {
 		if entry.ss == nil {
-			ss, err := simplestreams.SimpleStreamsClient(url, d.proxy)
+			myhttp, err := d.httpClient("")
+			if err != nil {
+				return err
+			}
+
+			ss, err := simplestreams.NewClient(url, *myhttp)
 			if err != nil {
 				return err
 			}
@@ -99,7 +104,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		if entry == nil || entry.expiry.Before(time.Now()) {
 			refresh := func() (*imageStreamCacheEntry, error) {
 				// Setup simplestreams client
-				ss, err = simplestreams.SimpleStreamsClient(server, d.proxy)
+				myhttp, err := d.httpClient(certificate)
+				if err != nil {
+					return nil, err
+				}
+
+				ss, err = simplestreams.NewClient(server, *myhttp)
 				if err != nil {
 					return nil, err
 				}
diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index 18b17acb2..fd45e4aeb 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"io/ioutil"
 	"net/http"
-	"net/url"
 	"os"
 	"path/filepath"
 	"sort"
@@ -263,25 +262,9 @@ type SimpleStreamsIndexStream struct {
 	Products []string `json:"products"`
 }
 
-func SimpleStreamsClient(url string, proxy func(*http.Request) (*url.URL, error)) (*SimpleStreams, error) {
-	// Setup a http client
-	tlsConfig, err := shared.GetTLSConfig("", "", nil)
-	if err != nil {
-		return nil, err
-	}
-
-	tr := &http.Transport{
-		TLSClientConfig: tlsConfig,
-		Dial:            shared.RFC3493Dialer,
-		Proxy:           proxy,
-	}
-
-	myHttp := http.Client{
-		Transport: tr,
-	}
-
+func NewClient(url string, httpClient http.Client) (*SimpleStreams, error) {
 	return &SimpleStreams{
-		http:           &myHttp,
+		http:           &httpClient,
 		url:            url,
 		cachedManifest: map[string]*SimpleStreamsManifest{}}, nil
 }

From b9750ff1ad4476e70ec4ca14569df4b4183310d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Dec 2016 15:27:12 -0500
Subject: [PATCH 0591/1193] simplestreams: Pass UserAgent as argument
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                             |  6 +-----
 lxd/daemon_images.go                  | 11 ++---------
 shared/simplestreams/simplestreams.go | 26 +++++++++++++++++++-------
 3 files changed, 22 insertions(+), 21 deletions(-)

diff --git a/client.go b/client.go
index 58a6cc613..a6856c762 100644
--- a/client.go
+++ b/client.go
@@ -330,11 +330,7 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 		}
 		c.Http.Transport = tr
 
-		ss, err := simplestreams.NewClient(c.Remote.Addr, c.Http)
-		if err != nil {
-			return nil, err
-		}
-
+		ss := simplestreams.NewClient(c.Remote.Addr, c.Http, shared.UserAgent)
 		c.simplestreams = ss
 	}
 
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index c2dd4f6f2..6afe492f5 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -72,11 +72,7 @@ func imageLoadStreamCache(d *Daemon) error {
 				return err
 			}
 
-			ss, err := simplestreams.NewClient(url, *myhttp)
-			if err != nil {
-				return err
-			}
-
+			ss := simplestreams.NewClient(url, *myhttp, shared.UserAgent)
 			entry.ss = ss
 		}
 	}
@@ -109,10 +105,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 					return nil, err
 				}
 
-				ss, err = simplestreams.NewClient(server, *myhttp)
-				if err != nil {
-					return nil, err
-				}
+				ss = simplestreams.NewClient(server, *myhttp, shared.UserAgent)
 
 				// Get all aliases
 				aliases, err := ss.ListAliases()
diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index fd45e4aeb..9be22d7ea 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -262,16 +262,19 @@ type SimpleStreamsIndexStream struct {
 	Products []string `json:"products"`
 }
 
-func NewClient(url string, httpClient http.Client) (*SimpleStreams, error) {
+func NewClient(url string, httpClient http.Client, useragent string) *SimpleStreams {
 	return &SimpleStreams{
 		http:           &httpClient,
 		url:            url,
-		cachedManifest: map[string]*SimpleStreamsManifest{}}, nil
+		cachedManifest: map[string]*SimpleStreamsManifest{},
+		useragent:      useragent,
+	}
 }
 
 type SimpleStreams struct {
-	http *http.Client
-	url  string
+	http      *http.Client
+	url       string
+	useragent string
 
 	cachedIndex    *SimpleStreamsIndex
 	cachedManifest map[string]*SimpleStreamsManifest
@@ -288,7 +291,10 @@ func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) {
 	if err != nil {
 		return nil, err
 	}
-	req.Header.Set("User-Agent", shared.UserAgent)
+
+	if s.useragent != "" {
+		req.Header.Set("User-Agent", s.useragent)
+	}
 
 	r, err := s.http.Do(req)
 	if err != nil {
@@ -322,7 +328,10 @@ func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, erro
 	if err != nil {
 		return nil, err
 	}
-	req.Header.Set("User-Agent", shared.UserAgent)
+
+	if s.useragent != "" {
+		req.Header.Set("User-Agent", s.useragent)
+	}
 
 	r, err := s.http.Do(req)
 	if err != nil {
@@ -510,7 +519,10 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 		if err != nil {
 			return err
 		}
-		req.Header.Set("User-Agent", shared.UserAgent)
+
+		if s.useragent != "" {
+			req.Header.Set("User-Agent", s.useragent)
+		}
 
 		resp, err := s.http.Do(req)
 		if err != nil {

From 6092377f6e8fd3e67b8f8a11958f1f67678f81d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Dec 2016 15:36:53 -0500
Subject: [PATCH 0592/1193] shared: Give version handling its own package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile                     |  2 +-
 client.go                    | 35 ++++++++++++++++++-----------------
 lxc/launch.go                |  7 ++++---
 lxc/version.go               |  4 ++--
 lxd/api_1.0.go               |  5 +++--
 lxd/certificates.go          |  5 +++--
 lxd/container_logs.go        |  3 ++-
 lxd/container_snapshot.go    |  3 ++-
 lxd/containers_get.go        |  3 ++-
 lxd/daemon.go                | 11 ++++++-----
 lxd/daemon_images.go         | 13 +++++++------
 lxd/devlxd.go                |  3 ++-
 lxd/images.go                | 13 +++++++------
 lxd/main.go                  |  3 ++-
 lxd/networks.go              |  5 +++--
 lxd/operations.go            |  5 +++--
 lxd/profiles.go              |  7 ++++---
 lxd/remote.go                |  3 ++-
 shared/{ => version}/flex.go |  2 +-
 19 files changed, 74 insertions(+), 58 deletions(-)
 rename shared/{ => version}/flex.go (94%)

diff --git a/Makefile b/Makefile
index bfb774df0..8424ce962 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ POTFILE=po/$(DOMAIN).pot
 # dist is primarily for use when packaging; for development we still manage
 # dependencies via `go get` explicitly.
 # TODO: use git describe for versioning
-VERSION=$(shell grep "var Version" shared/flex.go | cut -d'"' -f2)
+VERSION=$(shell grep "var Version" shared/version/flex.go | cut -d'"' -f2)
 ARCHIVE=lxd-$(VERSION).tar
 
 .PHONY: default
diff --git a/client.go b/client.go
index a6856c762..f5f661397 100644
--- a/client.go
+++ b/client.go
@@ -26,6 +26,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/simplestreams"
+	"github.com/lxc/lxd/shared/version"
 )
 
 // Client can talk to a LXD daemon.
@@ -330,7 +331,7 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 		}
 		c.Http.Transport = tr
 
-		ss := simplestreams.NewClient(c.Remote.Addr, c.Http, shared.UserAgent)
+		ss := simplestreams.NewClient(c.Remote.Addr, c.Http, version.UserAgent)
 		c.simplestreams = ss
 	}
 
@@ -370,7 +371,7 @@ func (c *Client) Addresses() ([]string, error) {
 }
 
 func (c *Client) get(base string) (*Response, error) {
-	uri := c.url(shared.APIVersion, base)
+	uri := c.url(version.APIVersion, base)
 
 	return c.baseGet(uri)
 }
@@ -381,7 +382,7 @@ func (c *Client) baseGet(getUrl string) (*Response, error) {
 		return nil, err
 	}
 
-	req.Header.Set("User-Agent", shared.UserAgent)
+	req.Header.Set("User-Agent", version.UserAgent)
 
 	resp, err := c.Http.Do(req)
 	if err != nil {
@@ -392,7 +393,7 @@ func (c *Client) baseGet(getUrl string) (*Response, error) {
 }
 
 func (c *Client) doUpdateMethod(method string, base string, args interface{}, rtype ResponseType) (*Response, error) {
-	uri := c.url(shared.APIVersion, base)
+	uri := c.url(version.APIVersion, base)
 
 	buf := bytes.Buffer{}
 	err := json.NewEncoder(&buf).Encode(args)
@@ -406,7 +407,7 @@ func (c *Client) doUpdateMethod(method string, base string, args interface{}, rt
 	if err != nil {
 		return nil, err
 	}
-	req.Header.Set("User-Agent", shared.UserAgent)
+	req.Header.Set("User-Agent", version.UserAgent)
 	req.Header.Set("Content-Type", "application/json")
 
 	resp, err := c.Http.Do(req)
@@ -438,7 +439,7 @@ func (c *Client) getRaw(uri string) (*http.Response, error) {
 	if err != nil {
 		return nil, err
 	}
-	req.Header.Set("User-Agent", shared.UserAgent)
+	req.Header.Set("User-Agent", version.UserAgent)
 
 	raw, err := c.Http.Do(req)
 	if err != nil {
@@ -499,7 +500,7 @@ func (c *Client) GetServerConfig() (*Response, error) {
 		return nil, fmt.Errorf("This function isn't supported by simplestreams remote.")
 	}
 
-	return c.baseGet(c.url(shared.APIVersion))
+	return c.baseGet(c.url(version.APIVersion))
 }
 
 // GetLocalLXDErr determines whether or not an error is likely due to a
@@ -747,7 +748,7 @@ func (c *Client) ExportImage(image string, target string) (string, error) {
 		return c.simplestreams.ExportImage(image, target)
 	}
 
-	uri := c.url(shared.APIVersion, "images", image, "export")
+	uri := c.url(version.APIVersion, "images", image, "export")
 	raw, err := c.getRaw(uri)
 	if err != nil {
 		return "", err
@@ -955,7 +956,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 		return "", fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	uri := c.url(shared.APIVersion, "images")
+	uri := c.url(version.APIVersion, "images")
 
 	var err error
 	var fImage *os.File
@@ -1055,7 +1056,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 	if err != nil {
 		return "", err
 	}
-	req.Header.Set("User-Agent", shared.UserAgent)
+	req.Header.Set("User-Agent", version.UserAgent)
 
 	if public {
 		req.Header.Set("X-LXD-public", "1")
@@ -1713,7 +1714,7 @@ func (c *Client) GetLog(container string, log string) (io.Reader, error) {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	uri := c.url(shared.APIVersion, "containers", container, "logs", log)
+	uri := c.url(version.APIVersion, "containers", container, "logs", log)
 	resp, err := c.getRaw(uri)
 	if err != nil {
 		return nil, err
@@ -1747,13 +1748,13 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode str
 	}
 
 	query := url.Values{"path": []string{p}}
-	uri := c.url(shared.APIVersion, "containers", container, "files") + "?" + query.Encode()
+	uri := c.url(version.APIVersion, "containers", container, "files") + "?" + query.Encode()
 
 	req, err := http.NewRequest("POST", uri, buf)
 	if err != nil {
 		return err
 	}
-	req.Header.Set("User-Agent", shared.UserAgent)
+	req.Header.Set("User-Agent", version.UserAgent)
 
 	if mode != "" {
 		req.Header.Set("X-LXD-mode", mode)
@@ -1779,7 +1780,7 @@ func (c *Client) PullFile(container string, p string) (int, int, int, io.ReadClo
 		return 0, 0, 0, nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	uri := c.url(shared.APIVersion, "containers", container, "files")
+	uri := c.url(version.APIVersion, "containers", container, "files")
 	query := url.Values{"path": []string{p}}
 
 	r, err := c.getRaw(uri + "?" + query.Encode())
@@ -2169,9 +2170,9 @@ func (c *Client) ListProfiles() ([]string, error) {
 
 	for _, url := range result {
 		toScan := strings.Replace(url, "/", " ", -1)
-		version := ""
+		urlVersion := ""
 		name := ""
-		count, err := fmt.Sscanf(toScan, " %s profiles %s", &version, &name)
+		count, err := fmt.Sscanf(toScan, " %s profiles %s", &urlVersion, &name)
 		if err != nil {
 			return nil, err
 		}
@@ -2180,7 +2181,7 @@ func (c *Client) ListProfiles() ([]string, error) {
 			return nil, fmt.Errorf("bad profile url %s", url)
 		}
 
-		if version != shared.APIVersion {
+		if urlVersion != version.APIVersion {
 			return nil, fmt.Errorf("bad version in profile url")
 		}
 
diff --git a/lxc/launch.go b/lxc/launch.go
index 70ada54a8..22df8eed3 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -8,6 +8,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
+	"github.com/lxc/lxd/shared/version"
 )
 
 type launchCmd struct {
@@ -101,9 +102,9 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
 		}
 
-		var version string
+		var restVersion string
 		toScan := strings.Replace(containers[0], "/", " ", -1)
-		count, err := fmt.Sscanf(toScan, " %s containers %s", &version, &name)
+		count, err := fmt.Sscanf(toScan, " %s containers %s", &restVersion, &name)
 		if err != nil {
 			return err
 		}
@@ -112,7 +113,7 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("bad number of things scanned from image, container or snapshot"))
 		}
 
-		if version != shared.APIVersion {
+		if restVersion != version.APIVersion {
 			return fmt.Errorf(i18n.G("got bad version"))
 		}
 	}
diff --git a/lxc/version.go b/lxc/version.go
index 87118494e..57e9919b4 100644
--- a/lxc/version.go
+++ b/lxc/version.go
@@ -4,8 +4,8 @@ import (
 	"fmt"
 
 	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/i18n"
+	"github.com/lxc/lxd/shared/version"
 )
 
 type versionCmd struct{}
@@ -28,6 +28,6 @@ func (c *versionCmd) run(_ *lxd.Config, args []string) error {
 	if len(args) > 0 {
 		return errArgs
 	}
-	fmt.Println(shared.Version)
+	fmt.Println(version.Version)
 	return nil
 }
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 92c355bd7..8de95cbd0 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
+	"github.com/lxc/lxd/shared/version"
 )
 
 var api10 = []Command{
@@ -60,7 +61,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		},
 
 		"api_status":  "stable",
-		"api_version": shared.APIVersion,
+		"api_version": version.APIVersion,
 	}
 
 	if d.isTrustedClient(r) {
@@ -135,7 +136,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			"storage_version":     d.Storage.GetStorageTypeVersion(),
 			"server":              "lxd",
 			"server_pid":          os.Getpid(),
-			"server_version":      shared.Version}
+			"server_version":      version.Version}
 
 		body["environment"] = env
 		body["public"] = false
diff --git a/lxd/certificates.go b/lxd/certificates.go
index e1e5c3ab1..8ae7c55c0 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -11,6 +11,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 )
 
 func certificatesGet(d *Daemon, r *http.Request) Response {
@@ -39,7 +40,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 
 	body := []string{}
 	for _, cert := range d.clientCerts {
-		fingerprint := fmt.Sprintf("/%s/certificates/%s", shared.APIVersion, shared.CertFingerprint(&cert))
+		fingerprint := fmt.Sprintf("/%s/certificates/%s", version.APIVersion, shared.CertFingerprint(&cert))
 		body = append(body, fingerprint)
 	}
 
@@ -150,7 +151,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 
 	d.clientCerts = append(d.clientCerts, *cert)
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", shared.APIVersion, fingerprint))
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
 }
 
 var certificatesCmd = Command{
diff --git a/lxd/container_logs.go b/lxd/container_logs.go
index 0f760fea3..39998fbec 100644
--- a/lxd/container_logs.go
+++ b/lxd/container_logs.go
@@ -10,6 +10,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 )
 
 func containerLogsGet(d *Daemon, r *http.Request) Response {
@@ -38,7 +39,7 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
 			continue
 		}
 
-		result = append(result, fmt.Sprintf("/%s/containers/%s/logs/%s", shared.APIVersion, name, f.Name()))
+		result = append(result, fmt.Sprintf("/%s/containers/%s/logs/%s", version.APIVersion, name, f.Name()))
 	}
 
 	return SyncResponse(true, result)
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index ff5eca84f..be0f8b217 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -10,6 +10,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 )
 
 type containerSnapshotPostReq struct {
@@ -41,7 +42,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	for _, snap := range snaps {
 		snapName := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)[1]
 		if recursion == 0 {
-			url := fmt.Sprintf("/%s/containers/%s/snapshots/%s", shared.APIVersion, cname, snapName)
+			url := fmt.Sprintf("/%s/containers/%s/snapshots/%s", version.APIVersion, cname, snapName)
 			resultString = append(resultString, url)
 		} else {
 			render, err := snap.Render()
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 8b36e1372..f460e2092 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 )
 
 func containersGet(d *Daemon, r *http.Request) Response {
@@ -42,7 +43,7 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 
 	for _, container := range result {
 		if !recursion {
-			url := fmt.Sprintf("/%s/containers/%s", shared.APIVersion, container)
+			url := fmt.Sprintf("/%s/containers/%s", version.APIVersion, container)
 			resultString = append(resultString, url)
 		} else {
 			c, err := doContainerGet(d, container)
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 0c7c96282..4c11ade10 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -33,6 +33,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
+	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -149,7 +150,7 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 		return nil, err
 	}
 
-	req.Header.Set("User-Agent", shared.UserAgent)
+	req.Header.Set("User-Agent", version.UserAgent)
 
 	myhttp, err := d.httpClient(certificate)
 	if err != nil {
@@ -186,7 +187,7 @@ func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, er
 		return nil, err
 	}
 
-	req.Header.Set("User-Agent", shared.UserAgent)
+	req.Header.Set("User-Agent", version.UserAgent)
 
 	raw, err := myhttp.Do(req)
 	if err != nil {
@@ -555,13 +556,13 @@ func (d *Daemon) Init() error {
 
 	/* Print welcome message */
 	if d.MockMode {
-		shared.LogInfo(fmt.Sprintf("LXD %s is starting in mock mode", shared.Version),
+		shared.LogInfo(fmt.Sprintf("LXD %s is starting in mock mode", version.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	} else if d.SetupMode {
-		shared.LogInfo(fmt.Sprintf("LXD %s is starting in setup mode", shared.Version),
+		shared.LogInfo(fmt.Sprintf("LXD %s is starting in setup mode", version.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	} else {
-		shared.LogInfo(fmt.Sprintf("LXD %s is starting in normal mode", shared.Version),
+		shared.LogInfo(fmt.Sprintf("LXD %s is starting in normal mode", version.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	}
 
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 6afe492f5..bcc1ec927 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -18,6 +18,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/simplestreams"
+	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -72,7 +73,7 @@ func imageLoadStreamCache(d *Daemon) error {
 				return err
 			}
 
-			ss := simplestreams.NewClient(url, *myhttp, shared.UserAgent)
+			ss := simplestreams.NewClient(url, *myhttp, version.UserAgent)
 			entry.ss = ss
 		}
 	}
@@ -105,7 +106,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 					return nil, err
 				}
 
-				ss = simplestreams.NewClient(server, *myhttp, shared.UserAgent)
+				ss = simplestreams.NewClient(server, *myhttp, version.UserAgent)
 
 				// Get all aliases
 				aliases, err := ss.ListAliases()
@@ -276,9 +277,9 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		if secret != "" {
 			url = fmt.Sprintf(
 				"%s/%s/images/%s?secret=%s",
-				server, shared.APIVersion, fp, secret)
+				server, version.APIVersion, fp, secret)
 		} else {
-			url = fmt.Sprintf("%s/%s/images/%s", server, shared.APIVersion, fp)
+			url = fmt.Sprintf("%s/%s/images/%s", server, version.APIVersion, fp)
 		}
 
 		resp, err := d.httpGetSync(url, certificate)
@@ -298,12 +299,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		if secret != "" {
 			exporturl = fmt.Sprintf(
 				"%s/%s/images/%s/export?secret=%s",
-				server, shared.APIVersion, fp, secret)
+				server, version.APIVersion, fp, secret)
 
 		} else {
 			exporturl = fmt.Sprintf(
 				"%s/%s/images/%s/export",
-				server, shared.APIVersion, fp)
+				server, version.APIVersion, fp)
 		}
 	} else if protocol == "simplestreams" {
 		err := ss.Download(fp, "meta", destName, nil)
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index af590006e..bf4edc851 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -16,6 +16,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 )
 
 type devLxdResponse struct {
@@ -74,7 +75,7 @@ var handlers = []devLxdHandler{
 		return okResponse([]string{"/1.0"}, "json")
 	}},
 	devLxdHandler{"/1.0", func(c container, r *http.Request) *devLxdResponse {
-		return okResponse(shared.Jmap{"api_version": shared.APIVersion}, "json")
+		return okResponse(shared.Jmap{"api_version": version.APIVersion}, "json")
 	}},
 	configGet,
 	configKeyGet,
diff --git a/lxd/images.go b/lxd/images.go
index 43340ac8f..9a0783e25 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -25,6 +25,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
+	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -384,9 +385,9 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 		architecturesStr = append(architecturesStr, fmt.Sprintf("%d", arch))
 	}
 
-	head.Header.Set("User-Agent", shared.UserAgent)
+	head.Header.Set("User-Agent", version.UserAgent)
 	head.Header.Set("LXD-Server-Architectures", strings.Join(architecturesStr, ", "))
-	head.Header.Set("LXD-Server-Version", shared.Version)
+	head.Header.Set("LXD-Server-Version", version.Version)
 
 	raw, err := myhttp.Do(head)
 	if err != nil {
@@ -820,7 +821,7 @@ func doImagesGet(d *Daemon, recursion bool, public bool) (interface{}, error) {
 	i := 0
 	for _, name := range results {
 		if !recursion {
-			url := fmt.Sprintf("/%s/images/%s", shared.APIVersion, name)
+			url := fmt.Sprintf("/%s/images/%s", version.APIVersion, name)
 			resultString[i] = url
 		} else {
 			image, response := doImageGet(d, name, public)
@@ -1120,7 +1121,7 @@ func aliasesPost(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, req.Name))
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
 }
 
 func aliasesGet(d *Daemon, r *http.Request) Response {
@@ -1139,7 +1140,7 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 	for _, res := range results {
 		name = res[0].(string)
 		if !recursion {
-			url := fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, name)
+			url := fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, name)
 			responseStr = append(responseStr, url)
 
 		} else {
@@ -1238,7 +1239,7 @@ func aliasPost(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, req.Name))
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
 }
 
 func imageExport(d *Daemon, r *http.Request) Response {
diff --git a/lxd/main.go b/lxd/main.go
index 60e30f2c2..fc119c308 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -9,6 +9,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/logging"
+	"github.com/lxc/lxd/shared/version"
 )
 
 // Global arguments
@@ -166,7 +167,7 @@ func run() error {
 
 	// Deal with --version right here
 	if *argVersion {
-		fmt.Println(shared.Version)
+		fmt.Println(version.Version)
 		return nil
 	}
 
diff --git a/lxd/networks.go b/lxd/networks.go
index a5b29e327..ed9fbee08 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -10,6 +10,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 )
 
 // Helper functions
@@ -52,7 +53,7 @@ func networksGet(d *Daemon, r *http.Request) Response {
 	resultMap := []network{}
 	for _, iface := range ifs {
 		if recursion == 0 {
-			resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, iface.Name))
+			resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", version.APIVersion, iface.Name))
 		} else {
 			net, err := doNetworkGet(d, iface.Name)
 			if err != nil {
@@ -113,7 +114,7 @@ func doNetworkGet(d *Daemon, name string) (network, error) {
 		}
 
 		if networkIsInUse(c, n.Name) {
-			n.UsedBy = append(n.UsedBy, fmt.Sprintf("/%s/containers/%s", shared.APIVersion, ct))
+			n.UsedBy = append(n.UsedBy, fmt.Sprintf("/%s/containers/%s", version.APIVersion, ct))
 		}
 	}
 
diff --git a/lxd/operations.go b/lxd/operations.go
index 8e5426300..52df1eca2 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -12,6 +12,7 @@ import (
 	"github.com/pborman/uuid"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 )
 
 var operationsLock sync.Mutex
@@ -252,7 +253,7 @@ func (op *operation) Render() (string, *shared.Operation, error) {
 		for key, value := range resources {
 			var values []string
 			for _, c := range value {
-				values = append(values, fmt.Sprintf("/%s/%s/%s", shared.APIVersion, key, c))
+				values = append(values, fmt.Sprintf("/%s/%s/%s", version.APIVersion, key, c))
 			}
 			tmpResources[key] = values
 		}
@@ -365,7 +366,7 @@ func operationCreate(opClass operationClass, opResources map[string][]string, op
 	op.createdAt = time.Now()
 	op.updatedAt = op.createdAt
 	op.status = shared.Pending
-	op.url = fmt.Sprintf("/%s/operations/%s", shared.APIVersion, op.id)
+	op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id)
 	op.resources = opResources
 	op.chanDone = make(chan error)
 
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 9f74e24c0..b92455368 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -11,6 +11,7 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -36,7 +37,7 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 	i := 0
 	for _, name := range results {
 		if !recursion {
-			url := fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, name)
+			url := fmt.Sprintf("/%s/profiles/%s", version.APIVersion, name)
 			resultString[i] = url
 		} else {
 			profile, err := doProfileGet(d, name)
@@ -97,7 +98,7 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name))
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
 }
 
 var profilesCmd = Command{
@@ -274,7 +275,7 @@ func profilePost(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name))
+	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
 }
 
 // The handler for the delete operation.
diff --git a/lxd/remote.go b/lxd/remote.go
index 9469ce232..ca7158a92 100644
--- a/lxd/remote.go
+++ b/lxd/remote.go
@@ -5,12 +5,13 @@ import (
 	"fmt"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/version"
 )
 
 func remoteGetImageFingerprint(d *Daemon, server string, certificate string, alias string) (string, error) {
 	url := fmt.Sprintf(
 		"%s/%s/images/aliases/%s",
-		server, shared.APIVersion, alias)
+		server, version.APIVersion, alias)
 
 	resp, err := d.httpGetSync(url, certificate)
 	if err != nil {
diff --git a/shared/flex.go b/shared/version/flex.go
similarity index 94%
rename from shared/flex.go
rename to shared/version/flex.go
index 0186d2c66..dba6c9217 100644
--- a/shared/flex.go
+++ b/shared/version/flex.go
@@ -1,7 +1,7 @@
 /* This is a FLEXible file which can be used by both client and daemon.
  * Teehee.
  */
-package shared
+package version
 
 var Version = "2.0.8"
 var UserAgent = "LXD " + Version

From ad2d27ac5d9f410eec9cc2fcbec2ead55f6762f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Dec 2016 16:21:49 -0500
Subject: [PATCH 0593/1193] Makefile: Rework "make dist"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This rework makes everything happen inside the temp directory, always
use the current HEAD of whatever git branch it's called from and doesn't
cause a re-download of the LXD tree.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 35 ++++++++++++++++++++++-------------
 1 file changed, 22 insertions(+), 13 deletions(-)

diff --git a/Makefile b/Makefile
index 8424ce962..09c537efd 100644
--- a/Makefile
+++ b/Makefile
@@ -56,20 +56,29 @@ gccgo:
 
 .PHONY: dist
 dist:
+	# Cleanup
+	rm -Rf $(ARCHIVE).gz
+	
+	# Create build dir
 	$(eval TMP := $(shell mktemp -d))
-	rm -Rf lxd-$(VERSION) $(ARCHIVE) $(ARCHIVE).gz
-	mkdir -p lxd-$(VERSION)/
-	-GOPATH=$(TMP) go get -t -v -d ./...
-	-GOPATH=$(TMP) go get -t -v -d ./...
-	-GOPATH=$(TMP) go get -t -v -d ./...
-	GOPATH=$(TMP) go get -t -v -d ./...
-	rm -rf $(TMP)/src/github.com/lxc/lxd
-	ln -s ../../../.. $(TMP)/src/github.com/lxc/lxd
-	mv $(TMP)/ lxd-$(VERSION)/dist
-	git archive --prefix=lxd-$(VERSION)/ --output=$(ARCHIVE) HEAD
-	tar -uf $(ARCHIVE) --exclude-vcs lxd-$(VERSION)/
-	gzip -9 $(ARCHIVE)
-	rm -Rf lxd-$(VERSION) $(ARCHIVE)
+	git archive --prefix=lxd-$(VERSION)/ HEAD | tar -x -C $(TMP)
+	mkdir -p $(TMP)/dist/src/github.com/lxc
+	ln -s ../../../../lxd-$(VERSION) $(TMP)/dist/src/github.com/lxc/lxd
+	
+	# Download dependencies
+	-cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
+	-cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
+	-cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
+	cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
+	
+	# Assemble tarball
+	rm $(TMP)/dist/src/github.com/lxc/lxd
+	ln -s ../../../../ $(TMP)/dist/src/github.com/lxc/lxd
+	mv $(TMP)/dist $(TMP)/lxd-$(VERSION)/
+	tar --exclude-vcs -C $(TMP) -zcf $(ARCHIVE).gz lxd-$(VERSION)/
+	
+	# Cleanup
+	rm -Rf $(TMP)
 
 .PHONY: i18n update-po update-pot build-mo static-analysis
 i18n: update-pot

From fc809625d7559c52c4a0db8b910d1376eb22131e Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 20 Dec 2016 10:15:06 -0700
Subject: [PATCH 0594/1193] just use init.go's flags()

...instead of duplicating the contents of this method.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/launch.go | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/lxc/launch.go b/lxc/launch.go
index 22df8eed3..fbc455512 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -6,7 +6,6 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -36,14 +35,7 @@ Example:
 
 func (c *launchCmd) flags() {
 	c.init = initCmd{}
-
-	c.init.massage_args()
-	gnuflag.Var(&c.init.confArgs, "config", i18n.G("Config key/value to apply to the new container"))
-	gnuflag.Var(&c.init.confArgs, "c", i18n.G("Config key/value to apply to the new container"))
-	gnuflag.Var(&c.init.profArgs, "profile", i18n.G("Profile to apply to the new container"))
-	gnuflag.Var(&c.init.profArgs, "p", i18n.G("Profile to apply to the new container"))
-	gnuflag.BoolVar(&c.init.ephem, "ephemeral", false, i18n.G("Ephemeral container"))
-	gnuflag.BoolVar(&c.init.ephem, "e", false, i18n.G("Ephemeral container"))
+	c.init.flags()
 }
 
 func (c *launchCmd) run(config *lxd.Config, args []string) error {

From 7d8423498dea79e2ceec3560a20624f6384a9f31 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 20 Dec 2016 17:29:51 +0000
Subject: [PATCH 0595/1193] cli: fix example in `lxc init`

Similar to 5161ddf32426df7a3f0f3889bbd555d71a70a262.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/init.go |  2 +-
 po/lxd.pot  | 22 +++++++++++-----------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index 5f59fc48b..acaf2fb1b 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -81,7 +81,7 @@ Not specifying -p will result in the default profile.
 Specifying "-p" with no argument will result in no profile.
 
 Example:
-    lxc init ubuntu u1`)
+    lxc init ubuntu:16.04 u1`)
 }
 
 func (c *initCmd) is_ephem(s string) bool {
diff --git a/po/lxd.pot b/po/lxd.pot
index f7de03612..5abacea36 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2016-12-15 14:19-0500\n"
+        "POT-Creation-Date: 2017-01-03 12:39-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -185,7 +185,7 @@ msgstr  ""
 msgid   "Columns"
 msgstr  ""
 
-#: lxc/init.go:134 lxc/init.go:135 lxc/launch.go:40 lxc/launch.go:41
+#: lxc/init.go:134 lxc/init.go:135
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
@@ -252,7 +252,7 @@ msgstr  ""
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:177 lxc/launch.go:119
+#: lxc/init.go:177 lxc/launch.go:112
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
@@ -307,7 +307,7 @@ msgstr  ""
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:29 lxc/copy.go:30 lxc/init.go:138 lxc/init.go:139 lxc/launch.go:44 lxc/launch.go:45
+#: lxc/copy.go:29 lxc/copy.go:30 lxc/init.go:138 lxc/init.go:139
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -420,7 +420,7 @@ msgid   "Initialize a container from a particular image.\n"
         "Specifying \"-p\" with no argument will result in no profile.\n"
         "\n"
         "Example:\n"
-        "    lxc init ubuntu u1"
+        "    lxc init ubuntu:16.04 u1"
 msgstr  ""
 
 #: lxc/remote.go:120
@@ -880,7 +880,7 @@ msgstr  ""
 msgid   "Profile %s deleted"
 msgstr  ""
 
-#: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
+#: lxc/init.go:136 lxc/init.go:137
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
@@ -1020,7 +1020,7 @@ msgstr  ""
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/launch.go:126
+#: lxc/launch.go:119
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -1096,7 +1096,7 @@ msgstr  ""
 msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:99 lxc/launch.go:139
+#: lxc/action.go:99 lxc/launch.go:132
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1155,7 +1155,7 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:112
+#: lxc/launch.go:105
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
@@ -1175,7 +1175,7 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:202 lxc/init.go:207 lxc/launch.go:96 lxc/launch.go:101
+#: lxc/init.go:202 lxc/init.go:207 lxc/launch.go:89 lxc/launch.go:94
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
@@ -1197,7 +1197,7 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:116
+#: lxc/launch.go:109
 msgid   "got bad version"
 msgstr  ""
 

From 4617a3dad31670500253c589929a666ab2bcada6 Mon Sep 17 00:00:00 2001
From: Vahe Khachikyan <vahe at live.ca>
Date: Tue, 20 Dec 2016 14:02:36 -0500
Subject: [PATCH 0596/1193] Rename idmapset_test_linux.go to
 idmapset_linux_test.go

Signed-off-by: Vahe Khachikyan <vahe at live.ca>
---
 shared/{idmapset_test_linux.go => idmapset_linux_test.go} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename shared/{idmapset_test_linux.go => idmapset_linux_test.go} (100%)

diff --git a/shared/idmapset_test_linux.go b/shared/idmapset_linux_test.go
similarity index 100%
rename from shared/idmapset_test_linux.go
rename to shared/idmapset_linux_test.go

From 11c03e6b609a726626d21cc448c544fb56a678d5 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Tue, 20 Dec 2016 12:12:09 -0700
Subject: [PATCH 0597/1193] use our custom http server when updating HTTPS
 address too

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/daemon.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 4c11ade10..46c9841e8 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -514,7 +514,7 @@ func (d *Daemon) UpdateHTTPsPort(newAddress string) error {
 			return fmt.Errorf("cannot listen on https socket: %v", err)
 		}
 
-		d.tomb.Go(func() error { return http.Serve(tcpl, d.mux) })
+		d.tomb.Go(func() error { return http.Serve(tcpl, &lxdHttpServer{d.mux, d}) })
 		d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true}
 	}
 

From e850edf7a1aac5ab19c8cac4e7fd076d2fda2b7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 20:59:03 -0500
Subject: [PATCH 0598/1193] shared: Move WebsocketUpgrader to network.go
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/network.go   | 5 +++++
 shared/operation.go | 7 -------
 2 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/shared/network.go b/shared/network.go
index 20b14c054..1e01d8ea4 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -8,6 +8,7 @@ import (
 	"io"
 	"io/ioutil"
 	"net"
+	"net/http"
 	"time"
 
 	"github.com/gorilla/websocket"
@@ -302,3 +303,7 @@ func WebsocketMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser, Re
 
 	return readDone, writeDone
 }
+
+var WebsocketUpgrader = websocket.Upgrader{
+	CheckOrigin: func(r *http.Request) bool { return true },
+}
diff --git a/shared/operation.go b/shared/operation.go
index fa955e809..121be2e43 100644
--- a/shared/operation.go
+++ b/shared/operation.go
@@ -1,16 +1,9 @@
 package shared
 
 import (
-	"net/http"
 	"time"
-
-	"github.com/gorilla/websocket"
 )
 
-var WebsocketUpgrader = websocket.Upgrader{
-	CheckOrigin: func(r *http.Request) bool { return true },
-}
-
 type Operation struct {
 	Id         string              `json:"id"`
 	Class      string              `json:"class"`

From c2319c0ad5d241dd638d591505a8d4a2469a517f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 21:14:57 -0500
Subject: [PATCH 0599/1193] Move FromLXCState out of shared
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 18 ++++++++++++++++--
 shared/status.go     | 19 -------------------
 2 files changed, 16 insertions(+), 21 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 02b6e5d06..b6bafbc1a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -163,6 +163,20 @@ func lxcValidConfig(rawLxc string) error {
 	return nil
 }
 
+func lxcStatusCode(state lxc.State) shared.StatusCode {
+	return map[int]shared.StatusCode{
+		1: shared.Stopped,
+		2: shared.Starting,
+		3: shared.Running,
+		4: shared.Stopping,
+		5: shared.Aborting,
+		6: shared.Freezing,
+		7: shared.Frozen,
+		8: shared.Thawed,
+		9: shared.Error,
+	}[int(state)]
+}
+
 // Loader functions
 func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	// Create the container struct
@@ -2211,7 +2225,7 @@ func (c *containerLXC) Render() (interface{}, error) {
 		if err != nil {
 			return nil, err
 		}
-		statusCode := shared.FromLXCState(int(cState))
+		statusCode := lxcStatusCode(cState)
 
 		return &shared.ContainerInfo{
 			Architecture:    architectureName,
@@ -2241,7 +2255,7 @@ func (c *containerLXC) RenderState() (*shared.ContainerState, error) {
 	if err != nil {
 		return nil, err
 	}
-	statusCode := shared.FromLXCState(int(cState))
+	statusCode := lxcStatusCode(cState)
 	status := shared.ContainerState{
 		Status:     statusCode.String(),
 		StatusCode: statusCode,
diff --git a/shared/status.go b/shared/status.go
index 96010c493..7651f5ccb 100644
--- a/shared/status.go
+++ b/shared/status.go
@@ -47,22 +47,3 @@ func (o StatusCode) String() string {
 func (o StatusCode) IsFinal() bool {
 	return int(o) >= 200
 }
-
-/*
- * Create a StatusCode from an lxc.State code. N.B.: we accept an int instead
- * of a lxc.State so that the shared code doesn't depend on lxc, which depends
- * on liblxc, etc.
- */
-func FromLXCState(state int) StatusCode {
-	return map[int]StatusCode{
-		1: Stopped,
-		2: Starting,
-		3: Running,
-		4: Stopping,
-		5: Aborting,
-		6: Freezing,
-		7: Frozen,
-		8: Thawed,
-		9: Error,
-	}[state]
-}

From fac4e74a5c5b66a8dbfd2bad048f7979c25f44e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Dec 2016 16:08:26 -0500
Subject: [PATCH 0600/1193] idmapset: Drop debugging code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 7db1a0383..2f9d847da 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -188,7 +188,6 @@ func (m IdmapSet) Len() int {
 func (m IdmapSet) Intersects(i IdmapEntry) bool {
 	for _, e := range m.Idmap {
 		if i.Intersects(e) {
-			fmt.Printf("%v and %v intersect\n", i, e)
 			return true
 		}
 	}

From 960010454e8da3a0a7f1d6ae6d473aca02eaf836 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Dec 2016 15:21:07 -0500
Subject: [PATCH 0601/1193] idmapset: Fix intersection test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This test got broken back when we fixed a number of idmap issues, but it
wasn't run until the testsuite was changed to be called again.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux_test.go | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/shared/idmapset_linux_test.go b/shared/idmapset_linux_test.go
index 448086e20..588b84762 100644
--- a/shared/idmapset_linux_test.go
+++ b/shared/idmapset_linux_test.go
@@ -85,8 +85,23 @@ func TestIdmapSetAddSafe_upper(t *testing.T) {
 func TestIdmapSetIntersects(t *testing.T) {
 	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 165536, Nsid: 0, Maprange: 65536}}}
 
-	if orig.Intersects(IdmapEntry{Isuid: true, Hostid: 231072, Nsid: 0, Maprange: 65536}) {
+	if !orig.Intersects(IdmapEntry{Isuid: true, Hostid: 231071, Nsid: 0, Maprange: 65536}) {
 		t.Error("ranges don't intersect")
 		return
 	}
+
+	if !orig.Intersects(IdmapEntry{Isuid: true, Hostid: 231072, Nsid: 0, Maprange: 65536}) {
+		t.Error("ranges don't intersect")
+		return
+	}
+
+	if !orig.Intersects(IdmapEntry{Isuid: true, Hostid: 231072, Nsid: 65535, Maprange: 65536}) {
+		t.Error("ranges don't intersect")
+		return
+	}
+
+	if orig.Intersects(IdmapEntry{Isuid: true, Hostid: 231072, Nsid: 65536, Maprange: 65536}) {
+		t.Error("ranges intersect")
+		return
+	}
 }

From 69ef00e265b73e4bc434854149b30c97af3a51ed Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 20 Dec 2016 12:56:57 +0100
Subject: [PATCH 0602/1193] util: GetByteSizeString() take precision argument

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxc/image.go         |  2 +-
 lxc/info.go          | 14 +++++++-------
 lxd/daemon_images.go |  2 +-
 shared/util.go       |  6 +++---
 4 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index b9e118c4b..6c07e7147 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -430,7 +430,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		} else {
 			progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")}
 			handler := func(percent int64, speed int64) {
-				progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed)))
+				progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed, 2)))
 			}
 
 			fingerprint, err = d.PostImage(imageFile, rootfsFile, properties, c.publicImage, c.addAliases, handler)
diff --git a/lxc/info.go b/lxc/info.go
index 8fdbe21e5..767c83cba 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -134,7 +134,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		if cs.Disk != nil {
 			for entry, disk := range cs.Disk {
 				if disk.Usage != 0 {
-					diskInfo += fmt.Sprintf("    %s: %s\n", entry, shared.GetByteSizeString(disk.Usage))
+					diskInfo += fmt.Sprintf("    %s: %s\n", entry, shared.GetByteSizeString(disk.Usage, 2))
 				}
 			}
 		}
@@ -147,19 +147,19 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		// Memory usage
 		memoryInfo := ""
 		if cs.Memory.Usage != 0 {
-			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Memory (current)"), shared.GetByteSizeString(cs.Memory.Usage))
+			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Memory (current)"), shared.GetByteSizeString(cs.Memory.Usage, 2))
 		}
 
 		if cs.Memory.UsagePeak != 0 {
-			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Memory (peak)"), shared.GetByteSizeString(cs.Memory.UsagePeak))
+			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Memory (peak)"), shared.GetByteSizeString(cs.Memory.UsagePeak, 2))
 		}
 
 		if cs.Memory.SwapUsage != 0 {
-			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Swap (current)"), shared.GetByteSizeString(cs.Memory.SwapUsage))
+			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Swap (current)"), shared.GetByteSizeString(cs.Memory.SwapUsage, 2))
 		}
 
 		if cs.Memory.SwapUsagePeak != 0 {
-			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Swap (peak)"), shared.GetByteSizeString(cs.Memory.SwapUsagePeak))
+			memoryInfo += fmt.Sprintf("    %s: %s\n", i18n.G("Swap (peak)"), shared.GetByteSizeString(cs.Memory.SwapUsagePeak, 2))
 		}
 
 		if memoryInfo != "" {
@@ -172,8 +172,8 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		if cs.Network != nil {
 			for netName, net := range cs.Network {
 				networkInfo += fmt.Sprintf("    %s:\n", netName)
-				networkInfo += fmt.Sprintf("      %s: %s\n", i18n.G("Bytes received"), shared.GetByteSizeString(net.Counters.BytesReceived))
-				networkInfo += fmt.Sprintf("      %s: %s\n", i18n.G("Bytes sent"), shared.GetByteSizeString(net.Counters.BytesSent))
+				networkInfo += fmt.Sprintf("      %s: %s\n", i18n.G("Bytes received"), shared.GetByteSizeString(net.Counters.BytesReceived, 2))
+				networkInfo += fmt.Sprintf("      %s: %s\n", i18n.G("Bytes sent"), shared.GetByteSizeString(net.Counters.BytesSent, 2))
 				networkInfo += fmt.Sprintf("      %s: %d\n", i18n.G("Packets received"), net.Counters.PacketsReceived)
 				networkInfo += fmt.Sprintf("      %s: %d\n", i18n.G("Packets sent"), net.Counters.PacketsSent)
 			}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index bcc1ec927..7324effdf 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -263,7 +263,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			meta = make(map[string]interface{})
 		}
 
-		progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(speedInt))
+		progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(speedInt, 2))
 
 		if meta["download_progress"] != progress {
 			meta["download_progress"] = progress
diff --git a/shared/util.go b/shared/util.go
index a221c650e..3b8fa9cd5 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -700,7 +700,7 @@ func ParseBitSizeString(input string) (int64, error) {
 	return valueInt * multiplicator, nil
 }
 
-func GetByteSizeString(input int64) string {
+func GetByteSizeString(input int64, precision uint) string {
 	if input < 1024 {
 		return fmt.Sprintf("%d bytes", input)
 	}
@@ -710,11 +710,11 @@ func GetByteSizeString(input int64) string {
 	for _, unit := range []string{"kB", "MB", "GB", "TB", "PB", "EB"} {
 		value = value / 1024
 		if value < 1024 {
-			return fmt.Sprintf("%.2f%s", value, unit)
+			return fmt.Sprintf("%.*f%s", precision, value, unit)
 		}
 	}
 
-	return fmt.Sprintf("%.2fEB", value)
+	return fmt.Sprintf("%.*fEB", precision, value)
 }
 
 func RunCommand(name string, arg ...string) error {

From 7daf905e4efbb2820d3f6022096ef10355354395 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Dec 2016 03:26:54 -0500
Subject: [PATCH 0603/1193] tests: Fix shellcheck being confused by cd
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/filemanip.sh | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index bca4138f2..efe5a1b66 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -1,6 +1,8 @@
 #!/bin/sh
 
 test_filemanip() {
+  # Workaround for shellcheck getting confused by "cd"
+  set -e
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
 

From 555c7544fc70000fcc818582ff6bdc27755c1722 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Wed, 21 Dec 2016 14:58:59 -0700
Subject: [PATCH 0604/1193] add a done signal to Monitor API

Otherwise there is no way to close these connections.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 client.go            | 41 +++++++++++++++++++++++++++++------------
 lxc/init.go          |  2 +-
 lxc/monitor.go       |  2 +-
 lxd/main_shutdown.go |  2 +-
 4 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/client.go b/client.go
index f5f661397..5d49a01d4 100644
--- a/client.go
+++ b/client.go
@@ -683,7 +683,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 	}
 
 	if progressHandler != nil {
-		go dest.Monitor([]string{"operation"}, handler)
+		go dest.Monitor([]string{"operation"}, handler, nil)
 	}
 
 	fingerprint := info.Fingerprint
@@ -915,7 +915,7 @@ func (c *Client) PostImageURL(imageFile string, properties []string, public bool
 	}
 
 	if progressHandler != nil {
-		go c.Monitor([]string{"operation"}, handler)
+		go c.Monitor([]string{"operation"}, handler, nil)
 	}
 
 	resp, err := c.post("images", body, Async)
@@ -1453,7 +1453,7 @@ func (c *Client) LocalCopy(source string, name string, config map[string]string,
 	return c.post("containers", body, Async)
 }
 
-func (c *Client) Monitor(types []string, handler func(interface{})) error {
+func (c *Client) Monitor(types []string, handler func(interface{}), done chan bool) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1469,20 +1469,37 @@ func (c *Client) Monitor(types []string, handler func(interface{})) error {
 	}
 	defer conn.Close()
 
-	for {
-		message := make(map[string]interface{})
+	readCh := make(chan []byte)
+	errCh := make(chan error)
 
-		_, data, err := conn.ReadMessage()
-		if err != nil {
-			return err
+	go func() {
+		for {
+			_, data, err := conn.ReadMessage()
+			if err != nil {
+				errCh <- err
+				return
+			}
+
+			readCh <- data
 		}
+	}()
 
-		err = json.Unmarshal(data, &message)
-		if err != nil {
+	for {
+		select {
+		case <-done:
+			return nil
+		case data := <-readCh:
+			message := make(map[string]interface{})
+
+			err = json.Unmarshal(data, &message)
+			if err != nil {
+				return err
+			}
+
+			handler(message)
+		case err := <-errCh:
 			return err
 		}
-
-		handler(message)
 	}
 }
 
diff --git a/lxc/init.go b/lxc/init.go
index acaf2fb1b..86b8d3670 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -250,7 +250,7 @@ func (c *initCmd) initProgressTracker(d *lxd.Client, progress *ProgressRenderer,
 			progress.Update(opMd["download_progress"].(string))
 		}
 	}
-	go d.Monitor([]string{"operation"}, handler)
+	go d.Monitor([]string{"operation"}, handler, nil)
 }
 
 func (c *initCmd) guessImage(config *lxd.Config, d *lxd.Client, remote string, iremote string, image string) (string, string) {
diff --git a/lxc/monitor.go b/lxc/monitor.go
index 877a1f9e6..74e3306e8 100644
--- a/lxc/monitor.go
+++ b/lxc/monitor.go
@@ -84,5 +84,5 @@ func (c *monitorCmd) run(config *lxd.Config, args []string) error {
 		fmt.Printf("%s\n\n", render)
 	}
 
-	return d.Monitor(c.typeArgs, handler)
+	return d.Monitor(c.typeArgs, handler, nil)
 }
diff --git a/lxd/main_shutdown.go b/lxd/main_shutdown.go
index 9110ed501..74c380f4b 100644
--- a/lxd/main_shutdown.go
+++ b/lxd/main_shutdown.go
@@ -34,7 +34,7 @@ func cmdShutdown() error {
 
 	monitor := make(chan error, 1)
 	go func() {
-		monitor <- c.Monitor(nil, func(m interface{}) {})
+		monitor <- c.Monitor(nil, func(m interface{}) {}, nil)
 	}()
 
 	select {

From 15ddc825834b9511e05772b5bff78c151f2a227e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Dec 2016 18:03:46 -0500
Subject: [PATCH 0605/1193] Move Device/Devices types to lxd package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We don't need any of their functions in the client code so move them to
be daemon-only and instead use generic go types in the client.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                             | 17 ++++++----
 lxc/config.go                         |  2 +-
 lxc/copy.go                           |  2 +-
 lxd/container.go                      | 11 ++++---
 lxd/container_lxc.go                  | 59 +++++++++++++++++----------------
 lxd/container_put.go                  |  3 +-
 lxd/container_test.go                 | 11 ++++---
 lxd/containers_post.go                |  5 +--
 lxd/db_containers.go                  |  3 +-
 lxd/db_devices.go                     | 12 +++----
 lxd/db_profiles.go                    |  9 ++---
 lxd/db_test.go                        | 17 +++++-----
 lxd/profiles.go                       |  3 +-
 lxd/storage.go                        |  3 +-
 {shared => lxd/types}/devices.go      | 10 +++---
 {shared => lxd/types}/devices_test.go |  2 +-
 shared/container.go                   | 62 +++++++++++++++++------------------
 17 files changed, 123 insertions(+), 108 deletions(-)
 rename {shared => lxd/types}/devices.go (94%)
 rename {shared => lxd/types}/devices_test.go (96%)

diff --git a/client.go b/client.go
index 5d49a01d4..91a9b1e3e 100644
--- a/client.go
+++ b/client.go
@@ -1829,7 +1829,7 @@ func (c *Client) GetMigrationSourceWS(container string) (*Response, error) {
 	return c.post(url, body, Async)
 }
 
-func (c *Client) MigrateFrom(name string, operation string, certificate string, secrets map[string]string, architecture string, config map[string]string, devices shared.Devices, profiles []string, baseImage string, ephemeral bool) (*Response, error) {
+func (c *Client) MigrateFrom(name string, operation string, certificate string, secrets map[string]string, architecture string, config map[string]string, devices map[string]map[string]string, profiles []string, baseImage string, ephemeral bool) (*Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2253,7 +2253,7 @@ func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []
 		return nil, err
 	}
 
-	newdev := shared.Device{}
+	newdev := map[string]string{}
 	for _, p := range props {
 		results := strings.SplitN(p, "=", 2)
 		if len(results) != 2 {
@@ -2264,13 +2264,13 @@ func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []
 		newdev[k] = v
 	}
 
-	if st.Devices != nil && st.Devices.ContainsName(devname) {
+	if st.Devices != nil && st.Devices[devname] != nil {
 		return nil, fmt.Errorf("device already exists")
 	}
 
 	newdev["type"] = devtype
 	if st.Devices == nil {
-		st.Devices = shared.Devices{}
+		st.Devices = map[string]map[string]string{}
 	}
 
 	st.Devices[devname] = newdev
@@ -2324,7 +2324,7 @@ func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []stri
 		return nil, err
 	}
 
-	newdev := shared.Device{}
+	newdev := map[string]string{}
 	for _, p := range props {
 		results := strings.SplitN(p, "=", 2)
 		if len(results) != 2 {
@@ -2334,13 +2334,16 @@ func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []stri
 		v := results[1]
 		newdev[k] = v
 	}
-	if st.Devices != nil && st.Devices.ContainsName(devname) {
+
+	if st.Devices != nil && st.Devices[devname] != nil {
 		return nil, fmt.Errorf("device already exists")
 	}
+
 	newdev["type"] = devtype
 	if st.Devices == nil {
-		st.Devices = shared.Devices{}
+		st.Devices = map[string]map[string]string{}
 	}
+
 	st.Devices[devname] = newdev
 
 	return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
diff --git a/lxc/config.go b/lxc/config.go
index 73091a4a8..59c67cd3c 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -872,7 +872,7 @@ func (c *configCmd) deviceShow(config *lxd.Config, which string, args []string)
 		return err
 	}
 
-	var devices map[string]shared.Device
+	var devices map[string]map[string]string
 	if which == "profile" {
 		resp, err := client.ProfileConfig(name)
 		if err != nil {
diff --git a/lxc/copy.go b/lxc/copy.go
index 27a5bf13a..024101018 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -49,7 +49,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 
 	var status struct {
 		Architecture string
-		Devices      shared.Devices
+		Devices      map[string]map[string]string
 		Config       map[string]string
 		Profiles     []string
 	}
diff --git a/lxd/container.go b/lxd/container.go
index 0afbf2192..ffe20a909 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -10,6 +10,7 @@ import (
 
 	"gopkg.in/lxc/go-lxc.v2"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 )
@@ -309,7 +310,7 @@ func containerValidConfig(config map[string]string, profile bool, expanded bool)
 	return nil
 }
 
-func containerValidDevices(devices shared.Devices, profile bool, expanded bool) error {
+func containerValidDevices(devices types.Devices, profile bool, expanded bool) error {
 	// Empty device list
 	if devices == nil {
 		return nil
@@ -401,7 +402,7 @@ type containerArgs struct {
 	Config       map[string]string
 	CreationDate time.Time
 	Ctype        containerType
-	Devices      shared.Devices
+	Devices      types.Devices
 	Ephemeral    bool
 	Name         string
 	Profiles     []string
@@ -478,9 +479,9 @@ type container interface {
 	Architecture() int
 	CreationDate() time.Time
 	ExpandedConfig() map[string]string
-	ExpandedDevices() shared.Devices
+	ExpandedDevices() types.Devices
 	LocalConfig() map[string]string
-	LocalDevices() shared.Devices
+	LocalDevices() types.Devices
 	Profiles() []string
 	InitPID() int
 	State() string
@@ -662,7 +663,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	if args.Devices == nil {
-		args.Devices = shared.Devices{}
+		args.Devices = types.Devices{}
 	}
 
 	if args.Architecture == 0 {
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b6bafbc1a..279054a84 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -24,6 +24,7 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 
@@ -230,7 +231,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 			deviceName += "_"
 		}
 
-		c.localDevices[deviceName] = shared.Device{"type": "disk", "path": "/"}
+		c.localDevices[deviceName] = types.Device{"type": "disk", "path": "/"}
 
 		updateArgs := containerArgs{
 			Architecture: c.architecture,
@@ -376,10 +377,10 @@ type containerLXC struct {
 
 	// Config
 	expandedConfig  map[string]string
-	expandedDevices shared.Devices
+	expandedDevices types.Devices
 	fromHook        bool
 	localConfig     map[string]string
-	localDevices    shared.Devices
+	localDevices    types.Devices
 	profiles        []string
 
 	// Cache
@@ -1382,7 +1383,7 @@ func (c *containerLXC) expandConfig() error {
 }
 
 func (c *containerLXC) expandDevices() error {
-	devices := shared.Devices{}
+	devices := types.Devices{}
 
 	// Apply all the profiles
 	for _, p := range c.profiles {
@@ -1535,7 +1536,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	c.removeUnixDevices()
 	c.removeDiskDevices()
 
-	diskDevices := map[string]shared.Device{}
+	diskDevices := map[string]types.Device{}
 
 	// Create the devices
 	for _, k := range c.expandedDevices.DeviceNames() {
@@ -1566,7 +1567,7 @@ func (c *containerLXC) startCommon() (string, error) {
 		}
 	}
 
-	err = c.addDiskDevices(diskDevices, func(name string, d shared.Device) error {
+	err = c.addDiskDevices(diskDevices, func(name string, d types.Device) error {
 		_, err := c.createDiskDevice(name, d)
 		return err
 	})
@@ -1850,7 +1851,7 @@ func (c *containerLXC) OnStart() error {
 			continue
 		}
 
-		go func(c *containerLXC, name string, m shared.Device) {
+		go func(c *containerLXC, name string, m types.Device) {
 			c.fromHook = false
 			err = c.setNetworkLimits(name, m)
 			if err != nil {
@@ -2582,7 +2583,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	}
 
 	if args.Devices == nil {
-		args.Devices = shared.Devices{}
+		args.Devices = types.Devices{}
 	}
 
 	if args.Profiles == nil {
@@ -2649,7 +2650,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
-	oldExpandedDevices := shared.Devices{}
+	oldExpandedDevices := types.Devices{}
 	err = shared.DeepCopy(&c.expandedDevices, &oldExpandedDevices)
 	if err != nil {
 		return err
@@ -2661,7 +2662,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
-	oldLocalDevices := shared.Devices{}
+	oldLocalDevices := types.Devices{}
 	err = shared.DeepCopy(&c.localDevices, &oldLocalDevices)
 	if err != nil {
 		return err
@@ -2822,7 +2823,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	// Apply the live changes
 	if c.IsRunning() {
 		// Confirm that the rootfs source didn't change
-		var oldRootfs shared.Device
+		var oldRootfs types.Device
 		for _, m := range oldExpandedDevices {
 			if m["type"] == "disk" && m["path"] == "/" {
 				oldRootfs = m
@@ -2830,7 +2831,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			}
 		}
 
-		var newRootfs shared.Device
+		var newRootfs types.Device
 		for _, name := range c.expandedDevices.DeviceNames() {
 			m := c.expandedDevices[name]
 			if m["type"] == "disk" && m["path"] == "/" {
@@ -3064,7 +3065,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			}
 		}
 
-		diskDevices := map[string]shared.Device{}
+		diskDevices := map[string]types.Device{}
 
 		for k, m := range addDevices {
 			if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
@@ -4419,7 +4420,7 @@ func (c *containerLXC) removeMount(mount string) error {
 }
 
 // Unix devices handling
-func (c *containerLXC) createUnixDevice(m shared.Device) (string, error) {
+func (c *containerLXC) createUnixDevice(m types.Device) (string, error) {
 	var err error
 	var major, minor int
 
@@ -4550,7 +4551,7 @@ func (c *containerLXC) createUnixDevice(m shared.Device) (string, error) {
 	return devPath, nil
 }
 
-func (c *containerLXC) insertUnixDevice(name string, m shared.Device) error {
+func (c *containerLXC) insertUnixDevice(name string, m types.Device) error {
 	// Check that the container is running
 	if !c.IsRunning() {
 		return fmt.Errorf("Can't insert device into stopped container")
@@ -4611,7 +4612,7 @@ func (c *containerLXC) insertUnixDevice(name string, m shared.Device) error {
 	return nil
 }
 
-func (c *containerLXC) removeUnixDevice(m shared.Device) error {
+func (c *containerLXC) removeUnixDevice(m types.Device) error {
 	// Check that the container is running
 	pid := c.InitPID()
 	if pid == -1 {
@@ -4720,7 +4721,7 @@ func (c *containerLXC) removeUnixDevices() error {
 }
 
 // Network device handling
-func (c *containerLXC) createNetworkDevice(name string, m shared.Device) (string, error) {
+func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string, error) {
 	var dev, n1 string
 
 	if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p", "macvlan"}) {
@@ -4800,8 +4801,8 @@ func (c *containerLXC) createNetworkDevice(name string, m shared.Device) (string
 	return dev, nil
 }
 
-func (c *containerLXC) fillNetworkDevice(name string, m shared.Device) (shared.Device, error) {
-	newDevice := shared.Device{}
+func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Device, error) {
+	newDevice := types.Device{}
 	err := shared.DeepCopy(&m, &newDevice)
 	if err != nil {
 		return nil, err
@@ -4947,7 +4948,7 @@ func (c *containerLXC) fillNetworkDevice(name string, m shared.Device) (shared.D
 	return newDevice, nil
 }
 
-func (c *containerLXC) insertNetworkDevice(name string, m shared.Device) error {
+func (c *containerLXC) insertNetworkDevice(name string, m types.Device) error {
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
@@ -4984,7 +4985,7 @@ func (c *containerLXC) insertNetworkDevice(name string, m shared.Device) error {
 	return nil
 }
 
-func (c *containerLXC) removeNetworkDevice(name string, m shared.Device) error {
+func (c *containerLXC) removeNetworkDevice(name string, m types.Device) error {
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
@@ -5031,7 +5032,7 @@ func (c *containerLXC) removeNetworkDevice(name string, m shared.Device) error {
 }
 
 // Disk device handling
-func (c *containerLXC) createDiskDevice(name string, m shared.Device) (string, error) {
+func (c *containerLXC) createDiskDevice(name string, m types.Device) (string, error) {
 	// Prepare all the paths
 	srcPath := m["source"]
 	tgtPath := strings.TrimPrefix(m["path"], "/")
@@ -5092,7 +5093,7 @@ func (c *containerLXC) createDiskDevice(name string, m shared.Device) (string, e
 	return devPath, nil
 }
 
-func (c *containerLXC) insertDiskDevice(name string, m shared.Device) error {
+func (c *containerLXC) insertDiskDevice(name string, m types.Device) error {
 	// Check that the container is running
 	if !c.IsRunning() {
 		return fmt.Errorf("Can't insert device into stopped container")
@@ -5121,7 +5122,7 @@ func (c *containerLXC) insertDiskDevice(name string, m shared.Device) error {
 	return nil
 }
 
-type byPath []shared.Device
+type byPath []types.Device
 
 func (a byPath) Len() int {
 	return len(a)
@@ -5135,7 +5136,7 @@ func (a byPath) Less(i, j int) bool {
 	return a[i]["path"] < a[j]["path"]
 }
 
-func (c *containerLXC) addDiskDevices(devices map[string]shared.Device, handler func(string, shared.Device) error) error {
+func (c *containerLXC) addDiskDevices(devices map[string]types.Device, handler func(string, types.Device) error) error {
 	ordered := byPath{}
 
 	for _, d := range devices {
@@ -5153,7 +5154,7 @@ func (c *containerLXC) addDiskDevices(devices map[string]shared.Device, handler
 	return nil
 }
 
-func (c *containerLXC) removeDiskDevice(name string, m shared.Device) error {
+func (c *containerLXC) removeDiskDevice(name string, m types.Device) error {
 	// Check that the container is running
 	pid := c.InitPID()
 	if pid == -1 {
@@ -5449,7 +5450,7 @@ func (c *containerLXC) getHostInterface(name string) string {
 	return ""
 }
 
-func (c *containerLXC) setNetworkLimits(name string, m shared.Device) error {
+func (c *containerLXC) setNetworkLimits(name string, m types.Device) error {
 	// We can only do limits on some network type
 	if m["nictype"] != "bridged" && m["nictype"] != "p2p" {
 		return fmt.Errorf("Network limits are only supported on bridged and p2p interfaces")
@@ -5581,7 +5582,7 @@ func (c *containerLXC) ExpandedConfig() map[string]string {
 	return c.expandedConfig
 }
 
-func (c *containerLXC) ExpandedDevices() shared.Devices {
+func (c *containerLXC) ExpandedDevices() types.Devices {
 	return c.expandedDevices
 }
 
@@ -5607,7 +5608,7 @@ func (c *containerLXC) LocalConfig() map[string]string {
 	return c.localConfig
 }
 
-func (c *containerLXC) LocalDevices() shared.Devices {
+func (c *containerLXC) LocalDevices() types.Devices {
 	return c.localDevices
 }
 
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 4dc73fd50..73a578e9f 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 
@@ -17,7 +18,7 @@ import (
 type containerPutReq struct {
 	Architecture string            `json:"architecture"`
 	Config       map[string]string `json:"config"`
-	Devices      shared.Devices    `json:"devices"`
+	Devices      types.Devices     `json:"devices"`
 	Ephemeral    bool              `json:"ephemeral"`
 	Profiles     []string          `json:"profiles"`
 	Restore      string            `json:"restore"`
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 842eb01be..c44af4e98 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -3,6 +3,7 @@ package main
 import (
 	"fmt"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -36,7 +37,7 @@ func (suite *lxdTestSuite) TestContainer_ProfilesMulti() {
 		"unprivileged",
 		"unprivileged",
 		map[string]string{"security.privileged": "true"},
-		shared.Devices{})
+		types.Devices{})
 
 	suite.Req.Nil(err, "Failed to create the unprivileged profile.")
 	defer func() {
@@ -70,8 +71,8 @@ func (suite *lxdTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
 		Config:    map[string]string{"security.privileged": "true"},
-		Devices: shared.Devices{
-			"eth0": shared.Device{
+		Devices: types.Devices{
+			"eth0": types.Device{
 				"type":    "nic",
 				"nictype": "bridged",
 				"parent":  "unknownbr0"}},
@@ -100,8 +101,8 @@ func (suite *lxdTestSuite) TestContainer_LoadFromDB() {
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
 		Config:    map[string]string{"security.privileged": "true"},
-		Devices: shared.Devices{
-			"eth0": shared.Device{
+		Devices: types.Devices{
+			"eth0": types.Device{
 				"type":    "nic",
 				"nictype": "bridged",
 				"parent":  "unknownbr0"}},
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index adf01930d..bbab57dcc 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -11,6 +11,7 @@ import (
 	"github.com/dustinkirkland/golang-petname"
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 
@@ -50,7 +51,7 @@ type containerImageSource struct {
 type containerPostReq struct {
 	Architecture string               `json:"architecture"`
 	Config       map[string]string    `json:"config"`
-	Devices      shared.Devices       `json:"devices"`
+	Devices      types.Devices        `json:"devices"`
 	Ephemeral    bool                 `json:"ephemeral"`
 	Name         string               `json:"name"`
 	Profiles     []string             `json:"profiles"`
@@ -396,7 +397,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	}
 
 	if req.Devices == nil {
-		req.Devices = shared.Devices{}
+		req.Devices = types.Devices{}
 	}
 
 	if req.Config == nil {
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index d865a52a8..24ad66eaa 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"time"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -100,7 +101,7 @@ func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
 	args.Profiles = profiles
 
 	/* get container_devices */
-	args.Devices = shared.Devices{}
+	args.Devices = types.Devices{}
 	newdevs, err := dbDevices(db, name, false)
 	if err != nil {
 		return args, err
diff --git a/lxd/db_devices.go b/lxd/db_devices.go
index 6a8eea29f..71164feb8 100644
--- a/lxd/db_devices.go
+++ b/lxd/db_devices.go
@@ -6,7 +6,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/lxd/types"
 )
 
 func dbDeviceTypeToString(t int) (string, error) {
@@ -43,7 +43,7 @@ func dbDeviceTypeToInt(t string) (int, error) {
 	}
 }
 
-func dbDevicesAdd(tx *sql.Tx, w string, cID int64, devices shared.Devices) error {
+func dbDevicesAdd(tx *sql.Tx, w string, cID int64, devices types.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)
@@ -94,10 +94,10 @@ func dbDevicesAdd(tx *sql.Tx, w string, cID int64, devices shared.Devices) error
 	return nil
 }
 
-func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (shared.Device, error) {
+func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (types.Device, error) {
 	var query string
 	var key, value string
-	newdev := shared.Device{} // That's a map[string]string
+	newdev := types.Device{} // That's a map[string]string
 	inargs := []interface{}{id}
 	outfmt := []interface{}{key, value}
 
@@ -122,7 +122,7 @@ func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (shared.Device, error) {
 	return newdev, nil
 }
 
-func dbDevices(db *sql.DB, qName string, isprofile bool) (shared.Devices, error) {
+func dbDevices(db *sql.DB, qName string, isprofile bool) (types.Devices, error) {
 	var q string
 	if isprofile {
 		q = `SELECT profiles_devices.id, profiles_devices.name, profiles_devices.type
@@ -144,7 +144,7 @@ func dbDevices(db *sql.DB, qName string, isprofile bool) (shared.Devices, error)
 		return nil, err
 	}
 
-	devices := shared.Devices{}
+	devices := types.Devices{}
 	for _, r := range results {
 		id = r[0].(int)
 		name = r[1].(string)
diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index 6e440ab99..4d108c7c3 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -6,6 +6,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -59,7 +60,7 @@ func dbProfileGet(db *sql.DB, profile string) (int64, *shared.ProfileConfig, err
 }
 
 func dbProfileCreate(db *sql.DB, profile string, description string, config map[string]string,
-	devices shared.Devices) (int64, error) {
+	devices types.Devices) (int64, error) {
 
 	tx, err := dbBegin(db)
 	if err != nil {
@@ -105,8 +106,8 @@ func dbProfileCreateDefault(db *sql.DB) error {
 	}
 
 	// TODO: We should scan for bridges and use the best available as default.
-	devices := shared.Devices{
-		"eth0": shared.Device{
+	devices := map[string]map[string]string{
+		"eth0": map[string]string{
 			"name":    "eth0",
 			"type":    "nic",
 			"nictype": "bridged",
@@ -135,7 +136,7 @@ func dbProfileCreateDocker(db *sql.DB) error {
 		"type":   "disk",
 		"source": "/dev/null",
 	}
-	devices := map[string]shared.Device{"aadisable": aadisable}
+	devices := map[string]map[string]string{"aadisable": aadisable}
 
 	_, err = dbProfileCreate(db, "docker", "Profile supporting docker in containers", config, devices)
 	return err
diff --git a/lxd/db_test.go b/lxd/db_test.go
index eece15e2c..b64c31139 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logging"
 )
@@ -580,9 +581,9 @@ func Test_dbContainerProfiles(t *testing.T) {
 func Test_dbDevices_profiles(t *testing.T) {
 	var db *sql.DB
 	var err error
-	var result shared.Devices
-	var subresult shared.Device
-	var expected shared.Device
+	var result types.Devices
+	var subresult types.Device
+	var expected types.Device
 
 	db = createTestDb(t)
 	defer db.Close()
@@ -592,7 +593,7 @@ func Test_dbDevices_profiles(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	expected = shared.Device{"type": "nic", "devicekey": "devicevalue"}
+	expected = types.Device{"type": "nic", "devicekey": "devicevalue"}
 	subresult = result["devicename"]
 
 	for key, value := range expected {
@@ -606,9 +607,9 @@ func Test_dbDevices_profiles(t *testing.T) {
 func Test_dbDevices_containers(t *testing.T) {
 	var db *sql.DB
 	var err error
-	var result shared.Devices
-	var subresult shared.Device
-	var expected shared.Device
+	var result types.Devices
+	var subresult types.Device
+	var expected types.Device
 
 	db = createTestDb(t)
 	defer db.Close()
@@ -618,7 +619,7 @@ func Test_dbDevices_containers(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	expected = shared.Device{"type": "nic", "configkey": "configvalue"}
+	expected = types.Device{"type": "nic", "configkey": "configvalue"}
 	subresult = result["somename"]
 
 	for key, value := range expected {
diff --git a/lxd/profiles.go b/lxd/profiles.go
index b92455368..f7a0bbdd4 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -10,6 +10,7 @@ import (
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/version"
 
@@ -21,7 +22,7 @@ type profilesPostReq struct {
 	Name        string            `json:"name"`
 	Config      map[string]string `json:"config"`
 	Description string            `json:"description"`
-	Devices     shared.Devices    `json:"devices"`
+	Devices     types.Devices     `json:"devices"`
 }
 
 func profilesGet(d *Daemon, r *http.Request) Response {
diff --git a/lxd/storage.go b/lxd/storage.go
index b315dc3a5..179b75446 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logging"
 
@@ -649,7 +650,7 @@ func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) conta
 		config[ent.GetKey()] = ent.GetValue()
 	}
 
-	devices := shared.Devices{}
+	devices := types.Devices{}
 	for _, ent := range snap.LocalDevices {
 		props := map[string]string{}
 		for _, prop := range ent.Config {
diff --git a/shared/devices.go b/lxd/types/devices.go
similarity index 94%
rename from shared/devices.go
rename to lxd/types/devices.go
index e2b22db5e..56b61aeef 100644
--- a/shared/devices.go
+++ b/lxd/types/devices.go
@@ -1,11 +1,13 @@
-package shared
+package types
 
 import (
 	"sort"
+
+	"github.com/lxc/lxd/shared"
 )
 
 type Device map[string]string
-type Devices map[string]Device
+type Devices map[string]map[string]string
 
 func (list Devices) ContainsName(k string) bool {
 	if list[k] != nil {
@@ -66,14 +68,14 @@ func (old Devices) Update(newlist Devices) (map[string]Device, map[string]Device
 	for key, d := range addlist {
 		srcOldDevice := rmlist[key]
 		var oldDevice Device
-		err := DeepCopy(&srcOldDevice, &oldDevice)
+		err := shared.DeepCopy(&srcOldDevice, &oldDevice)
 		if err != nil {
 			continue
 		}
 
 		srcNewDevice := newlist[key]
 		var newDevice Device
-		err = DeepCopy(&srcNewDevice, &newDevice)
+		err = shared.DeepCopy(&srcNewDevice, &newDevice)
 		if err != nil {
 			continue
 		}
diff --git a/shared/devices_test.go b/lxd/types/devices_test.go
similarity index 96%
rename from shared/devices_test.go
rename to lxd/types/devices_test.go
index 07aaed6c5..4eee79195 100644
--- a/shared/devices_test.go
+++ b/lxd/types/devices_test.go
@@ -1,4 +1,4 @@
-package shared
+package types
 
 import (
 	"reflect"
diff --git a/shared/container.go b/shared/container.go
index bc919c2fb..d1fba7fc0 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -55,31 +55,31 @@ type ContainerExecControl struct {
 }
 
 type SnapshotInfo struct {
-	Architecture    string            `json:"architecture"`
-	Config          map[string]string `json:"config"`
-	CreationDate    time.Time         `json:"created_at"`
-	Devices         Devices           `json:"devices"`
-	Ephemeral       bool              `json:"ephemeral"`
-	ExpandedConfig  map[string]string `json:"expanded_config"`
-	ExpandedDevices Devices           `json:"expanded_devices"`
-	Name            string            `json:"name"`
-	Profiles        []string          `json:"profiles"`
-	Stateful        bool              `json:"stateful"`
+	Architecture    string                       `json:"architecture"`
+	Config          map[string]string            `json:"config"`
+	CreationDate    time.Time                    `json:"created_at"`
+	Devices         map[string]map[string]string `json:"devices"`
+	Ephemeral       bool                         `json:"ephemeral"`
+	ExpandedConfig  map[string]string            `json:"expanded_config"`
+	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
+	Name            string                       `json:"name"`
+	Profiles        []string                     `json:"profiles"`
+	Stateful        bool                         `json:"stateful"`
 }
 
 type ContainerInfo struct {
-	Architecture    string            `json:"architecture"`
-	Config          map[string]string `json:"config"`
-	CreationDate    time.Time         `json:"created_at"`
-	Devices         Devices           `json:"devices"`
-	Ephemeral       bool              `json:"ephemeral"`
-	ExpandedConfig  map[string]string `json:"expanded_config"`
-	ExpandedDevices Devices           `json:"expanded_devices"`
-	Name            string            `json:"name"`
-	Profiles        []string          `json:"profiles"`
-	Stateful        bool              `json:"stateful"`
-	Status          string            `json:"status"`
-	StatusCode      StatusCode        `json:"status_code"`
+	Architecture    string                       `json:"architecture"`
+	Config          map[string]string            `json:"config"`
+	CreationDate    time.Time                    `json:"created_at"`
+	Devices         map[string]map[string]string `json:"devices"`
+	Ephemeral       bool                         `json:"ephemeral"`
+	ExpandedConfig  map[string]string            `json:"expanded_config"`
+	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
+	Name            string                       `json:"name"`
+	Profiles        []string                     `json:"profiles"`
+	Stateful        bool                         `json:"stateful"`
+	Status          string                       `json:"status"`
+	StatusCode      StatusCode                   `json:"status_code"`
 }
 
 func (c ContainerInfo) IsActive() bool {
@@ -98,11 +98,11 @@ func (c ContainerInfo) IsActive() bool {
  * ContainerState, namely those which a user may update
  */
 type BriefContainerInfo struct {
-	Name      string            `json:"name"`
-	Profiles  []string          `json:"profiles"`
-	Config    map[string]string `json:"config"`
-	Devices   Devices           `json:"devices"`
-	Ephemeral bool              `json:"ephemeral"`
+	Name      string                       `json:"name"`
+	Profiles  []string                     `json:"profiles"`
+	Config    map[string]string            `json:"config"`
+	Devices   map[string]map[string]string `json:"devices"`
+	Ephemeral bool                         `json:"ephemeral"`
 }
 
 func (c *ContainerInfo) Brief() BriefContainerInfo {
@@ -134,8 +134,8 @@ const (
 )
 
 type ProfileConfig struct {
-	Name        string            `json:"name"`
-	Config      map[string]string `json:"config"`
-	Description string            `json:"description"`
-	Devices     Devices           `json:"devices"`
+	Name        string                       `json:"name"`
+	Config      map[string]string            `json:"config"`
+	Description string                       `json:"description"`
+	Devices     map[string]map[string]string `json:"devices"`
 }

From 70a630e91615a819ed33bcb0eccdd0f24328fa1b Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Thu, 22 Dec 2016 08:38:03 -0700
Subject: [PATCH 0606/1193] allow passing in-memory buffers to a FileResponse

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/response.go | 56 +++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 39 insertions(+), 17 deletions(-)

diff --git a/lxd/response.go b/lxd/response.go
index 0bdc1e9e5..7545fddcf 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -9,6 +9,7 @@ import (
 	"mime/multipart"
 	"net/http"
 	"os"
+	"time"
 
 	"github.com/mattn/go-sqlite3"
 
@@ -81,6 +82,7 @@ type fileResponseEntry struct {
 	identifier string
 	path       string
 	filename   string
+	buffer     []byte /* either a path or a buffer must be provided */
 }
 
 type fileResponse struct {
@@ -104,24 +106,38 @@ func (r *fileResponse) Render(w http.ResponseWriter) error {
 
 	// For a single file, return it inline
 	if len(r.files) == 1 {
-		f, err := os.Open(r.files[0].path)
-		if err != nil {
-			return err
-		}
-		defer f.Close()
+		var rs io.ReadSeeker
+		var mt time.Time
+		var sz int64
+
+		if r.files[0].path == "" {
+			rs = bytes.NewReader(r.files[0].buffer)
+			mt = time.Now()
+			sz = int64(len(r.files[0].buffer))
+		} else {
+			f, err := os.Open(r.files[0].path)
+			if err != nil {
+				return err
+			}
+			defer f.Close()
 
-		fi, err := f.Stat()
-		if err != nil {
-			return err
+			fi, err := f.Stat()
+			if err != nil {
+				return err
+			}
+
+			mt = fi.ModTime()
+			sz = fi.Size()
+			rs = f
 		}
 
 		w.Header().Set("Content-Type", "application/octet-stream")
-		w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
+		w.Header().Set("Content-Length", fmt.Sprintf("%d", sz))
 		w.Header().Set("Content-Disposition", fmt.Sprintf("inline;filename=%s", r.files[0].filename))
 
-		http.ServeContent(w, r.req, r.files[0].filename, fi.ModTime(), f)
-		if r.removeAfterServe {
-			err = os.Remove(r.files[0].path)
+		http.ServeContent(w, r.req, r.files[0].filename, mt, rs)
+		if r.files[0].path != "" && r.removeAfterServe {
+			err := os.Remove(r.files[0].path)
 			if err != nil {
 				return err
 			}
@@ -135,18 +151,24 @@ func (r *fileResponse) Render(w http.ResponseWriter) error {
 	mw := multipart.NewWriter(body)
 
 	for _, entry := range r.files {
-		fd, err := os.Open(entry.path)
-		if err != nil {
-			return err
+		var rd io.Reader
+		if entry.path != "" {
+			fd, err := os.Open(entry.path)
+			if err != nil {
+				return err
+			}
+			defer fd.Close()
+			rd = fd
+		} else {
+			rd = bytes.NewReader(entry.buffer)
 		}
-		defer fd.Close()
 
 		fw, err := mw.CreateFormFile(entry.identifier, entry.filename)
 		if err != nil {
 			return err
 		}
 
-		_, err = io.Copy(fw, fd)
+		_, err = io.Copy(fw, rd)
 		if err != nil {
 			return err
 		}

From 77b81dc58d97126c7d400bfc2e5cee854724629a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 31 Dec 2016 19:12:07 +0100
Subject: [PATCH 0607/1193] Update README.md
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Update CI layout a bit
 - Add weblate

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 README.md | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index bef7d8b7d..a396aef1d 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,12 @@ LXD is pronounced lex-dee.
 
 To easily see what LXD is about, you can [try it online](https://linuxcontainers.org/lxd/try-it).
 
-## CI status
+## Status
 
- * Travis: [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd)
- * AppVeyor: [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd)
- * Jenkins: [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/)
+ * Jenkins (Linux): [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/)
+ * Travis (macOS): [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd/)
+ * AppVeyor (Windows): [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd/)
+ * Weblate (translations): [![Translation status](https://hosted.weblate.org/widgets/linux-containers/-/svg-badge.svg)](https://hosted.weblate.org/projects/linux-containers/lxd/)
 
 ## Getting started with LXD
 

From b72f0515bc3585bef0f3adba0f43385e78539feb Mon Sep 17 00:00:00 2001
From: Vahe Khachikyan <vahe at live.ca>
Date: Fri, 6 Jan 2017 23:43:48 -0500
Subject: [PATCH 0608/1193] Added reference to godoc to README

Signed-off-by: Vahe Khachikyan <vahe at live.ca>
---
 README.md | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index a396aef1d..8e3300e9e 100644
--- a/README.md
+++ b/README.md
@@ -8,10 +8,12 @@ To easily see what LXD is about, you can [try it online](https://linuxcontainers
 
 ## Status
 
- * Jenkins (Linux): [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/)
- * Travis (macOS): [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd/)
- * AppVeyor (Windows): [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd/)
- * Weblate (translations): [![Translation status](https://hosted.weblate.org/widgets/linux-containers/-/svg-badge.svg)](https://hosted.weblate.org/projects/linux-containers/lxd/)
+* GoDoc: [![GoDoc](https://godoc.org/github.com/lxc/lxd?status.svg)](https://godoc.org/github.com/lxc/lxd)
+* Jenkins (Linux): [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/)
+* Travis (macOS): [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd/)
+* AppVeyor (Windows): [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd/)
+* Weblate (translations): [![Translation status](https://hosted.weblate.org/widgets/linux-containers/-/svg-badge.svg)](https://hosted.weblate.org/projects/linux-containers/lxd/)
+
 
 ## Getting started with LXD
 

From 53f68d0413c546faa2730e12a16f99e83bd27661 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 7 Jan 2017 19:52:23 +0100
Subject: [PATCH 0609/1193] Drop unused code from db.go
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/lxd/db.go b/lxd/db.go
index b1eabf4ef..f5161e8b6 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -25,15 +25,6 @@ var (
 	NoSuchObjectError = fmt.Errorf("No such object")
 )
 
-// Profile is here to order Profiles.
-type Profile struct {
-	name  string
-	order int
-}
-
-// Profiles will contain a list of all Profiles.
-type Profiles []Profile
-
 // CURRENT_SCHEMA contains the current SQLite SQL Schema.
 const CURRENT_SCHEMA string = `
 CREATE TABLE IF NOT EXISTS certificates (

From 486d134716d6f2f3d4991ab7102eee03453d6ff3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 11:46:44 +0200
Subject: [PATCH 0610/1193] lxc: Export image last use date
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxc/image.go b/lxc/image.go
index 6c07e7147..786f36b5d 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -360,6 +360,11 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		} else {
 			fmt.Printf("    " + i18n.G("Expires: never") + "\n")
 		}
+		if info.LastUsedAt.UTC().Unix() != 0 {
+			fmt.Printf("    "+i18n.G("Last used: %s")+"\n", info.LastUsedAt.UTC().Format(layout))
+		} else {
+			fmt.Printf("    " + i18n.G("Last used: never") + "\n")
+		}
 		fmt.Println(i18n.G("Properties:"))
 		for key, value := range info.Properties {
 			fmt.Printf("    %s: %s\n", key, value)

From 48213b83d13b0b0bc2a736bd70d22474d3bf36a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 16:56:24 +0200
Subject: [PATCH 0611/1193] client: Better handle http errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/client.go b/client.go
index 91a9b1e3e..5f3a82b08 100644
--- a/client.go
+++ b/client.go
@@ -1027,6 +1027,10 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 		}
 
 		req, err = http.NewRequest("POST", uri, progress)
+		if err != nil {
+			return "", err
+		}
+
 		req.Header.Set("Content-Type", w.FormDataContentType())
 	} else {
 		fImage, err = os.Open(imageFile)
@@ -1049,6 +1053,10 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 		}
 
 		req, err = http.NewRequest("POST", uri, progress)
+		if err != nil {
+			return "", err
+		}
+
 		req.Header.Set("X-LXD-filename", filepath.Base(imageFile))
 		req.Header.Set("Content-Type", "application/octet-stream")
 	}

From 2c474913bb17009d1d12ed3676452bdf53058a63 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 16:58:41 +0200
Subject: [PATCH 0612/1193] init: Properly replace args list
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/init.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxc/init.go b/lxc/init.go
index 86b8d3670..6ab4aeeb5 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -125,6 +125,7 @@ func (c *initCmd) massage_args() {
 		initRequestedEmptyProfiles = true
 		newargs := os.Args[0 : l-2]
 		newargs = append(newargs, os.Args[l-1])
+		os.Args = newargs
 		return
 	}
 }

From 754b1cbd34c8db86a6eb13c905edd34509efd8d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 17:00:04 +0200
Subject: [PATCH 0613/1193] tests: Don't ignore errors in db tests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_test.go | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/lxd/db_test.go b/lxd/db_test.go
index b64c31139..e18b19cc5 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -98,6 +98,9 @@ func Test_deleting_a_container_cascades_on_related_tables(t *testing.T) {
 	// Make sure there are 0 containers_devices_config entries left.
 	statements = `SELECT count(*) FROM containers_devices_config;`
 	err = db.QueryRow(statements).Scan(&count)
+	if err != nil {
+		t.Error(err)
+	}
 
 	if count != 0 {
 		t.Errorf("Deleting a container didn't delete the associated container_devices_config! There are %d left", count)
@@ -150,6 +153,9 @@ func Test_deleting_a_profile_cascades_on_related_tables(t *testing.T) {
 	// Make sure there are 0 profiles_devices_config entries left.
 	statements = `SELECT count(*) FROM profiles_devices_config WHERE profile_device_id == 4;`
 	err = db.QueryRow(statements).Scan(&count)
+	if err != nil {
+		t.Error(err)
+	}
 
 	if count != 0 {
 		t.Errorf("Deleting a profile didn't delete the related profiles_devices_config! There are %d left", count)
@@ -185,6 +191,9 @@ func Test_deleting_an_image_cascades_on_related_tables(t *testing.T) {
 	// Make sure there are 0 images_properties entries left.
 	statements = `SELECT count(*) FROM images_properties;`
 	err = db.QueryRow(statements).Scan(&count)
+	if err != nil {
+		t.Error(err)
+	}
 
 	if count != 0 {
 		t.Errorf("Deleting an image didn't delete the related images_properties! There are %d left", count)
@@ -284,6 +293,9 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	// Make sure there are 0 container_profiles entries left.
 	statements = `SELECT count(*) FROM containers_profiles;`
 	err = d.db.QueryRow(statements).Scan(&count)
+	if err != nil {
+		t.Error(err)
+	}
 
 	if count != 0 {
 		t.Errorf("Deleting a container didn't delete the profile association! There are %d left", count)
@@ -391,6 +403,9 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	// Make sure there are 0 containers_config entries left.
 	statements = `SELECT count(*) FROM containers_config;`
 	err = db.QueryRow(statements).Scan(&count)
+	if err != nil {
+		t.Error(err)
+	}
 
 	if count != 0 {
 		t.Fatal("updateDb did not delete orphaned child entries after adding ON DELETE CASCADE!")

From 0edca949a4767e3cbc4241a7333542315f9e7270 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 13 Jan 2017 12:25:39 +0200
Subject: [PATCH 0614/1193] sort addresses in `lxc list` output

This closes an issue in a different repo: https://github.com/lxc/lxc/issues/1383

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxc/list.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxc/list.go b/lxc/list.go
index 063a8bf14..17b96f996 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -443,6 +443,7 @@ func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 				}
 			}
 		}
+		sort.Sort(sort.Reverse(sort.StringSlice(ipv4s)))
 		return strings.Join(ipv4s, "\n")
 	} else {
 		return ""
@@ -467,6 +468,7 @@ func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 				}
 			}
 		}
+		sort.Sort(sort.Reverse(sort.StringSlice(ipv6s)))
 		return strings.Join(ipv6s, "\n")
 	} else {
 		return ""

From f5bf43cc12698ccbf46cc9679e8a398a4ed3c5b4 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sat, 14 Jan 2017 14:28:03 +0100
Subject: [PATCH 0615/1193] shared/util: ParseByteSizeString() deal with bytes

When we are passed in a number with a suffix, assume it is already in bytes.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util.go | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/shared/util.go b/shared/util.go
index 3b8fa9cd5..b8481ea3f 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -19,6 +19,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"unicode"
 )
 
 const SnapshotDelimiter = "/"
@@ -613,6 +614,18 @@ func ParseByteSizeString(input string) (int64, error) {
 		return 0, nil
 	}
 
+	// COMMENT(brauner): In case the last character is a number we assume
+	// that we are passed a simple integer which we interpret as being in
+	// bytes. So we parse it directly and return.
+	if unicode.IsNumber(rune(input[len(input)-1])) {
+		valueInt, err := strconv.ParseInt(input, 10, 64)
+		if err != nil {
+			return -1, fmt.Errorf("Invalid integer: %s", input)
+		}
+
+		return valueInt, nil
+	}
+
 	if len(input) < 3 {
 		return -1, fmt.Errorf("Invalid value: %s", input)
 	}

From 24759582407778524c24ede30a78f323675fb05c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sun, 15 Jan 2017 02:25:13 +0100
Subject: [PATCH 0616/1193] shared/util: add Int64InSlice()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/shared/util.go b/shared/util.go
index b8481ea3f..6473df797 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -387,6 +387,15 @@ func IntInSlice(key int, list []int) bool {
 	return false
 }
 
+func Int64InSlice(key int64, list []int64) bool {
+	for _, entry := range list {
+		if entry == key {
+			return true
+		}
+	}
+	return false
+}
+
 func IsTrue(value string) bool {
 	if StringInSlice(strings.ToLower(value), []string{"true", "1", "yes", "on"}) {
 		return true

From 7c033630f77aca9f2ec5b31be5c6e36a99d8bb28 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sun, 15 Jan 2017 02:25:34 +0100
Subject: [PATCH 0617/1193] shared/util: improve byte parsing

- GetByteSizeString() --> switch to IEEE 1541 standardized "B" suffix
- ParseByteSizeString() --> handle "B" suffix and legacy " bytes" suffix

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util.go | 36 +++++++++++++++++++++---------------
 1 file changed, 21 insertions(+), 15 deletions(-)

diff --git a/shared/util.go b/shared/util.go
index 6473df797..e97b00712 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -619,31 +619,32 @@ func ParseMetadata(metadata interface{}) (map[string]interface{}, error) {
 // Parse a size string in bytes (e.g. 200kB or 5GB) into the number of bytes it
 // represents. Supports suffixes up to EB. "" == 0.
 func ParseByteSizeString(input string) (int64, error) {
+	suffixLen := 2
+
 	if input == "" {
 		return 0, nil
 	}
 
-	// COMMENT(brauner): In case the last character is a number we assume
-	// that we are passed a simple integer which we interpret as being in
-	// bytes. So we parse it directly and return.
 	if unicode.IsNumber(rune(input[len(input)-1])) {
-		valueInt, err := strconv.ParseInt(input, 10, 64)
-		if err != nil {
-			return -1, fmt.Errorf("Invalid integer: %s", input)
-		}
-
-		return valueInt, nil
-	}
-
-	if len(input) < 3 {
+		// COMMENT(brauner): No suffix --> bytes.
+		suffixLen = 0
+	} else if (len(input) >= 2) && (input[len(input)-1] == 'B') && unicode.IsNumber(rune(input[len(input)-2])) {
+		// COMMENT(brauner): "B" suffix --> bytes.
+		suffixLen = 1
+	} else if strings.HasSuffix(input, " bytes") {
+		// COMMENT(brauner): Backward compatible behaviour in case we
+		// talk to a LXD that still uses GetByteSizeString() that
+		// returns "n bytes".
+		suffixLen = 6
+	} else if (len(input) < 3) && (suffixLen == 2) {
 		return -1, fmt.Errorf("Invalid value: %s", input)
 	}
 
 	// Extract the suffix
-	suffix := input[len(input)-2:]
+	suffix := input[len(input)-suffixLen:]
 
 	// Extract the value
-	value := input[0 : len(input)-2]
+	value := input[0 : len(input)-suffixLen]
 	valueInt, err := strconv.ParseInt(value, 10, 64)
 	if err != nil {
 		return -1, fmt.Errorf("Invalid integer: %s", input)
@@ -653,6 +654,11 @@ func ParseByteSizeString(input string) (int64, error) {
 		return -1, fmt.Errorf("Invalid value: %d", valueInt)
 	}
 
+	// COMMENT(brauner): The value is already in bytes.
+	if suffixLen != 2 {
+		return valueInt, nil
+	}
+
 	// Figure out the multiplicator
 	multiplicator := int64(0)
 	switch suffix {
@@ -724,7 +730,7 @@ func ParseBitSizeString(input string) (int64, error) {
 
 func GetByteSizeString(input int64, precision uint) string {
 	if input < 1024 {
-		return fmt.Sprintf("%d bytes", input)
+		return fmt.Sprintf("%dB", input)
 	}
 
 	value := float64(input)

From 52035f9ef58a8c46341646bbdc4729073f8ed25a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 19 Jan 2017 01:27:24 +0100
Subject: [PATCH 0618/1193] test/suites: use lxc restart <c>

Replace all calls to lxc exec <c> reboot since they might receive SIGTERM and
incorrectly report a failed exit status.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/basic.sh  | 2 +-
 test/suites/config.sh | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index c85679a6a..ad989dbc7 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -333,7 +333,7 @@ test_basic_usage() {
   lxc launch testimage foo -e
 
   OLD_INIT=$(lxc info foo | grep ^Pid)
-  lxc exec foo reboot
+  lxc exec foo reboot || true
 
   REBOOTED="false"
 
diff --git a/test/suites/config.sh b/test/suites/config.sh
index 18e158c89..3e4870914 100644
--- a/test/suites/config.sh
+++ b/test/suites/config.sh
@@ -12,13 +12,13 @@ dounixdevtest() {
     lxc start foo
     lxc config device add foo tty unix-char "$@"
     lxc exec foo -- stat /dev/ttyS0
-    lxc exec foo reboot
+    lxc restart foo
     lxc exec foo -- stat /dev/ttyS0
     lxc restart foo --force
     lxc exec foo -- stat /dev/ttyS0
     lxc config device remove foo tty
     ensure_removed "was not hot-removed"
-    lxc exec foo reboot
+    lxc restart foo
     ensure_removed "removed device re-appeared after container reboot"
     lxc restart foo --force
     ensure_removed "removed device re-appaared after lxc reboot"
@@ -70,7 +70,7 @@ testloopmounts() {
   lxc exec foo stat /mnt/hello
   lxc config device remove foo mnt
   ensure_fs_unmounted "fs should have been hot-unmounted"
-  lxc exec foo reboot
+  lxc restart foo
   ensure_fs_unmounted "removed fs re-appeared after reboot"
   lxc restart foo --force
   ensure_fs_unmounted "removed fs re-appeared after restart"

From 27656caa0ce692dd0464cc9cb81dbd697ed0cdd0 Mon Sep 17 00:00:00 2001
From: Dmitrii Shcherbakov <dmitrii.shcherbakov at canonical.com>
Date: Mon, 23 Jan 2017 00:02:08 +0300
Subject: [PATCH 0619/1193] cloud-init.md: Add Documentation on Network
 Configuration via cloud-init

At the moment there is no definitive documentation on how to use
cloud-init for custom network configuration of lxd containers. Not to
mention that it is not clear from the current documentation that this
behavior depends on image contents and templates shipped with the image.

This guide is a bit practical and Ubuntu-specific but should give a
good starting point as it describes the implementation used right now
and at least gives a working example and shows the resulting layout.

Signed-off-by: Dmitrii Shcherbakov <dmitrii.shcherbakov at canonical.com>
---
 doc/cloud-init.md | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 145 insertions(+)
 create mode 100644 doc/cloud-init.md

diff --git a/doc/cloud-init.md b/doc/cloud-init.md
new file mode 100644
index 000000000..948066748
--- /dev/null
+++ b/doc/cloud-init.md
@@ -0,0 +1,145 @@
+# Custom Network Configuration With cloud-init
+
+[cloud-init](https://launchpad.net/cloud-init) may be used for custom network configuration of containers.
+
+Before trying to use it, however, first determine which image source you are
+about to use as not all container images have cloud-init package installed.
+At the time of writing, images provided at images.linuxcontainers.org do not
+have the cloud-init package installed, therefore, any of the configuration
+options mentioned in this guide will not work. On the contrary, images
+provided at cloud-images.ubuntu.com have the necessary package installed
+and also have a templates directory in their archive populated with
+ * cloud-init-meta.tpl
+ * cloud-init-user.tpl
+ * cloud-init-vendor.tpl
+ * cloud-init-network.tpl
+
+and others not related to cloud-init.
+
+Templates provided with container images at cloud-images.ubuntu.com have
+the following in their metadata.yaml:
+
+```
+/var/lib/cloud/seed/nocloud-net/network-config:
+  when:
+    - create
+    - copy
+  template: cloud-init-network.tpl
+```
+
+Therefore, either when you create or copy a container it gets a newly rendered
+network configuration from a pre-defined template. cloud-init uses the
+network-config file to render /etc/network/interfaces.d/50-cloud-init.cfg when
+you first start a container. It will not react to any changes if you restart
+a container afterwards unless you force it.
+
+The default behavior is to use a DHCP client on a container's eth0 interface.
+
+In order to change this you need to define your own network configuration
+using user.network-config key in the config dictionary which will override
+the default configuration (this is due to how the template is structured).
+
+The allowed values follow /etc/network/interfaces syntax in case of Ubuntu
+images.
+
+For example, to configure a specific network interface with a static IPv4
+address and also use a custom nameserver use
+
+```
+config:
+  user.network-config: |
+    version: 1
+    config:
+      - type: physical
+        name: eth1
+        subnets:
+          - type: static
+            ipv4: true
+            address: 10.10.101.20
+            netmask: 255.255.255.0
+            gateway: 10.10.101.1
+            control: auto
+      - type: nameserver
+        address: 10.10.10.254
+```
+
+A container's rootfs will contain the following files as a result:
+
+ * /var/lib/cloud/seed/nocloud-net/network-config
+ * /etc/network/interfaces.d/50-cloud-init.cfg
+
+The former will be the same as the value provided in user.network-config,
+the latter will be a file in /etc/network/interfaces format converted from
+the network-config file by cloud-init (if it is not check syslog for cloud-init
+error messages).
+
+
+/etc/network/interfaces.d/50-cloud-init.cfg should then contain
+
+```
+# This file is generated from information provided by
+# the datasource.  Changes to it will not persist across an instance.
+# To disable cloud-init's network configuration capabilities, write a file
+# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
+# network: {config: disabled}
+auto lo
+iface lo inet loopback
+    dns-nameservers 10.10.10.254
+
+auto eth1
+iface eth1 inet static
+    address 10.10.101.20
+    gateway 10.10.101.1
+    netmask 255.255.255.0
+```
+
+You will also notice that /run/resolvconf/resolv.conf or /etc/resolv.conf
+which is pointing to it will contain the desired dns server after boot-up.
+
+```
+# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
+#     DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
+nameserver 10.10.10.254
+```
+
+# Implementation Details
+
+cloud-init allows you to seed instance configuration using the following files
+located at /var/lib/cloud/seed/nocloud-net:
+ * user-data (required)
+ * meta-data (required)
+ * vendor-data (optional)
+ * network-config (optional)
+
+The network-config file is written to by lxd using data provided in templates
+that come with an image. This is governed by metadata.yaml but naming of the
+configuration keys and template content is not hard-coded as far as lxd is
+concerned - this is purely image data that can be modified if needed.
+
+ * [NoCloud data source documentation](https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html)
+ * The source code for [NoCloud data source](https://git.launchpad.net/cloud-init/tree/cloudinit/sources/DataSourceNoCloud.py)
+ * A good reference on which values you can use are [unit tests for cloud-init](https://git.launchpad.net/cloud-init/tree/tests/unittests/test_datasource/test_nocloud.py#n163)
+ * [cloud-init directory layout](https://cloudinit.readthedocs.io/en/latest/topics/dir_layout.html)
+
+A default cloud-init-network.tpl provided with images from the "ubuntu:" image
+source looks like this:
+
+```
+{% if config_get("user.network-config", "") == "" %}version: 1
+config:
+    - type: physical
+      name: eth0
+      subnets:
+          - type: {% if config_get("user.network_mode", "") == "link-local" %}manual{% else %}dhcp{% endif %}
+            control: auto{% else %}{{ config_get("user.network-config", "") }}{% endif %}
+```
+
+The template syntax is the one used in the pongo2 template engine. A custom
+config_get function is defined to retrieve values from a container
+configuration.
+
+Options available with such a template structure:
+ * Use DHCP by default on your eth0 interface;
+ * Set user.network_mode to "link-local" and configure networking by hand;
+ * Seed cloud-init by defining user.network-config.
+

From 9f677e4856e860a0f48b0d3d92743ac51559f0ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 19:24:41 -0500
Subject: [PATCH 0620/1193] shared: Move REST API to new package: image
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                             | 17 ++++----
 lxc/image.go                          | 39 ++++++++---------
 lxd/containers_post.go                |  5 ++-
 lxd/daemon_images.go                  | 11 ++---
 lxd/db_images.go                      | 57 +++++++++++++------------
 lxd/db_test.go                        | 15 ++++---
 lxd/images.go                         | 72 +++++++++++--------------------
 lxd/remote.go                         |  4 +-
 lxd/storage.go                        |  3 +-
 shared/api/image.go                   | 80 +++++++++++++++++++++++++++++++++++
 shared/image.go                       | 64 ----------------------------
 shared/simplestreams/simplestreams.go | 55 ++++++++++++------------
 12 files changed, 213 insertions(+), 209 deletions(-)
 create mode 100644 shared/api/image.go
 delete mode 100644 shared/image.go

diff --git a/client.go b/client.go
index 5f3a82b08..016866326 100644
--- a/client.go
+++ b/client.go
@@ -24,6 +24,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/simplestreams"
 	"github.com/lxc/lxd/shared/version"
@@ -1122,7 +1123,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 	return fingerprint, nil
 }
 
-func (c *Client) GetImageInfo(image string) (*shared.ImageInfo, error) {
+func (c *Client) GetImageInfo(image string) (*api.Image, error) {
 	if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil {
 		return c.simplestreams.GetImageInfo(image)
 	}
@@ -1132,7 +1133,7 @@ func (c *Client) GetImageInfo(image string) (*shared.ImageInfo, error) {
 		return nil, err
 	}
 
-	info := shared.ImageInfo{}
+	info := api.Image{}
 	if err := json.Unmarshal(resp.Metadata, &info); err != nil {
 		return nil, err
 	}
@@ -1140,7 +1141,7 @@ func (c *Client) GetImageInfo(image string) (*shared.ImageInfo, error) {
 	return &info, nil
 }
 
-func (c *Client) PutImageInfo(name string, p shared.BriefImageInfo) error {
+func (c *Client) PutImageInfo(name string, p api.ImagePut) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1149,7 +1150,7 @@ func (c *Client) PutImageInfo(name string, p shared.BriefImageInfo) error {
 	return err
 }
 
-func (c *Client) ListImages() ([]shared.ImageInfo, error) {
+func (c *Client) ListImages() ([]api.Image, error) {
 	if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil {
 		return c.simplestreams.ListImages()
 	}
@@ -1159,7 +1160,7 @@ func (c *Client) ListImages() ([]shared.ImageInfo, error) {
 		return nil, err
 	}
 
-	var result []shared.ImageInfo
+	var result []api.Image
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
 	}
@@ -1201,7 +1202,7 @@ func (c *Client) DeleteAlias(alias string) error {
 	return err
 }
 
-func (c *Client) ListAliases() (shared.ImageAliases, error) {
+func (c *Client) ListAliases() ([]api.ImageAliasesEntry, error) {
 	if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil {
 		return c.simplestreams.ListAliases()
 	}
@@ -1211,7 +1212,7 @@ func (c *Client) ListAliases() (shared.ImageAliases, error) {
 		return nil, err
 	}
 
-	var result shared.ImageAliases
+	var result []api.ImageAliasesEntry
 
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
@@ -1294,7 +1295,7 @@ func (c *Client) GetAlias(alias string) string {
 		return ""
 	}
 
-	var result shared.ImageAliasesEntry
+	var result api.ImageAliasesEntry
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return ""
 	}
diff --git a/lxc/image.go b/lxc/image.go
index 786f36b5d..2f161971b 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
@@ -351,12 +352,12 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		fmt.Printf(i18n.G("Public: %s")+"\n", public)
 		fmt.Printf(i18n.G("Timestamps:") + "\n")
 		const layout = "2006/01/02 15:04 UTC"
-		if info.CreationDate.UTC().Unix() != 0 {
-			fmt.Printf("    "+i18n.G("Created: %s")+"\n", info.CreationDate.UTC().Format(layout))
+		if info.CreatedAt.UTC().Unix() != 0 {
+			fmt.Printf("    "+i18n.G("Created: %s")+"\n", info.CreatedAt.UTC().Format(layout))
 		}
-		fmt.Printf("    "+i18n.G("Uploaded: %s")+"\n", info.UploadDate.UTC().Format(layout))
-		if info.ExpiryDate.UTC().Unix() != 0 {
-			fmt.Printf("    "+i18n.G("Expires: %s")+"\n", info.ExpiryDate.UTC().Format(layout))
+		fmt.Printf("    "+i18n.G("Uploaded: %s")+"\n", info.UploadedAt.UTC().Format(layout))
+		if info.ExpiresAt.UTC().Unix() != 0 {
+			fmt.Printf("    "+i18n.G("Expires: %s")+"\n", info.ExpiresAt.UTC().Format(layout))
 		} else {
 			fmt.Printf("    " + i18n.G("Expires: never") + "\n")
 		}
@@ -374,11 +375,11 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			fmt.Printf("    - %s\n", alias.Name)
 		}
 		fmt.Printf(i18n.G("Auto update: %s")+"\n", autoUpdate)
-		if info.Source != nil {
+		if info.UpdateSource != nil {
 			fmt.Println(i18n.G("Source:"))
-			fmt.Printf("    Server: %s\n", info.Source.Server)
-			fmt.Printf("    Protocol: %s\n", info.Source.Protocol)
-			fmt.Printf("    Alias: %s\n", info.Source.Alias)
+			fmt.Printf("    Server: %s\n", info.UpdateSource.Server)
+			fmt.Printf("    Protocol: %s\n", info.UpdateSource.Protocol)
+			fmt.Printf("    Alias: %s\n", info.UpdateSource.Alias)
 		}
 		return nil
 
@@ -558,7 +559,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		properties := info.Brief()
+		properties := info.Writable()
 
 		data, err := yaml.Marshal(&properties)
 		fmt.Printf("%s", data)
@@ -577,7 +578,7 @@ func (c *imageCmd) dereferenceAlias(d *lxd.Client, inName string) string {
 	return result
 }
 
-func (c *imageCmd) shortestAlias(list []shared.ImageAlias) string {
+func (c *imageCmd) shortestAlias(list []api.ImageAlias) string {
 	shortest := ""
 	for _, l := range list {
 		if shortest == "" {
@@ -601,7 +602,7 @@ func (c *imageCmd) findDescription(props map[string]string) string {
 	return ""
 }
 
-func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error {
+func (c *imageCmd) showImages(images []api.Image, filters []string) error {
 	data := [][]string{}
 	for _, image := range images {
 		if !c.imageShouldShow(filters, &image) {
@@ -621,7 +622,7 @@ func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error
 		}
 
 		const layout = "Jan 2, 2006 at 3:04pm (MST)"
-		uploaded := image.UploadDate.UTC().Format(layout)
+		uploaded := image.UploadedAt.UTC().Format(layout)
 		size := fmt.Sprintf("%.2fMB", float64(image.Size)/1024.0/1024.0)
 		data = append(data, []string{shortest, fp, public, description, image.Architecture, size, uploaded})
 	}
@@ -645,7 +646,7 @@ func (c *imageCmd) showImages(images []shared.ImageInfo, filters []string) error
 	return nil
 }
 
-func (c *imageCmd) showAliases(aliases shared.ImageAliases, filters []string) error {
+func (c *imageCmd) showAliases(aliases []api.ImageAliasesEntry, filters []string) error {
 	data := [][]string{}
 	for _, alias := range aliases {
 		if !c.aliasShouldShow(filters, &alias) {
@@ -678,7 +679,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 			return err
 		}
 
-		newdata := shared.BriefImageInfo{}
+		newdata := api.ImagePut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -692,7 +693,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 		return err
 	}
 
-	brief := config.Brief()
+	brief := config.Writable()
 	data, err := yaml.Marshal(&brief)
 	if err != nil {
 		return err
@@ -706,7 +707,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.BriefImageInfo{}
+		newdata := api.ImagePut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			err = client.PutImageInfo(image, newdata)
@@ -733,7 +734,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 	return nil
 }
 
-func (c *imageCmd) imageShouldShow(filters []string, state *shared.ImageInfo) bool {
+func (c *imageCmd) imageShouldShow(filters []string, state *api.Image) bool {
 	if len(filters) == 0 {
 		return true
 	}
@@ -792,7 +793,7 @@ func (c *imageCmd) imageShouldShow(filters []string, state *shared.ImageInfo) bo
 	return true
 }
 
-func (c *imageCmd) aliasShouldShow(filters []string, state *shared.ImageAliasesEntry) bool {
+func (c *imageCmd) aliasShouldShow(filters []string, state *api.ImageAliasesEntry) bool {
 	if len(filters) == 0 {
 		return true
 	}
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index bbab57dcc..c59f55ff8 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -87,7 +88,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 			return InternalError(err)
 		}
 
-		var image *shared.ImageInfo
+		var image *api.Image
 
 		for _, hash := range hashes {
 			_, img, err := dbImageGet(d.db, hash, false, true)
@@ -95,7 +96,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 				continue
 			}
 
-			if image != nil && img.CreationDate.Before(image.CreationDate) {
+			if image != nil && img.CreatedAt.Before(image.CreatedAt) {
 				continue
 			}
 
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 7324effdf..6a8c7ee1c 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -16,6 +16,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/simplestreams"
 	"github.com/lxc/lxd/shared/version"
@@ -25,8 +26,8 @@ import (
 
 // Simplestream cache
 type imageStreamCacheEntry struct {
-	Aliases      shared.ImageAliases `yaml:"aliases"`
-	Fingerprints []string            `yaml:"fingerprints"`
+	Aliases      []api.ImageAliasesEntry `yaml:"aliases"`
+	Fingerprints []string                `yaml:"fingerprints"`
 	expiry       time.Time
 	ss           *simplestreams.SimpleStreams
 }
@@ -244,7 +245,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	exporturl := server
 
-	var info shared.ImageInfo
+	var info api.Image
 	info.Fingerprint = fp
 
 	destDir := shared.VarPath("images")
@@ -481,8 +482,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 
 		info.Architecture = imageMeta.Architecture
-		info.CreationDate = time.Unix(imageMeta.CreationDate, 0)
-		info.ExpiryDate = time.Unix(imageMeta.ExpiryDate, 0)
+		info.CreatedAt = time.Unix(imageMeta.CreationDate, 0)
+		info.ExpiresAt = time.Unix(imageMeta.ExpiryDate, 0)
 		info.Properties = imageMeta.Properties
 	}
 
diff --git a/lxd/db_images.go b/lxd/db_images.go
index ac515a3d0..c3aef6798 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -7,7 +7,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
@@ -76,27 +76,27 @@ func dbImageSourceInsert(db *sql.DB, imageId int, server string, protocol string
 	return err
 }
 
-func dbImageSourceGet(db *sql.DB, imageId int) (int, shared.ImageSource, error) {
+func dbImageSourceGet(db *sql.DB, imageId int) (int, api.ImageSource, error) {
 	q := `SELECT id, server, protocol, certificate, alias FROM images_source WHERE image_id=?`
 
 	id := 0
 	protocolInt := -1
-	result := shared.ImageSource{}
+	result := api.ImageSource{}
 
 	arg1 := []interface{}{imageId}
 	arg2 := []interface{}{&id, &result.Server, &protocolInt, &result.Certificate, &result.Alias}
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		if err == sql.ErrNoRows {
-			return -1, shared.ImageSource{}, NoSuchObjectError
+			return -1, api.ImageSource{}, NoSuchObjectError
 		}
 
-		return -1, shared.ImageSource{}, err
+		return -1, api.ImageSource{}, err
 	}
 
 	protocol, found := dbImageSourceProtocol[protocolInt]
 	if !found {
-		return -1, shared.ImageSource{}, fmt.Errorf("Invalid protocol: %d", protocolInt)
+		return -1, api.ImageSource{}, fmt.Errorf("Invalid protocol: %d", protocolInt)
 	}
 
 	result.Protocol = protocol
@@ -110,12 +110,12 @@ func dbImageSourceGet(db *sql.DB, imageId int) (int, shared.ImageSource, error)
 // pass a shortform and will get the full fingerprint.
 // There can never be more than one image with a given fingerprint, as it is
 // enforced by a UNIQUE constraint in the schema.
-func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool) (int, *shared.ImageInfo, error) {
+func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool) (int, *api.Image, error) {
 	var err error
 	var create, expire, used, upload *time.Time // These hold the db-returned times
 
 	// The object we'll actually return
-	image := shared.ImageInfo{}
+	image := api.Image{}
 	id := -1
 	arch := -1
 
@@ -159,27 +159,27 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 
 	// Some of the dates can be nil in the DB, let's process them.
 	if create != nil {
-		image.CreationDate = *create
+		image.CreatedAt = *create
 	} else {
-		image.CreationDate = time.Time{}
+		image.CreatedAt = time.Time{}
 	}
 
 	if expire != nil {
-		image.ExpiryDate = *expire
+		image.ExpiresAt = *expire
 	} else {
-		image.ExpiryDate = time.Time{}
+		image.ExpiresAt = time.Time{}
 	}
 
 	if used != nil {
-		image.LastUsedDate = *used
+		image.LastUsedAt = *used
 	} else {
-		image.LastUsedDate = time.Time{}
+		image.LastUsedAt = time.Time{}
 	}
 
 	image.Architecture, _ = osarch.ArchitectureName(arch)
 
 	// The upload date is enforced by NOT NULL in the schema, so it can never be nil.
-	image.UploadDate = *upload
+	image.UploadedAt = *upload
 
 	// Get the properties
 	q := "SELECT key, value FROM images_properties where image_id=?"
@@ -209,11 +209,11 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 		return -1, nil, err
 	}
 
-	aliases := []shared.ImageAlias{}
+	aliases := []api.ImageAlias{}
 	for _, r := range results {
 		name = r[0].(string)
 		desc = r[0].(string)
-		a := shared.ImageAlias{Name: name, Description: desc}
+		a := api.ImageAlias{Name: name, Description: desc}
 		aliases = append(aliases, a)
 	}
 
@@ -221,7 +221,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 
 	_, source, err := dbImageSourceGet(db, id)
 	if err == nil {
-		image.Source = &source
+		image.UpdateSource = &source
 	}
 
 	return id, &image, nil
@@ -245,7 +245,7 @@ func dbImageDelete(db *sql.DB, id int) error {
 	return nil
 }
 
-func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, shared.ImageAliasesEntry, error) {
+func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, api.ImageAliasesEntry, error) {
 	q := `SELECT images_aliases.id, images.fingerprint, images_aliases.description
 			 FROM images_aliases
 			 INNER JOIN images
@@ -257,19 +257,24 @@ func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, shared
 
 	var fingerprint, description string
 	id := -1
+	entry := api.ImageAliasesEntry{}
 
 	arg1 := []interface{}{name}
 	arg2 := []interface{}{&id, &fingerprint, &description}
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		if err == sql.ErrNoRows {
-			return -1, shared.ImageAliasesEntry{}, NoSuchObjectError
+			return -1, entry, NoSuchObjectError
 		}
 
-		return -1, shared.ImageAliasesEntry{}, err
+		return -1, entry, err
 	}
 
-	return id, shared.ImageAliasesEntry{Name: name, Target: fingerprint, Description: description}, nil
+	entry.Name = name
+	entry.Target = fingerprint
+	entry.Description = description
+
+	return id, entry, nil
 }
 
 func dbImageAliasRename(db *sql.DB, id int, name string) error {
@@ -312,7 +317,7 @@ func dbImageLastAccessInit(db *sql.DB, fingerprint string) error {
 	return err
 }
 
-func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error {
+func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
 	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
@@ -340,7 +345,7 @@ func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, auto
 	}
 	defer stmt.Close()
 
-	_, err = stmt.Exec(fname, sz, publicInt, autoUpdateInt, arch, creationDate, expiryDate, id)
+	_, err = stmt.Exec(fname, sz, publicInt, autoUpdateInt, arch, createdAt, expiresAt, id)
 	if err != nil {
 		tx.Rollback()
 		return err
@@ -369,7 +374,7 @@ func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, auto
 	return nil
 }
 
-func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, creationDate time.Time, expiryDate time.Time, properties map[string]string) error {
+func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
 	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
@@ -397,7 +402,7 @@ func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, a
 	}
 	defer stmt.Close()
 
-	result, err := stmt.Exec(fp, fname, sz, publicInt, autoUpdateInt, arch, creationDate, expiryDate)
+	result, err := stmt.Exec(fp, fname, sz, publicInt, autoUpdateInt, arch, createdAt, expiresAt)
 	if err != nil {
 		tx.Rollback()
 		return err
diff --git a/lxd/db_test.go b/lxd/db_test.go
index e18b19cc5..0d3a861cb 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logging"
 )
 
@@ -416,7 +417,7 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 func Test_dbImageGet_finds_image_for_fingerprint(t *testing.T) {
 	var db *sql.DB
 	var err error
-	var result *shared.ImageInfo
+	var result *api.Image
 
 	db = createTestDb(t)
 	defer db.Close()
@@ -435,16 +436,16 @@ func Test_dbImageGet_finds_image_for_fingerprint(t *testing.T) {
 		t.Fatal("Filename should be set.")
 	}
 
-	if result.CreationDate.UTC() != time.Unix(1431547174, 0).UTC() {
-		t.Fatal(fmt.Sprintf("%s != %s", result.CreationDate, time.Unix(1431547174, 0)))
+	if result.CreatedAt.UTC() != time.Unix(1431547174, 0).UTC() {
+		t.Fatal(fmt.Sprintf("%s != %s", result.CreatedAt, time.Unix(1431547174, 0)))
 	}
 
-	if result.ExpiryDate.UTC() != time.Unix(1431547175, 0).UTC() { // It was short lived
-		t.Fatal(fmt.Sprintf("%s != %s", result.ExpiryDate, time.Unix(1431547175, 0)))
+	if result.ExpiresAt.UTC() != time.Unix(1431547175, 0).UTC() { // It was short lived
+		t.Fatal(fmt.Sprintf("%s != %s", result.ExpiresAt, time.Unix(1431547175, 0)))
 	}
 
-	if result.UploadDate.UTC() != time.Unix(1431547176, 0).UTC() {
-		t.Fatal(fmt.Sprintf("%s != %s", result.UploadDate, time.Unix(1431547176, 0)))
+	if result.UploadedAt.UTC() != time.Unix(1431547176, 0).UTC() {
+		t.Fatal(fmt.Sprintf("%s != %s", result.UploadedAt, time.Unix(1431547176, 0)))
 	}
 }
 
diff --git a/lxd/images.go b/lxd/images.go
index 9a0783e25..ab2c36280 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -23,6 +23,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
@@ -208,14 +209,6 @@ type templateEntry struct {
 	Properties map[string]string `yaml:"properties"`
 }
 
-type imagePostReq struct {
-	Filename   string            `json:"filename"`
-	Public     bool              `json:"public"`
-	Source     map[string]string `json:"source"`
-	Properties map[string]string `json:"properties"`
-	AutoUpdate bool              `json:"auto_update"`
-}
-
 type imageMetadata struct {
 	Architecture string                    `yaml:"architecture"`
 	CreationDate int64                     `yaml:"creation_date"`
@@ -228,8 +221,8 @@ type imageMetadata struct {
  * This function takes a container or snapshot from the local image server and
  * exports it as an image.
  */
-func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq,
-	builddir string) (info shared.ImageInfo, err error) {
+func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
+	builddir string) (info api.Image, err error) {
 
 	info.Properties = map[string]string{}
 	name := req.Source["name"]
@@ -319,7 +312,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq,
 	return info, nil
 }
 
-func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
+func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 	var err error
 	var hash string
 
@@ -348,7 +341,7 @@ func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
 
 	// Update the DB record if needed
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
-		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties)
+		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
 			return err
 		}
@@ -362,7 +355,7 @@ func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
 	return nil
 }
 
-func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
+func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 	var err error
 
 	if req.Source["url"] == "" {
@@ -421,7 +414,7 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 	}
 
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
-		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties)
+		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
 			return err
 		}
@@ -436,7 +429,7 @@ func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
 }
 
 func getImgPostInfo(d *Daemon, r *http.Request,
-	builddir string, post *os.File) (info shared.ImageInfo, err error) {
+	builddir string, post *os.File) (info api.Image, err error) {
 
 	var imageMeta *imageMetadata
 	logger := logging.AddContext(shared.Log, log.Ctx{"function": "getImgPostInfo"})
@@ -610,8 +603,8 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 	}
 
 	info.Architecture = imageMeta.Architecture
-	info.CreationDate = time.Unix(imageMeta.CreationDate, 0)
-	info.ExpiryDate = time.Unix(imageMeta.ExpiryDate, 0)
+	info.CreatedAt = time.Unix(imageMeta.CreationDate, 0)
+	info.ExpiresAt = time.Unix(imageMeta.ExpiryDate, 0)
 
 	info.Properties = imageMeta.Properties
 	if len(propHeaders) > 0 {
@@ -626,7 +619,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 	return info, nil
 }
 
-func imageBuildFromInfo(d *Daemon, info shared.ImageInfo) (metadata map[string]string, err error) {
+func imageBuildFromInfo(d *Daemon, info api.Image) (metadata map[string]string, err error) {
 	err = d.Storage.ImageCreate(info.Fingerprint)
 	if err != nil {
 		os.Remove(shared.VarPath("images", info.Fingerprint))
@@ -643,8 +636,8 @@ func imageBuildFromInfo(d *Daemon, info shared.ImageInfo) (metadata map[string]s
 		info.Public,
 		info.AutoUpdate,
 		info.Architecture,
-		info.CreationDate,
-		info.ExpiryDate,
+		info.CreatedAt,
+		info.ExpiresAt,
 		info.Properties)
 	if err != nil {
 		return metadata, err
@@ -694,7 +687,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 	decoder := json.NewDecoder(post)
 	imageUpload := false
 
-	req := imagePostReq{}
+	req := api.ImagesPost{}
 	err = decoder.Decode(&req)
 	if err != nil {
 		if r.Header.Get("Content-Type") == "application/json" {
@@ -710,7 +703,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 
 	// Begin background operation
 	run := func(op *operation) error {
-		var info shared.ImageInfo
+		var info api.Image
 
 		// Setup the cleanup function
 		defer cleanup(builddir, post)
@@ -817,7 +810,7 @@ func doImagesGet(d *Daemon, recursion bool, public bool) (interface{}, error) {
 	}
 
 	resultString := make([]string, len(results))
-	resultMap := make([]*shared.ImageInfo, len(results))
+	resultMap := make([]*api.Image, len(results))
 	i := 0
 	for _, name := range results {
 		if !recursion {
@@ -895,7 +888,7 @@ func autoUpdateImages(d *Daemon) {
 			continue
 		}
 
-		err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedDate)
+		err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedAt)
 		if err != nil {
 			shared.LogError("Error setting last use date", log.Ctx{"err": err, "fp": hash})
 			continue
@@ -999,7 +992,7 @@ func imageDelete(d *Daemon, r *http.Request) Response {
 	return OperationResponse(op)
 }
 
-func doImageGet(d *Daemon, fingerprint string, public bool) (*shared.ImageInfo, Response) {
+func doImageGet(d *Daemon, fingerprint string, public bool) (*api.Image, Response) {
 	_, imgInfo, err := dbImageGet(d.db, fingerprint, public, false)
 	if err != nil {
 		return nil, SmartError(err)
@@ -1055,16 +1048,10 @@ func imageGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, info)
 }
 
-type imagePutReq struct {
-	Properties map[string]string `json:"properties"`
-	Public     bool              `json:"public"`
-	AutoUpdate bool              `json:"auto_update"`
-}
-
 func imagePut(d *Daemon, r *http.Request) Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
-	req := imagePutReq{}
+	req := api.ImagePut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -1074,7 +1061,7 @@ func imagePut(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	err = dbImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, req.Properties)
+	err = dbImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1084,19 +1071,8 @@ func imagePut(d *Daemon, r *http.Request) Response {
 
 var imageCmd = Command{name: "images/{fingerprint}", untrustedGet: true, get: imageGet, put: imagePut, delete: imageDelete}
 
-type aliasPostReq struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Target      string `json:"target"`
-}
-
-type aliasPutReq struct {
-	Description string `json:"description"`
-	Target      string `json:"target"`
-}
-
 func aliasesPost(d *Daemon, r *http.Request) Response {
-	req := aliasPostReq{}
+	req := api.ImageAliasesPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -1136,7 +1112,7 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 	responseStr := []string{}
-	responseMap := shared.ImageAliases{}
+	responseMap := []api.ImageAliasesEntry{}
 	for _, res := range results {
 		name = res[0].(string)
 		if !recursion {
@@ -1188,7 +1164,7 @@ func aliasDelete(d *Daemon, r *http.Request) Response {
 func aliasPut(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	req := aliasPutReq{}
+	req := api.ImageAliasesEntryPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -1218,7 +1194,7 @@ func aliasPut(d *Daemon, r *http.Request) Response {
 func aliasPost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	req := aliasPostReq{}
+	req := api.ImageAliasesEntryPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/remote.go b/lxd/remote.go
index ca7158a92..0a424835c 100644
--- a/lxd/remote.go
+++ b/lxd/remote.go
@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -18,7 +18,7 @@ func remoteGetImageFingerprint(d *Daemon, server string, certificate string, ali
 		return "", err
 	}
 
-	var result shared.ImageAliasesEntry
+	var result api.ImageAliasesEntry
 	if err = json.Unmarshal(resp.Metadata, &result); err != nil {
 		return "", fmt.Errorf("Error reading alias")
 	}
diff --git a/lxd/storage.go b/lxd/storage.go
index 179b75446..8c6a7907f 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logging"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -273,7 +274,7 @@ func storageForFilename(d *Daemon, filename string) (storage, error) {
 	return newStorageWithConfig(d, storageType, config)
 }
 
-func storageForImage(d *Daemon, imgInfo *shared.ImageInfo) (storage, error) {
+func storageForImage(d *Daemon, imgInfo *api.Image) (storage, error) {
 	imageFilename := shared.VarPath("images", imgInfo.Fingerprint)
 	return storageForFilename(d, imageFilename)
 }
diff --git a/shared/api/image.go b/shared/api/image.go
new file mode 100644
index 000000000..408968b79
--- /dev/null
+++ b/shared/api/image.go
@@ -0,0 +1,80 @@
+package api
+
+import (
+	"time"
+)
+
+// ImagesPost represents the fields available for a new LXD image
+type ImagesPost struct {
+	ImagePut
+
+	Filename string            `json:"filename"`
+	Source   map[string]string `json:"source"`
+}
+
+// ImagePut represents the modifiable fields of a LXD image
+type ImagePut struct {
+	AutoUpdate bool              `json:"auto_update"`
+	Properties map[string]string `json:"properties"`
+	Public     bool              `json:"public"`
+}
+
+// Image represents a LXD image
+type Image struct {
+	ImagePut
+
+	Aliases      []ImageAlias `json:"aliases"`
+	Architecture string       `json:"architecture"`
+	Cached       bool         `json:"cached"`
+	Filename     string       `json:"filename"`
+	Fingerprint  string       `json:"fingerprint"`
+	Size         int64        `json:"size"`
+	UpdateSource *ImageSource `json:"update_source,omitempty"`
+
+	CreatedAt  time.Time `json:"created_at"`
+	ExpiresAt  time.Time `json:"expires_at"`
+	LastUsedAt time.Time `json:"last_used_at"`
+	UploadedAt time.Time `json:"uploaded_at"`
+}
+
+// Writable converts a full Image struct into a ImagePut struct (filters read-only fields)
+func (img *Image) Writable() ImagePut {
+	return img.ImagePut
+}
+
+// ImageAlias represents an alias from the alias list of a LXD image
+type ImageAlias struct {
+	Name        string `json:"name"`
+	Description string `json:"description"`
+}
+
+// ImageSource represents the source of a LXD image
+type ImageSource struct {
+	Alias       string `json:"alias"`
+	Certificate string `json:"certificate"`
+	Protocol    string `json:"protocol"`
+	Server      string `json:"server"`
+}
+
+// ImageAliasesPost represents a new LXD image alias
+type ImageAliasesPost struct {
+	ImageAliasesEntry
+}
+
+// ImageAliasesEntryPost represents the required fields to rename a LXD image alias
+type ImageAliasesEntryPost struct {
+	Name string `json:"name"`
+}
+
+// ImageAliasesEntryPut represents the modifiable fields of a LXD image alias
+type ImageAliasesEntryPut struct {
+	Description string `json:"description"`
+	Target      string `json:"target"`
+}
+
+// ImageAliasesEntry represents a LXD image alias
+type ImageAliasesEntry struct {
+	ImageAliasesEntryPut
+
+	Name string `json:"name"`
+}
diff --git a/shared/image.go b/shared/image.go
deleted file mode 100644
index e2e39d473..000000000
--- a/shared/image.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package shared
-
-import (
-	"time"
-)
-
-type ImageProperties map[string]string
-
-type ImageAliasesEntry struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Target      string `json:"target"`
-}
-
-type ImageAliases []ImageAliasesEntry
-
-type ImageAlias struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-}
-
-type ImageSource struct {
-	Server      string `json:"server"`
-	Protocol    string `json:"protocol"`
-	Certificate string `json:"certificate"`
-	Alias       string `json:"alias"`
-}
-
-type ImageInfo struct {
-	Aliases      []ImageAlias      `json:"aliases"`
-	Architecture string            `json:"architecture"`
-	Cached       bool              `json:"cached"`
-	Filename     string            `json:"filename"`
-	Fingerprint  string            `json:"fingerprint"`
-	Properties   map[string]string `json:"properties"`
-	Public       bool              `json:"public"`
-	Size         int64             `json:"size"`
-
-	AutoUpdate bool         `json:"auto_update"`
-	Source     *ImageSource `json:"update_source,omitempty"`
-
-	CreationDate time.Time `json:"created_at"`
-	ExpiryDate   time.Time `json:"expires_at"`
-	LastUsedDate time.Time `json:"last_used_at"`
-	UploadDate   time.Time `json:"uploaded_at"`
-}
-
-/*
- * BriefImageInfo contains a subset of the fields in
- * ImageInfo, namely those which a user may update
- */
-type BriefImageInfo struct {
-	AutoUpdate bool              `json:"auto_update"`
-	Properties map[string]string `json:"properties"`
-	Public     bool              `json:"public"`
-}
-
-func (i *ImageInfo) Brief() BriefImageInfo {
-	retstate := BriefImageInfo{
-		AutoUpdate: i.AutoUpdate,
-		Properties: i.Properties,
-		Public:     i.Public}
-	return retstate
-}
diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index 9be22d7ea..dd5d206c4 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -14,11 +14,12 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
-type ssSortImage []shared.ImageInfo
+type ssSortImage []api.Image
 
 func (a ssSortImage) Len() int {
 	return len(a)
@@ -31,15 +32,15 @@ func (a ssSortImage) Swap(i, j int) {
 func (a ssSortImage) Less(i, j int) bool {
 	if a[i].Properties["os"] == a[j].Properties["os"] {
 		if a[i].Properties["release"] == a[j].Properties["release"] {
-			if a[i].CreationDate.UTC().Unix() == 0 {
+			if a[i].CreatedAt.UTC().Unix() == 0 {
 				return true
 			}
 
-			if a[j].CreationDate.UTC().Unix() == 0 {
+			if a[j].CreatedAt.UTC().Unix() == 0 {
 				return false
 			}
 
-			return a[i].CreationDate.UTC().Unix() > a[j].CreationDate.UTC().Unix()
+			return a[i].CreatedAt.UTC().Unix() > a[j].CreatedAt.UTC().Unix()
 		}
 
 		if a[i].Properties["release"] == "" {
@@ -76,10 +77,10 @@ type SimpleStreamsManifest struct {
 	Products map[string]SimpleStreamsManifestProduct `json:"products"`
 }
 
-func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]string) {
+func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 	downloads := map[string][][]string{}
 
-	images := []shared.ImageInfo{}
+	images := []api.Image{}
 	nameLayout := "20060102"
 	eolLayout := "2006-01-02"
 
@@ -169,12 +170,12 @@ func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]stri
 			}
 			description = fmt.Sprintf("%s (%s)", description, name)
 
-			image := shared.ImageInfo{}
+			image := api.Image{}
 			image.Architecture = architectureName
 			image.Public = true
 			image.Size = size
-			image.CreationDate = creationDate
-			image.UploadDate = creationDate
+			image.CreatedAt = creationDate
+			image.UploadedAt = creationDate
 			image.Filename = filename
 			image.Fingerprint = fingerprint
 			image.Properties = map[string]string{
@@ -189,9 +190,9 @@ func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]stri
 
 			// Add the provided aliases
 			if product.Aliases != "" {
-				image.Aliases = []shared.ImageAlias{}
+				image.Aliases = []api.ImageAlias{}
 				for _, entry := range strings.Split(product.Aliases, ",") {
-					image.Aliases = append(image.Aliases, shared.ImageAlias{Name: entry})
+					image.Aliases = append(image.Aliases, api.ImageAlias{Name: entry})
 				}
 			}
 
@@ -203,11 +204,11 @@ func (s *SimpleStreamsManifest) ToLXD() ([]shared.ImageInfo, map[string][][]stri
 			}
 
 			// Attempt to parse the EOL
-			image.ExpiryDate = time.Unix(0, 0).UTC()
+			image.ExpiresAt = time.Unix(0, 0).UTC()
 			if product.SupportedEOL != "" {
 				eolDate, err := time.Parse(eolLayout, product.SupportedEOL)
 				if err == nil {
-					image.ExpiryDate = eolDate
+					image.ExpiresAt = eolDate
 				}
 			}
 
@@ -278,8 +279,8 @@ type SimpleStreams struct {
 
 	cachedIndex    *SimpleStreamsIndex
 	cachedManifest map[string]*SimpleStreamsManifest
-	cachedImages   []shared.ImageInfo
-	cachedAliases  map[string]*shared.ImageAliasesEntry
+	cachedImages   []api.Image
+	cachedAliases  map[string]*api.ImageAliasesEntry
 }
 
 func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) {
@@ -356,8 +357,8 @@ func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, erro
 	return &ssManifest, nil
 }
 
-func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) {
-	aliases := map[string]*shared.ImageAliasesEntry{}
+func (s *SimpleStreams) applyAliases(images []api.Image) ([]api.Image, map[string]*api.ImageAliasesEntry, error) {
+	aliases := map[string]*api.ImageAliasesEntry{}
 
 	sort.Sort(ssSortImage(images))
 
@@ -369,7 +370,7 @@ func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageI
 		}
 	}
 
-	addAlias := func(name string, fingerprint string) *shared.ImageAlias {
+	addAlias := func(name string, fingerprint string) *api.ImageAlias {
 		if defaultOS != "" {
 			name = strings.TrimPrefix(name, fmt.Sprintf("%s/", defaultOS))
 		}
@@ -378,17 +379,17 @@ func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageI
 			return nil
 		}
 
-		alias := shared.ImageAliasesEntry{}
+		alias := api.ImageAliasesEntry{}
 		alias.Name = name
 		alias.Target = fingerprint
 		aliases[name] = &alias
 
-		return &shared.ImageAlias{Name: name}
+		return &api.ImageAlias{Name: name}
 	}
 
 	architectureName, _ := osarch.ArchitectureGetLocal()
 
-	newImages := []shared.ImageInfo{}
+	newImages := []api.Image{}
 	for _, image := range images {
 		if image.Aliases != nil {
 			// Build a new list of aliases from the provided ones
@@ -418,12 +419,12 @@ func (s *SimpleStreams) applyAliases(images []shared.ImageInfo) ([]shared.ImageI
 	return newImages, aliases, nil
 }
 
-func (s *SimpleStreams) getImages() ([]shared.ImageInfo, map[string]*shared.ImageAliasesEntry, error) {
+func (s *SimpleStreams) getImages() ([]api.Image, map[string]*api.ImageAliasesEntry, error) {
 	if s.cachedImages != nil && s.cachedAliases != nil {
 		return s.cachedImages, s.cachedAliases, nil
 	}
 
-	images := []shared.ImageInfo{}
+	images := []api.Image{}
 
 	// Load the main index
 	ssIndex, err := s.parseIndex()
@@ -573,13 +574,13 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 	return nil
 }
 
-func (s *SimpleStreams) ListAliases() (shared.ImageAliases, error) {
+func (s *SimpleStreams) ListAliases() ([]api.ImageAliasesEntry, error) {
 	_, aliasesMap, err := s.getImages()
 	if err != nil {
 		return nil, err
 	}
 
-	aliases := shared.ImageAliases{}
+	aliases := []api.ImageAliasesEntry{}
 
 	for _, alias := range aliasesMap {
 		aliases = append(aliases, *alias)
@@ -588,7 +589,7 @@ func (s *SimpleStreams) ListAliases() (shared.ImageAliases, error) {
 	return aliases, nil
 }
 
-func (s *SimpleStreams) ListImages() ([]shared.ImageInfo, error) {
+func (s *SimpleStreams) ListImages() ([]api.Image, error) {
 	images, _, err := s.getImages()
 	return images, err
 }
@@ -607,7 +608,7 @@ func (s *SimpleStreams) GetAlias(name string) string {
 	return alias.Target
 }
 
-func (s *SimpleStreams) GetImageInfo(fingerprint string) (*shared.ImageInfo, error) {
+func (s *SimpleStreams) GetImageInfo(fingerprint string) (*api.Image, error) {
 	images, _, err := s.getImages()
 	if err != nil {
 		return nil, err

From f3b8b6f01ec9e8ba09f9270e2f8491c5725f22d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 14:23:38 -0500
Subject: [PATCH 0621/1193] shared: Move REST API to new package: certificate
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                 |  4 ++--
 lxd/certificates.go       | 18 ++++++------------
 shared/api/certificate.go | 17 +++++++++++++++++
 shared/cert.go            |  8 --------
 4 files changed, 25 insertions(+), 22 deletions(-)
 create mode 100644 shared/api/certificate.go

diff --git a/client.go b/client.go
index 016866326..0663922ec 100644
--- a/client.go
+++ b/client.go
@@ -1221,7 +1221,7 @@ func (c *Client) ListAliases() ([]api.ImageAliasesEntry, error) {
 	return result, nil
 }
 
-func (c *Client) CertificateList() ([]shared.CertInfo, error) {
+func (c *Client) CertificateList() ([]api.Certificate, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1231,7 +1231,7 @@ func (c *Client) CertificateList() ([]shared.CertInfo, error) {
 		return nil, err
 	}
 
-	var result []shared.CertInfo
+	var result []api.Certificate
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
 	}
diff --git a/lxd/certificates.go b/lxd/certificates.go
index 8ae7c55c0..903a88ad8 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -11,6 +11,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -18,14 +19,14 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 	recursion := d.isRecursionRequest(r)
 
 	if recursion {
-		certResponses := []shared.CertInfo{}
+		certResponses := []api.Certificate{}
 
 		baseCerts, err := dbCertsGet(d.db)
 		if err != nil {
 			return SmartError(err)
 		}
 		for _, baseCert := range baseCerts {
-			resp := shared.CertInfo{}
+			resp := api.Certificate{}
 			resp.Fingerprint = baseCert.Fingerprint
 			resp.Certificate = baseCert.Certificate
 			if baseCert.Type == 1 {
@@ -47,13 +48,6 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, body)
 }
 
-type certificatesPostBody struct {
-	Type        string `json:"type"`
-	Certificate string `json:"certificate"`
-	Name        string `json:"name"`
-	Password    string `json:"password"`
-}
-
 func readSavedClientCAList(d *Daemon) {
 	d.clientCerts = []x509.Certificate{}
 
@@ -93,7 +87,7 @@ func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
 
 func certificatesPost(d *Daemon, r *http.Request) Response {
 	// Parse the request
-	req := certificatesPostBody{}
+	req := api.CertificatesPost{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
 		return BadRequest(err)
 	}
@@ -175,8 +169,8 @@ func certificateFingerprintGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, cert)
 }
 
-func doCertificateGet(d *Daemon, fingerprint string) (shared.CertInfo, error) {
-	resp := shared.CertInfo{}
+func doCertificateGet(d *Daemon, fingerprint string) (api.Certificate, error) {
+	resp := api.Certificate{}
 
 	dbCertInfo, err := dbCertGet(d.db, fingerprint)
 	if err != nil {
diff --git a/shared/api/certificate.go b/shared/api/certificate.go
new file mode 100644
index 000000000..12bcc315b
--- /dev/null
+++ b/shared/api/certificate.go
@@ -0,0 +1,17 @@
+package api
+
+// CertificatesPost represents the fields of a new LXD certificate
+type CertificatesPost struct {
+	Name        string `json:"name"`
+	Type        string `json:"type"`
+	Certificate string `json:"certificate"`
+	Password    string `json:"password"`
+}
+
+// Certificate represents a LXD certificate
+type Certificate struct {
+	Name        string `json:"name"`
+	Type        string `json:"type"`
+	Certificate string `json:"certificate"`
+	Fingerprint string `json:"fingerprint"`
+}
diff --git a/shared/cert.go b/shared/cert.go
index d1225e5d5..b264ee1d8 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -23,14 +23,6 @@ import (
 	"time"
 )
 
-// CertInfo is the representation of a Certificate in the API.
-type CertInfo struct {
-	Certificate string `json:"certificate"`
-	Fingerprint string `json:"fingerprint"`
-	Name        string `json:"name"`
-	Type        string `json:"type"`
-}
-
 /*
  * Generate a list of names for which the certificate will be valid.
  * This will include the hostname and ip address

From f2fddd86b7e5eaa26b12ac6b1c7316b5c7c35f58 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 14:40:07 -0500
Subject: [PATCH 0622/1193] shared: Move REST API to new package: network
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go       | 19 +++++++------------
 shared/api/network.go |  8 ++++++++
 2 files changed, 15 insertions(+), 12 deletions(-)
 create mode 100644 shared/api/network.go

diff --git a/lxd/networks.go b/lxd/networks.go
index ed9fbee08..8c452795e 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -10,6 +10,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -50,7 +51,7 @@ func networksGet(d *Daemon, r *http.Request) Response {
 	}
 
 	resultString := []string{}
-	resultMap := []network{}
+	resultMap := []api.Network{}
 	for _, iface := range ifs {
 		if recursion == 0 {
 			resultString = append(resultString, fmt.Sprintf("/%s/networks/%s", version.APIVersion, iface.Name))
@@ -73,12 +74,6 @@ func networksGet(d *Daemon, r *http.Request) Response {
 
 var networksCmd = Command{name: "networks", get: networksGet}
 
-type network struct {
-	Name   string   `json:"name"`
-	Type   string   `json:"type"`
-	UsedBy []string `json:"used_by"`
-}
-
 func networkGet(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
@@ -90,27 +85,27 @@ func networkGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, &n)
 }
 
-func doNetworkGet(d *Daemon, name string) (network, error) {
+func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 	iface, err := net.InterfaceByName(name)
 	if err != nil {
-		return network{}, err
+		return api.Network{}, err
 	}
 
 	// Prepare the response
-	n := network{}
+	n := api.Network{}
 	n.Name = iface.Name
 	n.UsedBy = []string{}
 
 	// Look for containers using the interface
 	cts, err := dbContainersList(d.db, cTypeRegular)
 	if err != nil {
-		return network{}, err
+		return api.Network{}, err
 	}
 
 	for _, ct := range cts {
 		c, err := containerLoadByName(d, ct)
 		if err != nil {
-			return network{}, err
+			return api.Network{}, err
 		}
 
 		if networkIsInUse(c, n.Name) {
diff --git a/shared/api/network.go b/shared/api/network.go
new file mode 100644
index 000000000..1fb138404
--- /dev/null
+++ b/shared/api/network.go
@@ -0,0 +1,8 @@
+package api
+
+// Network represents a LXD network
+type Network struct {
+	Name   string   `json:"name"`
+	Type   string   `json:"type"`
+	UsedBy []string `json:"used_by"`
+}

From b9b207a1ab1436d50865483980861ccc45940680 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 20:57:36 -0500
Subject: [PATCH 0623/1193] shared: Move REST API to new package: server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go            |   6 +-
 lxc/config.go        |   9 +--
 lxd/api_1.0.go       | 157 +++++++++++++++++++++++++--------------------------
 shared/api/server.go |  46 +++++++++++++++
 shared/server.go     |  37 ------------
 5 files changed, 131 insertions(+), 124 deletions(-)
 create mode 100644 shared/api/server.go
 delete mode 100644 shared/server.go

diff --git a/client.go b/client.go
index 0663922ec..a26416733 100644
--- a/client.go
+++ b/client.go
@@ -1673,8 +1673,8 @@ func (c *Client) Delete(name string) (*Response, error) {
 	return c.delete(url, nil, Async)
 }
 
-func (c *Client) ServerStatus() (*shared.ServerState, error) {
-	ss := shared.ServerState{}
+func (c *Client) ServerStatus() (*api.Server, error) {
+	ss := api.Server{}
 
 	resp, err := c.GetServerConfig()
 	if err != nil {
@@ -2032,7 +2032,7 @@ func (c *Client) SetServerConfig(key string, value string) (*Response, error) {
 	return c.put("", ss, Sync)
 }
 
-func (c *Client) UpdateServerConfig(ss shared.BriefServerState) (*Response, error) {
+func (c *Client) UpdateServerConfig(ss api.ServerPut) (*Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxc/config.go b/lxc/config.go
index 59c67cd3c..dc7c5589f 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
@@ -341,7 +342,7 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 				return err
 			}
 
-			brief := config.Brief()
+			brief := config.Writable()
 			data, err = yaml.Marshal(&brief)
 		} else {
 			var brief shared.BriefContainerInfo
@@ -554,7 +555,7 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 			return err
 		}
 
-		newdata := shared.BriefServerState{}
+		newdata := api.ServerPut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -570,7 +571,7 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 		return err
 	}
 
-	brief := config.Brief()
+	brief := config.Writable()
 	data, err := yaml.Marshal(&brief)
 	if err != nil {
 		return err
@@ -584,7 +585,7 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.BriefServerState{}
+		newdata := api.ServerPut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			_, err = client.UpdateServerConfig(newdata)
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 8de95cbd0..4f26acda4 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -11,6 +11,7 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -46,7 +47,7 @@ var api10 = []Command{
 }
 
 func api10Get(d *Daemon, r *http.Request) Response {
-	body := shared.Jmap{
+	srv := api.ServerUntrusted{
 		/* List of API extensions in the order they were added.
 		 *
 		 * The following kind of changes require an addition to api_extensions:
@@ -56,101 +57,98 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		 *  - New argument inside an existing REST API call
 		 *  - New HTTPs authentication mechanisms or protocols
 		 */
-		"api_extensions": []string{
+		APIExtensions: []string{
 			"id_map",
 		},
-
-		"api_status":  "stable",
-		"api_version": version.APIVersion,
+		APIStatus:  "stable",
+		APIVersion: version.APIVersion,
+		Public:     false,
+		Auth:       "untrusted",
 	}
 
-	if d.isTrustedClient(r) {
-		body["auth"] = "trusted"
+	// If untrusted, return now
+	if !d.isTrustedClient(r) {
+		return SyncResponse(true, srv)
+	}
 
-		/*
-		 * Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
-		 * there is really no better way to do this, which is
-		 * unfortunate. Also, we ditch the more accepted CharsToString
-		 * version in that thread, since it doesn't seem as portable,
-		 * viz. github issue #206.
-		 */
-		uname := syscall.Utsname{}
-		if err := syscall.Uname(&uname); err != nil {
-			return InternalError(err)
-		}
+	srv.Auth = "trusted"
+
+	/*
+	 * Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
+	 * there is really no better way to do this, which is
+	 * unfortunate. Also, we ditch the more accepted CharsToString
+	 * version in that thread, since it doesn't seem as portable,
+	 * viz. github issue #206.
+	 */
+	uname := syscall.Utsname{}
+	if err := syscall.Uname(&uname); err != nil {
+		return InternalError(err)
+	}
 
-		kernel := ""
-		for _, c := range uname.Sysname {
-			if c == 0 {
-				break
-			}
-			kernel += string(byte(c))
+	kernel := ""
+	for _, c := range uname.Sysname {
+		if c == 0 {
+			break
 		}
+		kernel += string(byte(c))
+	}
 
-		kernelVersion := ""
-		for _, c := range uname.Release {
-			if c == 0 {
-				break
-			}
-			kernelVersion += string(byte(c))
+	kernelVersion := ""
+	for _, c := range uname.Release {
+		if c == 0 {
+			break
 		}
+		kernelVersion += string(byte(c))
+	}
 
-		kernelArchitecture := ""
-		for _, c := range uname.Machine {
-			if c == 0 {
-				break
-			}
-			kernelArchitecture += string(byte(c))
+	kernelArchitecture := ""
+	for _, c := range uname.Machine {
+		if c == 0 {
+			break
 		}
+		kernelArchitecture += string(byte(c))
+	}
 
-		addresses, err := d.ListenAddresses()
-		if err != nil {
-			return InternalError(err)
-		}
+	addresses, err := d.ListenAddresses()
+	if err != nil {
+		return InternalError(err)
+	}
 
-		var certificate string
-		if len(d.tlsConfig.Certificates) != 0 {
-			certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: d.tlsConfig.Certificates[0].Certificate[0]}))
-		}
+	var certificate string
+	if len(d.tlsConfig.Certificates) != 0 {
+		certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: d.tlsConfig.Certificates[0].Certificate[0]}))
+	}
 
-		architectures := []string{}
+	architectures := []string{}
 
-		for _, architecture := range d.architectures {
-			architectureName, err := osarch.ArchitectureName(architecture)
-			if err != nil {
-				return InternalError(err)
-			}
-			architectures = append(architectures, architectureName)
+	for _, architecture := range d.architectures {
+		architectureName, err := osarch.ArchitectureName(architecture)
+		if err != nil {
+			return InternalError(err)
 		}
-
-		env := shared.Jmap{
-			"addresses":           addresses,
-			"architectures":       architectures,
-			"certificate":         certificate,
-			"driver":              "lxc",
-			"driver_version":      lxc.Version(),
-			"kernel":              kernel,
-			"kernel_architecture": kernelArchitecture,
-			"kernel_version":      kernelVersion,
-			"storage":             d.Storage.GetStorageTypeName(),
-			"storage_version":     d.Storage.GetStorageTypeVersion(),
-			"server":              "lxd",
-			"server_pid":          os.Getpid(),
-			"server_version":      version.Version}
-
-		body["environment"] = env
-		body["public"] = false
-		body["config"] = daemonConfigRender()
-	} else {
-		body["auth"] = "untrusted"
-		body["public"] = false
+		architectures = append(architectures, architectureName)
 	}
 
-	return SyncResponse(true, body)
-}
-
-type apiPut struct {
-	Config shared.Jmap `json:"config"`
+	env := api.ServerEnvironment{
+		Addresses:          addresses,
+		Architectures:      architectures,
+		Certificate:        certificate,
+		Driver:             "lxc",
+		DriverVersion:      lxc.Version(),
+		Kernel:             kernel,
+		KernelArchitecture: kernelArchitecture,
+		KernelVersion:      kernelVersion,
+		Storage:            d.Storage.GetStorageTypeName(),
+		StorageVersion:     d.Storage.GetStorageTypeVersion(),
+		Server:             "lxd",
+		ServerPid:          os.Getpid(),
+		ServerVersion:      version.Version}
+
+	fullSrv := api.Server{ServerUntrusted: srv}
+	fullSrv.Environment = env
+	fullSrv.Config = daemonConfigRender()
+
+	return SyncResponse(true, fullSrv)
 }
 
 func api10Put(d *Daemon, r *http.Request) Response {
@@ -159,8 +157,7 @@ func api10Put(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
-	req := apiPut{}
-
+	req := api.ServerPut{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/shared/api/server.go b/shared/api/server.go
new file mode 100644
index 000000000..952bba4b1
--- /dev/null
+++ b/shared/api/server.go
@@ -0,0 +1,46 @@
+package api
+
+// ServerEnvironment represents the read-only environment fields of a LXD server
+type ServerEnvironment struct {
+	Addresses              []string `json:"addresses"`
+	Architectures          []string `json:"architectures"`
+	Certificate            string   `json:"certificate"`
+	CertificateFingerprint string   `json:"certificate_fingerprint"`
+	Driver                 string   `json:"driver"`
+	DriverVersion          string   `json:"driver_version"`
+	Kernel                 string   `json:"kernel"`
+	KernelArchitecture     string   `json:"kernel_architecture"`
+	KernelVersion          string   `json:"kernel_version"`
+	Server                 string   `json:"server"`
+	ServerPid              int      `json:"server_pid"`
+	ServerVersion          string   `json:"server_version"`
+	Storage                string   `json:"storage"`
+	StorageVersion         string   `json:"storage_version"`
+}
+
+// ServerPut represents the modifiable fields of a LXD server configuration
+type ServerPut struct {
+	Config map[string]interface{} `json:"config"`
+}
+
+// ServerUntrusted represents a LXD server for an untrusted client
+type ServerUntrusted struct {
+	APIExtensions []string `json:"api_extensions"`
+	APIStatus     string   `json:"api_status"`
+	APIVersion    string   `json:"api_version"`
+	Auth          string   `json:"auth"`
+	Public        bool     `json:"public"`
+}
+
+// Server represents a LXD server
+type Server struct {
+	ServerPut
+	ServerUntrusted
+
+	Environment ServerEnvironment `json:"environment"`
+}
+
+// Writable converts a full Server struct into a ServerPut struct (filters read-only fields)
+func (srv *Server) Writable() ServerPut {
+	return srv.ServerPut
+}
diff --git a/shared/server.go b/shared/server.go
deleted file mode 100644
index 96044daa7..000000000
--- a/shared/server.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package shared
-
-type ServerStateEnvironment struct {
-	Addresses              []string `json:"addresses"`
-	Architectures          []string `json:"architectures"`
-	Certificate            string   `json:"certificate"`
-	CertificateFingerprint string   `json:"certificate_fingerprint"`
-	Driver                 string   `json:"driver"`
-	DriverVersion          string   `json:"driver_version"`
-	Kernel                 string   `json:"kernel"`
-	KernelArchitecture     string   `json:"kernel_architecture"`
-	KernelVersion          string   `json:"kernel_version"`
-	Server                 string   `json:"server"`
-	ServerPid              int      `json:"server_pid"`
-	ServerVersion          string   `json:"server_version"`
-	Storage                string   `json:"storage"`
-	StorageVersion         string   `json:"storage_version"`
-}
-
-type ServerState struct {
-	APIExtensions []string               `json:"api_extensions"`
-	APIStatus     string                 `json:"api_status"`
-	APIVersion    string                 `json:"api_version"`
-	Auth          string                 `json:"auth"`
-	Environment   ServerStateEnvironment `json:"environment"`
-	Config        map[string]interface{} `json:"config"`
-	Public        bool                   `json:"public"`
-}
-
-type BriefServerState struct {
-	Config map[string]interface{} `json:"config"`
-}
-
-func (c *ServerState) Brief() BriefServerState {
-	retstate := BriefServerState{Config: c.Config}
-	return retstate
-}

From 1aa190bb6326a1ad80d443d3f746f8ce875ac1bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 19 Dec 2016 22:41:47 -0500
Subject: [PATCH 0624/1193] shared: Move REST API to new package: status
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                                | 12 ++++++------
 lxc/action.go                            |  3 ++-
 lxc/delete.go                            |  5 +++--
 lxc/init.go                              |  4 ++--
 lxc/publish.go                           |  5 +++--
 lxd/container_lxc.go                     | 27 ++++++++++++++-------------
 lxd/containers_get.go                    |  5 +++--
 lxd/operations.go                        | 29 +++++++++++++++--------------
 lxd/response.go                          | 27 ++++++++++++++-------------
 shared/{status.go => api/status_code.go} |  6 +++++-
 shared/container.go                      | 10 ++++++----
 shared/operation.go                      |  4 +++-
 12 files changed, 76 insertions(+), 61 deletions(-)
 rename shared/{status.go => api/status_code.go} (83%)

diff --git a/client.go b/client.go
index a26416733..5d3d292ac 100644
--- a/client.go
+++ b/client.go
@@ -1625,11 +1625,11 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		return -1, err
 	}
 
-	if op.StatusCode == shared.Failure {
+	if op.StatusCode == api.Failure {
 		return -1, fmt.Errorf(op.Err)
 	}
 
-	if op.StatusCode != shared.Success {
+	if op.StatusCode != api.Success {
 		return -1, fmt.Errorf("got bad op status %s", op.Status)
 	}
 
@@ -1911,7 +1911,7 @@ func (c *Client) WaitForSuccess(waitURL string) error {
 		return err
 	}
 
-	if op.StatusCode == shared.Success {
+	if op.StatusCode == api.Success {
 		return nil
 	}
 
@@ -1924,7 +1924,7 @@ func (c *Client) WaitForSuccessOp(waitURL string) (*shared.Operation, error) {
 		return nil, err
 	}
 
-	if op.StatusCode == shared.Success {
+	if op.StatusCode == api.Success {
 		return op, nil
 	}
 
@@ -2412,11 +2412,11 @@ func (c *Client) AsyncWaitMeta(resp *Response) (*shared.Jmap, error) {
 		return nil, err
 	}
 
-	if op.StatusCode == shared.Failure {
+	if op.StatusCode == api.Failure {
 		return nil, fmt.Errorf(op.Err)
 	}
 
-	if op.StatusCode != shared.Success {
+	if op.StatusCode != api.Success {
 		return nil, fmt.Errorf("got bad op status %s", op.Status)
 	}
 
diff --git a/lxc/action.go b/lxc/action.go
index 15b356924..ab66cfe16 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -76,7 +77,7 @@ func (c *actionCmd) run(config *lxd.Config, args []string) error {
 			}
 
 			// "start" for a frozen container means "unfreeze"
-			if current.StatusCode == shared.Frozen {
+			if current.StatusCode == api.Frozen {
 				c.action = shared.Unfreeze
 			}
 
diff --git a/lxc/delete.go b/lxc/delete.go
index 866e5a385..6900fb3b6 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -87,7 +88,7 @@ func (c *deleteCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		if ct.StatusCode != 0 && ct.StatusCode != shared.Stopped {
+		if ct.StatusCode != 0 && ct.StatusCode != api.Stopped {
 			if !c.force {
 				return fmt.Errorf(i18n.G("The container is currently running, stop it first or pass --force."))
 			}
@@ -102,7 +103,7 @@ func (c *deleteCmd) run(config *lxd.Config, args []string) error {
 				return err
 			}
 
-			if op.StatusCode == shared.Failure {
+			if op.StatusCode == api.Failure {
 				return fmt.Errorf(i18n.G("Stopping container failed!"))
 			}
 
diff --git a/lxc/init.go b/lxc/init.go
index 6ab4aeeb5..ac0837522 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -6,7 +6,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -241,7 +241,7 @@ func (c *initCmd) initProgressTracker(d *lxd.Client, progress *ProgressRenderer,
 			return
 		}
 
-		if shared.StatusCode(md["status_code"].(float64)).IsFinal() {
+		if api.StatusCode(md["status_code"].(float64)).IsFinal() {
 			return
 		}
 
diff --git a/lxc/publish.go b/lxc/publish.go
index 51bc2645c..9a7ef90a1 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 
@@ -81,7 +82,7 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		wasRunning := ct.StatusCode != 0 && ct.StatusCode != shared.Stopped
+		wasRunning := ct.StatusCode != 0 && ct.StatusCode != api.Stopped
 		wasEphemeral := ct.Ephemeral
 
 		if wasRunning {
@@ -107,7 +108,7 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 				return err
 			}
 
-			if op.StatusCode == shared.Failure {
+			if op.StatusCode == api.Failure {
 				return fmt.Errorf(i18n.G("Stopping container failed!"))
 			}
 			defer s.Action(cName, shared.Start, -1, true, false)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 279054a84..c013aae75 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -26,6 +26,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -164,17 +165,17 @@ func lxcValidConfig(rawLxc string) error {
 	return nil
 }
 
-func lxcStatusCode(state lxc.State) shared.StatusCode {
-	return map[int]shared.StatusCode{
-		1: shared.Stopped,
-		2: shared.Starting,
-		3: shared.Running,
-		4: shared.Stopping,
-		5: shared.Aborting,
-		6: shared.Freezing,
-		7: shared.Frozen,
-		8: shared.Thawed,
-		9: shared.Error,
+func lxcStatusCode(state lxc.State) api.StatusCode {
+	return map[int]api.StatusCode{
+		1: api.Stopped,
+		2: api.Starting,
+		3: api.Running,
+		4: api.Stopping,
+		5: api.Aborting,
+		6: api.Freezing,
+		7: api.Frozen,
+		8: api.Thawed,
+		9: api.Error,
 	}[int(state)]
 }
 
@@ -5665,12 +5666,12 @@ func (c *containerLXC) State() string {
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
-		return "BROKEN"
+		return "Broken"
 	}
 
 	state, err := c.getLxcState()
 	if err != nil {
-		return shared.Error.String()
+		return api.Error.String()
 	}
 	return state.String()
 }
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index f460e2092..2b0005f4b 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -50,8 +51,8 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 			if err != nil {
 				c = &shared.ContainerInfo{
 					Name:       container,
-					Status:     shared.Error.String(),
-					StatusCode: shared.Error}
+					Status:     api.Error.String(),
+					StatusCode: api.Error}
 			}
 			resultList = append(resultList, c)
 		}
diff --git a/lxd/operations.go b/lxd/operations.go
index 52df1eca2..dec968dcf 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -12,6 +12,7 @@ import (
 	"github.com/pborman/uuid"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -39,7 +40,7 @@ type operation struct {
 	class     operationClass
 	createdAt time.Time
 	updatedAt time.Time
-	status    shared.StatusCode
+	status    api.StatusCode
 	url       string
 	resources map[string][]string
 	metadata  map[string]interface{}
@@ -97,21 +98,21 @@ func (op *operation) done() {
 }
 
 func (op *operation) Run() (chan error, error) {
-	if op.status != shared.Pending {
+	if op.status != api.Pending {
 		return nil, fmt.Errorf("Only pending operations can be started")
 	}
 
 	chanRun := make(chan error, 1)
 
 	op.lock.Lock()
-	op.status = shared.Running
+	op.status = api.Running
 
 	if op.onRun != nil {
 		go func(op *operation, chanRun chan error) {
 			err := op.onRun(op)
 			if err != nil {
 				op.lock.Lock()
-				op.status = shared.Failure
+				op.status = api.Failure
 				op.err = SmartError(err).String()
 				op.lock.Unlock()
 				op.done()
@@ -125,7 +126,7 @@ func (op *operation) Run() (chan error, error) {
 			}
 
 			op.lock.Lock()
-			op.status = shared.Success
+			op.status = api.Success
 			op.lock.Unlock()
 			op.done()
 			chanRun <- nil
@@ -147,7 +148,7 @@ func (op *operation) Run() (chan error, error) {
 }
 
 func (op *operation) Cancel() (chan error, error) {
-	if op.status != shared.Running {
+	if op.status != api.Running {
 		return nil, fmt.Errorf("Only running operations can be cancelled")
 	}
 
@@ -159,11 +160,11 @@ func (op *operation) Cancel() (chan error, error) {
 
 	op.lock.Lock()
 	oldStatus := op.status
-	op.status = shared.Cancelling
+	op.status = api.Cancelling
 	op.lock.Unlock()
 
 	if op.onCancel != nil {
-		go func(op *operation, oldStatus shared.StatusCode, chanCancel chan error) {
+		go func(op *operation, oldStatus api.StatusCode, chanCancel chan error) {
 			err := op.onCancel(op)
 			if err != nil {
 				op.lock.Lock()
@@ -178,7 +179,7 @@ func (op *operation) Cancel() (chan error, error) {
 			}
 
 			op.lock.Lock()
-			op.status = shared.Cancelled
+			op.status = api.Cancelled
 			op.lock.Unlock()
 			op.done()
 			chanCancel <- nil
@@ -195,7 +196,7 @@ func (op *operation) Cancel() (chan error, error) {
 
 	if op.onCancel == nil {
 		op.lock.Lock()
-		op.status = shared.Cancelled
+		op.status = api.Cancelled
 		op.lock.Unlock()
 		op.done()
 		chanCancel <- nil
@@ -213,7 +214,7 @@ func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error
 		return nil, fmt.Errorf("Only websocket operations can be connected")
 	}
 
-	if op.status != shared.Running {
+	if op.status != api.Running {
 		return nil, fmt.Errorf("Only running operations can be connected")
 	}
 
@@ -308,7 +309,7 @@ func (op *operation) WaitFinal(timeout int) (bool, error) {
 }
 
 func (op *operation) UpdateResources(opResources map[string][]string) error {
-	if op.status != shared.Pending && op.status != shared.Running {
+	if op.status != api.Pending && op.status != api.Running {
 		return fmt.Errorf("Only pending or running operations can be updated")
 	}
 
@@ -329,7 +330,7 @@ func (op *operation) UpdateResources(opResources map[string][]string) error {
 }
 
 func (op *operation) UpdateMetadata(opMetadata interface{}) error {
-	if op.status != shared.Pending && op.status != shared.Running {
+	if op.status != api.Pending && op.status != api.Running {
 		return fmt.Errorf("Only pending or running operations can be updated")
 	}
 
@@ -365,7 +366,7 @@ func operationCreate(opClass operationClass, opResources map[string][]string, op
 	op.class = opClass
 	op.createdAt = time.Now()
 	op.updatedAt = op.createdAt
-	op.status = shared.Pending
+	op.status = api.Pending
 	op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id)
 	op.resources = opResources
 	op.chanDone = make(chan error)
diff --git a/lxd/response.go b/lxd/response.go
index 7545fddcf..b492aa1c3 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -15,21 +15,22 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 type syncResp struct {
-	Type       lxd.ResponseType  `json:"type"`
-	Status     string            `json:"status"`
-	StatusCode shared.StatusCode `json:"status_code"`
-	Metadata   interface{}       `json:"metadata"`
+	Type       lxd.ResponseType `json:"type"`
+	Status     string           `json:"status"`
+	StatusCode api.StatusCode   `json:"status_code"`
+	Metadata   interface{}      `json:"metadata"`
 }
 
 type asyncResp struct {
-	Type       lxd.ResponseType  `json:"type"`
-	Status     string            `json:"status"`
-	StatusCode shared.StatusCode `json:"status_code"`
-	Metadata   interface{}       `json:"metadata"`
-	Operation  string            `json:"operation"`
+	Type       lxd.ResponseType `json:"type"`
+	Status     string           `json:"status"`
+	StatusCode api.StatusCode   `json:"status_code"`
+	Metadata   interface{}      `json:"metadata"`
+	Operation  string           `json:"operation"`
 }
 
 type Response interface {
@@ -45,9 +46,9 @@ type syncResponse struct {
 }
 
 func (r *syncResponse) Render(w http.ResponseWriter) error {
-	status := shared.Success
+	status := api.Success
 	if !r.success {
-		status = shared.Failure
+		status = api.Failure
 	}
 
 	if r.location != "" {
@@ -208,8 +209,8 @@ func (r *operationResponse) Render(w http.ResponseWriter) error {
 
 	body := asyncResp{
 		Type:       lxd.Async,
-		Status:     shared.OperationCreated.String(),
-		StatusCode: shared.OperationCreated,
+		Status:     api.OperationCreated.String(),
+		StatusCode: api.OperationCreated,
 		Operation:  url,
 		Metadata:   md}
 
diff --git a/shared/status.go b/shared/api/status_code.go
similarity index 83%
rename from shared/status.go
rename to shared/api/status_code.go
index 7651f5ccb..bf2986607 100644
--- a/shared/status.go
+++ b/shared/api/status_code.go
@@ -1,7 +1,9 @@
-package shared
+package api
 
+// StatusCode represents a valid LXD operation and container status
 type StatusCode int
 
+// LXD status codes
 const (
 	OperationCreated StatusCode = 100
 	Started          StatusCode = 101
@@ -23,6 +25,7 @@ const (
 	Cancelled StatusCode = 401
 )
 
+// String returns a suitable string representation for the status code
 func (o StatusCode) String() string {
 	return map[StatusCode]string{
 		OperationCreated: "Operation created",
@@ -44,6 +47,7 @@ func (o StatusCode) String() string {
 	}[o]
 }
 
+// IsFinal will return true if the status code indicates an end state
 func (o StatusCode) IsFinal() bool {
 	return int(o) >= 200
 }
diff --git a/shared/container.go b/shared/container.go
index d1fba7fc0..310581c69 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -2,11 +2,13 @@ package shared
 
 import (
 	"time"
+
+	"github.com/lxc/lxd/shared/api"
 )
 
 type ContainerState struct {
 	Status     string                           `json:"status"`
-	StatusCode StatusCode                       `json:"status_code"`
+	StatusCode api.StatusCode                   `json:"status_code"`
 	Disk       map[string]ContainerStateDisk    `json:"disk"`
 	Memory     ContainerStateMemory             `json:"memory"`
 	Network    map[string]ContainerStateNetwork `json:"network"`
@@ -79,14 +81,14 @@ type ContainerInfo struct {
 	Profiles        []string                     `json:"profiles"`
 	Stateful        bool                         `json:"stateful"`
 	Status          string                       `json:"status"`
-	StatusCode      StatusCode                   `json:"status_code"`
+	StatusCode      api.StatusCode               `json:"status_code"`
 }
 
 func (c ContainerInfo) IsActive() bool {
 	switch c.StatusCode {
-	case Stopped:
+	case api.Stopped:
 		return false
-	case Error:
+	case api.Error:
 		return false
 	default:
 		return true
diff --git a/shared/operation.go b/shared/operation.go
index 121be2e43..7abe917af 100644
--- a/shared/operation.go
+++ b/shared/operation.go
@@ -2,6 +2,8 @@ package shared
 
 import (
 	"time"
+
+	"github.com/lxc/lxd/shared/api"
 )
 
 type Operation struct {
@@ -10,7 +12,7 @@ type Operation struct {
 	CreatedAt  time.Time           `json:"created_at"`
 	UpdatedAt  time.Time           `json:"updated_at"`
 	Status     string              `json:"status"`
-	StatusCode StatusCode          `json:"status_code"`
+	StatusCode api.StatusCode      `json:"status_code"`
 	Resources  map[string][]string `json:"resources"`
 	Metadata   *Jmap               `json:"metadata"`
 	MayCancel  bool                `json:"may_cancel"`

From 6c294e0bece197725bcc776557be46ccfa2b81c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Dec 2016 14:47:50 -0500
Subject: [PATCH 0625/1193] shared: Move REST API to new package: operation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go               | 30 +++++++++++++++---------------
 lxc/copy.go             |  2 +-
 lxd/operations.go       | 16 +++++++---------
 lxd/response.go         |  2 +-
 shared/api/operation.go | 19 +++++++++++++++++++
 shared/operation.go     | 20 --------------------
 6 files changed, 43 insertions(+), 46 deletions(-)
 create mode 100644 shared/api/operation.go
 delete mode 100644 shared/operation.go

diff --git a/client.go b/client.go
index 5d3d292ac..0d79998b2 100644
--- a/client.go
+++ b/client.go
@@ -90,8 +90,8 @@ func (r *Response) MetadataAsMap() (*shared.Jmap, error) {
 	return &ret, nil
 }
 
-func (r *Response) MetadataAsOperation() (*shared.Operation, error) {
-	op := shared.Operation{}
+func (r *Response) MetadataAsOperation() (*api.Operation, error) {
+	op := api.Operation{}
 	if err := json.Unmarshal(r.Metadata, &op); err != nil {
 		return nil, err
 	}
@@ -638,7 +638,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 			return err
 		}
 
-		secret, err = op.Metadata.GetString("secret")
+		secret, err = shared.Jmap(op.Metadata).GetString("secret")
 		if err != nil {
 			return err
 		}
@@ -708,7 +708,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 		}
 
 		if op.Metadata != nil {
-			value, err := op.Metadata.GetString("fingerprint")
+			value, err := shared.Jmap(op.Metadata).GetString("fingerprint")
 			if err == nil {
 				fingerprint = value
 			}
@@ -935,7 +935,7 @@ func (c *Client) PostImageURL(imageFile string, properties []string, public bool
 		return "", fmt.Errorf("Missing operation metadata")
 	}
 
-	fingerprint, err := op.Metadata.GetString("fingerprint")
+	fingerprint, err := shared.Jmap(op.Metadata).GetString("fingerprint")
 	if err != nil {
 		return "", err
 	}
@@ -1101,12 +1101,12 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 		return "", err
 	}
 
-	jmap, err := c.AsyncWaitMeta(resp)
+	meta, err := c.AsyncWaitMeta(resp)
 	if err != nil {
 		return "", err
 	}
 
-	fingerprint, err := jmap.GetString("fingerprint")
+	fingerprint, err := shared.Jmap(meta).GetString("fingerprint")
 	if err != nil {
 		return "", err
 	}
@@ -1362,7 +1362,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 					return nil, err
 				}
 
-				secret, err = op.Metadata.GetString("secret")
+				secret, err = shared.Jmap(op.Metadata).GetString("secret")
 				if err != nil {
 					return nil, err
 				}
@@ -1550,7 +1550,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		return -1, err
 	}
 
-	fds, err = op.Metadata.GetMap("fds")
+	fds, err = shared.Jmap(op.Metadata).GetMap("fds")
 	if err != nil {
 		return -1, err
 	}
@@ -1637,7 +1637,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		return -1, fmt.Errorf("no metadata received")
 	}
 
-	return op.Metadata.GetInt("return")
+	return shared.Jmap(op.Metadata).GetInt("return")
 }
 
 func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*Response, error) {
@@ -1886,7 +1886,7 @@ func (c *Client) Rename(name string, newName string) (*Response, error) {
 }
 
 /* Wait for an operation */
-func (c *Client) WaitFor(waitURL string) (*shared.Operation, error) {
+func (c *Client) WaitFor(waitURL string) (*api.Operation, error) {
 	if len(waitURL) < 1 {
 		return nil, fmt.Errorf("invalid wait url %s", waitURL)
 	}
@@ -1918,7 +1918,7 @@ func (c *Client) WaitForSuccess(waitURL string) error {
 	return fmt.Errorf(op.Err)
 }
 
-func (c *Client) WaitForSuccessOp(waitURL string) (*shared.Operation, error) {
+func (c *Client) WaitForSuccessOp(waitURL string) (*api.Operation, error) {
 	op, err := c.WaitFor(waitURL)
 	if err != nil {
 		return nil, err
@@ -2406,7 +2406,7 @@ func (c *Client) ProfileCopy(name, newname string, dest *Client) error {
 	return err
 }
 
-func (c *Client) AsyncWaitMeta(resp *Response) (*shared.Jmap, error) {
+func (c *Client) AsyncWaitMeta(resp *Response) (map[string]interface{}, error) {
 	op, err := c.WaitFor(resp.Operation)
 	if err != nil {
 		return nil, err
@@ -2439,12 +2439,12 @@ func (c *Client) ImageFromContainer(cname string, public bool, aliases []string,
 		return "", err
 	}
 
-	jmap, err := c.AsyncWaitMeta(resp)
+	meta, err := c.AsyncWaitMeta(resp)
 	if err != nil {
 		return "", err
 	}
 
-	fingerprint, err := jmap.GetString("fingerprint")
+	fingerprint, err := shared.Jmap(meta).GetString("fingerprint")
 	if err != nil {
 		return "", err
 	}
diff --git a/lxc/copy.go b/lxc/copy.go
index 024101018..14821f0ca 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -147,7 +147,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 		return err
 	}
 
-	for k, v := range *op.Metadata {
+	for k, v := range op.Metadata {
 		secrets[k] = v.(string)
 	}
 
diff --git a/lxd/operations.go b/lxd/operations.go
index dec968dcf..f285d051a 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -246,7 +246,7 @@ func (op *operation) mayCancel() bool {
 	return op.onCancel != nil || op.class == operationClassToken
 }
 
-func (op *operation) Render() (string, *shared.Operation, error) {
+func (op *operation) Render() (string, *api.Operation, error) {
 	// Setup the resource URLs
 	resources := op.resources
 	if resources != nil {
@@ -261,17 +261,15 @@ func (op *operation) Render() (string, *shared.Operation, error) {
 		resources = tmpResources
 	}
 
-	md := shared.Jmap(op.metadata)
-
-	return op.url, &shared.Operation{
-		Id:         op.id,
+	return op.url, &api.Operation{
+		ID:         op.id,
 		Class:      op.class.String(),
 		CreatedAt:  op.createdAt,
 		UpdatedAt:  op.updatedAt,
 		Status:     op.status.String(),
 		StatusCode: op.status,
 		Resources:  resources,
-		Metadata:   &md,
+		Metadata:   op.metadata,
 		MayCancel:  op.mayCancel(),
 		Err:        op.err,
 	}, nil
@@ -473,7 +471,7 @@ func operationsAPIGet(d *Daemon, r *http.Request) Response {
 		_, ok := md[status]
 		if !ok {
 			if recursion {
-				md[status] = make([]*shared.Operation, 0)
+				md[status] = make([]*api.Operation, 0)
 			} else {
 				md[status] = make([]string, 0)
 			}
@@ -489,7 +487,7 @@ func operationsAPIGet(d *Daemon, r *http.Request) Response {
 			continue
 		}
 
-		md[status] = append(md[status].([]*shared.Operation), body)
+		md[status] = append(md[status].([]*api.Operation), body)
 	}
 
 	return SyncResponse(true, md)
@@ -545,7 +543,7 @@ func (r *operationWebSocket) String() string {
 		return fmt.Sprintf("error: %s", err)
 	}
 
-	return md.Id
+	return md.ID
 }
 
 func operationAPIWebsocketGet(d *Daemon, r *http.Request) Response {
diff --git a/lxd/response.go b/lxd/response.go
index b492aa1c3..cc42c14ce 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -226,7 +226,7 @@ func (r *operationResponse) String() string {
 		return fmt.Sprintf("error: %s", err)
 	}
 
-	return md.Id
+	return md.ID
 }
 
 func OperationResponse(op *operation) Response {
diff --git a/shared/api/operation.go b/shared/api/operation.go
new file mode 100644
index 000000000..14db8fd0f
--- /dev/null
+++ b/shared/api/operation.go
@@ -0,0 +1,19 @@
+package api
+
+import (
+	"time"
+)
+
+// Operation represents a LXD background operation
+type Operation struct {
+	ID         string                 `json:"id"`
+	Class      string                 `json:"class"`
+	CreatedAt  time.Time              `json:"created_at"`
+	UpdatedAt  time.Time              `json:"updated_at"`
+	Status     string                 `json:"status"`
+	StatusCode StatusCode             `json:"status_code"`
+	Resources  map[string][]string    `json:"resources"`
+	Metadata   map[string]interface{} `json:"metadata"`
+	MayCancel  bool                   `json:"may_cancel"`
+	Err        string                 `json:"err"`
+}
diff --git a/shared/operation.go b/shared/operation.go
deleted file mode 100644
index 7abe917af..000000000
--- a/shared/operation.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package shared
-
-import (
-	"time"
-
-	"github.com/lxc/lxd/shared/api"
-)
-
-type Operation struct {
-	Id         string              `json:"id"`
-	Class      string              `json:"class"`
-	CreatedAt  time.Time           `json:"created_at"`
-	UpdatedAt  time.Time           `json:"updated_at"`
-	Status     string              `json:"status"`
-	StatusCode api.StatusCode      `json:"status_code"`
-	Resources  map[string][]string `json:"resources"`
-	Metadata   *Jmap               `json:"metadata"`
-	MayCancel  bool                `json:"may_cancel"`
-	Err        string              `json:"err"`
-}

From 885ebab0af577421dd989c256ded893364e008f7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Dec 2016 18:40:50 -0500
Subject: [PATCH 0626/1193] shared: Move REST API to new package: profile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go             | 10 +++-------
 lxc/config.go         |  4 ++--
 lxc/profile.go        |  5 +++--
 lxd/db_profiles.go    | 25 ++++++++++++++-----------
 lxd/profiles.go       | 19 ++++++-------------
 shared/api/profile.go | 32 ++++++++++++++++++++++++++++++++
 shared/container.go   |  7 -------
 7 files changed, 60 insertions(+), 42 deletions(-)
 create mode 100644 shared/api/profile.go

diff --git a/client.go b/client.go
index 0d79998b2..e3214f090 100644
--- a/client.go
+++ b/client.go
@@ -1749,12 +1749,12 @@ func (c *Client) GetLog(container string, log string) (io.Reader, error) {
 	return resp.Body, nil
 }
 
-func (c *Client) ProfileConfig(name string) (*shared.ProfileConfig, error) {
+func (c *Client) ProfileConfig(name string) (*api.Profile, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	ct := shared.ProfileConfig{}
+	ct := api.Profile{}
 
 	resp, err := c.get(fmt.Sprintf("profiles/%s", name))
 	if err != nil {
@@ -2163,15 +2163,11 @@ func (c *Client) SetProfileConfigItem(profile, key, value string) error {
 	return err
 }
 
-func (c *Client) PutProfile(name string, profile shared.ProfileConfig) error {
+func (c *Client) PutProfile(name string, profile api.ProfilePut) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	if profile.Name != name {
-		return fmt.Errorf("Cannot change profile name")
-	}
-
 	_, err := c.put(fmt.Sprintf("profiles/%s", name), profile, Sync)
 	return err
 }
diff --git a/lxc/config.go b/lxc/config.go
index dc7c5589f..1a91fa754 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -724,7 +724,7 @@ func (c *configCmd) deviceSet(config *lxd.Config, which string, args []string) e
 		dev[key] = value
 		st.Devices[devname] = dev
 
-		err = client.PutProfile(name, *st)
+		err = client.PutProfile(name, st.Writable())
 		if err != nil {
 			return err
 		}
@@ -780,7 +780,7 @@ func (c *configCmd) deviceUnset(config *lxd.Config, which string, args []string)
 		delete(dev, key)
 		st.Devices[devname] = dev
 
-		err = client.PutProfile(name, *st)
+		err = client.PutProfile(name, st.Writable())
 		if err != nil {
 			return err
 		}
diff --git a/lxc/profile.go b/lxc/profile.go
index 8b797c9e3..7860f571d 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
 )
@@ -151,7 +152,7 @@ func (c *profileCmd) doProfileEdit(client *lxd.Client, p string) error {
 			return err
 		}
 
-		newdata := shared.ProfileConfig{}
+		newdata := api.ProfilePut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -178,7 +179,7 @@ func (c *profileCmd) doProfileEdit(client *lxd.Client, p string) error {
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.ProfileConfig{}
+		newdata := api.ProfilePut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			err = client.PutProfile(p, newdata)
diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index 4d108c7c3..401ceb887 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -7,7 +7,7 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/lxd/types"
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 // dbProfiles returns a string list of profiles.
@@ -29,34 +29,37 @@ func dbProfiles(db *sql.DB) ([]string, error) {
 	return response, nil
 }
 
-func dbProfileGet(db *sql.DB, profile string) (int64, *shared.ProfileConfig, error) {
+func dbProfileGet(db *sql.DB, name string) (int64, *api.Profile, error) {
 	id := int64(-1)
 	description := sql.NullString{}
 
 	q := "SELECT id, description FROM profiles WHERE name=?"
-	arg1 := []interface{}{profile}
+	arg1 := []interface{}{name}
 	arg2 := []interface{}{&id, &description}
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		return -1, nil, err
 	}
 
-	config, err := dbProfileConfig(db, profile)
+	config, err := dbProfileConfig(db, name)
 	if err != nil {
 		return -1, nil, err
 	}
 
-	devices, err := dbDevices(db, profile, true)
+	devices, err := dbDevices(db, name, true)
 	if err != nil {
 		return -1, nil, err
 	}
 
-	return id, &shared.ProfileConfig{
-		Name:        profile,
-		Config:      config,
-		Description: description.String,
-		Devices:     devices,
-	}, nil
+	profile := api.Profile{
+		Name: name,
+	}
+
+	profile.Config = config
+	profile.Description = description.String
+	profile.Devices = devices
+
+	return id, &profile, nil
 }
 
 func dbProfileCreate(db *sql.DB, profile string, description string, config map[string]string,
diff --git a/lxd/profiles.go b/lxd/profiles.go
index f7a0bbdd4..1aa5c7f06 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -10,21 +10,14 @@ import (
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
 /* This is used for both profiles post and profile put */
-type profilesPostReq struct {
-	Name        string            `json:"name"`
-	Config      map[string]string `json:"config"`
-	Description string            `json:"description"`
-	Devices     types.Devices     `json:"devices"`
-}
-
 func profilesGet(d *Daemon, r *http.Request) Response {
 	results, err := dbProfiles(d.db)
 	if err != nil {
@@ -34,7 +27,7 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 	recursion := d.isRecursionRequest(r)
 
 	resultString := make([]string, len(results))
-	resultMap := make([]*shared.ProfileConfig, len(results))
+	resultMap := make([]*api.Profile, len(results))
 	i := 0
 	for _, name := range results {
 		if !recursion {
@@ -59,7 +52,7 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 }
 
 func profilesPost(d *Daemon, r *http.Request) Response {
-	req := profilesPostReq{}
+	req := api.ProfilesPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -107,7 +100,7 @@ var profilesCmd = Command{
 	get:  profilesGet,
 	post: profilesPost}
 
-func doProfileGet(d *Daemon, name string) (*shared.ProfileConfig, error) {
+func doProfileGet(d *Daemon, name string) (*api.Profile, error) {
 	_, profile, err := dbProfileGet(d.db, name)
 	return profile, err
 }
@@ -146,7 +139,7 @@ func getContainersWithProfile(d *Daemon, profile string) []container {
 func profilePut(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	req := profilesPostReq{}
+	req := api.ProfilePut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -247,7 +240,7 @@ func profilePut(d *Daemon, r *http.Request) Response {
 func profilePost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	req := profilesPostReq{}
+	req := api.ProfilePost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/shared/api/profile.go b/shared/api/profile.go
new file mode 100644
index 000000000..efa4c7cc3
--- /dev/null
+++ b/shared/api/profile.go
@@ -0,0 +1,32 @@
+package api
+
+// ProfilesPost represents the fields of a new LXD profile
+type ProfilesPost struct {
+	ProfilePut
+
+	Name string `json:"name"`
+}
+
+// ProfilePost represents the fields required to rename a LXD profile
+type ProfilePost struct {
+	Name string `json:"name"`
+}
+
+// ProfilePut represents the modifiable fields of a LXD profile
+type ProfilePut struct {
+	Config      map[string]string            `json:"config"`
+	Description string                       `json:"description"`
+	Devices     map[string]map[string]string `json:"devices"`
+}
+
+// Profile represents a LXD profile
+type Profile struct {
+	ProfilePut
+
+	Name string `json:"name"`
+}
+
+// Writable converts a full Profile struct into a ProfilePut struct (filters read-only fields)
+func (profile *Profile) Writable() ProfilePut {
+	return profile.ProfilePut
+}
diff --git a/shared/container.go b/shared/container.go
index 310581c69..e923b9fda 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -134,10 +134,3 @@ const (
 	Freeze   ContainerAction = "freeze"
 	Unfreeze ContainerAction = "unfreeze"
 )
-
-type ProfileConfig struct {
-	Name        string                       `json:"name"`
-	Config      map[string]string            `json:"config"`
-	Description string                       `json:"description"`
-	Devices     map[string]map[string]string `json:"devices"`
-}

From 46a27ba0e32f2a2ff11d3f8b7cc919db242360c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Dec 2016 15:16:30 -0500
Subject: [PATCH 0627/1193] shared: Move REST API to new package: container
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                        |  22 +++----
 lxc/config.go                    |  21 +++----
 lxc/exec.go                      |   3 +-
 lxc/info.go                      |   4 +-
 lxc/list.go                      |  46 +++++++-------
 lxc/list_test.go                 |   3 +-
 lxc/publish.go                   |   4 +-
 lxd/container.go                 |   3 +-
 lxd/container_exec.go            |  14 +----
 lxd/container_lxc.go             |  41 +++++++------
 lxd/container_post.go            |   9 +--
 lxd/container_put.go             |  13 +---
 lxd/container_snapshot.go        |  14 ++---
 lxd/container_state.go           |  10 +---
 lxd/container_test.go            |   3 +-
 lxd/containers_get.go            |   8 +--
 lxd/containers_post.go           |  50 ++--------------
 lxd/main_forkgetnet.go           |  12 ++--
 shared/api/container.go          |  84 ++++++++++++++++++++++++++
 shared/api/container_exec.go     |  18 ++++++
 shared/api/container_snapshot.go |  32 ++++++++++
 shared/api/container_state.go    |  60 +++++++++++++++++++
 shared/container.go              | 125 ---------------------------------------
 test/lxd-benchmark/main.go       |   5 +-
 24 files changed, 307 insertions(+), 297 deletions(-)
 create mode 100644 shared/api/container.go
 create mode 100644 shared/api/container_exec.go
 create mode 100644 shared/api/container_snapshot.go
 create mode 100644 shared/api/container_state.go

diff --git a/client.go b/client.go
index e3214f090..40b05a88b 100644
--- a/client.go
+++ b/client.go
@@ -587,7 +587,7 @@ func (c *Client) IsPublic() bool {
 	return public
 }
 
-func (c *Client) ListContainers() ([]shared.ContainerInfo, error) {
+func (c *Client) ListContainers() ([]api.Container, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -597,7 +597,7 @@ func (c *Client) ListContainers() ([]shared.ContainerInfo, error) {
 		return nil, err
 	}
 
-	var result []shared.ContainerInfo
+	var result []api.Container
 
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
@@ -1697,12 +1697,12 @@ func (c *Client) ServerStatus() (*api.Server, error) {
 	return &ss, nil
 }
 
-func (c *Client) ContainerInfo(name string) (*shared.ContainerInfo, error) {
+func (c *Client) ContainerInfo(name string) (*api.Container, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	ct := shared.ContainerInfo{}
+	ct := api.Container{}
 
 	resp, err := c.get(fmt.Sprintf("containers/%s", name))
 	if err != nil {
@@ -1716,12 +1716,12 @@ func (c *Client) ContainerInfo(name string) (*shared.ContainerInfo, error) {
 	return &ct, nil
 }
 
-func (c *Client) ContainerState(name string) (*shared.ContainerState, error) {
+func (c *Client) ContainerState(name string) (*api.ContainerState, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	ct := shared.ContainerState{}
+	ct := api.ContainerState{}
 
 	resp, err := c.get(fmt.Sprintf("containers/%s/state", name))
 	if err != nil {
@@ -1949,7 +1949,7 @@ func (c *Client) Snapshot(container string, snapshotName string, stateful bool)
 	return c.post(fmt.Sprintf("containers/%s/snapshots", container), body, Async)
 }
 
-func (c *Client) ListSnapshots(container string) ([]shared.SnapshotInfo, error) {
+func (c *Client) ListSnapshots(container string) ([]api.ContainerSnapshot, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1960,7 +1960,7 @@ func (c *Client) ListSnapshots(container string) ([]shared.SnapshotInfo, error)
 		return nil, err
 	}
 
-	var result []shared.SnapshotInfo
+	var result []api.ContainerSnapshot
 
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
@@ -1969,7 +1969,7 @@ func (c *Client) ListSnapshots(container string) ([]shared.SnapshotInfo, error)
 	return result, nil
 }
 
-func (c *Client) SnapshotInfo(snapName string) (*shared.SnapshotInfo, error) {
+func (c *Client) SnapshotInfo(snapName string) (*api.ContainerSnapshot, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1985,7 +1985,7 @@ func (c *Client) SnapshotInfo(snapName string) (*shared.SnapshotInfo, error) {
 		return nil, err
 	}
 
-	var result shared.SnapshotInfo
+	var result api.ContainerSnapshot
 
 	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
 		return nil, err
@@ -2096,7 +2096,7 @@ func (c *Client) SetContainerConfig(container, key, value string) error {
 	return c.WaitForSuccess(resp.Operation)
 }
 
-func (c *Client) UpdateContainerConfig(container string, st shared.BriefContainerInfo) error {
+func (c *Client) UpdateContainerConfig(container string, st api.ContainerPut) error {
 	if c.Remote.Public {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxc/config.go b/lxc/config.go
index 1a91fa754..0f2ad48eb 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -345,21 +345,21 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 			brief := config.Writable()
 			data, err = yaml.Marshal(&brief)
 		} else {
-			var brief shared.BriefContainerInfo
+			var brief api.ContainerPut
 			if shared.IsSnapshot(container) {
 				config, err := d.SnapshotInfo(container)
 				if err != nil {
 					return err
 				}
 
-				brief = shared.BriefContainerInfo{
+				brief = api.ContainerPut{
 					Profiles:  config.Profiles,
 					Config:    config.Config,
 					Devices:   config.Devices,
 					Ephemeral: config.Ephemeral,
 				}
 				if c.expanded {
-					brief = shared.BriefContainerInfo{
+					brief = api.ContainerPut{
 						Profiles:  config.Profiles,
 						Config:    config.ExpandedConfig,
 						Devices:   config.ExpandedDevices,
@@ -372,9 +372,10 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 					return err
 				}
 
-				brief = config.Brief()
+				brief = config.Writable()
 				if c.expanded {
-					brief = config.BriefExpanded()
+					brief.Config = config.ExpandedConfig
+					brief.Devices = config.ExpandedDevices
 				}
 			}
 
@@ -492,7 +493,7 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 			return err
 		}
 
-		newdata := shared.BriefContainerInfo{}
+		newdata := api.ContainerPut{}
 		err = yaml.Unmarshal(contents, &newdata)
 		if err != nil {
 			return err
@@ -506,7 +507,7 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 		return err
 	}
 
-	brief := config.Brief()
+	brief := config.Writable()
 	data, err := yaml.Marshal(&brief)
 	if err != nil {
 		return err
@@ -520,7 +521,7 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 
 	for {
 		// Parse the text received from the editor
-		newdata := shared.BriefContainerInfo{}
+		newdata := api.ContainerPut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
 			err = client.UpdateContainerConfig(cont, newdata)
@@ -742,7 +743,7 @@ func (c *configCmd) deviceSet(config *lxd.Config, which string, args []string) e
 		dev[key] = value
 		st.Devices[devname] = dev
 
-		err = client.UpdateContainerConfig(name, st.Brief())
+		err = client.UpdateContainerConfig(name, st.Writable())
 		if err != nil {
 			return err
 		}
@@ -798,7 +799,7 @@ func (c *configCmd) deviceUnset(config *lxd.Config, which string, args []string)
 		delete(dev, key)
 		st.Devices[devname] = dev
 
-		err = client.UpdateContainerConfig(name, st.Brief())
+		err = client.UpdateContainerConfig(name, st.Writable())
 		if err != nil {
 			return err
 		}
diff --git a/lxc/exec.go b/lxc/exec.go
index f142bba74..ce5466645 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/termios"
@@ -68,7 +69,7 @@ func (c *execCmd) sendTermSize(control *websocket.Conn) error {
 		return err
 	}
 
-	msg := shared.ContainerExecControl{}
+	msg := api.ContainerExecControl{}
 	msg.Command = "window-resize"
 	msg.Args = make(map[string]string)
 	msg.Args["width"] = strconv.Itoa(width)
diff --git a/lxc/info.go b/lxc/info.go
index 767c83cba..0245a7526 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -91,8 +91,8 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		fmt.Printf(i18n.G("Remote: %s")+"\n", d.Remote.Addr)
 	}
 	fmt.Printf(i18n.G("Architecture: %s")+"\n", ct.Architecture)
-	if ct.CreationDate.UTC().Unix() != 0 {
-		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreationDate.UTC().Format(layout))
+	if ct.CreatedAt.UTC().Unix() != 0 {
+		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreatedAt.UTC().Format(layout))
 	}
 
 	fmt.Printf(i18n.G("Status: %s")+"\n", ct.Status)
diff --git a/lxc/list.go b/lxc/list.go
index 17b96f996..60764fa49 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -24,7 +25,7 @@ type column struct {
 	NeedsSnapshots bool
 }
 
-type columnData func(shared.ContainerInfo, *shared.ContainerState, []shared.SnapshotInfo) string
+type columnData func(api.Container, *api.ContainerState, []api.ContainerSnapshot) string
 
 type byName [][]string
 
@@ -120,7 +121,7 @@ func (c *listCmd) dotPrefixMatch(short string, full string) bool {
 	return true
 }
 
-func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool {
+func (c *listCmd) shouldShow(filters []string, state *api.Container) bool {
 	for _, filter := range filters {
 		if strings.Contains(filter, "=") {
 			membs := strings.SplitN(filter, "=", 2)
@@ -185,7 +186,7 @@ func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool
 	return true
 }
 
-func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, filters []string, columns []column) error {
+func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters []string, columns []column) error {
 	headers := []string{}
 	for _, column := range columns {
 		headers = append(headers, column.Name)
@@ -196,12 +197,12 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 		threads = len(cinfos)
 	}
 
-	cStates := map[string]*shared.ContainerState{}
+	cStates := map[string]*api.ContainerState{}
 	cStatesLock := sync.Mutex{}
 	cStatesQueue := make(chan string, threads)
 	cStatesWg := sync.WaitGroup{}
 
-	cSnapshots := map[string][]shared.SnapshotInfo{}
+	cSnapshots := map[string][]api.ContainerSnapshot{}
 	cSnapshotsLock := sync.Mutex{}
 	cSnapshotsQueue := make(chan string, threads)
 	cSnapshotsWg := sync.WaitGroup{}
@@ -325,7 +326,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 	case listFormatJSON:
 		data := make([]listContainerItem, len(cinfos))
 		for i := range cinfos {
-			data[i].ContainerInfo = &cinfos[i]
+			data[i].Container = &cinfos[i]
 			data[i].State = cStates[cinfos[i].Name]
 			data[i].Snapshots = cSnapshots[cinfos[i].Name]
 		}
@@ -342,9 +343,10 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, f
 }
 
 type listContainerItem struct {
-	*shared.ContainerInfo
-	State     *shared.ContainerState `json:"state"`
-	Snapshots []shared.SnapshotInfo  `json:"snapshots"`
+	*api.Container
+
+	State     *api.ContainerState
+	Snapshots []api.ContainerSnapshot
 }
 
 func (c *listCmd) run(config *lxd.Config, args []string) error {
@@ -374,7 +376,7 @@ func (c *listCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	var cts []shared.ContainerInfo
+	var cts []api.Container
 	ctslist, err := d.ListContainers()
 	if err != nil {
 		return err
@@ -417,15 +419,15 @@ func (c *listCmd) run(config *lxd.Config, args []string) error {
 	return c.listContainers(d, cts, filters, columns)
 }
 
-func (c *listCmd) nameColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) nameColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	return cInfo.Name
 }
 
-func (c *listCmd) statusColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) statusColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	return strings.ToUpper(cInfo.Status)
 }
 
-func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) IP4ColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cInfo.IsActive() && cState != nil && cState.Network != nil {
 		ipv4s := []string{}
 		for netName, net := range cState.Network {
@@ -450,7 +452,7 @@ func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 	}
 }
 
-func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) IP6ColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cInfo.IsActive() && cState != nil && cState.Network != nil {
 		ipv6s := []string{}
 		for netName, net := range cState.Network {
@@ -475,7 +477,7 @@ func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 	}
 }
 
-func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) typeColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cInfo.Ephemeral {
 		return i18n.G("EPHEMERAL")
 	} else {
@@ -483,7 +485,7 @@ func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.Cont
 	}
 }
 
-func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) numberSnapshotsColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cSnaps != nil {
 		return fmt.Sprintf("%d", len(cSnaps))
 	}
@@ -491,7 +493,7 @@ func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *
 	return ""
 }
 
-func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) PIDColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	if cInfo.IsActive() && cState != nil {
 		return fmt.Sprintf("%d", cState.Pid)
 	}
@@ -499,19 +501,19 @@ func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.Conta
 	return ""
 }
 
-func (c *listCmd) ArchitectureColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) ArchitectureColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	return cInfo.Architecture
 }
 
-func (c *listCmd) ProfilesColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) ProfilesColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	return strings.Join(cInfo.Profiles, "\n")
 }
 
-func (c *listCmd) CreatedColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
+func (c *listCmd) CreatedColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	layout := "2006/01/02 15:04 UTC"
 
-	if cInfo.CreationDate.UTC().Unix() != 0 {
-		return cInfo.CreationDate.UTC().Format(layout)
+	if cInfo.CreatedAt.UTC().Unix() != 0 {
+		return cInfo.CreatedAt.UTC().Format(layout)
 	}
 
 	return ""
diff --git a/lxc/list_test.go b/lxc/list_test.go
index 47e813557..a055d033c 100644
--- a/lxc/list_test.go
+++ b/lxc/list_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func TestDotPrefixMatch(t *testing.T) {
@@ -21,7 +22,7 @@ func TestDotPrefixMatch(t *testing.T) {
 func TestShouldShow(t *testing.T) {
 	list := listCmd{}
 
-	state := &shared.ContainerInfo{
+	state := &api.Container{
 		Name: "foo",
 		ExpandedConfig: map[string]string{
 			"security.privileged": "1",
diff --git a/lxc/publish.go b/lxc/publish.go
index 9a7ef90a1..544724a86 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -92,7 +92,7 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 
 			if ct.Ephemeral {
 				ct.Ephemeral = false
-				err := s.UpdateContainerConfig(cName, ct.Brief())
+				err := s.UpdateContainerConfig(cName, ct.Writable())
 				if err != nil {
 					return err
 				}
@@ -115,7 +115,7 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 
 			if wasEphemeral {
 				ct.Ephemeral = true
-				err := s.UpdateContainerConfig(cName, ct.Brief())
+				err := s.UpdateContainerConfig(cName, ct.Writable())
 				if err != nil {
 					return err
 				}
diff --git a/lxd/container.go b/lxd/container.go
index ffe20a909..64a95ce83 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
@@ -460,7 +461,7 @@ type container interface {
 
 	// Status
 	Render() (interface{}, error)
-	RenderState() (*shared.ContainerState, error)
+	RenderState() (*api.ContainerState, error)
 	IsPrivileged() bool
 	IsRunning() bool
 	IsFrozen() bool
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 61017575e..3f46153a4 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -15,19 +15,11 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-type commandPostContent struct {
-	Command     []string          `json:"command"`
-	WaitForWS   bool              `json:"wait-for-websocket"`
-	Interactive bool              `json:"interactive"`
-	Environment map[string]string `json:"environment"`
-	Width       int               `json:"width"`
-	Height      int               `json:"height"`
-}
-
 type execWs struct {
 	command   []string
 	container container
@@ -171,7 +163,7 @@ func (s *execWs) Do(op *operation) error {
 					break
 				}
 
-				command := shared.ContainerExecControl{}
+				command := api.ContainerExecControl{}
 
 				if err := json.Unmarshal(buf, &command); err != nil {
 					shared.LogDebugf("Failed to unmarshal control socket command: %s", err)
@@ -309,7 +301,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("Container is frozen."))
 	}
 
-	post := commandPostContent{}
+	post := api.ContainerExecPost{}
 	buf, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		return BadRequest(err)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index c013aae75..4b57a417d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2209,7 +2209,7 @@ func (c *containerLXC) Render() (interface{}, error) {
 	architectureName, _ := osarch.ArchitectureName(c.architecture)
 
 	if c.IsSnapshot() {
-		return &shared.SnapshotInfo{
+		return &api.ContainerSnapshot{
 			Architecture:    architectureName,
 			Config:          c.localConfig,
 			CreationDate:    c.creationDate,
@@ -2229,24 +2229,27 @@ func (c *containerLXC) Render() (interface{}, error) {
 		}
 		statusCode := lxcStatusCode(cState)
 
-		return &shared.ContainerInfo{
-			Architecture:    architectureName,
-			Config:          c.localConfig,
-			CreationDate:    c.creationDate,
-			Devices:         c.localDevices,
-			Ephemeral:       c.ephemeral,
+		ct := api.Container{
 			ExpandedConfig:  c.expandedConfig,
 			ExpandedDevices: c.expandedDevices,
 			Name:            c.name,
-			Profiles:        c.profiles,
 			Status:          statusCode.String(),
 			StatusCode:      statusCode,
 			Stateful:        c.stateful,
-		}, nil
+		}
+
+		ct.Architecture = architectureName
+		ct.Config = c.localConfig
+		ct.CreatedAt = c.creationDate
+		ct.Devices = c.localDevices
+		ct.Ephemeral = c.ephemeral
+		ct.Profiles = c.profiles
+
+		return &ct, nil
 	}
 }
 
-func (c *containerLXC) RenderState() (*shared.ContainerState, error) {
+func (c *containerLXC) RenderState() (*api.ContainerState, error) {
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
@@ -2258,7 +2261,7 @@ func (c *containerLXC) RenderState() (*shared.ContainerState, error) {
 		return nil, err
 	}
 	statusCode := lxcStatusCode(cState)
-	status := shared.ContainerState{
+	status := api.ContainerState{
 		Status:     statusCode.String(),
 		StatusCode: statusCode,
 	}
@@ -4088,8 +4091,8 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	return 0, attachedPid, nil
 }
 
-func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk {
-	disk := map[string]shared.ContainerStateDisk{}
+func (c *containerLXC) diskState() map[string]api.ContainerStateDisk {
+	disk := map[string]api.ContainerStateDisk{}
 
 	for _, name := range c.expandedDevices.DeviceNames() {
 		d := c.expandedDevices[name]
@@ -4106,14 +4109,14 @@ func (c *containerLXC) diskState() map[string]shared.ContainerStateDisk {
 			continue
 		}
 
-		disk[name] = shared.ContainerStateDisk{Usage: usage}
+		disk[name] = api.ContainerStateDisk{Usage: usage}
 	}
 
 	return disk
 }
 
-func (c *containerLXC) memoryState() shared.ContainerStateMemory {
-	memory := shared.ContainerStateMemory{}
+func (c *containerLXC) memoryState() api.ContainerStateMemory {
+	memory := api.ContainerStateMemory{}
 
 	if !cgMemoryController {
 		return memory
@@ -4159,8 +4162,8 @@ func (c *containerLXC) memoryState() shared.ContainerStateMemory {
 	return memory
 }
 
-func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
-	result := map[string]shared.ContainerStateNetwork{}
+func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
+	result := map[string]api.ContainerStateNetwork{}
 
 	pid := c.InitPID()
 	if pid < 1 {
@@ -4179,7 +4182,7 @@ func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
 		return result
 	}
 
-	networks := map[string]shared.ContainerStateNetwork{}
+	networks := map[string]api.ContainerStateNetwork{}
 
 	err = json.Unmarshal(out, &networks)
 	if err != nil {
diff --git a/lxd/container_post.go b/lxd/container_post.go
index 4139dd756..066a2a3b0 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -6,12 +6,9 @@ import (
 	"net/http"
 
 	"github.com/gorilla/mux"
-)
 
-type containerPostBody struct {
-	Migration bool   `json:"migration"`
-	Name      string `json:"name"`
-}
+	"github.com/lxc/lxd/shared/api"
+)
 
 func containerPost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
@@ -25,7 +22,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
-	body := containerPostBody{}
+	body := api.ContainerPost{}
 	if err := json.Unmarshal(buf, &body); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 73a578e9f..8ab75112a 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -8,22 +8,13 @@ import (
 
 	"github.com/gorilla/mux"
 
-	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-type containerPutReq struct {
-	Architecture string            `json:"architecture"`
-	Config       map[string]string `json:"config"`
-	Devices      types.Devices     `json:"devices"`
-	Ephemeral    bool              `json:"ephemeral"`
-	Profiles     []string          `json:"profiles"`
-	Restore      string            `json:"restore"`
-}
-
 /*
  * Update configuration, or, if 'restore:snapshot-name' is present, restore
  * the named snapshot
@@ -35,7 +26,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
 		return NotFound
 	}
 
-	configRaw := containerPutReq{}
+	configRaw := api.ContainerPut{}
 	if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index be0f8b217..a8576d60d 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -10,14 +10,10 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
-type containerSnapshotPostReq struct {
-	Name     string `json:"name"`
-	Stateful bool   `json:"stateful"`
-}
-
 func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	recursionStr := r.FormValue("recursion")
 	recursion, err := strconv.Atoi(recursionStr)
@@ -37,7 +33,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	}
 
 	resultString := []string{}
-	resultMap := []*shared.SnapshotInfo{}
+	resultMap := []*api.ContainerSnapshot{}
 
 	for _, snap := range snaps {
 		snapName := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)[1]
@@ -50,7 +46,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 				continue
 			}
 
-			resultMap = append(resultMap, render.(*shared.SnapshotInfo))
+			resultMap = append(resultMap, render.(*api.ContainerSnapshot))
 		}
 	}
 
@@ -111,7 +107,7 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	req := containerSnapshotPostReq{}
+	req := api.ContainerSnapshotsPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -189,7 +185,7 @@ func snapshotGet(sc container, name string) Response {
 		return SmartError(err)
 	}
 
-	return SyncResponse(true, render.(*shared.SnapshotInfo))
+	return SyncResponse(true, render.(*api.ContainerSnapshot))
 }
 
 func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) Response {
diff --git a/lxd/container_state.go b/lxd/container_state.go
index 1a4ca8cb0..3115f586a 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -9,15 +9,9 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
-type containerStatePutReq struct {
-	Action   string `json:"action"`
-	Timeout  int    `json:"timeout"`
-	Force    bool   `json:"force"`
-	Stateful bool   `json:"stateful"`
-}
-
 func containerState(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, name)
@@ -36,7 +30,7 @@ func containerState(d *Daemon, r *http.Request) Response {
 func containerStatePut(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	raw := containerStatePutReq{}
+	raw := api.ContainerStatePut{}
 
 	// We default to -1 (i.e. no timeout) here instead of 0 (instant
 	// timeout).
diff --git a/lxd/container_test.go b/lxd/container_test.go
index c44af4e98..54105f141 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func (suite *lxdTestSuite) TestContainer_ProfilesDefault() {
@@ -87,7 +88,7 @@ func (suite *lxdTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 	out, err := c.Render()
 	suite.Req.Nil(err)
 
-	state := out.(*shared.ContainerInfo)
+	state := out.(*api.Container)
 	defer c.Delete()
 
 	suite.Equal(
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 2b0005f4b..38e9deb8d 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -37,7 +37,7 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 	}
 
 	resultString := []string{}
-	resultList := []*shared.ContainerInfo{}
+	resultList := []*api.Container{}
 	if err != nil {
 		return []string{}, err
 	}
@@ -49,7 +49,7 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 		} else {
 			c, err := doContainerGet(d, container)
 			if err != nil {
-				c = &shared.ContainerInfo{
+				c = &api.Container{
 					Name:       container,
 					Status:     api.Error.String(),
 					StatusCode: api.Error}
@@ -65,7 +65,7 @@ func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
 	return resultList, nil
 }
 
-func doContainerGet(d *Daemon, cname string) (*shared.ContainerInfo, error) {
+func doContainerGet(d *Daemon, cname string) (*api.Container, error) {
 	c, err := containerLoadByName(d, cname)
 	if err != nil {
 		return nil, err
@@ -76,5 +76,5 @@ func doContainerGet(d *Daemon, cname string) (*shared.ContainerInfo, error) {
 		return nil, err
 	}
 
-	return cts.(*shared.ContainerInfo), nil
+	return cts.(*api.Container), nil
 }
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index c59f55ff8..8f2ff35b0 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -19,47 +19,7 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-type containerImageSource struct {
-	Type        string `json:"type"`
-	Certificate string `json:"certificate"`
-
-	/* for "image" type */
-	Alias       string            `json:"alias"`
-	Fingerprint string            `json:"fingerprint"`
-	Properties  map[string]string `json:"properties"`
-	Server      string            `json:"server"`
-	Secret      string            `json:"secret"`
-	Protocol    string            `json:"protocol"`
-
-	/*
-	 * for "migration" and "copy" types, as an optimization users can
-	 * provide an image hash to extract before the filesystem is rsync'd,
-	 * potentially cutting down filesystem transfer time. LXD will not go
-	 * and fetch this image, it will simply use it if it exists in the
-	 * image store.
-	 */
-	BaseImage string `json:"base-image"`
-
-	/* for "migration" type */
-	Mode       string            `json:"mode"`
-	Operation  string            `json:"operation"`
-	Websockets map[string]string `json:"secrets"`
-
-	/* for "copy" type */
-	Source string `json:"source"`
-}
-
-type containerPostReq struct {
-	Architecture string               `json:"architecture"`
-	Config       map[string]string    `json:"config"`
-	Devices      types.Devices        `json:"devices"`
-	Ephemeral    bool                 `json:"ephemeral"`
-	Name         string               `json:"name"`
-	Profiles     []string             `json:"profiles"`
-	Source       containerImageSource `json:"source"`
-}
-
-func createFromImage(d *Daemon, req *containerPostReq) Response {
+func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 	var hash string
 	var err error
 
@@ -172,7 +132,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 	return OperationResponse(op)
 }
 
-func createFromNone(d *Daemon, req *containerPostReq) Response {
+func createFromNone(d *Daemon, req *api.ContainersPost) Response {
 	architecture, err := osarch.ArchitectureId(req.Architecture)
 	if err != nil {
 		architecture = 0
@@ -204,7 +164,7 @@ func createFromNone(d *Daemon, req *containerPostReq) Response {
 	return OperationResponse(op)
 }
 
-func createFromMigration(d *Daemon, req *containerPostReq) Response {
+func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
 	if req.Source.Mode != "pull" {
 		return NotImplemented
 	}
@@ -300,7 +260,7 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 	return OperationResponse(op)
 }
 
-func createFromCopy(d *Daemon, req *containerPostReq) Response {
+func createFromCopy(d *Daemon, req *api.ContainersPost) Response {
 	if req.Source.Source == "" {
 		return BadRequest(fmt.Errorf("must specify a source container"))
 	}
@@ -371,7 +331,7 @@ func createFromCopy(d *Daemon, req *containerPostReq) Response {
 func containersPost(d *Daemon, r *http.Request) Response {
 	shared.LogDebugf("Responding to container create")
 
-	req := containerPostReq{}
+	req := api.ContainersPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/main_forkgetnet.go b/lxd/main_forkgetnet.go
index 7e016f389..5ca9fdbf7 100644
--- a/lxd/main_forkgetnet.go
+++ b/lxd/main_forkgetnet.go
@@ -8,11 +8,11 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdForkGetNet() error {
-	networks := map[string]shared.ContainerStateNetwork{}
+	networks := map[string]api.ContainerStateNetwork{}
 
 	interfaces, err := net.Interfaces()
 	if err != nil {
@@ -75,9 +75,9 @@ func cmdForkGetNet() error {
 			netState = "up"
 		}
 
-		network := shared.ContainerStateNetwork{
-			Addresses: []shared.ContainerStateNetworkAddress{},
-			Counters:  shared.ContainerStateNetworkCounters{},
+		network := api.ContainerStateNetwork{
+			Addresses: []api.ContainerStateNetworkAddress{},
+			Counters:  api.ContainerStateNetworkCounters{},
 			Hwaddr:    netIf.HardwareAddr.String(),
 			Mtu:       netIf.MTU,
 			State:     netState,
@@ -114,7 +114,7 @@ func cmdForkGetNet() error {
 					scope = "link"
 				}
 
-				address := shared.ContainerStateNetworkAddress{}
+				address := api.ContainerStateNetworkAddress{}
 				address.Family = family
 				address.Address = fields[0]
 				address.Netmask = fields[1]
diff --git a/shared/api/container.go b/shared/api/container.go
new file mode 100644
index 000000000..e11afbcfb
--- /dev/null
+++ b/shared/api/container.go
@@ -0,0 +1,84 @@
+package api
+
+import (
+	"time"
+)
+
+// ContainersPost represents the fields available for a new LXD container
+type ContainersPost struct {
+	ContainerPut
+
+	Name   string          `json:"name"`
+	Source ContainerSource `json:"source"`
+}
+
+// ContainerPost represents the fields required to rename/move a LXD container
+type ContainerPost struct {
+	Migration bool   `json:"migration"`
+	Name      string `json:"name"`
+}
+
+// ContainerPut represents the modifiable fields of a LXD container
+type ContainerPut struct {
+	Architecture string                       `json:"architecture"`
+	Config       map[string]string            `json:"config"`
+	Devices      map[string]map[string]string `json:"devices"`
+	Ephemeral    bool                         `json:"ephemeral"`
+	Profiles     []string                     `json:"profiles"`
+	Restore      string                       `json:"restore,omitempty"`
+}
+
+// Container represents a LXD container
+type Container struct {
+	ContainerPut
+
+	CreatedAt       time.Time                    `json:"created_at"`
+	ExpandedConfig  map[string]string            `json:"expanded_config"`
+	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
+	Name            string                       `json:"name"`
+	Stateful        bool                         `json:"stateful"`
+	Status          string                       `json:"status"`
+	StatusCode      StatusCode                   `json:"status_code"`
+}
+
+// Writable converts a full Container struct into a ContainerPut struct (filters read-only fields)
+func (c *Container) Writable() ContainerPut {
+	return c.ContainerPut
+}
+
+// IsActive checks whether the container state indicates the container is active
+func (c Container) IsActive() bool {
+	switch c.StatusCode {
+	case Stopped:
+		return false
+	case Error:
+		return false
+	default:
+		return true
+	}
+}
+
+// ContainerSource represents the creation source for a new container
+type ContainerSource struct {
+	Type        string `json:"type"`
+	Certificate string `json:"certificate"`
+
+	/* For "image" type */
+	Alias       string            `json:"alias,omitempty"`
+	Fingerprint string            `json:"fingerprint,omitempty"`
+	Properties  map[string]string `json:"properties,omitempty"`
+	Server      string            `json:"server,omitempty"`
+	Secret      string            `json:"secret,omitempty"`
+	Protocol    string            `json:"protocol,omitempty"`
+
+	/* For "migration" and "copy" types */
+	BaseImage string `json:"base-image,omitempty"`
+
+	/* For "migration" type */
+	Mode       string            `json:"mode,omitempty"`
+	Operation  string            `json:"operation,omitempty"`
+	Websockets map[string]string `json:"secrets,omitempty"`
+
+	/* For "copy" type */
+	Source string `json:"source,omitempty"`
+}
diff --git a/shared/api/container_exec.go b/shared/api/container_exec.go
new file mode 100644
index 000000000..f4cd86944
--- /dev/null
+++ b/shared/api/container_exec.go
@@ -0,0 +1,18 @@
+package api
+
+// ContainerExecControl represents a message on the container exec "control" socket
+type ContainerExecControl struct {
+	Command string            `json:"command"`
+	Args    map[string]string `json:"args"`
+	Signal  int               `json:"signal"`
+}
+
+// ContainerExecPost represents a LXD container exec request
+type ContainerExecPost struct {
+	Command     []string          `json:"command"`
+	WaitForWS   bool              `json:"wait-for-websocket"`
+	Interactive bool              `json:"interactive"`
+	Environment map[string]string `json:"environment"`
+	Width       int               `json:"width"`
+	Height      int               `json:"height"`
+}
diff --git a/shared/api/container_snapshot.go b/shared/api/container_snapshot.go
new file mode 100644
index 000000000..ab65ab7de
--- /dev/null
+++ b/shared/api/container_snapshot.go
@@ -0,0 +1,32 @@
+package api
+
+import (
+	"time"
+)
+
+// ContainerSnapshotsPost represents the fields available for a new LXD container snapshot
+type ContainerSnapshotsPost struct {
+	Name     string `json:"name"`
+	Stateful bool   `json:"stateful"`
+}
+
+// ContainerSnapshotPost represents the fields required to rename/move a LXD container snapshot
+type ContainerSnapshotPost struct {
+	Name      string `json:"name"`
+	Migration bool   `json:"migration"`
+}
+
+// ContainerSnapshot represents a LXD conainer snapshot
+type ContainerSnapshot struct {
+	Architecture    string                       `json:"architecture"`
+	Config          map[string]string            `json:"config"`
+	CreationDate    time.Time                    `json:"created_at"`
+	Devices         map[string]map[string]string `json:"devices"`
+	Ephemeral       bool                         `json:"ephemeral"`
+	ExpandedConfig  map[string]string            `json:"expanded_config"`
+	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
+	LastUsedDate    time.Time                    `json:"last_used_at"`
+	Name            string                       `json:"name"`
+	Profiles        []string                     `json:"profiles"`
+	Stateful        bool                         `json:"stateful"`
+}
diff --git a/shared/api/container_state.go b/shared/api/container_state.go
new file mode 100644
index 000000000..955a1278e
--- /dev/null
+++ b/shared/api/container_state.go
@@ -0,0 +1,60 @@
+package api
+
+// ContainerStatePut represents the modifiable fields of a LXD container's state
+type ContainerStatePut struct {
+	Action   string `json:"action"`
+	Timeout  int    `json:"timeout"`
+	Force    bool   `json:"force"`
+	Stateful bool   `json:"stateful"`
+}
+
+// ContainerState represents a LXD container's state
+type ContainerState struct {
+	Status     string                           `json:"status"`
+	StatusCode StatusCode                       `json:"status_code"`
+	Disk       map[string]ContainerStateDisk    `json:"disk"`
+	Memory     ContainerStateMemory             `json:"memory"`
+	Network    map[string]ContainerStateNetwork `json:"network"`
+	Pid        int64                            `json:"pid"`
+	Processes  int64                            `json:"processes"`
+}
+
+// ContainerStateDisk represents the disk information section of a LXD container's state
+type ContainerStateDisk struct {
+	Usage int64 `json:"usage"`
+}
+
+// ContainerStateMemory represents the memory information section of a LXD container's state
+type ContainerStateMemory struct {
+	Usage         int64 `json:"usage"`
+	UsagePeak     int64 `json:"usage_peak"`
+	SwapUsage     int64 `json:"swap_usage"`
+	SwapUsagePeak int64 `json:"swap_usage_peak"`
+}
+
+// ContainerStateNetwork represents the network information section of a LXD container's state
+type ContainerStateNetwork struct {
+	Addresses []ContainerStateNetworkAddress `json:"addresses"`
+	Counters  ContainerStateNetworkCounters  `json:"counters"`
+	Hwaddr    string                         `json:"hwaddr"`
+	HostName  string                         `json:"host_name"`
+	Mtu       int                            `json:"mtu"`
+	State     string                         `json:"state"`
+	Type      string                         `json:"type"`
+}
+
+// ContainerStateNetworkAddress represents a network address as part of the network section of a LXD container's state
+type ContainerStateNetworkAddress struct {
+	Family  string `json:"family"`
+	Address string `json:"address"`
+	Netmask string `json:"netmask"`
+	Scope   string `json:"scope"`
+}
+
+// ContainerStateNetworkCounters represents packet counters as part of the network section of a LXD container's state
+type ContainerStateNetworkCounters struct {
+	BytesReceived   int64 `json:"bytes_received"`
+	BytesSent       int64 `json:"bytes_sent"`
+	PacketsReceived int64 `json:"packets_received"`
+	PacketsSent     int64 `json:"packets_sent"`
+}
diff --git a/shared/container.go b/shared/container.go
index e923b9fda..d91c551e8 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -1,130 +1,5 @@
 package shared
 
-import (
-	"time"
-
-	"github.com/lxc/lxd/shared/api"
-)
-
-type ContainerState struct {
-	Status     string                           `json:"status"`
-	StatusCode api.StatusCode                   `json:"status_code"`
-	Disk       map[string]ContainerStateDisk    `json:"disk"`
-	Memory     ContainerStateMemory             `json:"memory"`
-	Network    map[string]ContainerStateNetwork `json:"network"`
-	Pid        int64                            `json:"pid"`
-	Processes  int64                            `json:"processes"`
-}
-
-type ContainerStateDisk struct {
-	Usage int64 `json:"usage"`
-}
-
-type ContainerStateMemory struct {
-	Usage         int64 `json:"usage"`
-	UsagePeak     int64 `json:"usage_peak"`
-	SwapUsage     int64 `json:"swap_usage"`
-	SwapUsagePeak int64 `json:"swap_usage_peak"`
-}
-
-type ContainerStateNetwork struct {
-	Addresses []ContainerStateNetworkAddress `json:"addresses"`
-	Counters  ContainerStateNetworkCounters  `json:"counters"`
-	Hwaddr    string                         `json:"hwaddr"`
-	HostName  string                         `json:"host_name"`
-	Mtu       int                            `json:"mtu"`
-	State     string                         `json:"state"`
-	Type      string                         `json:"type"`
-}
-
-type ContainerStateNetworkAddress struct {
-	Family  string `json:"family"`
-	Address string `json:"address"`
-	Netmask string `json:"netmask"`
-	Scope   string `json:"scope"`
-}
-
-type ContainerStateNetworkCounters struct {
-	BytesReceived   int64 `json:"bytes_received"`
-	BytesSent       int64 `json:"bytes_sent"`
-	PacketsReceived int64 `json:"packets_received"`
-	PacketsSent     int64 `json:"packets_sent"`
-}
-
-type ContainerExecControl struct {
-	Command string            `json:"command"`
-	Args    map[string]string `json:"args"`
-}
-
-type SnapshotInfo struct {
-	Architecture    string                       `json:"architecture"`
-	Config          map[string]string            `json:"config"`
-	CreationDate    time.Time                    `json:"created_at"`
-	Devices         map[string]map[string]string `json:"devices"`
-	Ephemeral       bool                         `json:"ephemeral"`
-	ExpandedConfig  map[string]string            `json:"expanded_config"`
-	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
-	Name            string                       `json:"name"`
-	Profiles        []string                     `json:"profiles"`
-	Stateful        bool                         `json:"stateful"`
-}
-
-type ContainerInfo struct {
-	Architecture    string                       `json:"architecture"`
-	Config          map[string]string            `json:"config"`
-	CreationDate    time.Time                    `json:"created_at"`
-	Devices         map[string]map[string]string `json:"devices"`
-	Ephemeral       bool                         `json:"ephemeral"`
-	ExpandedConfig  map[string]string            `json:"expanded_config"`
-	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
-	Name            string                       `json:"name"`
-	Profiles        []string                     `json:"profiles"`
-	Stateful        bool                         `json:"stateful"`
-	Status          string                       `json:"status"`
-	StatusCode      api.StatusCode               `json:"status_code"`
-}
-
-func (c ContainerInfo) IsActive() bool {
-	switch c.StatusCode {
-	case api.Stopped:
-		return false
-	case api.Error:
-		return false
-	default:
-		return true
-	}
-}
-
-/*
- * BriefContainerState contains a subset of the fields in
- * ContainerState, namely those which a user may update
- */
-type BriefContainerInfo struct {
-	Name      string                       `json:"name"`
-	Profiles  []string                     `json:"profiles"`
-	Config    map[string]string            `json:"config"`
-	Devices   map[string]map[string]string `json:"devices"`
-	Ephemeral bool                         `json:"ephemeral"`
-}
-
-func (c *ContainerInfo) Brief() BriefContainerInfo {
-	retstate := BriefContainerInfo{Name: c.Name,
-		Profiles:  c.Profiles,
-		Config:    c.Config,
-		Devices:   c.Devices,
-		Ephemeral: c.Ephemeral}
-	return retstate
-}
-
-func (c *ContainerInfo) BriefExpanded() BriefContainerInfo {
-	retstate := BriefContainerInfo{Name: c.Name,
-		Profiles:  c.Profiles,
-		Config:    c.ExpandedConfig,
-		Devices:   c.ExpandedDevices,
-		Ephemeral: c.Ephemeral}
-	return retstate
-}
-
 type ContainerAction string
 
 const (
diff --git a/test/lxd-benchmark/main.go b/test/lxd-benchmark/main.go
index b35ded504..45c90fe6f 100644
--- a/test/lxd-benchmark/main.go
+++ b/test/lxd-benchmark/main.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 )
 
@@ -258,7 +259,7 @@ func deleteContainers(c *lxd.Client) error {
 		return err
 	}
 
-	containers := []shared.ContainerInfo{}
+	containers := []api.Container{}
 	for _, container := range allContainers {
 		if container.Config["user.lxd-benchmark"] != "true" {
 			continue
@@ -277,7 +278,7 @@ func deleteContainers(c *lxd.Client) error {
 	wgBatch := sync.WaitGroup{}
 	nextStat := batch
 
-	deleteContainer := func(ct shared.ContainerInfo) {
+	deleteContainer := func(ct api.Container) {
 		defer wgBatch.Done()
 
 		// Stop

From f026e95cda52dfeeca55461bdca30b909889c6fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Dec 2016 17:12:21 -0500
Subject: [PATCH 0628/1193] shared: Move REST API to new package: response
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go              | 225 ++++++++++++++++++++-----------------------------
 lxc/action.go          |   2 +-
 lxc/config.go          |   4 +-
 lxc/copy.go            |   3 +-
 lxc/init.go            |   2 +-
 lxc/launch.go          |   3 +-
 lxd/daemon.go          |   7 +-
 lxd/daemon_images.go   |   3 +-
 lxd/main_callhook.go   |   3 +-
 lxd/main_ready.go      |   3 +-
 lxd/main_waitready.go  |   3 +-
 lxd/remote.go          |   3 +-
 lxd/response.go        |  42 ++++-----
 shared/api/response.go |  83 ++++++++++++++++++
 14 files changed, 212 insertions(+), 174 deletions(-)
 create mode 100644 shared/api/response.go

diff --git a/client.go b/client.go
index 40b05a88b..4230aa861 100644
--- a/client.go
+++ b/client.go
@@ -45,14 +45,6 @@ type Client struct {
 	simplestreams   *simplestreams.SimpleStreams
 }
 
-type ResponseType string
-
-const (
-	Sync  ResponseType = "sync"
-	Async ResponseType = "async"
-	Error ResponseType = "error"
-)
-
 var (
 	// LXDErrors are special errors; the client library hoists error codes
 	// to these errors internally so that user code can compare against
@@ -64,52 +56,17 @@ var (
 	}
 )
 
-type Response struct {
-	Type ResponseType `json:"type"`
-
-	/* Valid only for Sync responses */
-	Status     string `json:"status"`
-	StatusCode int    `json:"status_code"`
-
-	/* Valid only for Async responses */
-	Operation string `json:"operation"`
-
-	/* Valid only for Error responses */
-	Code  int    `json:"error_code"`
-	Error string `json:"error"`
-
-	/* Valid for Sync and Error responses */
-	Metadata json.RawMessage `json:"metadata"`
-}
-
-func (r *Response) MetadataAsMap() (*shared.Jmap, error) {
-	ret := shared.Jmap{}
-	if err := json.Unmarshal(r.Metadata, &ret); err != nil {
-		return nil, err
-	}
-	return &ret, nil
-}
-
-func (r *Response) MetadataAsOperation() (*api.Operation, error) {
-	op := api.Operation{}
-	if err := json.Unmarshal(r.Metadata, &op); err != nil {
-		return nil, err
-	}
-
-	return &op, nil
-}
-
 // ParseResponse parses a lxd style response out of an http.Response. Note that
 // this does _not_ automatically convert error responses to golang errors. To
 // do that, use ParseError. Internal client library uses should probably use
 // HoistResponse, unless they are interested in accessing the underlying Error
 // response (e.g. to inspect the error code).
-func ParseResponse(r *http.Response) (*Response, error) {
+func ParseResponse(r *http.Response) (*api.Response, error) {
 	if r == nil {
 		return nil, fmt.Errorf("no response!")
 	}
 	defer r.Body.Close()
-	ret := Response{}
+	ret := api.Response{}
 
 	s, err := ioutil.ReadAll(r.Body)
 	if err != nil {
@@ -126,13 +83,13 @@ func ParseResponse(r *http.Response) (*Response, error) {
 
 // HoistResponse hoists a regular http response into a response of type rtype
 // or returns a golang error.
-func HoistResponse(r *http.Response, rtype ResponseType) (*Response, error) {
+func HoistResponse(r *http.Response, rtype api.ResponseType) (*api.Response, error) {
 	resp, err := ParseResponse(r)
 	if err != nil {
 		return nil, err
 	}
 
-	if resp.Type == Error {
+	if resp.Type == api.ErrorResponse {
 		// Try and use a known error if we have one for this code.
 		err, ok := LXDErrors[resp.Code]
 		if !ok {
@@ -371,13 +328,13 @@ func (c *Client) Addresses() ([]string, error) {
 	return addresses, nil
 }
 
-func (c *Client) get(base string) (*Response, error) {
+func (c *Client) get(base string) (*api.Response, error) {
 	uri := c.url(version.APIVersion, base)
 
 	return c.baseGet(uri)
 }
 
-func (c *Client) baseGet(getUrl string) (*Response, error) {
+func (c *Client) baseGet(getUrl string) (*api.Response, error) {
 	req, err := http.NewRequest("GET", getUrl, nil)
 	if err != nil {
 		return nil, err
@@ -390,10 +347,10 @@ func (c *Client) baseGet(getUrl string) (*Response, error) {
 		return nil, err
 	}
 
-	return HoistResponse(resp, Sync)
+	return HoistResponse(resp, api.SyncResponse)
 }
 
-func (c *Client) doUpdateMethod(method string, base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) doUpdateMethod(method string, base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	uri := c.url(version.APIVersion, base)
 
 	buf := bytes.Buffer{}
@@ -419,19 +376,19 @@ func (c *Client) doUpdateMethod(method string, base string, args interface{}, rt
 	return HoistResponse(resp, rtype)
 }
 
-func (c *Client) put(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) put(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	return c.doUpdateMethod("PUT", base, args, rtype)
 }
 
-func (c *Client) patch(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) patch(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	return c.doUpdateMethod("PATCH", base, args, rtype)
 }
 
-func (c *Client) post(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) post(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	return c.doUpdateMethod("POST", base, args, rtype)
 }
 
-func (c *Client) delete(base string, args interface{}, rtype ResponseType) (*Response, error) {
+func (c *Client) delete(base string, args interface{}, rtype api.ResponseType) (*api.Response, error) {
 	return c.doUpdateMethod("DELETE", base, args, rtype)
 }
 
@@ -449,7 +406,7 @@ func (c *Client) getRaw(uri string) (*http.Response, error) {
 
 	// because it is raw data, we need to check for http status
 	if raw.StatusCode != 200 {
-		resp, err := HoistResponse(raw, Sync)
+		resp, err := HoistResponse(raw, api.SyncResponse)
 		if err != nil {
 			return nil, err
 		}
@@ -496,7 +453,7 @@ func (c *Client) url(elem ...string) string {
 	return strings.TrimSuffix(uri, "/")
 }
 
-func (c *Client) GetServerConfig() (*Response, error) {
+func (c *Client) GetServerConfig() (*api.Response, error) {
 	if c.Remote.Protocol == "simplestreams" {
 		return nil, fmt.Errorf("This function isn't supported by simplestreams remote.")
 	}
@@ -553,12 +510,12 @@ func (c *Client) AmTrusted() bool {
 
 	shared.LogDebugf("%s", resp)
 
-	jmap, err := resp.MetadataAsMap()
+	meta, err := resp.MetadataAsMap()
 	if err != nil {
 		return false
 	}
 
-	auth, err := jmap.GetString("auth")
+	auth, err := shared.Jmap(meta).GetString("auth")
 	if err != nil {
 		return false
 	}
@@ -574,12 +531,12 @@ func (c *Client) IsPublic() bool {
 
 	shared.LogDebugf("%s", resp)
 
-	jmap, err := resp.MetadataAsMap()
+	meta, err := resp.MetadataAsMap()
 	if err != nil {
 		return false
 	}
 
-	public, err := jmap.GetBool("public")
+	public, err := shared.Jmap(meta).GetBool("public")
 	if err != nil {
 		return false
 	}
@@ -599,7 +556,7 @@ func (c *Client) ListContainers() ([]api.Container, error) {
 
 	var result []api.Container
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -628,7 +585,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 	if c.Remote.Protocol != "simplestreams" && !info.Public {
 		var secret string
 
-		resp, err := c.post("images/"+image+"/secret", nil, Async)
+		resp, err := c.post("images/"+image+"/secret", nil, api.AsyncResponse)
 		if err != nil {
 			return err
 		}
@@ -695,7 +652,7 @@ func (c *Client) CopyImage(image string, dest *Client, copy_aliases bool, aliase
 		source["server"] = sourceUrl
 		body := shared.Jmap{"public": public, "auto_update": autoUpdate, "source": source}
 
-		resp, err := dest.post("images", body, Async)
+		resp, err := dest.post("images", body, api.AsyncResponse)
 		if err != nil {
 			continue
 		}
@@ -919,7 +876,7 @@ func (c *Client) PostImageURL(imageFile string, properties []string, public bool
 		go c.Monitor([]string{"operation"}, handler, nil)
 	}
 
-	resp, err := c.post("images", body, Async)
+	resp, err := c.post("images", body, api.AsyncResponse)
 	if err != nil {
 		return "", err
 	}
@@ -1096,7 +1053,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 		return "", err
 	}
 
-	resp, err := HoistResponse(raw, Async)
+	resp, err := HoistResponse(raw, api.AsyncResponse)
 	if err != nil {
 		return "", err
 	}
@@ -1134,7 +1091,7 @@ func (c *Client) GetImageInfo(image string) (*api.Image, error) {
 	}
 
 	info := api.Image{}
-	if err := json.Unmarshal(resp.Metadata, &info); err != nil {
+	if err := resp.MetadataAsStruct(&info); err != nil {
 		return nil, err
 	}
 
@@ -1146,7 +1103,7 @@ func (c *Client) PutImageInfo(name string, p api.ImagePut) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.put(fmt.Sprintf("images/%s", name), p, Sync)
+	_, err := c.put(fmt.Sprintf("images/%s", name), p, api.SyncResponse)
 	return err
 }
 
@@ -1161,7 +1118,7 @@ func (c *Client) ListImages() ([]api.Image, error) {
 	}
 
 	var result []api.Image
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -1173,7 +1130,7 @@ func (c *Client) DeleteImage(image string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	resp, err := c.delete(fmt.Sprintf("images/%s", image), nil, Async)
+	resp, err := c.delete(fmt.Sprintf("images/%s", image), nil, api.AsyncResponse)
 
 	if err != nil {
 		return err
@@ -1189,7 +1146,7 @@ func (c *Client) PostAlias(alias string, desc string, target string) error {
 
 	body := shared.Jmap{"description": desc, "target": target, "name": alias}
 
-	_, err := c.post("images/aliases", body, Sync)
+	_, err := c.post("images/aliases", body, api.SyncResponse)
 	return err
 }
 
@@ -1198,7 +1155,7 @@ func (c *Client) DeleteAlias(alias string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.delete(fmt.Sprintf("images/aliases/%s", alias), nil, Sync)
+	_, err := c.delete(fmt.Sprintf("images/aliases/%s", alias), nil, api.SyncResponse)
 	return err
 }
 
@@ -1214,7 +1171,7 @@ func (c *Client) ListAliases() ([]api.ImageAliasesEntry, error) {
 
 	var result []api.ImageAliasesEntry
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -1232,7 +1189,7 @@ func (c *Client) CertificateList() ([]api.Certificate, error) {
 	}
 
 	var result []api.Certificate
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -1246,7 +1203,7 @@ func (c *Client) AddMyCertToServer(pwd string) error {
 
 	body := shared.Jmap{"type": "client", "password": pwd}
 
-	_, err := c.post("certificates", body, Sync)
+	_, err := c.post("certificates", body, api.SyncResponse)
 	return err
 }
 
@@ -1256,7 +1213,7 @@ func (c *Client) CertificateAdd(cert *x509.Certificate, name string) error {
 	}
 
 	b64 := base64.StdEncoding.EncodeToString(cert.Raw)
-	_, err := c.post("certificates", shared.Jmap{"type": "client", "certificate": b64, "name": name}, Sync)
+	_, err := c.post("certificates", shared.Jmap{"type": "client", "certificate": b64, "name": name}, api.SyncResponse)
 	return err
 }
 
@@ -1265,7 +1222,7 @@ func (c *Client) CertificateRemove(fingerprint string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.delete(fmt.Sprintf("certificates/%s", fingerprint), nil, Sync)
+	_, err := c.delete(fmt.Sprintf("certificates/%s", fingerprint), nil, api.SyncResponse)
 	return err
 }
 
@@ -1291,12 +1248,12 @@ func (c *Client) GetAlias(alias string) string {
 		return ""
 	}
 
-	if resp.Type == Error {
+	if resp.Type == api.ErrorResponse {
 		return ""
 	}
 
 	var result api.ImageAliasesEntry
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return ""
 	}
 	return result.Target
@@ -1304,7 +1261,7 @@ func (c *Client) GetAlias(alias string) string {
 
 // Init creates a container from either a fingerprint or an alias; you must
 // provide at least one.
-func (c *Client) Init(name string, imgremote string, image string, profiles *[]string, config map[string]string, ephem bool) (*Response, error) {
+func (c *Client) Init(name string, imgremote string, image string, profiles *[]string, config map[string]string, ephem bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1352,7 +1309,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 
 				image = target
 
-				resp, err := tmpremote.post("images/"+image+"/secret", nil, Async)
+				resp, err := tmpremote.post("images/"+image+"/secret", nil, api.AsyncResponse)
 				if err != nil {
 					return nil, err
 				}
@@ -1410,7 +1367,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 		body["ephemeral"] = ephem
 	}
 
-	var resp *Response
+	var resp *api.Response
 
 	if imgremote != c.Name {
 		var addresses []string
@@ -1422,7 +1379,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 		for _, addr := range addresses {
 			body["source"].(shared.Jmap)["server"] = "https://" + addr
 
-			resp, err = c.post("containers", body, Async)
+			resp, err = c.post("containers", body, api.AsyncResponse)
 			if err != nil {
 				continue
 			}
@@ -1430,7 +1387,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 			break
 		}
 	} else {
-		resp, err = c.post("containers", body, Async)
+		resp, err = c.post("containers", body, api.AsyncResponse)
 	}
 
 	if err != nil {
@@ -1443,7 +1400,7 @@ func (c *Client) Init(name string, imgremote string, image string, profiles *[]s
 	return resp, nil
 }
 
-func (c *Client) LocalCopy(source string, name string, config map[string]string, profiles []string, ephemeral bool) (*Response, error) {
+func (c *Client) LocalCopy(source string, name string, config map[string]string, profiles []string, ephemeral bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1459,7 +1416,7 @@ func (c *Client) LocalCopy(source string, name string, config map[string]string,
 		"ephemeral": ephemeral,
 	}
 
-	return c.post("containers", body, Async)
+	return c.post("containers", body, api.AsyncResponse)
 }
 
 func (c *Client) Monitor(types []string, handler func(interface{}), done chan bool) error {
@@ -1538,7 +1495,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 		body["height"] = height
 	}
 
-	resp, err := c.post(fmt.Sprintf("containers/%s/exec", name), body, Async)
+	resp, err := c.post(fmt.Sprintf("containers/%s/exec", name), body, api.AsyncResponse)
 	if err != nil {
 		return -1, err
 	}
@@ -1640,7 +1597,7 @@ func (c *Client) Exec(name string, cmd []string, env map[string]string,
 	return shared.Jmap(op.Metadata).GetInt("return")
 }
 
-func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*Response, error) {
+func (c *Client) Action(name string, action shared.ContainerAction, timeout int, force bool, stateful bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1654,10 +1611,10 @@ func (c *Client) Action(name string, action shared.ContainerAction, timeout int,
 		body["stateful"] = stateful
 	}
 
-	return c.put(fmt.Sprintf("containers/%s/state", name), body, Async)
+	return c.put(fmt.Sprintf("containers/%s/state", name), body, api.AsyncResponse)
 }
 
-func (c *Client) Delete(name string) (*Response, error) {
+func (c *Client) Delete(name string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1670,7 +1627,7 @@ func (c *Client) Delete(name string) (*Response, error) {
 		url = fmt.Sprintf("containers/%s", name)
 	}
 
-	return c.delete(url, nil, Async)
+	return c.delete(url, nil, api.AsyncResponse)
 }
 
 func (c *Client) ServerStatus() (*api.Server, error) {
@@ -1681,7 +1638,7 @@ func (c *Client) ServerStatus() (*api.Server, error) {
 		return nil, err
 	}
 
-	if err := json.Unmarshal(resp.Metadata, &ss); err != nil {
+	if err := resp.MetadataAsStruct(&ss); err != nil {
 		return nil, err
 	}
 
@@ -1709,7 +1666,7 @@ func (c *Client) ContainerInfo(name string) (*api.Container, error) {
 		return nil, err
 	}
 
-	if err := json.Unmarshal(resp.Metadata, &ct); err != nil {
+	if err := resp.MetadataAsStruct(&ct); err != nil {
 		return nil, err
 	}
 
@@ -1728,7 +1685,7 @@ func (c *Client) ContainerState(name string) (*api.ContainerState, error) {
 		return nil, err
 	}
 
-	if err := json.Unmarshal(resp.Metadata, &ct); err != nil {
+	if err := resp.MetadataAsStruct(&ct); err != nil {
 		return nil, err
 	}
 
@@ -1761,7 +1718,7 @@ func (c *Client) ProfileConfig(name string) (*api.Profile, error) {
 		return nil, err
 	}
 
-	if err := json.Unmarshal(resp.Metadata, &ct); err != nil {
+	if err := resp.MetadataAsStruct(&ct); err != nil {
 		return nil, err
 	}
 
@@ -1797,7 +1754,7 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode str
 		return err
 	}
 
-	_, err = HoistResponse(raw, Sync)
+	_, err = HoistResponse(raw, api.SyncResponse)
 	return err
 }
 
@@ -1819,7 +1776,7 @@ func (c *Client) PullFile(container string, p string) (int, int, int, io.ReadClo
 	return uid, gid, mode, r.Body, nil
 }
 
-func (c *Client) GetMigrationSourceWS(container string) (*Response, error) {
+func (c *Client) GetMigrationSourceWS(container string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1835,10 +1792,10 @@ func (c *Client) GetMigrationSourceWS(container string) (*Response, error) {
 		url = fmt.Sprintf("containers/%s/snapshots/%s", pieces[0], pieces[1])
 	}
 
-	return c.post(url, body, Async)
+	return c.post(url, body, api.AsyncResponse)
 }
 
-func (c *Client) MigrateFrom(name string, operation string, certificate string, secrets map[string]string, architecture string, config map[string]string, devices map[string]map[string]string, profiles []string, baseImage string, ephemeral bool) (*Response, error) {
+func (c *Client) MigrateFrom(name string, operation string, certificate string, secrets map[string]string, architecture string, config map[string]string, devices map[string]map[string]string, profiles []string, baseImage string, ephemeral bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1861,10 +1818,10 @@ func (c *Client) MigrateFrom(name string, operation string, certificate string,
 		"source":       source,
 	}
 
-	return c.post("containers", body, Async)
+	return c.post("containers", body, api.AsyncResponse)
 }
 
-func (c *Client) Rename(name string, newName string) (*Response, error) {
+func (c *Client) Rename(name string, newName string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -1876,13 +1833,13 @@ func (c *Client) Rename(name string, newName string) (*Response, error) {
 	}
 	if len(oldNameParts) == 1 {
 		body := shared.Jmap{"name": newName}
-		return c.post(fmt.Sprintf("containers/%s", name), body, Async)
+		return c.post(fmt.Sprintf("containers/%s", name), body, api.AsyncResponse)
 	}
 	if oldNameParts[0] != newNameParts[0] {
 		return nil, fmt.Errorf("Attempting to rename snapshot of one container into a snapshot of another container.")
 	}
 	body := shared.Jmap{"name": newNameParts[1]}
-	return c.post(fmt.Sprintf("containers/%s/snapshots/%s", oldNameParts[0], oldNameParts[1]), body, Async)
+	return c.post(fmt.Sprintf("containers/%s/snapshots/%s", oldNameParts[0], oldNameParts[1]), body, api.AsyncResponse)
 }
 
 /* Wait for an operation */
@@ -1931,22 +1888,22 @@ func (c *Client) WaitForSuccessOp(waitURL string) (*api.Operation, error) {
 	return op, fmt.Errorf(op.Err)
 }
 
-func (c *Client) RestoreSnapshot(container string, snapshotName string, stateful bool) (*Response, error) {
+func (c *Client) RestoreSnapshot(container string, snapshotName string, stateful bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
 	body := shared.Jmap{"restore": snapshotName, "stateful": stateful}
-	return c.put(fmt.Sprintf("containers/%s", container), body, Async)
+	return c.put(fmt.Sprintf("containers/%s", container), body, api.AsyncResponse)
 }
 
-func (c *Client) Snapshot(container string, snapshotName string, stateful bool) (*Response, error) {
+func (c *Client) Snapshot(container string, snapshotName string, stateful bool) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
 	body := shared.Jmap{"name": snapshotName, "stateful": stateful}
-	return c.post(fmt.Sprintf("containers/%s/snapshots", container), body, Async)
+	return c.post(fmt.Sprintf("containers/%s/snapshots", container), body, api.AsyncResponse)
 }
 
 func (c *Client) ListSnapshots(container string) ([]api.ContainerSnapshot, error) {
@@ -1962,7 +1919,7 @@ func (c *Client) ListSnapshots(container string) ([]api.ContainerSnapshot, error
 
 	var result []api.ContainerSnapshot
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -1987,7 +1944,7 @@ func (c *Client) SnapshotInfo(snapName string) (*api.ContainerSnapshot, error) {
 
 	var result api.ContainerSnapshot
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -2017,7 +1974,7 @@ func (c *Client) GetServerConfigString() ([]string, error) {
 	return resp, nil
 }
 
-func (c *Client) SetServerConfig(key string, value string) (*Response, error) {
+func (c *Client) SetServerConfig(key string, value string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2029,15 +1986,15 @@ func (c *Client) SetServerConfig(key string, value string) (*Response, error) {
 
 	ss.Config[key] = value
 
-	return c.put("", ss, Sync)
+	return c.put("", ss, api.SyncResponse)
 }
 
-func (c *Client) UpdateServerConfig(ss api.ServerPut) (*Response, error) {
+func (c *Client) UpdateServerConfig(ss api.ServerPut) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	return c.put("", ss, Sync)
+	return c.put("", ss, api.SyncResponse)
 }
 
 /*
@@ -2088,7 +2045,7 @@ func (c *Client) SetContainerConfig(container, key, value string) error {
 	 * snapshot), we expect config to be a sync operation, so let's just
 	 * handle it here.
 	 */
-	resp, err := c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	resp, err := c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 	if err != nil {
 		return err
 	}
@@ -2101,7 +2058,7 @@ func (c *Client) UpdateContainerConfig(container string, st api.ContainerPut) er
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	resp, err := c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	resp, err := c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 	if err != nil {
 		return err
 	}
@@ -2116,7 +2073,7 @@ func (c *Client) ProfileCreate(p string) error {
 
 	body := shared.Jmap{"name": p}
 
-	_, err := c.post("profiles", body, Sync)
+	_, err := c.post("profiles", body, api.SyncResponse)
 	return err
 }
 
@@ -2125,7 +2082,7 @@ func (c *Client) ProfileDelete(p string) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.delete(fmt.Sprintf("profiles/%s", p), nil, Sync)
+	_, err := c.delete(fmt.Sprintf("profiles/%s", p), nil, api.SyncResponse)
 	return err
 }
 
@@ -2159,7 +2116,7 @@ func (c *Client) SetProfileConfigItem(profile, key, value string) error {
 		st.Config[key] = value
 	}
 
-	_, err = c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
+	_, err = c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse)
 	return err
 }
 
@@ -2168,7 +2125,7 @@ func (c *Client) PutProfile(name string, profile api.ProfilePut) error {
 		return fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	_, err := c.put(fmt.Sprintf("profiles/%s", name), profile, Sync)
+	_, err := c.put(fmt.Sprintf("profiles/%s", name), profile, api.SyncResponse)
 	return err
 }
 
@@ -2184,7 +2141,7 @@ func (c *Client) ListProfiles() ([]string, error) {
 
 	var result []string
 
-	if err := json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err := resp.MetadataAsStruct(&result); err != nil {
 		return nil, err
 	}
 
@@ -2213,7 +2170,7 @@ func (c *Client) ListProfiles() ([]string, error) {
 	return names, nil
 }
 
-func (c *Client) ApplyProfile(container, profile string) (*Response, error) {
+func (c *Client) ApplyProfile(container, profile string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2225,10 +2182,10 @@ func (c *Client) ApplyProfile(container, profile string) (*Response, error) {
 
 	st.Profiles = strings.Split(profile, ",")
 
-	return c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 }
 
-func (c *Client) ContainerDeviceDelete(container, devname string) (*Response, error) {
+func (c *Client) ContainerDeviceDelete(container, devname string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2241,14 +2198,14 @@ func (c *Client) ContainerDeviceDelete(container, devname string) (*Response, er
 	for n, _ := range st.Devices {
 		if n == devname {
 			delete(st.Devices, n)
-			return c.put(fmt.Sprintf("containers/%s", container), st, Async)
+			return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 		}
 	}
 
 	return nil, fmt.Errorf("Device doesn't exist.")
 }
 
-func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []string) (*Response, error) {
+func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2280,7 +2237,7 @@ func (c *Client) ContainerDeviceAdd(container, devname, devtype string, props []
 
 	st.Devices[devname] = newdev
 
-	return c.put(fmt.Sprintf("containers/%s", container), st, Async)
+	return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
 }
 
 func (c *Client) ContainerListDevices(container string) ([]string, error) {
@@ -2299,7 +2256,7 @@ func (c *Client) ContainerListDevices(container string) ([]string, error) {
 	return devs, nil
 }
 
-func (c *Client) ProfileDeviceDelete(profile, devname string) (*Response, error) {
+func (c *Client) ProfileDeviceDelete(profile, devname string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2312,14 +2269,14 @@ func (c *Client) ProfileDeviceDelete(profile, devname string) (*Response, error)
 	for n, _ := range st.Devices {
 		if n == devname {
 			delete(st.Devices, n)
-			return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
+			return c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse)
 		}
 	}
 
 	return nil, fmt.Errorf("Device doesn't exist.")
 }
 
-func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []string) (*Response, error) {
+func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []string) (*api.Response, error) {
 	if c.Remote.Public {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
@@ -2351,7 +2308,7 @@ func (c *Client) ProfileDeviceAdd(profile, devname, devtype string, props []stri
 
 	st.Devices[devname] = newdev
 
-	return c.put(fmt.Sprintf("profiles/%s", profile), st, Sync)
+	return c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse)
 }
 
 func (c *Client) ProfileListDevices(profile string) ([]string, error) {
@@ -2375,7 +2332,7 @@ func (c *Client) ProfileListDevices(profile string) ([]string, error) {
 func WebsocketDial(dialer websocket.Dialer, url string) (*websocket.Conn, error) {
 	conn, raw, err := dialer.Dial(url, http.Header{})
 	if err != nil {
-		_, err2 := HoistResponse(raw, Error)
+		_, err2 := HoistResponse(raw, api.ErrorResponse)
 		if err2 != nil {
 			/* The response isn't one we understand, so return
 			 * whatever the original error was. */
@@ -2398,11 +2355,11 @@ func (c *Client) ProfileCopy(name, newname string, dest *Client) error {
 	}
 
 	body := shared.Jmap{"config": st.Config, "name": newname, "devices": st.Devices}
-	_, err = dest.post("profiles", body, Sync)
+	_, err = dest.post("profiles", body, api.SyncResponse)
 	return err
 }
 
-func (c *Client) AsyncWaitMeta(resp *Response) (map[string]interface{}, error) {
+func (c *Client) AsyncWaitMeta(resp *api.Response) (map[string]interface{}, error) {
 	op, err := c.WaitFor(resp.Operation)
 	if err != nil {
 		return nil, err
@@ -2430,7 +2387,7 @@ func (c *Client) ImageFromContainer(cname string, public bool, aliases []string,
 	}
 	body := shared.Jmap{"public": public, "source": source, "properties": properties}
 
-	resp, err := c.post("images", body, Async)
+	resp, err := c.post("images", body, api.AsyncResponse)
 	if err != nil {
 		return "", err
 	}
diff --git a/lxc/action.go b/lxc/action.go
index ab66cfe16..238cad92f 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -92,7 +92,7 @@ func (c *actionCmd) run(config *lxd.Config, args []string) error {
 			return err
 		}
 
-		if resp.Type != lxd.Async {
+		if resp.Type != api.AsyncResponse {
 			return fmt.Errorf(i18n.G("bad result type from action"))
 		}
 
diff --git a/lxc/config.go b/lxc/config.go
index 0f2ad48eb..3314ccf16 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -633,7 +633,7 @@ func (c *configCmd) deviceAdd(config *lxd.Config, which string, args []string) e
 		props = []string{}
 	}
 
-	var resp *lxd.Response
+	var resp *api.Response
 	if which == "profile" {
 		resp, err = client.ProfileDeviceAdd(name, devname, devtype, props)
 	} else {
@@ -820,7 +820,7 @@ func (c *configCmd) deviceRm(config *lxd.Config, which string, args []string) er
 	}
 
 	devname := args[3]
-	var resp *lxd.Response
+	var resp *api.Response
 	if which == "profile" {
 		resp, err = client.ProfileDeviceDelete(name, devname)
 	} else {
diff --git a/lxc/copy.go b/lxc/copy.go
index 14821f0ca..da28c73d8 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -165,7 +166,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 	 * report that.
 	 */
 	for _, addr := range addresses {
-		var migration *lxd.Response
+		var migration *api.Response
 
 		sourceWSUrl := "https://" + addr + sourceWSResponse.Operation
 		migration, err = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1)
diff --git a/lxc/init.go b/lxc/init.go
index ac0837522..bcc3713ab 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -171,7 +171,7 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 		profiles = append(profiles, p)
 	}
 
-	var resp *lxd.Response
+	var resp *api.Response
 	if name == "" {
 		fmt.Printf(i18n.G("Creating the container") + "\n")
 	} else {
diff --git a/lxc/launch.go b/lxc/launch.go
index fbc455512..05532e9bb 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -62,7 +63,7 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 	 * initRequestedEmptyProfiles means user requested empty
 	 * !initRequestedEmptyProfiles but len(profArgs) == 0 means use profile default
 	 */
-	var resp *lxd.Response
+	var resp *api.Response
 	profiles := []string{}
 	for _, p := range c.init.profArgs {
 		profiles = append(profiles, p)
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 46c9841e8..9cad310e1 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -31,6 +31,7 @@ import (
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
@@ -142,7 +143,7 @@ func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
 	return &myhttp, nil
 }
 
-func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, error) {
+func (d *Daemon) httpGetSync(url string, certificate string) (*api.Response, error) {
 	var err error
 
 	req, err := http.NewRequest("GET", url, nil)
@@ -167,7 +168,7 @@ func (d *Daemon) httpGetSync(url string, certificate string) (*lxd.Response, err
 		return nil, err
 	}
 
-	if resp.Type != lxd.Sync {
+	if resp.Type != api.SyncResponse {
 		return nil, fmt.Errorf("unexpected non-sync response")
 	}
 
@@ -195,7 +196,7 @@ func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, er
 	}
 
 	if raw.StatusCode != 200 {
-		_, err := lxd.HoistResponse(raw, lxd.Error)
+		_, err := lxd.HoistResponse(raw, api.ErrorResponse)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 6a8c7ee1c..3998d7b2c 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -292,7 +291,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			return "", err
 		}
 
-		if err := json.Unmarshal(resp.Metadata, &info); err != nil {
+		if err := resp.MetadataAsStruct(&info); err != nil {
 			return "", err
 		}
 
diff --git a/lxd/main_callhook.go b/lxd/main_callhook.go
index b8e273702..31e2231de 100644
--- a/lxd/main_callhook.go
+++ b/lxd/main_callhook.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdCallHook(args []string) error {
@@ -52,7 +53,7 @@ func cmdCallHook(args []string) error {
 			return
 		}
 
-		_, err = lxd.HoistResponse(raw, lxd.Sync)
+		_, err = lxd.HoistResponse(raw, api.SyncResponse)
 		if err != nil {
 			hook <- err
 			return
diff --git a/lxd/main_ready.go b/lxd/main_ready.go
index 4e4ebfbd1..109323412 100644
--- a/lxd/main_ready.go
+++ b/lxd/main_ready.go
@@ -4,6 +4,7 @@ import (
 	"net/http"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdReady() error {
@@ -22,7 +23,7 @@ func cmdReady() error {
 		return err
 	}
 
-	_, err = lxd.HoistResponse(raw, lxd.Sync)
+	_, err = lxd.HoistResponse(raw, api.SyncResponse)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/main_waitready.go b/lxd/main_waitready.go
index 7e2b4cfd4..74534b3e6 100644
--- a/lxd/main_waitready.go
+++ b/lxd/main_waitready.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdWaitReady() error {
@@ -38,7 +39,7 @@ func cmdWaitReady() error {
 				continue
 			}
 
-			_, err = lxd.HoistResponse(raw, lxd.Sync)
+			_, err = lxd.HoistResponse(raw, api.SyncResponse)
 			if err != nil {
 				time.Sleep(500 * time.Millisecond)
 				continue
diff --git a/lxd/remote.go b/lxd/remote.go
index 0a424835c..da8bfc4ca 100644
--- a/lxd/remote.go
+++ b/lxd/remote.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"encoding/json"
 	"fmt"
 
 	"github.com/lxc/lxd/shared/api"
@@ -19,7 +18,7 @@ func remoteGetImageFingerprint(d *Daemon, server string, certificate string, ali
 	}
 
 	var result api.ImageAliasesEntry
-	if err = json.Unmarshal(resp.Metadata, &result); err != nil {
+	if err = resp.MetadataAsStruct(&result); err != nil {
 		return "", fmt.Errorf("Error reading alias")
 	}
 
diff --git a/lxd/response.go b/lxd/response.go
index cc42c14ce..e238a1d98 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -13,26 +13,10 @@ import (
 
 	"github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
 
-type syncResp struct {
-	Type       lxd.ResponseType `json:"type"`
-	Status     string           `json:"status"`
-	StatusCode api.StatusCode   `json:"status_code"`
-	Metadata   interface{}      `json:"metadata"`
-}
-
-type asyncResp struct {
-	Type       lxd.ResponseType `json:"type"`
-	Status     string           `json:"status"`
-	StatusCode api.StatusCode   `json:"status_code"`
-	Metadata   interface{}      `json:"metadata"`
-	Operation  string           `json:"operation"`
-}
-
 type Response interface {
 	Render(w http.ResponseWriter) error
 	String() string
@@ -56,7 +40,14 @@ func (r *syncResponse) Render(w http.ResponseWriter) error {
 		w.WriteHeader(201)
 	}
 
-	resp := syncResp{Type: lxd.Sync, Status: status.String(), StatusCode: status, Metadata: r.metadata}
+	resp := api.ResponseRaw{
+		Response: api.Response{
+			Type:       api.SyncResponse,
+			Status:     status.String(),
+			StatusCode: int(status)},
+		Metadata: r.metadata,
+	}
+
 	return WriteJSON(w, resp)
 }
 
@@ -207,12 +198,15 @@ func (r *operationResponse) Render(w http.ResponseWriter) error {
 		return err
 	}
 
-	body := asyncResp{
-		Type:       lxd.Async,
-		Status:     api.OperationCreated.String(),
-		StatusCode: api.OperationCreated,
-		Operation:  url,
-		Metadata:   md}
+	body := api.ResponseRaw{
+		Response: api.Response{
+			Type:       api.AsyncResponse,
+			Status:     api.OperationCreated.String(),
+			StatusCode: int(api.OperationCreated),
+			Operation:  url,
+		},
+		Metadata: md,
+	}
 
 	w.Header().Set("Location", url)
 	w.WriteHeader(202)
@@ -254,7 +248,7 @@ func (r *errorResponse) Render(w http.ResponseWriter) error {
 		output = io.MultiWriter(buf, captured)
 	}
 
-	err := json.NewEncoder(output).Encode(shared.Jmap{"type": lxd.Error, "error": r.msg, "error_code": r.code})
+	err := json.NewEncoder(output).Encode(shared.Jmap{"type": api.ErrorResponse, "error": r.msg, "error_code": r.code})
 
 	if err != nil {
 		return err
diff --git a/shared/api/response.go b/shared/api/response.go
new file mode 100644
index 000000000..3e25e7479
--- /dev/null
+++ b/shared/api/response.go
@@ -0,0 +1,83 @@
+package api
+
+import (
+	"encoding/json"
+)
+
+// ResponseRaw represents a LXD operation in its original form
+type ResponseRaw struct {
+	Response
+
+	Metadata interface{} `json:"metadata"`
+}
+
+// Response represents a LXD operation
+type Response struct {
+	Type ResponseType `json:"type"`
+
+	/* Valid only for Sync responses */
+	Status     string `json:"status"`
+	StatusCode int    `json:"status_code"`
+
+	/* Valid only for Async responses */
+	Operation string `json:"operation"`
+
+	/* Valid only for Error responses */
+	Code  int    `json:"error_code"`
+	Error string `json:"error"`
+
+	/* Valid for Sync and Error responses */
+	Metadata json.RawMessage `json:"metadata"`
+}
+
+// MetadataAsMap parses the Response metadata into a map
+func (r *Response) MetadataAsMap() (map[string]interface{}, error) {
+	ret := map[string]interface{}{}
+	err := r.MetadataAsStruct(&ret)
+	if err != nil {
+		return nil, err
+	}
+
+	return ret, nil
+}
+
+// MetadataAsOperation turns the Response metadata into an Operation
+func (r *Response) MetadataAsOperation() (*Operation, error) {
+	op := Operation{}
+	err := r.MetadataAsStruct(&op)
+	if err != nil {
+		return nil, err
+	}
+
+	return &op, nil
+}
+
+// MetadataAsStringSlice parses the Response metadata into a slice of string
+func (r *Response) MetadataAsStringSlice() ([]string, error) {
+	sl := []string{}
+	err := r.MetadataAsStruct(&sl)
+	if err != nil {
+		return nil, err
+	}
+
+	return sl, nil
+}
+
+// MetadataAsStruct parses the Response metadata into a provided struct
+func (r *Response) MetadataAsStruct(target interface{}) error {
+	if err := json.Unmarshal(r.Metadata, &target); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ResponseType represents a valid LXD response type
+type ResponseType string
+
+// LXD response types
+const (
+	SyncResponse  ResponseType = "sync"
+	AsyncResponse ResponseType = "async"
+	ErrorResponse ResponseType = "error"
+)

From 76f63f5399215b5d65fbb51e18544e3c45657905 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 22 Dec 2016 18:15:16 -0500
Subject: [PATCH 0629/1193] shared: Move REST API to new package: godoc
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/doc.go | 13 +++++++++++++
 1 file changed, 13 insertions(+)
 create mode 100644 shared/api/doc.go

diff --git a/shared/api/doc.go b/shared/api/doc.go
new file mode 100644
index 000000000..f7400524d
--- /dev/null
+++ b/shared/api/doc.go
@@ -0,0 +1,13 @@
+// Package api contains Go structs for all LXD API objects
+//
+// Overview
+//
+// This package has Go structs for every API object, all the various
+// structs are named after the object they represent and some variations of
+// those structs exist for initial object creation, object update and
+// object retrieval.
+//
+// A few convenience functions are also tied to those structs which let
+// you convert between the various strucs for a given object and also query
+// some of the more complex metadata that LXD can export.
+package api

From 4ba76e78f4ab0ad78c9796e516d9bd97db5a4082 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 23 Dec 2016 13:18:53 -0500
Subject: [PATCH 0630/1193] Fix yaml rendering of composite structs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/container.go | 4 ++--
 shared/api/image.go     | 8 ++++----
 shared/api/profile.go   | 4 ++--
 shared/api/response.go  | 2 +-
 shared/api/server.go    | 4 ++--
 5 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/shared/api/container.go b/shared/api/container.go
index e11afbcfb..d64445d3f 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -6,7 +6,7 @@ import (
 
 // ContainersPost represents the fields available for a new LXD container
 type ContainersPost struct {
-	ContainerPut
+	ContainerPut `yaml:",inline"`
 
 	Name   string          `json:"name"`
 	Source ContainerSource `json:"source"`
@@ -30,7 +30,7 @@ type ContainerPut struct {
 
 // Container represents a LXD container
 type Container struct {
-	ContainerPut
+	ContainerPut `yaml:",inline"`
 
 	CreatedAt       time.Time                    `json:"created_at"`
 	ExpandedConfig  map[string]string            `json:"expanded_config"`
diff --git a/shared/api/image.go b/shared/api/image.go
index 408968b79..c4456a332 100644
--- a/shared/api/image.go
+++ b/shared/api/image.go
@@ -6,7 +6,7 @@ import (
 
 // ImagesPost represents the fields available for a new LXD image
 type ImagesPost struct {
-	ImagePut
+	ImagePut `yaml:",inline"`
 
 	Filename string            `json:"filename"`
 	Source   map[string]string `json:"source"`
@@ -21,7 +21,7 @@ type ImagePut struct {
 
 // Image represents a LXD image
 type Image struct {
-	ImagePut
+	ImagePut `yaml:",inline"`
 
 	Aliases      []ImageAlias `json:"aliases"`
 	Architecture string       `json:"architecture"`
@@ -58,7 +58,7 @@ type ImageSource struct {
 
 // ImageAliasesPost represents a new LXD image alias
 type ImageAliasesPost struct {
-	ImageAliasesEntry
+	ImageAliasesEntry `yaml:",inline"`
 }
 
 // ImageAliasesEntryPost represents the required fields to rename a LXD image alias
@@ -74,7 +74,7 @@ type ImageAliasesEntryPut struct {
 
 // ImageAliasesEntry represents a LXD image alias
 type ImageAliasesEntry struct {
-	ImageAliasesEntryPut
+	ImageAliasesEntryPut `yaml:",inline"`
 
 	Name string `json:"name"`
 }
diff --git a/shared/api/profile.go b/shared/api/profile.go
index efa4c7cc3..2ade83ea8 100644
--- a/shared/api/profile.go
+++ b/shared/api/profile.go
@@ -2,7 +2,7 @@ package api
 
 // ProfilesPost represents the fields of a new LXD profile
 type ProfilesPost struct {
-	ProfilePut
+	ProfilePut `yaml:",inline"`
 
 	Name string `json:"name"`
 }
@@ -21,7 +21,7 @@ type ProfilePut struct {
 
 // Profile represents a LXD profile
 type Profile struct {
-	ProfilePut
+	ProfilePut `yaml:",inline"`
 
 	Name string `json:"name"`
 }
diff --git a/shared/api/response.go b/shared/api/response.go
index 3e25e7479..846343f0a 100644
--- a/shared/api/response.go
+++ b/shared/api/response.go
@@ -6,7 +6,7 @@ import (
 
 // ResponseRaw represents a LXD operation in its original form
 type ResponseRaw struct {
-	Response
+	Response `yaml:",inline"`
 
 	Metadata interface{} `json:"metadata"`
 }
diff --git a/shared/api/server.go b/shared/api/server.go
index 952bba4b1..4790cea47 100644
--- a/shared/api/server.go
+++ b/shared/api/server.go
@@ -34,8 +34,8 @@ type ServerUntrusted struct {
 
 // Server represents a LXD server
 type Server struct {
-	ServerPut
-	ServerUntrusted
+	ServerPut       `yaml:",inline"`
+	ServerUntrusted `yaml:",inline"`
 
 	Environment ServerEnvironment `json:"environment"`
 }

From 6fb1dd5ea23942cf8369d917a856c9f338940558 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 31 Dec 2016 19:08:09 +0100
Subject: [PATCH 0631/1193] api: Hide container restore field in yaml
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/container.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/api/container.go b/shared/api/container.go
index d64445d3f..52491656a 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -25,7 +25,7 @@ type ContainerPut struct {
 	Devices      map[string]map[string]string `json:"devices"`
 	Ephemeral    bool                         `json:"ephemeral"`
 	Profiles     []string                     `json:"profiles"`
-	Restore      string                       `json:"restore,omitempty"`
+	Restore      string                       `json:"restore,omitempty" yaml:"restore,omitempty"`
 }
 
 // Container represents a LXD container

From 72e2ffafd8ceeabc4b80b3242a960bfbda42c1cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 3 Jan 2017 22:29:12 -0500
Subject: [PATCH 0632/1193] api: comment style
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/container.go | 8 ++++----
 shared/api/response.go  | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/shared/api/container.go b/shared/api/container.go
index 52491656a..3ef0949cd 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -63,7 +63,7 @@ type ContainerSource struct {
 	Type        string `json:"type"`
 	Certificate string `json:"certificate"`
 
-	/* For "image" type */
+	// For "image" type
 	Alias       string            `json:"alias,omitempty"`
 	Fingerprint string            `json:"fingerprint,omitempty"`
 	Properties  map[string]string `json:"properties,omitempty"`
@@ -71,14 +71,14 @@ type ContainerSource struct {
 	Secret      string            `json:"secret,omitempty"`
 	Protocol    string            `json:"protocol,omitempty"`
 
-	/* For "migration" and "copy" types */
+	// For "migration" and "copy" types
 	BaseImage string `json:"base-image,omitempty"`
 
-	/* For "migration" type */
+	// For "migration" type
 	Mode       string            `json:"mode,omitempty"`
 	Operation  string            `json:"operation,omitempty"`
 	Websockets map[string]string `json:"secrets,omitempty"`
 
-	/* For "copy" type */
+	// For "copy" type
 	Source string `json:"source,omitempty"`
 }
diff --git a/shared/api/response.go b/shared/api/response.go
index 846343f0a..a049c4cd1 100644
--- a/shared/api/response.go
+++ b/shared/api/response.go
@@ -15,18 +15,18 @@ type ResponseRaw struct {
 type Response struct {
 	Type ResponseType `json:"type"`
 
-	/* Valid only for Sync responses */
+	// Valid only for Sync responses
 	Status     string `json:"status"`
 	StatusCode int    `json:"status_code"`
 
-	/* Valid only for Async responses */
+	// Valid only for Async responses
 	Operation string `json:"operation"`
 
-	/* Valid only for Error responses */
+	// Valid only for Error responses
 	Code  int    `json:"error_code"`
 	Error string `json:"error"`
 
-	/* Valid for Sync and Error responses */
+	// Valid for Sync and Error responses
 	Metadata json.RawMessage `json:"metadata"`
 }
 

From 7fc7c7e93bd10da55d78bbf9a2ca56f6c960c162 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 7 Jan 2017 19:52:35 +0100
Subject: [PATCH 0633/1193] tests: Fix deadcode to work with new upstream
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/static_analysis.sh | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index ae8785642..2a8cc29fc 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -38,8 +38,8 @@ test_static_analysis() {
 
     ## deadcode
     if which deadcode >/dev/null 2>&1; then
-      for path in . lxc/ lxd/ shared/ shared/i18n shared/termios fuidshift/ lxd-bridge/lxd-bridge-proxy/; do
-        OUT=$(deadcode ${path} 2>&1 | grep -v lxd/migrate.pb.go || true)
+      for path in . fuidshift lxc lxd lxd/types shared shared/api shared/i18n shared/ioprogress shared/logging shared/osarch shared/simplestreams shared/termios shared/version test/lxd-benchmark; do
+        OUT=$(deadcode ./${path} 2>&1 | grep -v lxd/migrate.pb.go: | grep -v /C: | grep -vi _cgo | grep -vi _cfunc || true)
         if [ -n "${OUT}" ]; then
           echo "${OUT}" >&2
           false

From 66db647a5ed3b61dd59ad9345151f17c0a04ec46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 23 Jan 2017 16:16:35 -0500
Subject: [PATCH 0634/1193] Update i18n
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/lxd.pot | 253 ++++++++++++++++++++++++++++++++-----------------------------
 1 file changed, 131 insertions(+), 122 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index 5abacea36..3135e7d79 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-01-03 12:39-0500\n"
+        "POT-Creation-Date: 2017-01-23 16:16-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -28,7 +28,7 @@ msgstr  ""
 msgid   "  Network usage:"
 msgstr  ""
 
-#: lxc/config.go:36
+#: lxc/config.go:37
 msgid   "### This is a yaml representation of the configuration.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -48,7 +48,7 @@ msgid   "### This is a yaml representation of the configuration.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:83
+#: lxc/image.go:84
 msgid   "### This is a yaml representation of the image properties.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -57,7 +57,7 @@ msgid   "### This is a yaml representation of the image properties.\n"
         "###  description: My custom image"
 msgstr  ""
 
-#: lxc/profile.go:26
+#: lxc/profile.go:27
 msgid   "### This is a yaml representation of the profile.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -77,7 +77,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:608
+#: lxc/image.go:614
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -86,19 +86,19 @@ msgstr  ""
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/profile.go:225
+#: lxc/profile.go:226
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:629 lxc/image.go:658
+#: lxc/image.go:635 lxc/image.go:664
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:633
+#: lxc/image.go:639
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:394
+#: lxc/list.go:396
 msgid   "ARCHITECTURE"
 msgstr  ""
 
@@ -111,16 +111,16 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:367
+#: lxc/image.go:373
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:350 lxc/info.go:93
+#: lxc/image.go:351 lxc/info.go:93
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:371
+#: lxc/image.go:377
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
@@ -137,25 +137,25 @@ msgstr  ""
 msgid   "Bytes sent"
 msgstr  ""
 
-#: lxc/config.go:273
+#: lxc/config.go:274
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:395
+#: lxc/list.go:397
 msgid   "CREATED AT"
 msgstr  ""
 
-#: lxc/config.go:113
+#: lxc/config.go:114
 #, c-format
 msgid   "Can't read from stdin: %s"
 msgstr  ""
 
-#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181
+#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182
 #, c-format
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:342
+#: lxc/profile.go:343
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
@@ -164,7 +164,7 @@ msgstr  ""
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
 
-#: lxc/action.go:33
+#: lxc/action.go:34
 #, c-format
 msgid   "Change state of one or more containers to %s.\n"
         "\n"
@@ -181,15 +181,15 @@ msgstr  ""
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
-#: lxc/list.go:99 lxc/list.go:100
+#: lxc/list.go:100 lxc/list.go:101
 msgid   "Columns"
 msgstr  ""
 
-#: lxc/init.go:134 lxc/init.go:135
+#: lxc/init.go:135 lxc/init.go:136
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:712 lxc/profile.go:189
+#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:718 lxc/profile.go:190
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -198,31 +198,31 @@ msgstr  ""
 msgid   "Connection refused; is LXD running?"
 msgstr  ""
 
-#: lxc/publish.go:59
+#: lxc/publish.go:60
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/init.go:212
+#: lxc/init.go:213
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
 
-#: lxc/publish.go:149 lxc/publish.go:164
+#: lxc/publish.go:150 lxc/publish.go:165
 #, c-format
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:163
+#: lxc/image.go:164
 msgid   "Copy aliases from source"
 msgstr  ""
 
-#: lxc/copy.go:22
+#: lxc/copy.go:23
 msgid   "Copy containers within or in between LXD instances.\n"
         "\n"
         "lxc copy [<remote>:]<source>[/<snapshot>] [<remote>:]<destination> [--ephemeral|e]"
 msgstr  ""
 
-#: lxc/image.go:275
+#: lxc/image.go:276
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
@@ -247,25 +247,25 @@ msgid   "Create a read-only snapshot of a container.\n"
         "    lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:355 lxc/info.go:95
+#: lxc/image.go:356 lxc/info.go:95
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:177 lxc/launch.go:112
+#: lxc/init.go:178 lxc/launch.go:113
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
 
-#: lxc/init.go:175
+#: lxc/init.go:176
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:632 lxc/image.go:660
+#: lxc/image.go:638 lxc/image.go:666
 msgid   "DESCRIPTION"
 msgstr  ""
 
-#: lxc/delete.go:25
+#: lxc/delete.go:26
 msgid   "Delete containers or snapshots.\n"
         "\n"
         "lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]\n"
@@ -273,21 +273,21 @@ msgid   "Delete containers or snapshots.\n"
         "Destroy containers or snapshots with any attached data (configuration, snapshots, ...)."
 msgstr  ""
 
-#: lxc/config.go:647
+#: lxc/config.go:649
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:834
+#: lxc/config.go:836
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
 
-#: lxc/list.go:478
+#: lxc/list.go:482
 msgid   "EPHEMERAL"
 msgstr  ""
 
-#: lxc/config.go:275
+#: lxc/config.go:276
 msgid   "EXPIRY DATE"
 msgstr  ""
 
@@ -299,7 +299,7 @@ msgstr  ""
 msgid   "Enable verbose mode"
 msgstr  ""
 
-#: lxc/exec.go:54
+#: lxc/exec.go:55
 msgid   "Environment variable to set (e.g. HOME=/home/foo)"
 msgstr  ""
 
@@ -307,7 +307,7 @@ msgstr  ""
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:29 lxc/copy.go:30 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:139 lxc/init.go:140
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -315,7 +315,7 @@ msgstr  ""
 msgid   "Event type to listen for"
 msgstr  ""
 
-#: lxc/exec.go:45
+#: lxc/exec.go:46
 msgid   "Execute the specified command in a container.\n"
         "\n"
         "lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>\n"
@@ -323,33 +323,33 @@ msgid   "Execute the specified command in a container.\n"
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
 
-#: lxc/image.go:359
+#: lxc/image.go:360
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:361
+#: lxc/image.go:362
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:272 lxc/image.go:630 lxc/image.go:659
+#: lxc/config.go:273 lxc/image.go:636 lxc/image.go:665
 msgid   "FINGERPRINT"
 msgstr  ""
 
-#: lxc/list.go:102
+#: lxc/list.go:103
 msgid   "Fast mode (same as --columns=nsacPt"
 msgstr  ""
 
-#: lxc/image.go:348
+#: lxc/image.go:349
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
 
-#: lxc/action.go:42 lxc/action.go:43
+#: lxc/action.go:43 lxc/action.go:44
 msgid   "Force the container to shutdown"
 msgstr  ""
 
-#: lxc/delete.go:34 lxc/delete.go:35
+#: lxc/delete.go:35 lxc/delete.go:36
 msgid   "Force the removal of stopped containers"
 msgstr  ""
 
@@ -357,7 +357,7 @@ msgstr  ""
 msgid   "Force using the local unix socket"
 msgstr  ""
 
-#: lxc/list.go:101
+#: lxc/list.go:102
 msgid   "Format"
 msgstr  ""
 
@@ -371,15 +371,15 @@ msgid   "Help page for the LXD client.\n"
         "lxc help [--all]"
 msgstr  ""
 
-#: lxc/list.go:392
+#: lxc/list.go:394
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:393
+#: lxc/list.go:395
 msgid   "IPV6"
 msgstr  ""
 
-#: lxc/config.go:274
+#: lxc/config.go:275
 msgid   "ISSUE DATE"
 msgstr  ""
 
@@ -391,20 +391,20 @@ msgstr  ""
 msgid   "Ignore aliases when determining what command to run"
 msgstr  ""
 
-#: lxc/action.go:46
+#: lxc/action.go:47
 msgid   "Ignore the container state (only for start)"
 msgstr  ""
 
-#: lxc/image.go:278
+#: lxc/image.go:279
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:426 lxc/image.go:438
+#: lxc/image.go:432 lxc/image.go:444
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:423
+#: lxc/image.go:429
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
@@ -428,7 +428,7 @@ msgstr  ""
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
 
-#: lxc/config.go:253
+#: lxc/config.go:254
 msgid   "Invalid certificate"
 msgstr  ""
 
@@ -450,7 +450,7 @@ msgstr  ""
 msgid   "Ips:"
 msgstr  ""
 
-#: lxc/image.go:164
+#: lxc/image.go:165
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
@@ -458,7 +458,16 @@ msgstr  ""
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/launch.go:22
+#: lxc/image.go:365
+#, c-format
+msgid   "Last used: %s"
+msgstr  ""
+
+#: lxc/image.go:367
+msgid   "Last used: never"
+msgstr  ""
+
+#: lxc/launch.go:23
 msgid   "Launch a container from a particular image.\n"
         "\n"
         "lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
@@ -482,7 +491,7 @@ msgid   "List information on LXD servers and containers.\n"
         "    lxc info [<remote:>]"
 msgstr  ""
 
-#: lxc/list.go:67
+#: lxc/list.go:68
 msgid   "Lists the containers.\n"
         "\n"
         "lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]\n"
@@ -517,15 +526,15 @@ msgstr  ""
 msgid   "Log:"
 msgstr  ""
 
-#: lxc/image.go:162
+#: lxc/image.go:163
 msgid   "Make image public"
 msgstr  ""
 
-#: lxc/publish.go:32
+#: lxc/publish.go:33
 msgid   "Make the image public"
 msgstr  ""
 
-#: lxc/profile.go:47
+#: lxc/profile.go:48
 msgid   "Manage configuration profiles.\n"
         "\n"
         "lxc profile list [<remote>:]                                    List available profiles.\n"
@@ -560,7 +569,7 @@ msgid   "Manage configuration profiles.\n"
         "    Add a profile device, such as a disk or a nic, to the containers using the specified profile."
 msgstr  ""
 
-#: lxc/config.go:57
+#: lxc/config.go:58
 msgid   "Manage configuration.\n"
         "\n"
         "lxc config device add [<remote>:]<container> <device> <type> [key=value...]   Add a device to a container.\n"
@@ -628,7 +637,7 @@ msgid   "Manage remote LXD servers.\n"
         "lxc remote get-default                                                      Print the default remote."
 msgstr  ""
 
-#: lxc/image.go:93
+#: lxc/image.go:94
 msgid   "Manipulate container images.\n"
         "\n"
         "In LXD containers are created from images. Those images were themselves\n"
@@ -739,11 +748,11 @@ msgid   "Move containers within or in between lxd instances.\n"
         "    Rename a snapshot."
 msgstr  ""
 
-#: lxc/action.go:69
+#: lxc/action.go:70
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:396 lxc/remote.go:351
+#: lxc/list.go:398 lxc/remote.go:351
 msgid   "NAME"
 msgstr  ""
 
@@ -756,15 +765,15 @@ msgstr  ""
 msgid   "Name: %s"
 msgstr  ""
 
-#: lxc/image.go:165 lxc/publish.go:33
+#: lxc/image.go:166 lxc/publish.go:34
 msgid   "New alias to define at target"
 msgstr  ""
 
-#: lxc/config.go:284
+#: lxc/config.go:285
 msgid   "No certificate provided to add"
 msgstr  ""
 
-#: lxc/config.go:307
+#: lxc/config.go:308
 msgid   "No fingerprint specified."
 msgstr  ""
 
@@ -772,7 +781,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:429
+#: lxc/image.go:435
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -780,24 +789,24 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:531
+#: lxc/image.go:537
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
 
-#: lxc/exec.go:55
+#: lxc/exec.go:56
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:480
+#: lxc/list.go:484
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:397
+#: lxc/list.go:399
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:398
+#: lxc/list.go:400
 msgid   "PROFILES"
 msgstr  ""
 
@@ -805,7 +814,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:631 lxc/remote.go:354
+#: lxc/image.go:637 lxc/remote.go:354
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -834,11 +843,11 @@ msgstr  ""
 msgid   "Pid: %d"
 msgstr  ""
 
-#: lxc/profile.go:190
+#: lxc/profile.go:191
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:713
+#: lxc/config.go:533 lxc/config.go:598 lxc/image.go:719
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -865,22 +874,22 @@ msgstr  ""
 msgid   "Processes: %d"
 msgstr  ""
 
-#: lxc/profile.go:227
+#: lxc/profile.go:228
 #, c-format
 msgid   "Profile %s applied to %s"
 msgstr  ""
 
-#: lxc/profile.go:141
+#: lxc/profile.go:142
 #, c-format
 msgid   "Profile %s created"
 msgstr  ""
 
-#: lxc/profile.go:211
+#: lxc/profile.go:212
 #, c-format
 msgid   "Profile %s deleted"
 msgstr  ""
 
-#: lxc/init.go:136 lxc/init.go:137
+#: lxc/init.go:137 lxc/init.go:138
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
@@ -889,7 +898,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:363
+#: lxc/image.go:369
 msgid   "Properties:"
 msgstr  ""
 
@@ -897,12 +906,12 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:351
+#: lxc/image.go:352
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
 
-#: lxc/publish.go:25
+#: lxc/publish.go:26
 msgid   "Publish containers as images.\n"
         "\n"
         "lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]"
@@ -917,12 +926,12 @@ msgstr  ""
 msgid   "Remote: %s"
 msgstr  ""
 
-#: lxc/delete.go:42
+#: lxc/delete.go:43
 #, c-format
 msgid   "Remove %s (yes/no): "
 msgstr  ""
 
-#: lxc/delete.go:36 lxc/delete.go:37
+#: lxc/delete.go:37 lxc/delete.go:38
 msgid   "Require user confirmation"
 msgstr  ""
 
@@ -946,20 +955,20 @@ msgid   "Restore a container's state to a previous snapshot.\n"
         "    lxc restore u1 snap0"
 msgstr  ""
 
-#: lxc/init.go:219
+#: lxc/init.go:220
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:634
+#: lxc/image.go:640
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:399
+#: lxc/list.go:401
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:400
+#: lxc/list.go:402
 msgid   "STATE"
 msgstr  ""
 
@@ -1003,11 +1012,11 @@ msgstr  ""
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/config.go:32
+#: lxc/config.go:33
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:349
+#: lxc/image.go:350
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -1016,11 +1025,11 @@ msgstr  ""
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/image.go:373
+#: lxc/image.go:379
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/launch.go:119
+#: lxc/launch.go:120
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -1030,15 +1039,15 @@ msgstr  ""
 msgid   "Status: %s"
 msgstr  ""
 
-#: lxc/publish.go:34 lxc/publish.go:35
+#: lxc/publish.go:35 lxc/publish.go:36
 msgid   "Stop the container if currently running"
 msgstr  ""
 
-#: lxc/delete.go:106 lxc/publish.go:111
+#: lxc/delete.go:107 lxc/publish.go:112
 msgid   "Stopping container failed!"
 msgstr  ""
 
-#: lxc/action.go:45
+#: lxc/action.go:46
 msgid   "Store the container state (only for stop)"
 msgstr  ""
 
@@ -1050,23 +1059,23 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:401
+#: lxc/list.go:403
 msgid   "TYPE"
 msgstr  ""
 
-#: lxc/delete.go:92
+#: lxc/delete.go:93
 msgid   "The container is currently running, stop it first or pass --force."
 msgstr  ""
 
-#: lxc/publish.go:89
+#: lxc/publish.go:90
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738 lxc/config.go:776 lxc/config.go:794
+#: lxc/config.go:677 lxc/config.go:689 lxc/config.go:722 lxc/config.go:740 lxc/config.go:778 lxc/config.go:796
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:276
+#: lxc/init.go:277
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -1075,15 +1084,15 @@ msgstr  ""
 msgid   "The opposite of `lxc pause` is `lxc start`."
 msgstr  ""
 
-#: lxc/publish.go:62
+#: lxc/publish.go:63
 msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
 
-#: lxc/action.go:41
+#: lxc/action.go:42
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:352
+#: lxc/image.go:353
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -1091,12 +1100,12 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:431
+#: lxc/image.go:437
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:99 lxc/launch.go:132
+#: lxc/action.go:100 lxc/launch.go:133
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1109,7 +1118,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:635
+#: lxc/image.go:641
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1121,7 +1130,7 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:357
+#: lxc/image.go:358
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1135,7 +1144,7 @@ msgstr  ""
 msgid   "Usage: lxc <command> [options]"
 msgstr  ""
 
-#: lxc/delete.go:46
+#: lxc/delete.go:47
 msgid   "User aborted delete operation."
 msgstr  ""
 
@@ -1155,15 +1164,15 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:105
+#: lxc/launch.go:106
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
-#: lxc/action.go:95
+#: lxc/action.go:96
 msgid   "bad result type from action"
 msgstr  ""
 
-#: lxc/copy.go:99
+#: lxc/copy.go:100
 msgid   "can't copy to the same container name"
 msgstr  ""
 
@@ -1175,15 +1184,15 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:202 lxc/init.go:207 lxc/launch.go:89 lxc/launch.go:94
+#: lxc/init.go:203 lxc/init.go:208 lxc/launch.go:90 lxc/launch.go:95
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:343
+#: lxc/image.go:344
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:345
+#: lxc/image.go:346
 msgid   "enabled"
 msgstr  ""
 
@@ -1197,15 +1206,15 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:109
+#: lxc/launch.go:110
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:338 lxc/image.go:611
+#: lxc/image.go:339 lxc/image.go:617
 msgid   "no"
 msgstr  ""
 
-#: lxc/copy.go:122
+#: lxc/copy.go:123
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
@@ -1251,7 +1260,7 @@ msgstr  ""
 msgid   "taken at %s"
 msgstr  ""
 
-#: lxc/exec.go:163
+#: lxc/exec.go:164
 msgid   "unreachable return reached"
 msgstr  ""
 
@@ -1259,11 +1268,11 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:340 lxc/image.go:615
+#: lxc/delete.go:46 lxc/image.go:341 lxc/image.go:621
 msgid   "yes"
 msgstr  ""
 
-#: lxc/copy.go:38
+#: lxc/copy.go:39
 msgid   "you must specify a source container name"
 msgstr  ""
 

From bb0e427bc6f10a6cbab7e2a79b484d35defd1a5e Mon Sep 17 00:00:00 2001
From: KATOH Yasufumi <karma at jazz.email.ne.jp>
Date: Wed, 11 Jan 2017 20:55:18 +0900
Subject: [PATCH 0635/1193] remote: Update help

Signed-off-by: KATOH Yasufumi <karma at jazz.email.ne.jp>
---
 lxc/remote.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index b31cec1d6..36df6c56d 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -38,11 +38,11 @@ func (c *remoteCmd) usage() string {
 		`Manage remote LXD servers.
 
 lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD]
-                                      [--public] [--protocol=PROTOCOL]      Add the remote <name> at <url>.
-lxc remote remove <remote>                                                  Remove the remote <name>.
+                                      [--public] [--protocol=PROTOCOL]      Add the remote <remote> at <url>.
+lxc remote remove <remote>                                                  Remove the remote <remote>.
 lxc remote list                                                             List all remotes.
-lxc remote rename <old name> <new name>                                     Rename remote <old> to <new>.
-lxc remote set-url <remote> <url>                                           Update <name>'s url to <url>.
+lxc remote rename <old name> <new name>                                     Rename remote <old name> to <new name>.
+lxc remote set-url <remote> <url>                                           Update <remote>'s url to <url>.
 lxc remote set-default <remote>                                             Set the default remote.
 lxc remote get-default                                                      Print the default remote.`)
 }

From a8675587ba01f1af6d3f469b8e26e676ea0466c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 16:24:55 +0200
Subject: [PATCH 0636/1193] "gofmt -s" run
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                             |  4 +--
 config.go                             |  2 +-
 lxc/list.go                           | 22 ++++++------
 lxc/main_test.go                      |  6 ++--
 lxd/container.go                      |  2 +-
 lxd/container_lxc.go                  | 10 +++---
 lxd/daemon_config.go                  | 40 +++++++++++-----------
 lxd/db_profiles.go                    |  2 +-
 lxd/db_update.go                      | 64 +++++++++++++++++------------------
 lxd/devlxd.go                         |  6 ++--
 lxd/main_forkexec.go                  |  2 +-
 lxd/main_test.go                      |  4 +--
 lxd/patches.go                        |  4 +--
 lxd/types/devices.go                  |  4 +--
 shared/idmapset_linux_test.go         |  8 ++---
 shared/osarch/architectures.go        | 30 ++++++++--------
 shared/simplestreams/simplestreams.go |  2 +-
 shared/stringset.go                   |  2 +-
 shared/util_linux.go                  |  2 +-
 19 files changed, 108 insertions(+), 108 deletions(-)

diff --git a/client.go b/client.go
index 4230aa861..20bef32cd 100644
--- a/client.go
+++ b/client.go
@@ -2195,7 +2195,7 @@ func (c *Client) ContainerDeviceDelete(container, devname string) (*api.Response
 		return nil, err
 	}
 
-	for n, _ := range st.Devices {
+	for n := range st.Devices {
 		if n == devname {
 			delete(st.Devices, n)
 			return c.put(fmt.Sprintf("containers/%s", container), st, api.AsyncResponse)
@@ -2266,7 +2266,7 @@ func (c *Client) ProfileDeviceDelete(profile, devname string) (*api.Response, er
 		return nil, err
 	}
 
-	for n, _ := range st.Devices {
+	for n := range st.Devices {
 		if n == devname {
 			delete(st.Devices, n)
 			return c.put(fmt.Sprintf("profiles/%s", profile), st, api.SyncResponse)
diff --git a/config.go b/config.go
index c7b720965..63cd67154 100644
--- a/config.go
+++ b/config.go
@@ -118,7 +118,7 @@ func LoadConfig(path string) (*Config, error) {
 
 // SaveConfig writes the provided configuration to the config file.
 func SaveConfig(c *Config, fname string) error {
-	for k, _ := range StaticRemotes {
+	for k := range StaticRemotes {
 		delete(c.Remotes, k)
 	}
 
diff --git a/lxc/list.go b/lxc/list.go
index 60764fa49..eb2f8a11f 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -112,7 +112,7 @@ func (c *listCmd) dotPrefixMatch(short string, full string) bool {
 		return false
 	}
 
-	for i, _ := range fullMembs {
+	for i := range fullMembs {
 		if !strings.HasPrefix(fullMembs[i], shortMembs[i]) {
 			return false
 		}
@@ -391,16 +391,16 @@ func (c *listCmd) run(config *lxd.Config, args []string) error {
 	}
 
 	columns_map := map[rune]column{
-		'4': column{i18n.G("IPV4"), c.IP4ColumnData, true, false},
-		'6': column{i18n.G("IPV6"), c.IP6ColumnData, true, false},
-		'a': column{i18n.G("ARCHITECTURE"), c.ArchitectureColumnData, false, false},
-		'c': column{i18n.G("CREATED AT"), c.CreatedColumnData, false, false},
-		'n': column{i18n.G("NAME"), c.nameColumnData, false, false},
-		'p': column{i18n.G("PID"), c.PIDColumnData, true, false},
-		'P': column{i18n.G("PROFILES"), c.ProfilesColumnData, false, false},
-		'S': column{i18n.G("SNAPSHOTS"), c.numberSnapshotsColumnData, false, true},
-		's': column{i18n.G("STATE"), c.statusColumnData, false, false},
-		't': column{i18n.G("TYPE"), c.typeColumnData, false, false},
+		'4': {i18n.G("IPV4"), c.IP4ColumnData, true, false},
+		'6': {i18n.G("IPV6"), c.IP6ColumnData, true, false},
+		'a': {i18n.G("ARCHITECTURE"), c.ArchitectureColumnData, false, false},
+		'c': {i18n.G("CREATED AT"), c.CreatedColumnData, false, false},
+		'n': {i18n.G("NAME"), c.nameColumnData, false, false},
+		'p': {i18n.G("PID"), c.PIDColumnData, true, false},
+		'P': {i18n.G("PROFILES"), c.ProfilesColumnData, false, false},
+		'S': {i18n.G("SNAPSHOTS"), c.numberSnapshotsColumnData, false, true},
+		's': {i18n.G("STATE"), c.statusColumnData, false, false},
+		't': {i18n.G("TYPE"), c.typeColumnData, false, false},
 	}
 
 	if c.fast {
diff --git a/lxc/main_test.go b/lxc/main_test.go
index 5545894b3..30fc0cb6f 100644
--- a/lxc/main_test.go
+++ b/lxc/main_test.go
@@ -40,15 +40,15 @@ func TestExpandAliases(t *testing.T) {
 	}
 
 	testcases := []aliasTestcase{
-		aliasTestcase{
+		{
 			input:    []string{"lxc", "list"},
 			expected: []string{"lxc", "list"},
 		},
-		aliasTestcase{
+		{
 			input:    []string{"lxc", "tester", "12"},
 			expected: []string{"lxc", "list", "--no-alias"},
 		},
-		aliasTestcase{
+		{
 			input:    []string{"lxc", "foo", "asdf"},
 			expected: []string{"lxc", "list", "--no-alias", "asdf", "-c", "n"},
 		},
diff --git a/lxd/container.go b/lxd/container.go
index 64a95ce83..51768184a 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -327,7 +327,7 @@ func containerValidDevices(devices types.Devices, profile bool, expanded bool) e
 			return fmt.Errorf("Invalid device type for device '%s'", name)
 		}
 
-		for k, _ := range m {
+		for k := range m {
 			if !containerValidDeviceConfigKey(m["type"], k) {
 				return fmt.Errorf("Invalid device configuration key for %s: %s", m["type"], k)
 			}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 4b57a417d..351d2a1e0 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -634,8 +634,8 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 
 	mkIdmap := func(offset int, size int) *shared.IdmapSet {
 		set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
-			shared.IdmapEntry{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
-			shared.IdmapEntry{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+			{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+			{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
 		}}
 
 		for _, ent := range rawMaps {
@@ -1601,7 +1601,7 @@ func (c *containerLXC) startCommon() (string, error) {
 		}
 	}
 
-	for k, _ := range c.localConfig {
+	for k := range c.localConfig {
 		// We only care about volatile
 		if !strings.HasPrefix(k, "volatile.") {
 			continue
@@ -2724,7 +2724,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 
 	// Diff the configurations
 	changedConfig := []string{}
-	for key, _ := range oldExpandedConfig {
+	for key := range oldExpandedConfig {
 		if oldExpandedConfig[key] != c.expandedConfig[key] {
 			if !shared.StringInSlice(key, changedConfig) {
 				changedConfig = append(changedConfig, key)
@@ -2732,7 +2732,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 	}
 
-	for key, _ := range c.expandedConfig {
+	for key := range c.expandedConfig {
 		if oldExpandedConfig[key] != c.expandedConfig[key] {
 			if !shared.StringInSlice(key, changedConfig) {
 				changedConfig = append(changedConfig, key)
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 1870b52c4..11ce7454c 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -173,25 +173,25 @@ func (k *daemonConfigKey) GetInt64() int64 {
 func daemonConfigInit(db *sql.DB) error {
 	// Set all the keys
 	daemonConfig = map[string]*daemonConfigKey{
-		"core.https_address":         &daemonConfigKey{valueType: "string", setter: daemonConfigSetAddress},
-		"core.https_allowed_headers": &daemonConfigKey{valueType: "string"},
-		"core.https_allowed_methods": &daemonConfigKey{valueType: "string"},
-		"core.https_allowed_origin":  &daemonConfigKey{valueType: "string"},
-		"core.proxy_http":            &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy},
-		"core.proxy_https":           &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy},
-		"core.proxy_ignore_hosts":    &daemonConfigKey{valueType: "string", setter: daemonConfigSetProxy},
-		"core.trust_password":        &daemonConfigKey{valueType: "string", hiddenValue: true, setter: daemonConfigSetPassword},
-
-		"images.auto_update_cached":    &daemonConfigKey{valueType: "bool", defaultValue: "true"},
-		"images.auto_update_interval":  &daemonConfigKey{valueType: "int", defaultValue: "6"},
-		"images.compression_algorithm": &daemonConfigKey{valueType: "string", validator: daemonConfigValidateCompression, defaultValue: "gzip"},
-		"images.remote_cache_expiry":   &daemonConfigKey{valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry},
-
-		"storage.lvm_fstype":        &daemonConfigKey{valueType: "string", defaultValue: "ext4", validValues: []string{"ext4", "xfs"}},
-		"storage.lvm_thinpool_name": &daemonConfigKey{valueType: "string", defaultValue: "LXDPool", validator: storageLVMValidateThinPoolName},
-		"storage.lvm_vg_name":       &daemonConfigKey{valueType: "string", validator: storageLVMValidateVolumeGroupName, setter: daemonConfigSetStorage},
-		"storage.lvm_volume_size":   &daemonConfigKey{valueType: "string", defaultValue: "10GiB"},
-		"storage.zfs_pool_name":     &daemonConfigKey{valueType: "string", validator: storageZFSValidatePoolName, setter: daemonConfigSetStorage},
+		"core.https_address":         {valueType: "string", setter: daemonConfigSetAddress},
+		"core.https_allowed_headers": {valueType: "string"},
+		"core.https_allowed_methods": {valueType: "string"},
+		"core.https_allowed_origin":  {valueType: "string"},
+		"core.proxy_http":            {valueType: "string", setter: daemonConfigSetProxy},
+		"core.proxy_https":           {valueType: "string", setter: daemonConfigSetProxy},
+		"core.proxy_ignore_hosts":    {valueType: "string", setter: daemonConfigSetProxy},
+		"core.trust_password":        {valueType: "string", hiddenValue: true, setter: daemonConfigSetPassword},
+
+		"images.auto_update_cached":    {valueType: "bool", defaultValue: "true"},
+		"images.auto_update_interval":  {valueType: "int", defaultValue: "6"},
+		"images.compression_algorithm": {valueType: "string", validator: daemonConfigValidateCompression, defaultValue: "gzip"},
+		"images.remote_cache_expiry":   {valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry},
+
+		"storage.lvm_fstype":        {valueType: "string", defaultValue: "ext4", validValues: []string{"ext4", "xfs"}},
+		"storage.lvm_thinpool_name": {valueType: "string", defaultValue: "LXDPool", validator: storageLVMValidateThinPoolName},
+		"storage.lvm_vg_name":       {valueType: "string", validator: storageLVMValidateVolumeGroupName, setter: daemonConfigSetStorage},
+		"storage.lvm_volume_size":   {valueType: "string", defaultValue: "10GiB"},
+		"storage.zfs_pool_name":     {valueType: "string", validator: storageZFSValidatePoolName, setter: daemonConfigSetStorage},
 	}
 
 	// Load the values from the DB
@@ -307,7 +307,7 @@ func daemonConfigSetProxy(d *Daemon, key string, value string) (string, error) {
 
 	// Clear the simplestreams cache as it's tied to the old proxy config
 	imageStreamCacheLock.Lock()
-	for k, _ := range imageStreamCache {
+	for k := range imageStreamCache {
 		delete(imageStreamCache, k)
 	}
 	imageStreamCacheLock.Unlock()
diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index 401ceb887..f7bd6b984 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -110,7 +110,7 @@ func dbProfileCreateDefault(db *sql.DB) error {
 
 	// TODO: We should scan for bridges and use the best available as default.
 	devices := map[string]map[string]string{
-		"eth0": map[string]string{
+		"eth0": {
 			"name":    "eth0",
 			"type":    "nic",
 			"nictype": "bridged",
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 6c7b6f710..22b3832e8 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -34,38 +34,38 @@ import (
 */
 
 var dbUpdates = []dbUpdate{
-	dbUpdate{version: 1, run: dbUpdateFromV0},
-	dbUpdate{version: 2, run: dbUpdateFromV1},
-	dbUpdate{version: 3, run: dbUpdateFromV2},
-	dbUpdate{version: 4, run: dbUpdateFromV3},
-	dbUpdate{version: 5, run: dbUpdateFromV4},
-	dbUpdate{version: 6, run: dbUpdateFromV5},
-	dbUpdate{version: 7, run: dbUpdateFromV6},
-	dbUpdate{version: 8, run: dbUpdateFromV7},
-	dbUpdate{version: 9, run: dbUpdateFromV8},
-	dbUpdate{version: 10, run: dbUpdateFromV9},
-	dbUpdate{version: 11, run: dbUpdateFromV10},
-	dbUpdate{version: 12, run: dbUpdateFromV11},
-	dbUpdate{version: 13, run: dbUpdateFromV12},
-	dbUpdate{version: 14, run: dbUpdateFromV13},
-	dbUpdate{version: 15, run: dbUpdateFromV14},
-	dbUpdate{version: 16, run: dbUpdateFromV15},
-	dbUpdate{version: 17, run: dbUpdateFromV16},
-	dbUpdate{version: 18, run: dbUpdateFromV17},
-	dbUpdate{version: 19, run: dbUpdateFromV18},
-	dbUpdate{version: 20, run: dbUpdateFromV19},
-	dbUpdate{version: 21, run: dbUpdateFromV20},
-	dbUpdate{version: 22, run: dbUpdateFromV21},
-	dbUpdate{version: 23, run: dbUpdateFromV22},
-	dbUpdate{version: 24, run: dbUpdateFromV23},
-	dbUpdate{version: 25, run: dbUpdateFromV24},
-	dbUpdate{version: 26, run: dbUpdateFromV25},
-	dbUpdate{version: 27, run: dbUpdateFromV26},
-	dbUpdate{version: 28, run: dbUpdateFromV27},
-	dbUpdate{version: 29, run: dbUpdateFromV28},
-	dbUpdate{version: 30, run: dbUpdateFromV29},
-	dbUpdate{version: 31, run: dbUpdateFromV30},
-	dbUpdate{version: 32, run: dbUpdateFromV31},
+	{version: 1, run: dbUpdateFromV0},
+	{version: 2, run: dbUpdateFromV1},
+	{version: 3, run: dbUpdateFromV2},
+	{version: 4, run: dbUpdateFromV3},
+	{version: 5, run: dbUpdateFromV4},
+	{version: 6, run: dbUpdateFromV5},
+	{version: 7, run: dbUpdateFromV6},
+	{version: 8, run: dbUpdateFromV7},
+	{version: 9, run: dbUpdateFromV8},
+	{version: 10, run: dbUpdateFromV9},
+	{version: 11, run: dbUpdateFromV10},
+	{version: 12, run: dbUpdateFromV11},
+	{version: 13, run: dbUpdateFromV12},
+	{version: 14, run: dbUpdateFromV13},
+	{version: 15, run: dbUpdateFromV14},
+	{version: 16, run: dbUpdateFromV15},
+	{version: 17, run: dbUpdateFromV16},
+	{version: 18, run: dbUpdateFromV17},
+	{version: 19, run: dbUpdateFromV18},
+	{version: 20, run: dbUpdateFromV19},
+	{version: 21, run: dbUpdateFromV20},
+	{version: 22, run: dbUpdateFromV21},
+	{version: 23, run: dbUpdateFromV22},
+	{version: 24, run: dbUpdateFromV23},
+	{version: 25, run: dbUpdateFromV24},
+	{version: 26, run: dbUpdateFromV25},
+	{version: 27, run: dbUpdateFromV26},
+	{version: 28, run: dbUpdateFromV27},
+	{version: 29, run: dbUpdateFromV28},
+	{version: 30, run: dbUpdateFromV29},
+	{version: 31, run: dbUpdateFromV30},
+	{version: 32, run: dbUpdateFromV31},
 }
 
 type dbUpdate struct {
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index bf4edc851..7e27a1639 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -43,7 +43,7 @@ type devLxdHandler struct {
 
 var configGet = devLxdHandler{"/1.0/config", func(c container, r *http.Request) *devLxdResponse {
 	filtered := []string{}
-	for k, _ := range c.ExpandedConfig() {
+	for k := range c.ExpandedConfig() {
 		if strings.HasPrefix(k, "user.") {
 			filtered = append(filtered, fmt.Sprintf("/1.0/config/%s", k))
 		}
@@ -71,10 +71,10 @@ var metadataGet = devLxdHandler{"/1.0/meta-data", func(c container, r *http.Requ
 }}
 
 var handlers = []devLxdHandler{
-	devLxdHandler{"/", func(c container, r *http.Request) *devLxdResponse {
+	{"/", func(c container, r *http.Request) *devLxdResponse {
 		return okResponse([]string{"/1.0"}, "json")
 	}},
-	devLxdHandler{"/1.0", func(c container, r *http.Request) *devLxdResponse {
+	{"/1.0", func(c container, r *http.Request) *devLxdResponse {
 		return okResponse(shared.Jmap{"api_version": version.APIVersion}, "json")
 	}},
 	configGet,
diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
index 8ebb0a694..b9c6cbb48 100644
--- a/lxd/main_forkexec.go
+++ b/lxd/main_forkexec.go
@@ -63,7 +63,7 @@ func cmdForkExec(args []string) (int, error) {
 	cmd := []string{}
 
 	section := ""
-	for _, arg := range args[5:len(args)] {
+	for _, arg := range args[5:] {
 		// The "cmd" section must come last as it may contain a --
 		if arg == "--" && section != "cmd" {
 			section = ""
diff --git a/lxd/main_test.go b/lxd/main_test.go
index 262c54170..f5f45186e 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -24,8 +24,8 @@ func mockStartDaemon() (*Daemon, error) {
 	}
 
 	d.IdmapSet = &shared.IdmapSet{Idmap: []shared.IdmapEntry{
-		shared.IdmapEntry{Isuid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
-		shared.IdmapEntry{Isgid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
+		{Isuid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
+		{Isgid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
 	}}
 
 	// Call this after Init so we have a log object.
diff --git a/lxd/patches.go b/lxd/patches.go
index 3d303dde3..ae1e258ac 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -26,8 +26,8 @@ import (
 */
 
 var patches = []patch{
-	patch{name: "invalid_profile_names", run: patchInvalidProfileNames},
-	patch{name: "leftover_profile_config", run: patchLeftoverProfileConfig},
+	{name: "invalid_profile_names", run: patchInvalidProfileNames},
+	{name: "leftover_profile_config", run: patchLeftoverProfileConfig},
 }
 
 type patch struct {
diff --git a/lxd/types/devices.go b/lxd/types/devices.go
index 56b61aeef..7e8708075 100644
--- a/lxd/types/devices.go
+++ b/lxd/types/devices.go
@@ -33,13 +33,13 @@ func (list Devices) Contains(k string, d Device) bool {
 
 func deviceEquals(old Device, d Device) bool {
 	// Check for any difference and addition/removal of properties
-	for k, _ := range d {
+	for k := range d {
 		if d[k] != old[k] {
 			return false
 		}
 	}
 
-	for k, _ := range old {
+	for k := range old {
 		if d[k] != old[k] {
 			return false
 		}
diff --git a/shared/idmapset_linux_test.go b/shared/idmapset_linux_test.go
index 588b84762..d8582c8d9 100644
--- a/shared/idmapset_linux_test.go
+++ b/shared/idmapset_linux_test.go
@@ -6,7 +6,7 @@ import (
 )
 
 func TestIdmapSetAddSafe_split(t *testing.T) {
-	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+	orig := IdmapSet{Idmap: []IdmapEntry{{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
 
 	if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 500, Maprange: 10}); err != nil {
 		t.Error(err)
@@ -35,7 +35,7 @@ func TestIdmapSetAddSafe_split(t *testing.T) {
 }
 
 func TestIdmapSetAddSafe_lower(t *testing.T) {
-	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+	orig := IdmapSet{Idmap: []IdmapEntry{{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
 
 	if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 0, Maprange: 10}); err != nil {
 		t.Error(err)
@@ -59,7 +59,7 @@ func TestIdmapSetAddSafe_lower(t *testing.T) {
 }
 
 func TestIdmapSetAddSafe_upper(t *testing.T) {
-	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
+	orig := IdmapSet{Idmap: []IdmapEntry{{Isuid: true, Hostid: 1000, Nsid: 0, Maprange: 1000}}}
 
 	if err := orig.AddSafe(IdmapEntry{Isuid: true, Hostid: 500, Nsid: 995, Maprange: 10}); err != nil {
 		t.Error(err)
@@ -83,7 +83,7 @@ func TestIdmapSetAddSafe_upper(t *testing.T) {
 }
 
 func TestIdmapSetIntersects(t *testing.T) {
-	orig := IdmapSet{Idmap: []IdmapEntry{IdmapEntry{Isuid: true, Hostid: 165536, Nsid: 0, Maprange: 65536}}}
+	orig := IdmapSet{Idmap: []IdmapEntry{{Isuid: true, Hostid: 165536, Nsid: 0, Maprange: 65536}}}
 
 	if !orig.Intersects(IdmapEntry{Isuid: true, Hostid: 231071, Nsid: 0, Maprange: 65536}) {
 		t.Error("ranges don't intersect")
diff --git a/shared/osarch/architectures.go b/shared/osarch/architectures.go
index 728098cc3..265d7cb02 100644
--- a/shared/osarch/architectures.go
+++ b/shared/osarch/architectures.go
@@ -28,13 +28,13 @@ var architectureNames = map[int]string{
 }
 
 var architectureAliases = map[int][]string{
-	ARCH_32BIT_INTEL_X86:             []string{"i386"},
-	ARCH_64BIT_INTEL_X86:             []string{"amd64"},
-	ARCH_32BIT_ARMV7_LITTLE_ENDIAN:   []string{"armel", "armhf"},
-	ARCH_64BIT_ARMV8_LITTLE_ENDIAN:   []string{"arm64"},
-	ARCH_32BIT_POWERPC_BIG_ENDIAN:    []string{"powerpc"},
-	ARCH_64BIT_POWERPC_BIG_ENDIAN:    []string{"powerpc64"},
-	ARCH_64BIT_POWERPC_LITTLE_ENDIAN: []string{"ppc64el"},
+	ARCH_32BIT_INTEL_X86:             {"i386"},
+	ARCH_64BIT_INTEL_X86:             {"amd64"},
+	ARCH_32BIT_ARMV7_LITTLE_ENDIAN:   {"armel", "armhf"},
+	ARCH_64BIT_ARMV8_LITTLE_ENDIAN:   {"arm64"},
+	ARCH_32BIT_POWERPC_BIG_ENDIAN:    {"powerpc"},
+	ARCH_64BIT_POWERPC_BIG_ENDIAN:    {"powerpc64"},
+	ARCH_64BIT_POWERPC_LITTLE_ENDIAN: {"ppc64el"},
 }
 
 var architecturePersonalities = map[int]string{
@@ -49,14 +49,14 @@ var architecturePersonalities = map[int]string{
 }
 
 var architectureSupportedPersonalities = map[int][]int{
-	ARCH_32BIT_INTEL_X86:             []int{},
-	ARCH_64BIT_INTEL_X86:             []int{ARCH_32BIT_INTEL_X86},
-	ARCH_32BIT_ARMV7_LITTLE_ENDIAN:   []int{},
-	ARCH_64BIT_ARMV8_LITTLE_ENDIAN:   []int{ARCH_32BIT_ARMV7_LITTLE_ENDIAN},
-	ARCH_32BIT_POWERPC_BIG_ENDIAN:    []int{},
-	ARCH_64BIT_POWERPC_BIG_ENDIAN:    []int{ARCH_32BIT_POWERPC_BIG_ENDIAN},
-	ARCH_64BIT_POWERPC_LITTLE_ENDIAN: []int{},
-	ARCH_64BIT_S390_BIG_ENDIAN:       []int{},
+	ARCH_32BIT_INTEL_X86:             {},
+	ARCH_64BIT_INTEL_X86:             {ARCH_32BIT_INTEL_X86},
+	ARCH_32BIT_ARMV7_LITTLE_ENDIAN:   {},
+	ARCH_64BIT_ARMV8_LITTLE_ENDIAN:   {ARCH_32BIT_ARMV7_LITTLE_ENDIAN},
+	ARCH_32BIT_POWERPC_BIG_ENDIAN:    {},
+	ARCH_64BIT_POWERPC_BIG_ENDIAN:    {ARCH_32BIT_POWERPC_BIG_ENDIAN},
+	ARCH_64BIT_POWERPC_LITTLE_ENDIAN: {},
+	ARCH_64BIT_S390_BIG_ENDIAN:       {},
 }
 
 const ArchitectureDefault = "x86_64"
diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index dd5d206c4..d71fcd393 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -212,7 +212,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 				}
 			}
 
-			downloads[fingerprint] = [][]string{[]string{metaPath, metaHash, "meta"}, []string{rootfsPath, rootfsHash, "root"}}
+			downloads[fingerprint] = [][]string{{metaPath, metaHash, "meta"}, {rootfsPath, rootfsHash, "root"}}
 			images = append(images, image)
 		}
 	}
diff --git a/shared/stringset.go b/shared/stringset.go
index 1094e6126..a851d7a3d 100644
--- a/shared/stringset.go
+++ b/shared/stringset.go
@@ -5,7 +5,7 @@ package shared
 type StringSet map[string]bool
 
 func (ss StringSet) IsSubset(oss StringSet) bool {
-	for k, _ := range map[string]bool(ss) {
+	for k := range map[string]bool(ss) {
 		if _, ok := map[string]bool(oss)[k]; !ok {
 			return false
 		}
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 8ef5b82a1..3adfc7843 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -413,7 +413,7 @@ func ReadLastNLines(f *os.File, lines int) (string, error) {
 		}
 
 		if lines < 0 {
-			return string(data[i+1 : len(data)]), nil
+			return string(data[i+1:]), nil
 		}
 	}
 

From 0305123563a57b490b3043235b031fccdfc696b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 16:27:27 +0200
Subject: [PATCH 0637/1193] Fix typos
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/devlxd.go                  | 4 ++--
 lxd/images.go                  | 2 +-
 shared/util_linux.go           | 2 +-
 test/suites/database_update.sh | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 7e27a1639..1d0acc11f 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -269,7 +269,7 @@ func getCred(conn *net.UnixConn) (*ucred, error) {
 /*
  * As near as I can tell, there is no nice way of extracting an underlying
  * net.Conn (or in our case, net.UnixConn) from an http.Request or
- * ResponseWriter without hijacking it [1]. Since we want to send and recieve
+ * ResponseWriter without hijacking it [1]. Since we want to send and receive
  * unix creds to figure out which container this request came from, we need to
  * do this.
  *
@@ -301,7 +301,7 @@ func findContainerForPid(pid int32, d *Daemon) (container, error) {
 	 *    an lxc monitor process and extract its name from there.
 	 *
 	 * 2. If this fails, it may be that someone did an `lxc exec foo bash`,
-	 *    so the process isn't actually a decendant of the container's
+	 *    so the process isn't actually a descendant of the container's
 	 *    init. In this case we just look through all the containers until
 	 *    we find an init with a matching pid namespace. This is probably
 	 *    uncommon, so hopefully the slowness won't hurt us.
diff --git a/lxd/images.go b/lxd/images.go
index ab2c36280..6527ae723 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -35,7 +35,7 @@ import (
    The CPU and I/O load of publish is such that running multiple ones in
    parallel takes longer than running them serially.
 
-   Additionaly, publishing the same container or container snapshot
+   Additionally, publishing the same container or container snapshot
    twice would lead to storage problem, not to mention a conflict at the
    end for whichever finishes last. */
 var imagePublishLock sync.Mutex
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 3adfc7843..9ce2fb05c 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -642,7 +642,7 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 					// still be handling a pure POLLIN event from a write prior to the childs
 					// exit. But the child might have exited right before and performed
 					// atomic.StoreInt32() to update attachedChildIsDead before we
-					// performed our atomic.LoadInt32(). This means we accidently hit this
+					// performed our atomic.LoadInt32(). This means we accidentally hit this
 					// codepath and are misinformed about the available poll() events. So we
 					// need to perform a non-blocking poll() again to exclude that case:
 					//
diff --git a/test/suites/database_update.sh b/test/suites/database_update.sh
index e178ee927..6f39bee89 100644
--- a/test/suites/database_update.sh
+++ b/test/suites/database_update.sh
@@ -15,7 +15,7 @@ test_database_update(){
   tables=$(sqlite3 "${MIGRATE_DB}" ".dump" | grep -c "CREATE TABLE")
   [ "${tables}" -eq "${expected_tables}" ] || { echo "FAIL: Wrong number of tables after database migration. Found: ${tables}, expected ${expected_tables}"; false; }
 
-  # There should be 10 "ON DELETE CASCADE" occurences
+  # There should be 10 "ON DELETE CASCADE" occurrences
   expected_cascades=11
   cascades=$(sqlite3 "${MIGRATE_DB}" ".dump" | grep -c "ON DELETE CASCADE")
   [ "${cascades}" -eq "${expected_cascades}" ] || { echo "FAIL: Wrong number of ON DELETE CASCADE foreign keys. Found: ${cascades}, exected: ${expected_cascades}"; false; }

From f3c307f5232b359f64a0ed1872b2b9b88e4ef4db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 16:57:04 +0200
Subject: [PATCH 0638/1193] Properly check yaml errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config.go  | 3 +++
 lxc/profile.go | 4 ++++
 2 files changed, 7 insertions(+)

diff --git a/lxc/config.go b/lxc/config.go
index 3314ccf16..ce79c9ef4 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -344,6 +344,9 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 
 			brief := config.Writable()
 			data, err = yaml.Marshal(&brief)
+			if err != nil {
+				return err
+			}
 		} else {
 			var brief api.ContainerPut
 			if shared.IsSnapshot(container) {
diff --git a/lxc/profile.go b/lxc/profile.go
index 7860f571d..ab190f42c 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -238,6 +238,10 @@ func (c *profileCmd) doProfileShow(client *lxd.Client, p string) error {
 	}
 
 	data, err := yaml.Marshal(&profile)
+	if err != nil {
+		return err
+	}
+
 	fmt.Printf("%s", data)
 
 	return nil

From b5ef3c3b6431ad198cd68f6590001fb1274f0a86 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 16:59:16 +0200
Subject: [PATCH 0639/1193] profiles: Fix unusued variable
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_profiles.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index f7bd6b984..69956f557 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -115,7 +115,7 @@ func dbProfileCreateDefault(db *sql.DB) error {
 			"type":    "nic",
 			"nictype": "bridged",
 			"parent":  "lxdbr0"}}
-	id, err := dbProfileCreate(db, "default", "Default LXD profile", map[string]string{}, devices)
+	_, err := dbProfileCreate(db, "default", "Default LXD profile", map[string]string{}, devices)
 	if err != nil {
 		return err
 	}

From 2076f0330ccda69bee81b5e06f9e968c44dea3b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 11 Jan 2017 16:59:49 +0200
Subject: [PATCH 0640/1193] migrate: Use the generate snapshot list
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/migrate.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/migrate.go b/lxd/migrate.go
index d5f3f43fd..042b85af3 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -674,7 +674,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 				snapshots = header.Snapshots
 			}
 
-			if err := mySink(c.src.live, c.src.container, header.Snapshots, c.src.fsConn, srcIdmap); err != nil {
+			if err := mySink(c.src.live, c.src.container, snapshots, c.src.fsConn, srcIdmap); err != nil {
 				fsTransfer <- err
 				return
 			}

From c179e465cd42b7218854856a23f308e49c77f7e2 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 19 Jan 2017 00:09:18 +0100
Subject: [PATCH 0641/1193] exec: report -1 (255) on signal exit

This makes exec behave even more similar to ssh.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_exec.go | 7 ++-----
 lxd/main_forkexec.go  | 7 ++-----
 2 files changed, 4 insertions(+), 10 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 3f46153a4..b5f6415d2 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -274,12 +274,9 @@ func (s *execWs) Do(op *operation) error {
 		if status.Exited() {
 			return finisher(status.ExitStatus(), nil)
 		}
-		// Backwards compatible behavior. Report success when we exited
-		// due to a signal. Otherwise this may break Jenkins, e.g. when
-		// lxc exec foo reboot receives SIGTERM and status.Exitstats()
-		// would report -1.
+
 		if status.Signaled() {
-			return finisher(0, nil)
+			return finisher(-1, nil)
 		}
 	}
 
diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
index b9c6cbb48..9f361def0 100644
--- a/lxd/main_forkexec.go
+++ b/lxd/main_forkexec.go
@@ -122,12 +122,9 @@ func cmdForkExec(args []string) (int, error) {
 		if exCode.Exited() {
 			return exCode.ExitStatus(), nil
 		}
-		// Backwards compatible behavior. Report success when we exited
-		// due to a signal. Otherwise this may break Jenkins, e.g. when
-		// lxc exec foo reboot receives SIGTERM and exCode.Exitstats()
-		// would report -1.
+
 		if exCode.Signaled() {
-			return 0, nil
+			return -1, nil
 		}
 	}
 

From 47d2fb336ee1ebcb837b4630b19c77b5948b42ab Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 19 Jan 2017 23:25:03 +0100
Subject: [PATCH 0642/1193] exec: report exit code when we got killed by signal

The standard shell convention is to report 128 + n where n := signal that killed
the process. This signal number is usually within a reasonable range so that the
magic 8-bit wall of sound is not breached. Essentially, we now report exit codes
like a standard shell would:

1.
	chb at conventiont|~/source/go/bin
	> ./lxc exec zest1 sleep 100
	chb at conventiont|~/source/go/bin
	> echo $?
	143

caused by sending:

	kill -15 $(pidof sleep 100)

2.
	chb at conventiont|~/source/go/bin
	> ./lxc exec zest1 sleep 100
	chb at conventiont|~/source/go/bin
	> echo $?
	129

caused by sending:

	kill -HUP $(pidof sleep 100)

3.
	chb at conventiont|~/source/go/bin
	> ./lxc exec zest1 sleep 100
	chb at conventiont|~/source/go/bin
	> echo $?
	137

caused by sending:

	kill -KILL $(pidof sleep 100)

This is way more fine-grained than what ssh does and more in line with standard
shell exit codes. Also, this allows us to even report back SIGKILL in the sense
that we can tell the user, "Hey, this was the grim 137 aka the ultimate 9.".

Note that there is a caveat: If a user were to write a program that uses one of
those 128 + n exit codes as exit code than being killed by a signal would be
indistinguishable for him from success. However, the user would face the same
problem running the program in a local shell or otherwise. So I wouldn't count
this as a counter-argument. (Also, there is the fact that these exit codes are
actually reserved.)

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_exec.go | 3 ++-
 lxd/container_lxc.go  | 5 +++++
 lxd/main_forkexec.go  | 9 +++++----
 3 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index b5f6415d2..463d8b69d 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -276,7 +276,8 @@ func (s *execWs) Do(op *operation) error {
 		}
 
 		if status.Signaled() {
-			return finisher(-1, nil)
+			// COMMENT(brauner): 128 + n == Fatal error signal "n"
+			return finisher(128+int(status.Signal()), nil)
 		}
 	}
 
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 351d2a1e0..1eb049a60 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4084,6 +4084,11 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 			if ok {
 				return status.ExitStatus(), attachedPid, nil
 			}
+
+			if status.Signaled() {
+				// COMMENT(brauner): 128 + n == Fatal error signal "n"
+				return 128 + int(status.Signal()), attachedPid, nil
+			}
 		}
 		return -1, -1, err
 	}
diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
index 9f361def0..3492fac7b 100644
--- a/lxd/main_forkexec.go
+++ b/lxd/main_forkexec.go
@@ -119,12 +119,13 @@ func cmdForkExec(args []string) (int, error) {
 
 	exCode, ok := procState.Sys().(syscall.WaitStatus)
 	if ok {
-		if exCode.Exited() {
-			return exCode.ExitStatus(), nil
+		if exCode.Signaled() {
+			// COMMENT(brauner): 128 + n == Fatal error signal "n"
+			return 128 + int(exCode.Signal()), nil
 		}
 
-		if exCode.Signaled() {
-			return -1, nil
+		if exCode.Exited() {
+			return exCode.ExitStatus(), nil
 		}
 	}
 

From 3fa784f1dc76ffb81752a59c55620e0e42ec15e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 23 Jan 2017 16:34:21 -0500
Subject: [PATCH 0643/1193] Update i18n
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/lxd.pot | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/po/lxd.pot b/po/lxd.pot
index 3135e7d79..daff8d2f7 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-01-23 16:16-0500\n"
+        "POT-Creation-Date: 2017-01-23 16:34-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -155,7 +155,7 @@ msgstr  ""
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:343
+#: lxc/profile.go:347
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
@@ -189,7 +189,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:532 lxc/config.go:597 lxc/image.go:718 lxc/profile.go:190
+#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:718 lxc/profile.go:190
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -273,12 +273,12 @@ msgid   "Delete containers or snapshots.\n"
         "Destroy containers or snapshots with any attached data (configuration, snapshots, ...)."
 msgstr  ""
 
-#: lxc/config.go:649
+#: lxc/config.go:652
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:836
+#: lxc/config.go:839
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
@@ -628,11 +628,11 @@ msgstr  ""
 msgid   "Manage remote LXD servers.\n"
         "\n"
         "lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD]\n"
-        "                                      [--public] [--protocol=PROTOCOL]      Add the remote <name> at <url>.\n"
-        "lxc remote remove <remote>                                                  Remove the remote <name>.\n"
+        "                                      [--public] [--protocol=PROTOCOL]      Add the remote <remote> at <url>.\n"
+        "lxc remote remove <remote>                                                  Remove the remote <remote>.\n"
         "lxc remote list                                                             List all remotes.\n"
-        "lxc remote rename <old name> <new name>                                     Rename remote <old> to <new>.\n"
-        "lxc remote set-url <remote> <url>                                           Update <name>'s url to <url>.\n"
+        "lxc remote rename <old name> <new name>                                     Rename remote <old name> to <new name>.\n"
+        "lxc remote set-url <remote> <url>                                           Update <remote>'s url to <url>.\n"
         "lxc remote set-default <remote>                                             Set the default remote.\n"
         "lxc remote get-default                                                      Print the default remote."
 msgstr  ""
@@ -847,7 +847,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:533 lxc/config.go:598 lxc/image.go:719
+#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:719
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -1071,7 +1071,7 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/config.go:677 lxc/config.go:689 lxc/config.go:722 lxc/config.go:740 lxc/config.go:778 lxc/config.go:796
+#: lxc/config.go:680 lxc/config.go:692 lxc/config.go:725 lxc/config.go:743 lxc/config.go:781 lxc/config.go:799
 msgid   "The device doesn't exist"
 msgstr  ""
 

From 8f6875938f987a21a9487767f6f23c3c50698432 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 23 Jan 2017 17:03:30 -0500
Subject: [PATCH 0644/1193] tests: Fix go test following shared/api change
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list_test.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lxc/list_test.go b/lxc/list_test.go
index a055d033c..746fd103d 100644
--- a/lxc/list_test.go
+++ b/lxc/list_test.go
@@ -3,7 +3,6 @@ package main
 import (
 	"testing"
 
-	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
 

From d2b7fbda57df8966101a5a127a171bf6b6633c97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 23 Jan 2017 17:52:20 -0500
Subject: [PATCH 0645/1193] network: Properly detect vlans
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2809

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/networks.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/networks.go b/lxd/networks.go
index 8c452795e..ac9534743 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -118,6 +118,8 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 		n.Type = "loopback"
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", n.Name)) {
 		n.Type = "bridge"
+	} else if shared.PathExists(fmt.Sprintf("/proc/net/vlan/%s", n.Name)) {
+		n.Type = "vlan"
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/device", n.Name)) {
 		n.Type = "physical"
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bonding", n.Name)) {

From 8010d1c6484659651e363b8b91296b9ee410d88f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 23 Jan 2017 18:03:06 -0500
Subject: [PATCH 0646/1193] patches: Mark all patches as applied on create
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/db.go b/lxd/db.go
index f5161e8b6..9f98482dc 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -187,6 +187,11 @@ func createDb(db *sql.DB) (err error) {
 		return err
 	}
 
+	// Mark all existing patches as applied
+	for _, p := range patches {
+		dbPatchesMarkApplied(db, p.name)
+	}
+
 	err = dbProfileCreateDefault(db)
 	if err != nil {
 		return err

From 28c3a6c9f14d31f3f80a962ae9ca9003eccf2e80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 23 Jan 2017 20:03:18 -0500
Subject: [PATCH 0647/1193] btrfs: Don't assume a path is a subvolume
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Don't assume that any directory that's stored on a btrfs partition is
going to be a subvolume.

This fixes problems when creating containers on a btrfs-backed system
that doesn't have the btrfs tools installed.

Closes #2748

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/storage.go b/lxd/storage.go
index 8c6a7907f..cb93fb3dd 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -255,6 +255,12 @@ func storageForFilename(d *Daemon, filename string) (storage, error) {
 		if err != nil {
 			return nil, fmt.Errorf("couldn't detect filesystem for '%s': %v", filename, err)
 		}
+
+		if filesystem == "btrfs" {
+			if !(*storageBtrfs).isSubvolume(nil, filename) {
+				filesystem = ""
+			}
+		}
 	}
 
 	if shared.PathExists(filename + ".lv") {

From 868c03d0d381e3890eeaade40f2a50d2f4543a19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 23 Jan 2017 23:42:29 -0500
Subject: [PATCH 0648/1193] Close race condition in image download
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2739

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go                |  2 +-
 lxd/daemon_images.go         | 24 +++++++++++-------------
 lxd/main_activateifneeded.go |  2 +-
 lxd/main_test.go             |  2 +-
 4 files changed, 14 insertions(+), 16 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 9cad310e1..5eb6f9331 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -90,7 +90,7 @@ type Daemon struct {
 	SetupMode bool
 
 	imagesDownloading     map[string]chan bool
-	imagesDownloadingLock sync.RWMutex
+	imagesDownloadingLock sync.Mutex
 
 	tlsConfig *tls.Config
 
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 3998d7b2c..97739ab2f 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -188,10 +188,10 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	}
 
 	// Now check if we already downloading the image
-	d.imagesDownloadingLock.RLock()
+	d.imagesDownloadingLock.Lock()
 	if waitChannel, ok := d.imagesDownloading[fp]; ok {
 		// We already download the image
-		d.imagesDownloadingLock.RUnlock()
+		d.imagesDownloadingLock.Unlock()
 
 		shared.LogDebug(
 			"Already downloading the image, waiting for it to succeed",
@@ -217,18 +217,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		return fp, nil
 	}
 
-	d.imagesDownloadingLock.RUnlock()
-
-	if op == nil {
-		ctxMap = log.Ctx{"alias": alias, "server": server}
-	} else {
-		ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server}
-	}
-
-	shared.LogInfo("Downloading image", ctxMap)
-
 	// Add the download to the queue
-	d.imagesDownloadingLock.Lock()
 	d.imagesDownloading[fp] = make(chan bool)
 	d.imagesDownloadingLock.Unlock()
 
@@ -242,6 +231,15 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		d.imagesDownloadingLock.Unlock()
 	}()
 
+	// Begin downloading
+	if op == nil {
+		ctxMap = log.Ctx{"alias": alias, "server": server}
+	} else {
+		ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server}
+	}
+
+	shared.LogInfo("Downloading image", ctxMap)
+
 	exporturl := server
 
 	var info api.Image
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index 50cce63dc..b0d66d278 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -11,7 +11,7 @@ func cmdActivateIfNeeded() error {
 	// Don't start a full daemon, we just need DB access
 	d := &Daemon{
 		imagesDownloading:     map[string]chan bool{},
-		imagesDownloadingLock: sync.RWMutex{},
+		imagesDownloadingLock: sync.Mutex{},
 		lxcpath:               shared.VarPath("containers"),
 	}
 
diff --git a/lxd/main_test.go b/lxd/main_test.go
index f5f45186e..e3af4e1a5 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -16,7 +16,7 @@ func mockStartDaemon() (*Daemon, error) {
 	d := &Daemon{
 		MockMode:              true,
 		imagesDownloading:     map[string]chan bool{},
-		imagesDownloadingLock: sync.RWMutex{},
+		imagesDownloadingLock: sync.Mutex{},
 	}
 
 	if err := d.Init(); err != nil {

From 7a6d6cd54d75f5ffb06df1af80930e46d3ba3f03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 24 Jan 2017 11:43:22 -0500
Subject: [PATCH 0649/1193] Better handle timestamps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go                          |  6 +++---
 lxc/info.go                           |  4 ++--
 lxc/list.go                           |  2 +-
 shared/simplestreams/simplestreams.go |  4 ++--
 shared/util.go                        | 13 +++++++++++++
 5 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 2f161971b..f2c7ecc44 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -352,16 +352,16 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		fmt.Printf(i18n.G("Public: %s")+"\n", public)
 		fmt.Printf(i18n.G("Timestamps:") + "\n")
 		const layout = "2006/01/02 15:04 UTC"
-		if info.CreatedAt.UTC().Unix() != 0 {
+		if shared.TimeIsSet(info.CreatedAt) {
 			fmt.Printf("    "+i18n.G("Created: %s")+"\n", info.CreatedAt.UTC().Format(layout))
 		}
 		fmt.Printf("    "+i18n.G("Uploaded: %s")+"\n", info.UploadedAt.UTC().Format(layout))
-		if info.ExpiresAt.UTC().Unix() != 0 {
+		if shared.TimeIsSet(info.ExpiresAt) {
 			fmt.Printf("    "+i18n.G("Expires: %s")+"\n", info.ExpiresAt.UTC().Format(layout))
 		} else {
 			fmt.Printf("    " + i18n.G("Expires: never") + "\n")
 		}
-		if info.LastUsedAt.UTC().Unix() != 0 {
+		if shared.TimeIsSet(info.LastUsedAt) {
 			fmt.Printf("    "+i18n.G("Last used: %s")+"\n", info.LastUsedAt.UTC().Format(layout))
 		} else {
 			fmt.Printf("    " + i18n.G("Last used: never") + "\n")
diff --git a/lxc/info.go b/lxc/info.go
index 0245a7526..66287460e 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -91,7 +91,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		fmt.Printf(i18n.G("Remote: %s")+"\n", d.Remote.Addr)
 	}
 	fmt.Printf(i18n.G("Architecture: %s")+"\n", ct.Architecture)
-	if ct.CreatedAt.UTC().Unix() != 0 {
+	if shared.TimeIsSet(ct.CreatedAt) {
 		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreatedAt.UTC().Format(layout))
 	}
 
@@ -200,7 +200,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		fields := strings.Split(snap.Name, shared.SnapshotDelimiter)
 		fmt.Printf("  %s", fields[len(fields)-1])
 
-		if snap.CreationDate.UTC().Unix() != 0 {
+		if shared.TimeIsSet(snap.CreationDate) {
 			fmt.Printf(" ("+i18n.G("taken at %s")+")", snap.CreationDate.UTC().Format(layout))
 		}
 
diff --git a/lxc/list.go b/lxc/list.go
index eb2f8a11f..f01f71074 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -512,7 +512,7 @@ func (c *listCmd) ProfilesColumnData(cInfo api.Container, cState *api.ContainerS
 func (c *listCmd) CreatedColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {
 	layout := "2006/01/02 15:04 UTC"
 
-	if cInfo.CreatedAt.UTC().Unix() != 0 {
+	if shared.TimeIsSet(cInfo.CreatedAt) {
 		return cInfo.CreatedAt.UTC().Format(layout)
 	}
 
diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index d71fcd393..490cc4f0c 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -32,11 +32,11 @@ func (a ssSortImage) Swap(i, j int) {
 func (a ssSortImage) Less(i, j int) bool {
 	if a[i].Properties["os"] == a[j].Properties["os"] {
 		if a[i].Properties["release"] == a[j].Properties["release"] {
-			if a[i].CreatedAt.UTC().Unix() == 0 {
+			if !shared.TimeIsSet(a[i].CreatedAt) {
 				return true
 			}
 
-			if a[j].CreatedAt.UTC().Unix() == 0 {
+			if !shared.TimeIsSet(a[j].CreatedAt) {
 				return false
 			}
 
diff --git a/shared/util.go b/shared/util.go
index e97b00712..c048b411c 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -19,6 +19,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"time"
 	"unicode"
 )
 
@@ -753,3 +754,15 @@ func RunCommand(name string, arg ...string) error {
 
 	return nil
 }
+
+func TimeIsSet(ts time.Time) bool {
+	if ts.Unix() <= 0 {
+		return false
+	}
+
+	if ts.UTC().Unix() <= 0 {
+		return false
+	}
+
+	return true
+}

From f773c22c5dfc9e9857b9075ecd73ffe203642c01 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 24 Jan 2017 14:02:11 -0500
Subject: [PATCH 0650/1193] Improve error handling and reporting during export
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Related to issue #2801

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 34 ++++++++++++++++++++++++----------
 1 file changed, 24 insertions(+), 10 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 1eb049a60..da017f9c1 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3376,21 +3376,30 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 
 	// Include all the rootfs files
 	fnam = c.RootfsPath()
-	filepath.Walk(fnam, writeToTar)
+	err = filepath.Walk(fnam, writeToTar)
+	if err != nil {
+		shared.LogError("Failed exporting container", ctxMap)
+		return err
+	}
 
 	// Include all the templates
 	fnam = c.TemplatesPath()
 	if shared.PathExists(fnam) {
-		filepath.Walk(fnam, writeToTar)
+		err = filepath.Walk(fnam, writeToTar)
+		if err != nil {
+			shared.LogError("Failed exporting container", ctxMap)
+			return err
+		}
 	}
 
 	err = tw.Close()
 	if err != nil {
 		shared.LogError("Failed exporting container", ctxMap)
+		return err
 	}
 
 	shared.LogInfo("Exported container", ctxMap)
-	return err
+	return nil
 }
 
 func collectCRIULogFile(c container, imagesDir string, function string, method string) error {
@@ -4253,13 +4262,15 @@ func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *t
 	if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
 		link, err = os.Readlink(path)
 		if err != nil {
-			return err
+			return fmt.Errorf("failed to resolve symlink: %s", err)
 		}
 	}
+
 	hdr, err := tar.FileInfoHeader(fi, link)
 	if err != nil {
-		return err
+		return fmt.Errorf("failed to create tar info header: %s", err)
 	}
+
 	hdr.Name = path[offset:]
 	if fi.IsDir() || fi.Mode()&os.ModeSymlink == os.ModeSymlink {
 		hdr.Size = 0
@@ -4269,7 +4280,7 @@ func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *t
 
 	hdr.Uid, hdr.Gid, major, minor, ino, nlink, err = shared.GetFileStat(path)
 	if err != nil {
-		return fmt.Errorf("error getting file info: %s", err)
+		return fmt.Errorf("failed to get file stat: %s", err)
 	}
 
 	// Unshift the id under /rootfs/ for unpriv containers
@@ -4279,6 +4290,7 @@ func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *t
 			return nil
 		}
 	}
+
 	if major != -1 {
 		hdr.Devmajor = int64(major)
 		hdr.Devminor = int64(minor)
@@ -4298,23 +4310,25 @@ func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *t
 	// Handle xattrs.
 	hdr.Xattrs, err = shared.GetAllXattr(path)
 	if err != nil {
-		return err
+		return fmt.Errorf("failed to read xattr: %s", err)
 	}
 
 	if err := tw.WriteHeader(hdr); err != nil {
-		return fmt.Errorf("error writing header: %s", err)
+		return fmt.Errorf("failed to write tar header: %s", err)
 	}
 
 	if hdr.Typeflag == tar.TypeReg {
 		f, err := os.Open(path)
 		if err != nil {
-			return fmt.Errorf("tarStoreFile: error opening file: %s", err)
+			return fmt.Errorf("failed to open the file: %s", err)
 		}
 		defer f.Close()
+
 		if _, err := io.Copy(tw, f); err != nil {
-			return fmt.Errorf("error copying file %s", err)
+			return fmt.Errorf("failed to copy file content: %s", err)
 		}
 	}
+
 	return nil
 }
 

From 0d7db0036e7024ba97a0aa45084e0bc1c440df6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 24 Jan 2017 15:45:59 -0500
Subject: [PATCH 0651/1193] Don't attempt to read xattrs from symlinks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2801

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index da017f9c1..0753b48ba 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4307,10 +4307,12 @@ func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *t
 		}
 	}
 
-	// Handle xattrs.
-	hdr.Xattrs, err = shared.GetAllXattr(path)
-	if err != nil {
-		return fmt.Errorf("failed to read xattr: %s", err)
+	// Handle xattrs (for real files only)
+	if link == "" {
+		hdr.Xattrs, err = shared.GetAllXattr(path)
+		if err != nil {
+			return fmt.Errorf("failed to read xattr: %s", err)
+		}
 	}
 
 	if err := tw.WriteHeader(hdr); err != nil {

From 0fdc6df49018f35797a2bfedf6a088a016cc367d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 24 Jan 2017 16:32:32 -0500
Subject: [PATCH 0652/1193] Remove GroupName function and add UserId one
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/util_linux.go | 46 ++++++++++++++++++++++------------------------
 1 file changed, 22 insertions(+), 24 deletions(-)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 9ce2fb05c..87b44d6e7 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -19,18 +19,19 @@ import (
 // #cgo LDFLAGS: -lutil -lpthread
 /*
 #define _GNU_SOURCE
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <grp.h>
-#include <pty.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <grp.h>
 #include <limits.h>
 #include <poll.h>
-#include <string.h>
+#include <pty.h>
+#include <pwd.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #ifndef AT_SYMLINK_FOLLOW
 #define AT_SYMLINK_FOLLOW    0x400
@@ -277,36 +278,36 @@ func Pipe() (master *os.File, slave *os.File, err error) {
 	return master, slave, nil
 }
 
-// GroupName is an adaption from https://codereview.appspot.com/4589049.
-func GroupName(gid int) (string, error) {
-	var grp C.struct_group
-	var result *C.struct_group
+// UserId is an adaption from https://codereview.appspot.com/4589049.
+func UserId(name string) (int, error) {
+	var pw C.struct_passwd
+	var result *C.struct_passwd
 
-	bufSize := C.size_t(C.sysconf(C._SC_GETGR_R_SIZE_MAX))
+	bufSize := C.size_t(C.sysconf(C._SC_GETPW_R_SIZE_MAX))
 	buf := C.malloc(bufSize)
 	if buf == nil {
-		return "", fmt.Errorf("allocation failed")
+		return -1, fmt.Errorf("allocation failed")
 	}
 	defer C.free(buf)
 
-	// mygetgrgid_r is a wrapper around getgrgid_r to
-	// to avoid using gid_t because C.gid_t(gid) for
-	// unknown reasons doesn't work on linux.
-	rv := C.mygetgrgid_r(C.int(gid),
-		&grp,
+	cname := C.CString(name)
+	defer C.free(unsafe.Pointer(cname))
+
+	rv := C.getpwnam_r(cname,
+		&pw,
 		(*C.char)(buf),
 		bufSize,
 		&result)
 
 	if rv != 0 {
-		return "", fmt.Errorf("failed group lookup: %s", syscall.Errno(rv))
+		return -1, fmt.Errorf("failed user lookup: %s", syscall.Errno(rv))
 	}
 
 	if result == nil {
-		return "", fmt.Errorf("unknown group %d", gid)
+		return -1, fmt.Errorf("unknown user %s", name)
 	}
 
-	return C.GoString(result.gr_name), nil
+	return int(C.int(result.pw_uid)), nil
 }
 
 // GroupId is an adaption from https://codereview.appspot.com/4589049.
@@ -321,9 +322,6 @@ func GroupId(name string) (int, error) {
 	}
 	defer C.free(buf)
 
-	// mygetgrgid_r is a wrapper around getgrgid_r to
-	// to avoid using gid_t because C.gid_t(gid) for
-	// unknown reasons doesn't work on linux.
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 

From 08c9881d2935f2c881b70a23f89952f7141b487a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 25 Jan 2017 13:06:27 -0500
Subject: [PATCH 0653/1193] zfs: Simplify device tracking logic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/devices.go | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index 2a517e94d..455f7d122 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -692,7 +692,7 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 		// Accessible zfs filesystems
 		poolName := strings.Split(device[1], "/")[0]
 
-		output, err := exec.Command("zpool", "status", poolName).CombinedOutput()
+		output, err := exec.Command("zpool", "status", "-P", "-L", poolName).CombinedOutput()
 		if err != nil {
 			return nil, fmt.Errorf("Failed to query zfs filesystem information for %s: %s", device[1], output)
 		}
@@ -727,12 +727,6 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 						devices = append(devices, dev)
 					}
 				}
-			} else if deviceIsBlockdev(fmt.Sprintf("/dev/%s", fields[0])) {
-				path = fmt.Sprintf("/dev/%s", fields[0])
-			} else if deviceIsBlockdev(fmt.Sprintf("/dev/disk/by-id/%s", fields[0])) {
-				path = fmt.Sprintf("/dev/disk/by-id/%s", fields[0])
-			} else if deviceIsBlockdev(fmt.Sprintf("/dev/mapper/%s", fields[0])) {
-				path = fmt.Sprintf("/dev/mapper/%s", fields[0])
 			} else {
 				continue
 			}

From 855957490fe73df6cf608c0668c4820c002482bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 26 Jan 2017 13:25:54 -0500
Subject: [PATCH 0654/1193] Don't block resolution on non-existing paths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0753b48ba..7b9c8033d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5303,6 +5303,11 @@ func (c *containerLXC) getDiskLimits() (map[string]deviceBlockLimit, error) {
 			source = c.RootfsPath()
 		}
 
+		// Don't try to resolve the block device behind a non-existing path
+		if !shared.PathExists(source) {
+			continue
+		}
+
 		// Get the backing block devices (major:minor)
 		blocks, err := deviceGetParentBlocks(source)
 		if err != nil {

From 7b8084db3564e9ef9be6aa750ffd6cb480cae977 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 26 Jan 2017 22:47:41 -0500
Subject: [PATCH 0655/1193] Fix bad cherry-pick
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 file.dat | 1 -
 1 file changed, 1 deletion(-)
 delete mode 100644 file.dat

diff --git a/file.dat b/file.dat
deleted file mode 100644
index 8baef1b4a..000000000
--- a/file.dat
+++ /dev/null
@@ -1 +0,0 @@
-abc

From b976532c0fe9064efd29eeea3341ed85024fd23c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 26 Jan 2017 22:48:04 -0500
Subject: [PATCH 0656/1193] Release LXD 2.0.9
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/version/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/version/flex.go b/shared/version/flex.go
index dba6c9217..03c8fa76c 100644
--- a/shared/version/flex.go
+++ b/shared/version/flex.go
@@ -3,7 +3,7 @@
  */
 package version
 
-var Version = "2.0.8"
+var Version = "2.0.9"
 var UserAgent = "LXD " + Version
 
 /*

From 2b04469e94479ea84d74395fb74a382513bba11e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 27 Jan 2017 13:12:59 -0500
Subject: [PATCH 0657/1193] Makefile: Use system libsqlite3 if available
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile
index 09c537efd..3970a1131 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@ POTFILE=po/$(DOMAIN).pot
 # TODO: use git describe for versioning
 VERSION=$(shell grep "var Version" shared/version/flex.go | cut -d'"' -f2)
 ARCHIVE=lxd-$(VERSION).tar
+TAGS=$(shell test -e /usr/include/sqlite3.h && echo "-tags libsqlite3")
 
 .PHONY: default
 default:
@@ -16,7 +17,7 @@ default:
 	-go get -t -v -d ./...
 	-go get -t -v -d ./...
 	-go get -t -v -d ./...
-	go install -v $(DEBUG) ./...
+	go install -v $(TAGS) $(DEBUG) ./...
 	@echo "LXD built successfully"
 
 .PHONY: client
@@ -25,7 +26,7 @@ client:
 	-go get -t -v -d ./...
 	-go get -t -v -d ./...
 	-go get -t -v -d ./...
-	go install -v $(DEBUG) ./lxc
+	go install -v $(TAGS) $(DEBUG) ./lxc
 	@echo "LXD client built successfully"
 
 .PHONY: update
@@ -47,11 +48,11 @@ protobuf:
 check: default
 	go get -v -x github.com/rogpeppe/godeps
 	go get -v -x github.com/remyoudompheng/go-misc/deadcode
-	go test -v ./...
+	go test -v $(TAGS) $(DEBUG) ./...
 	cd test && ./main.sh
 
 gccgo:
-	go build -compiler gccgo ./...
+	go build -v $(TAGS) $(DEBUG) -compiler gccgo ./...
 	@echo "LXD built successfully with gccgo"
 
 .PHONY: dist
@@ -99,7 +100,6 @@ update-pot:
 	go get -v -x github.com/snapcore/snapd/i18n/xgettext-go/
 	xgettext-go -o po/$(DOMAIN).pot --add-comments-tag=TRANSLATORS: --sort-output --package-name=$(DOMAIN) --msgid-bugs-address=lxc-devel at lists.linuxcontainers.org --keyword=i18n.G --keyword-plural=i18n.NG *.go shared/*.go lxc/*.go lxd/*.go
 
-
 build-mo: $(MOFILES)
 
 static-analysis:

From 6faf9854675ebcaedd9ba60bca7747d69cb4730d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 27 Jan 2017 13:17:18 -0500
Subject: [PATCH 0658/1193] Raise DB lock timeout to 30s, retry every 30ms
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2826

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/lxd/db.go b/lxd/db.go
index 9f98482dc..b0bce05bb 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -273,7 +273,7 @@ func isNoMatchError(err error) bool {
 }
 
 func dbBegin(db *sql.DB) (*sql.Tx, error) {
-	for i := 0; i < 100; i++ {
+	for i := 0; i < 1000; i++ {
 		tx, err := db.Begin()
 		if err == nil {
 			return tx, nil
@@ -282,7 +282,7 @@ func dbBegin(db *sql.DB) (*sql.Tx, error) {
 			shared.LogDebugf("DbBegin: error %q", err)
 			return nil, err
 		}
-		time.Sleep(100 * time.Millisecond)
+		time.Sleep(30 * time.Millisecond)
 	}
 
 	shared.LogDebugf("DbBegin: DB still locked")
@@ -291,7 +291,7 @@ func dbBegin(db *sql.DB) (*sql.Tx, error) {
 }
 
 func txCommit(tx *sql.Tx) error {
-	for i := 0; i < 100; i++ {
+	for i := 0; i < 1000; i++ {
 		err := tx.Commit()
 		if err == nil {
 			return nil
@@ -300,7 +300,7 @@ func txCommit(tx *sql.Tx) error {
 			shared.LogDebugf("Txcommit: error %q", err)
 			return err
 		}
-		time.Sleep(100 * time.Millisecond)
+		time.Sleep(30 * time.Millisecond)
 	}
 
 	shared.LogDebugf("Txcommit: db still locked")
@@ -309,7 +309,7 @@ func txCommit(tx *sql.Tx) error {
 }
 
 func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interface{}) error {
-	for i := 0; i < 100; i++ {
+	for i := 0; i < 1000; i++ {
 		err := db.QueryRow(q, args...).Scan(outargs...)
 		if err == nil {
 			return nil
@@ -320,7 +320,7 @@ func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interfac
 		if !isDbLockedError(err) {
 			return err
 		}
-		time.Sleep(100 * time.Millisecond)
+		time.Sleep(30 * time.Millisecond)
 	}
 
 	shared.LogDebugf("DbQueryRowScan: query %q args %q, DB still locked", q, args)
@@ -329,7 +329,7 @@ func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interfac
 }
 
 func dbQuery(db *sql.DB, q string, args ...interface{}) (*sql.Rows, error) {
-	for i := 0; i < 100; i++ {
+	for i := 0; i < 1000; i++ {
 		result, err := db.Query(q, args...)
 		if err == nil {
 			return result, nil
@@ -338,7 +338,7 @@ func dbQuery(db *sql.DB, q string, args ...interface{}) (*sql.Rows, error) {
 			shared.LogDebugf("DbQuery: query %q error %q", q, err)
 			return nil, err
 		}
-		time.Sleep(100 * time.Millisecond)
+		time.Sleep(30 * time.Millisecond)
 	}
 
 	shared.LogDebugf("DbQuery: query %q args %q, DB still locked", q, args)
@@ -409,7 +409,7 @@ func doDbQueryScan(db *sql.DB, q string, args []interface{}, outargs []interface
  * of interfaces, containing pointers to the actual output arguments.
  */
 func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{}) ([][]interface{}, error) {
-	for i := 0; i < 100; i++ {
+	for i := 0; i < 1000; i++ {
 		result, err := doDbQueryScan(db, q, inargs, outfmt)
 		if err == nil {
 			return result, nil
@@ -418,7 +418,7 @@ func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{
 			shared.LogDebugf("DbQuery: query %q error %q", q, err)
 			return nil, err
 		}
-		time.Sleep(100 * time.Millisecond)
+		time.Sleep(30 * time.Millisecond)
 	}
 
 	shared.LogDebugf("DbQueryscan: query %q inargs %q, DB still locked", q, inargs)
@@ -427,7 +427,7 @@ func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{
 }
 
 func dbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) {
-	for i := 0; i < 100; i++ {
+	for i := 0; i < 1000; i++ {
 		result, err := db.Exec(q, args...)
 		if err == nil {
 			return result, nil
@@ -436,7 +436,7 @@ func dbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) {
 			shared.LogDebugf("DbExec: query %q error %q", q, err)
 			return nil, err
 		}
-		time.Sleep(100 * time.Millisecond)
+		time.Sleep(30 * time.Millisecond)
 	}
 
 	shared.LogDebugf("DbExec: query %q args %q, DB still locked", q, args)

From 2c3e357cd468a37e929ca195a81cfd6a532a7bb0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 27 Jan 2017 16:46:55 -0500
Subject: [PATCH 0659/1193] Fix partial image fingerprint matches
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We should always ensure that we have a single result.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_images.go | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/lxd/db_images.go b/lxd/db_images.go
index c3aef6798..c8bc4fa94 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -152,11 +152,26 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 	}
 
 	err = dbQueryRowScan(db, query, inargs, outfmt)
-
 	if err != nil {
 		return -1, nil, err // Likely: there are no rows for this fingerprint
 	}
 
+	// Validate we only have a single match
+	if !strictMatching {
+		query = "SELECT COUNT(id) FROM images WHERE fingerprint LIKE ?"
+		count := 0
+		outfmt := []interface{}{&count}
+
+		err = dbQueryRowScan(db, query, inargs, outfmt)
+		if err != nil {
+			return -1, nil, err
+		}
+
+		if count > 1 {
+			return -1, nil, fmt.Errorf("Partial fingerprint matches more than one image")
+		}
+	}
+
 	// Some of the dates can be nil in the DB, let's process them.
 	if create != nil {
 		image.CreatedAt = *create

From 867c21eb53ab35d136196758a23401e1f37568e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 28 Jan 2017 17:16:29 -0500
Subject: [PATCH 0660/1193] tests: Properly cleanup in template testsuite
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/template.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/template.sh b/test/suites/template.sh
index 90c452f65..398b79b34 100644
--- a/test/suites/template.sh
+++ b/test/suites/template.sh
@@ -85,5 +85,5 @@ test_template() {
 
   # Cleanup
   lxc image delete template-test
-  lxc delete template --force
+  lxc delete template template1 --force
 }

From fa86acbc628012d9ee7e6fa1cd68458af558efc4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 30 Jan 2017 16:34:41 -0500
Subject: [PATCH 0661/1193] simplestreams: Always prefer squashfs when
 available
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Makes things less random for users and is usually a bit faster to unpack.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams/simplestreams.go | 76 ++++++++++++++++++-----------------
 1 file changed, 39 insertions(+), 37 deletions(-)

diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index 490cc4f0c..6ca766687 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -107,58 +107,60 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 				continue
 			}
 
-			size := int64(0)
-			filename := ""
-			fingerprint := ""
+			var meta SimpleStreamsManifestProductVersionItem
+			var rootTar SimpleStreamsManifestProductVersionItem
+			var rootSquash SimpleStreamsManifestProductVersionItem
 
-			metaPath := ""
-			metaHash := ""
-			rootfsPath := ""
-			rootfsHash := ""
-
-			found := 0
 			for _, item := range version.Items {
 				// Skip the files we don't care about
 				if !shared.StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz", "squashfs"}) {
 					continue
 				}
-				found += 1
-
-				if fingerprint == "" {
-					if item.LXDHashSha256SquashFs != "" {
-						fingerprint = item.LXDHashSha256SquashFs
-					} else if item.LXDHashSha256RootXz != "" {
-						fingerprint = item.LXDHashSha256RootXz
-					} else if item.LXDHashSha256 != "" {
-						fingerprint = item.LXDHashSha256
-					}
-				}
 
 				if item.FileType == "lxd.tar.xz" {
-					fields := strings.Split(item.Path, "/")
-					filename = fields[len(fields)-1]
-					metaPath = item.Path
-					metaHash = item.HashSha256
-
-					size += item.Size
+					meta = item
+				} else if item.FileType == "squashfs" {
+					rootSquash = item
+				} else if item.FileType == "root.tar.xz" {
+					rootTar = item
 				}
+			}
 
-				if rootfsPath == "" || rootfsHash == "" {
-					if item.FileType == "squashfs" {
-						rootfsPath = item.Path
-						rootfsHash = item.HashSha256
-					}
+			if meta.FileType == "" || (rootTar.FileType == "" && rootSquash.FileType == "") {
+				// Invalid image
+				continue
+			}
 
-					if item.FileType == "root.tar.xz" {
-						rootfsPath = item.Path
-						rootfsHash = item.HashSha256
-					}
+			metaPath := meta.Path
+			metaHash := meta.HashSha256
+			rootfsPath := ""
+			rootfsHash := ""
+			fields := strings.Split(meta.Path, "/")
+			filename := fields[len(fields)-1]
+			size := meta.Size
+			fingerprint := ""
 
-					size += item.Size
+			if rootSquash.FileType != "" {
+				if meta.LXDHashSha256SquashFs != "" {
+					fingerprint = meta.LXDHashSha256SquashFs
+				} else {
+					fingerprint = meta.LXDHashSha256
+				}
+				size += rootSquash.Size
+				rootfsPath = rootSquash.Path
+				rootfsHash = rootSquash.HashSha256
+			} else {
+				if meta.LXDHashSha256RootXz != "" {
+					fingerprint = meta.LXDHashSha256RootXz
+				} else {
+					fingerprint = meta.LXDHashSha256
 				}
+				size += rootTar.Size
+				rootfsPath = rootTar.Path
+				rootfsHash = rootTar.HashSha256
 			}
 
-			if found < 2 || size == 0 || filename == "" || fingerprint == "" {
+			if size == 0 || filename == "" || fingerprint == "" {
 				// Invalid image
 				continue
 			}

From 7f9077563e551ca1019272058f0acb1e683e2b34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 30 Jan 2017 17:38:34 -0500
Subject: [PATCH 0662/1193] btrfs: Fix recursive subvol deletion
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Always iterate through the filesystem to find the subvolumes as there
   doesn't seem to be a good way to get a long tree of nested subvolumes
   out of btrfs subvolume list.

 - To improve performance of above, change isSubvolume to do a single
   syscall and not fork btrfs.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_btrfs.go | 129 ++++++++++++---------------------------------------
 1 file changed, 30 insertions(+), 99 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 845fd7222..3f0a8ec68 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -7,6 +7,7 @@ import (
 	"os/exec"
 	"path"
 	"path/filepath"
+	"sort"
 	"strconv"
 	"strings"
 	"syscall"
@@ -693,38 +694,14 @@ func (s *storageBtrfs) subvolsSnapshot(
  * else false.
  */
 func (s *storageBtrfs) isSubvolume(subvolPath string) bool {
-	if runningInUserns {
-		// subvolume show is restricted to real root, use a workaround
-
-		fs := syscall.Statfs_t{}
-		err := syscall.Statfs(subvolPath, &fs)
-		if err != nil {
-			return false
-		}
-
-		if fs.Type != filesystemSuperMagicBtrfs {
-			return false
-		}
-
-		parentFs := syscall.Statfs_t{}
-		err = syscall.Statfs(path.Dir(subvolPath), &parentFs)
-		if err != nil {
-			return false
-		}
-
-		if fs.Fsid == parentFs.Fsid {
-			return false
-		}
-
-		return true
+	fs := syscall.Stat_t{}
+	err := syscall.Lstat(subvolPath, &fs)
+	if err != nil {
+		return false
 	}
 
-	output, err := exec.Command(
-		"btrfs",
-		"subvolume",
-		"show",
-		subvolPath).CombinedOutput()
-	if err != nil || strings.HasPrefix(string(output), "ERROR: ") {
+	// Check if BTRFS_FIRST_FREE_OBJECTID
+	if fs.Ino != 256 {
 		return false
 	}
 
@@ -735,82 +712,36 @@ func (s *storageBtrfs) isSubvolume(subvolPath string) bool {
 func (s *storageBtrfs) getSubVolumes(path string) ([]string, error) {
 	result := []string{}
 
-	if runningInUserns {
-		if !strings.HasSuffix(path, "/") {
-			path = path + "/"
-		}
-
-		// Unprivileged users can't get to fs internals
-		filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error {
-			if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
-				return nil
-			}
-
-			if err != nil {
-				return nil
-			}
-
-			if !fi.IsDir() {
-				return nil
-			}
-
-			if s.isSubvolume(fpath) {
-				result = append(result, strings.TrimPrefix(fpath, path))
-			}
-			return nil
-		})
-
-		return result, nil
+	if !strings.HasSuffix(path, "/") {
+		path = path + "/"
 	}
 
-	out, err := exec.Command(
-		"btrfs",
-		"inspect-internal",
-		"rootid",
-		path).CombinedOutput()
-	if err != nil {
-		return result, fmt.Errorf(
-			"Unable to get btrfs rootid, path='%s', err='%s'",
-			path,
-			err)
-	}
-	rootid := strings.TrimRight(string(out), "\n")
+	// Unprivileged users can't get to fs internals
+	filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error {
+		// Skip walk errors
+		if err != nil {
+			return nil
+		}
 
-	out, err = exec.Command(
-		"btrfs",
-		"inspect-internal",
-		"subvolid-resolve",
-		rootid, path).CombinedOutput()
-	if err != nil {
-		return result, fmt.Errorf(
-			"Unable to resolve btrfs rootid, path='%s', err='%s'",
-			path,
-			err)
-	}
-	basePath := strings.TrimRight(string(out), "\n")
+		// Ignore the base path
+		if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
+			return nil
+		}
 
-	out, err = exec.Command(
-		"btrfs",
-		"subvolume",
-		"list",
-		"-o",
-		path).CombinedOutput()
-	if err != nil {
-		return result, fmt.Errorf(
-			"Unable to list subvolumes, path='%s', err='%s'",
-			path,
-			err)
-	}
+		// Subvolumes can only be directories
+		if !fi.IsDir() {
+			return nil
+		}
 
-	lines := strings.Split(string(out), "\n")
-	for _, line := range lines {
-		if line == "" {
-			continue
+		// Check if a btrfs subvolume
+		if s.isSubvolume(fpath) {
+			result = append(result, strings.TrimPrefix(fpath, path))
 		}
 
-		cols := strings.Fields(line)
-		result = append(result, cols[8][len(basePath):])
-	}
+		return nil
+	})
+
+	sort.Sort(sort.Reverse(sort.StringSlice(result)))
 
 	return result, nil
 }

From d0e910f49888dfd217ce69932482c5df3bc5eecd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 30 Jan 2017 17:47:00 -0500
Subject: [PATCH 0663/1193] Set default values for USER, HOME and LANG
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

USER and HOME were already set by our default client but will now be set
by the daemon for any exec session which doesn't specify another value.

Closes #2830

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/exec.go           |  3 +++
 lxd/container_exec.go | 19 +++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/lxc/exec.go b/lxc/exec.go
index ce5466645..f73257269 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -96,6 +96,9 @@ func (c *execCmd) run(config *lxd.Config, args []string) error {
 		return err
 	}
 
+	/* FIXME: Default values for HOME and USER are now handled by LXD.
+	   This code should be removed after most users upgraded.
+	*/
 	env := map[string]string{"HOME": "/root", "USER": "root"}
 	if myTerm, ok := c.getTERM(); ok {
 		env["TERM"] = myTerm
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 463d8b69d..34c25d99a 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -323,6 +323,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		}
 	}
 
+	// Set default value for PATH
 	_, ok := env["PATH"]
 	if !ok {
 		env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
@@ -331,6 +332,24 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		}
 	}
 
+	// Set default value for HOME
+	_, ok = env["HOME"]
+	if !ok {
+		env["HOME"] = "/root"
+	}
+
+	// Set default value for USER
+	_, ok = env["USER"]
+	if !ok {
+		env["USER"] = "root"
+	}
+
+	// Set default value for USER
+	_, ok = env["LANG"]
+	if !ok {
+		env["LANG"] = "C.UTF-8"
+	}
+
 	if post.WaitForWS {
 		ws := &execWs{}
 		ws.fds = map[int]string{}

From f415e013d11060368ce4187d068a120a09b4b6ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 1 Feb 2017 19:01:47 +0100
Subject: [PATCH 0664/1193] Don't report migration success on failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7b9c8033d..d7c9f6e46 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3579,11 +3579,13 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 			shared.LogInfo("Failed migrating container", ctxMap)
 			migrateErr = fmt.Errorf("%s %s failed\n%s", function, prettyCmd, log)
 		}
+
+		return migrateErr
 	}
 
 	shared.LogInfo("Migrated container", ctxMap)
 
-	return migrateErr
+	return nil
 }
 
 func (c *containerLXC) TemplateApply(trigger string) error {

From 47d212066ca516f392155367213fd37dfbdba207 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 3 Feb 2017 11:29:19 +0100
Subject: [PATCH 0665/1193] db: actually enable foreign keys per connection

According to: https://www.sqlite.org/pragma.html#pragma_foreign_keys (and
the comments in the code deleted by this patch); the foreign keys pragma
needs to be set per connection to a sqlite database. The problem here is
that the golang sql driver hides the fact that there might be more than one
connection, and they're pooled, so only the first connection would have
foreign keys enabled. This means that e.g. the constraints aren't enforced
on other connections that were automatically created by the sql driver.

This patch installs our own sql driver and uses that, which uses the
sqlite3 hook interface to always enable foreign keys on every connection
that's created, so we don't have this problem any more.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/db.go | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/lxd/db.go b/lxd/db.go
index b0bce05bb..2d9c777d5 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -167,6 +167,15 @@ CREATE TABLE IF NOT EXISTS schema (
     UNIQUE (version)
 );`
 
+func enableForeignKeys(conn *sqlite3.SQLiteConn) error {
+	_, err := conn.Exec("PRAGMA foreign_keys=ON;", nil)
+	return err
+}
+
+func init() {
+	sql.Register("sqlite3_with_fk", &sqlite3.SQLiteDriver{ConnectHook: enableForeignKeys})
+}
+
 // Create the initial (current) schema for a given SQLite DB connection.
 func createDb(db *sql.DB) (err error) {
 	latestVersion := dbGetSchema(db)
@@ -226,7 +235,7 @@ func initializeDbObject(d *Daemon, path string) (err error) {
 	openPath = fmt.Sprintf("%s?_busy_timeout=%d&_txlock=exclusive", path, timeout*1000)
 
 	// Open the database. If the file doesn't exist it is created.
-	d.db, err = sql.Open("sqlite3", openPath)
+	d.db, err = sql.Open("sqlite3_with_fk", openPath)
 	if err != nil {
 		return err
 	}
@@ -237,9 +246,6 @@ func initializeDbObject(d *Daemon, path string) (err error) {
 		return fmt.Errorf("Error creating database: %s", err)
 	}
 
-	// Run PRAGMA statements now since they are *per-connection*.
-	d.db.Exec("PRAGMA foreign_keys=ON;") // This allows us to use ON DELETE CASCADE
-
 	// Apply any update
 	err = dbUpdatesApplyAll(d)
 	if err != nil {

From 576c136e3a0ceeeb82443fc563fa9d7594ab211b Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 3 Feb 2017 11:37:43 +0100
Subject: [PATCH 0666/1193] db: remove some extra cleanup code

since we have foregin key support for real now, we shouldn't need this.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/db_images.go | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/lxd/db_images.go b/lxd/db_images.go
index c8bc4fa94..4672be06f 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -248,9 +248,6 @@ func dbImageDelete(db *sql.DB, id int) error {
 		return err
 	}
 
-	_, _ = tx.Exec("DELETE FROM images_aliases WHERE image_id=?", id)
-	_, _ = tx.Exec("DELETE FROM images_properties WHERE image_id=?", id)
-	_, _ = tx.Exec("DELETE FROM images_source WHERE image_id=?", id)
 	_, _ = tx.Exec("DELETE FROM images WHERE id=?", id)
 
 	if err := txCommit(tx); err != nil {

From 362c038763606297dac4673e4add076b876613f9 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Sat, 4 Feb 2017 13:13:26 +0100
Subject: [PATCH 0667/1193] shared: FileCopy should keep the same mode

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/util.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/shared/util.go b/shared/util.go
index c048b411c..fcd480c44 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -267,10 +267,15 @@ func FileCopy(source string, dest string) error {
 	}
 	defer s.Close()
 
+	fi, err := s.Stat()
+	if err != nil {
+		return err
+	}
+
 	d, err := os.Create(dest)
 	if err != nil {
 		if os.IsExist(err) {
-			d, err = os.OpenFile(dest, os.O_WRONLY, 0700)
+			d, err = os.OpenFile(dest, os.O_WRONLY, fi.Mode())
 			if err != nil {
 				return err
 			}

From 49f32ac773438d7a719a584a6f581354ef81a373 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Sat, 4 Feb 2017 13:53:50 +0100
Subject: [PATCH 0668/1193] shared: FileCopy should also keep owner

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/util.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/shared/util.go b/shared/util.go
index fcd480c44..ee22b0e4b 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -286,7 +286,12 @@ func FileCopy(source string, dest string) error {
 	defer d.Close()
 
 	_, err = io.Copy(d, s)
-	return err
+	if err != nil {
+		return err
+	}
+
+	_, uid, gid := GetOwnerMode(fi)
+	return d.Chown(uid, gid)
 }
 
 type BytesReadCloser struct {

From acd8021a14350431486eff0e19e587d6a394e443 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Sat, 4 Feb 2017 15:02:26 +0100
Subject: [PATCH 0669/1193] util: don't do chown on windows

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 shared/util.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/shared/util.go b/shared/util.go
index ee22b0e4b..f159d5a9a 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -17,6 +17,7 @@ import (
 	"path/filepath"
 	"reflect"
 	"regexp"
+	"runtime"
 	"strconv"
 	"strings"
 	"time"
@@ -290,8 +291,13 @@ func FileCopy(source string, dest string) error {
 		return err
 	}
 
-	_, uid, gid := GetOwnerMode(fi)
-	return d.Chown(uid, gid)
+	/* chown not supported on windows */
+	if runtime.GOOS != "windows" {
+		_, uid, gid := GetOwnerMode(fi)
+		return d.Chown(uid, gid)
+	}
+
+	return nil
 }
 
 type BytesReadCloser struct {

From 882f59b5b3be74ad67dd7281226be2a744810c4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=AD=99=E5=BA=9A=E6=B3=BD?= <690388648 at qq.com>
Date: Wed, 8 Feb 2017 09:34:14 +0800
Subject: [PATCH 0670/1193] The 'storageBackend' has already been checked.

The 'storageBackend' has already been checked.
Signed-off-by:sungengze
---
 lxd/main_init.go | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/lxd/main_init.go b/lxd/main_init.go
index 76ddb1fd2..172ed35ca 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -323,10 +323,6 @@ they otherwise would.
 		}
 	}
 
-	if !shared.StringInSlice(storageBackend, []string{"dir", "zfs"}) {
-		return fmt.Errorf("Invalid storage backend: %s", storageBackend)
-	}
-
 	// Unset all storage keys, core.https_address and core.trust_password
 	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
 		_, err = c.SetServerConfig(key, "")

From 1a74eb116068e323bc4495cc862ec46538a82a87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=AD=99=E5=BA=9A=E6=B3=BD?= <690388648 at qq.com>
Date: Wed, 8 Feb 2017 14:18:33 +0800
Subject: [PATCH 0671/1193] This condition has already been deal.

The condition of 'req.Source.Fingerprint != ""' has already been deal.
Signed-off-by:sungengze
---
 lxd/containers_post.go | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 8f2ff35b0..ba21dc43a 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -36,8 +36,6 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 
 			hash = alias.Target
 		}
-	} else if req.Source.Fingerprint != "" {
-		hash = req.Source.Fingerprint
 	} else if req.Source.Properties != nil {
 		if req.Source.Server != "" {
 			return BadRequest(fmt.Errorf("Property match is only supported for local images"))

From 74a0ba9d29a8162f02e087d3d88b4759d3148740 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Feb 2017 19:37:45 -0500
Subject: [PATCH 0672/1193] Disable IPv6 on host side veth when bridged
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2845

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d7c9f6e46..8c980989d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4788,6 +4788,11 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 					return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
 				}
 			}
+
+			// Attempt to disable IPv6 on the host side interface
+			if shared.PathExists(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", n1)) {
+				ioutil.WriteFile(fmt.Sprintf("/proc/sys/net/ipv6/conf/%s/disable_ipv6", n1), []byte("1"), 0644)
+			}
 		}
 
 		dev = n2

From a0c6df76b88cdd2af65fcf48da3caa1a3ab5ddf9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Feb 2017 19:50:04 -0500
Subject: [PATCH 0673/1193] tests: Switch to use gofmt instead of "go fmt"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/static_analysis.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 2a8cc29fc..d8da11de1 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -62,7 +62,7 @@ test_static_analysis() {
 
     # go fmt
     git add -u :/
-    go fmt ./...
+    gofmt -w -s ./
     git diff --exit-code
 
     # make sure the .pot is updated

From 43832cc9f592ee80452eb6e5a3dbea2025bedbc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Feb 2017 20:17:21 -0500
Subject: [PATCH 0674/1193] tests: Avoid a zfs race
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/backends/zfs.sh | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/test/backends/zfs.sh b/test/backends/zfs.sh
index 41013fee1..f33802e9c 100644
--- a/test/backends/zfs.sh
+++ b/test/backends/zfs.sh
@@ -27,6 +27,10 @@ zfs_configure() {
   echo "==> Configuring ZFS backend in ${LXD_DIR}"
 
   lxc config set storage.zfs_pool_name "lxdtest-$(basename "${LXD_DIR}")"
+
+  # Avoid a zfs bug in "-p" handling during concurent create
+  zfs create -p -o mountpoint=none "lxdtest-$(basename "${LXD_DIR}")/containers"
+  zfs create -p -o mountpoint=none "lxdtest-$(basename "${LXD_DIR}")/images"
 }
 
 zfs_teardown() {

From 963a68ef2f818260e80f0ac31b9e9fcd3eb54f6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Feb 2017 19:51:41 -0500
Subject: [PATCH 0675/1193] db: Rely on CASCADE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2844

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_certificates.go | 11 +++++------
 lxd/db_containers.go   | 16 ++--------------
 lxd/db_images.go       |  8 +-------
 lxd/db_profiles.go     | 20 ++------------------
 4 files changed, 10 insertions(+), 45 deletions(-)

diff --git a/lxd/db_certificates.go b/lxd/db_certificates.go
index a194627b7..63fd0e52c 100644
--- a/lxd/db_certificates.go
+++ b/lxd/db_certificates.go
@@ -110,11 +110,10 @@ func dbCertSave(db *sql.DB, cert *dbCertInfo) error {
 
 // dbCertDelete deletes a certificate from the db.
 func dbCertDelete(db *sql.DB, fingerprint string) error {
-	_, err := dbExec(
-		db,
-		"DELETE FROM certificates WHERE fingerprint=?",
-		fingerprint,
-	)
+	_, err := dbExec(db, "DELETE FROM certificates WHERE fingerprint=?", fingerprint)
+	if err != nil {
+		return err
+	}
 
-	return err
+	return nil
 }
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index 24ad66eaa..843f0184a 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -24,24 +24,12 @@ func dbContainerRemove(db *sql.DB, name string) error {
 		return err
 	}
 
-	tx, err := dbBegin(db)
+	_, err = dbExec(db, "DELETE FROM containers WHERE id=?", id)
 	if err != nil {
 		return err
 	}
 
-	err = dbContainerConfigClear(tx, id)
-	if err != nil {
-		tx.Rollback()
-		return err
-	}
-
-	_, err = tx.Exec("DELETE FROM containers WHERE id=?", id)
-	if err != nil {
-		tx.Rollback()
-		return err
-	}
-
-	return txCommit(tx)
+	return nil
 }
 
 func dbContainerName(db *sql.DB, id int) (string, error) {
diff --git a/lxd/db_images.go b/lxd/db_images.go
index 4672be06f..081f1a338 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -243,17 +243,11 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 }
 
 func dbImageDelete(db *sql.DB, id int) error {
-	tx, err := dbBegin(db)
+	_, err := dbExec(db, "DELETE FROM images WHERE id=?", id)
 	if err != nil {
 		return err
 	}
 
-	_, _ = tx.Exec("DELETE FROM images WHERE id=?", id)
-
-	if err := txCommit(tx); err != nil {
-		return err
-	}
-
 	return nil
 }
 
diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index 69956f557..4217aaea3 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -196,28 +196,12 @@ func dbProfileDelete(db *sql.DB, name string) error {
 		return err
 	}
 
-	tx, err := dbBegin(db)
-	if err != nil {
-		return err
-	}
-
-	_, err = tx.Exec("DELETE FROM profiles WHERE id=?", id)
+	_, err = dbExec(db, "DELETE FROM profiles WHERE id=?", id)
 	if err != nil {
-		tx.Rollback()
 		return err
 	}
 
-	err = dbProfileConfigClear(tx, id)
-	if err != nil {
-		return err
-	}
-
-	_, err = tx.Exec("DELETE FROM containers_profiles WHERE profile_id=?", id)
-	if err != nil {
-		return err
-	}
-
-	return txCommit(tx)
+	return nil
 }
 
 func dbProfileUpdate(db *sql.DB, name string, newName string) error {

From 0215cf9a42b5ea6b8cd122d87e7857c7e55cccd4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 1 Feb 2017 18:02:52 +0100
Subject: [PATCH 0676/1193] Clarify CRIU related errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go     | 10 ++++++----
 lxd/container_lxc.go | 19 ++++++-------------
 lxd/migrate.go       | 11 +++++++----
 3 files changed, 19 insertions(+), 21 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 51768184a..b2fb5a834 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"io"
 	"os"
+	"os/exec"
 	"strconv"
 	"strings"
 	"time"
@@ -599,15 +600,16 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 	// Deal with state
 	if args.Stateful {
 		if !sourceContainer.IsRunning() {
-			return nil, fmt.Errorf("Container not running, cannot do stateful snapshot")
+			return nil, fmt.Errorf("Unable to create a stateful snapshot. The container isn't running.")
 		}
 
-		if err := findCriu("snapshot"); err != nil {
-			return nil, err
+		_, err := exec.LookPath("criu")
+		if err != nil {
+			return nil, fmt.Errorf("Unable to create a stateful snapshot. CRIU isn't installed.")
 		}
 
 		stateDir := sourceContainer.StatePath()
-		err := os.MkdirAll(stateDir, 0700)
+		err = os.MkdirAll(stateDir, 0700)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8c980989d..2b2e40059 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2312,8 +2312,9 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 	 * filesystem manipulations
 	 */
 	if shared.PathExists(c.StatePath()) {
-		if err := findCriu("snapshot"); err != nil {
-			return err
+		_, err := exec.LookPath("criu")
+		if err != nil {
+			return fmt.Errorf("Failed to restore container state. CRIU isn't installed.")
 		}
 	}
 
@@ -3428,15 +3429,6 @@ func getCRIULogErrors(imagesDir string, method string) (string, error) {
 	return strings.Join(ret, "\n"), nil
 }
 
-func findCriu(host string) error {
-	_, err := exec.LookPath("criu")
-	if err != nil {
-		return fmt.Errorf("CRIU is required for live migration but its binary couldn't be found on the %s server. Is it installed in LXD's path?", host)
-	}
-
-	return nil
-}
-
 func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop bool, actionScript bool) error {
 	ctxMap := log.Ctx{"name": c.name,
 		"created":      c.creationDate,
@@ -3445,8 +3437,9 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 		"actionscript": actionScript,
 		"stop":         stop}
 
-	if err := findCriu(function); err != nil {
-		return err
+	_, err := exec.LookPath("criu")
+	if err != nil {
+		return fmt.Errorf("Unable to perform container live migration. CRIU isn't installed.")
 	}
 
 	shared.LogInfo("Migrating container", ctxMap)
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 042b85af3..6b5aa7f53 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -11,6 +11,7 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"strings"
 	"sync"
@@ -166,8 +167,9 @@ func NewMigrationSource(c container) (*migrationSourceWs, error) {
 	}
 
 	if c.IsRunning() {
-		if err := findCriu("source"); err != nil {
-			return nil, err
+		_, err := exec.LookPath("criu")
+		if err != nil {
+			return nil, fmt.Errorf("Unable to perform container live migration. CRIU isn't installed on the source server.")
 		}
 
 		ret.live = true
@@ -560,8 +562,9 @@ func NewMigrationSink(args *MigrationSinkArgs) (*migrationSink, error) {
 	sink.src.criuSecret, ok = args.Secrets["criu"]
 	sink.src.live = ok
 
-	if err := findCriu("destination"); sink.src.live && err != nil {
-		return nil, err
+	_, err := exec.LookPath("criu")
+	if sink.src.live && err != nil {
+		return nil, fmt.Errorf("Unable to perform container live migration. CRIU isn't installed on the destination server.")
 	}
 
 	return &sink, nil

From ee4ccbc336a365ee9d6b031695b9aba6f521425c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sat, 11 Feb 2017 02:21:51 +0100
Subject: [PATCH 0677/1193] cgo: free allocated memory

Well it is surprising that this bug didn't blow up in our face before. A couple
things:

- the pointer returned by dirname() is not allowed to be free()ed
  (See man 3 dirname)
- strdup() was not checked for NULL
- strdup() and dirname() assigned to the same variable
- Even though the pointer returned by dirname() was not supposed to be free()ed
  we were lucky enough to not be bitten by this since the free() call was
  basically a noop thanks to the switch statement.

Do none of these things!

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/main_nsexec.go | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/lxd/main_nsexec.go b/lxd/main_nsexec.go
index 527b1b916..a2d044df3 100644
--- a/lxd/main_nsexec.go
+++ b/lxd/main_nsexec.go
@@ -325,21 +325,27 @@ void ensure_file(char *dest) {
 }
 
 void create(char *src, char *dest) {
+	char *dirdup;
 	char *destdirname;
+
 	struct stat sb;
 	if (stat(src, &sb) < 0) {
 		fprintf(stderr, "source %s does not exist\n", src);
 		_exit(1);
 	}
 
-	destdirname = strdup(dest);
-	destdirname = dirname(destdirname);
+	dirdup = strdup(dest);
+	if (!dirdup)
+		_exit(1);
+
+	destdirname = dirname(dirdup);
 
 	if (mkdir_p(destdirname, 0755) < 0) {
 		fprintf(stderr, "failed to create path: %s\n", destdirname);
-		free(destdirname);
+		free(dirdup);
 		_exit(1);
 	}
+	free(dirdup);
 
 	switch (sb.st_mode & S_IFMT) {
 	case S_IFDIR:
@@ -349,8 +355,6 @@ void create(char *src, char *dest) {
 		ensure_file(dest);
 		return;
 	}
-
-	free(destdirname);
 }
 
 void forkmount(char *buf, char *cur, ssize_t size) {

From af9cc86b10693aa73b881fcb4ed18efd188e973e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 13 Feb 2017 18:14:01 -0500
Subject: [PATCH 0678/1193] Move imagesDownloading out of the daemon struct
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go                |  5 -----
 lxd/daemon_images.go         | 21 ++++++++++++---------
 lxd/main_activateifneeded.go |  6 +-----
 lxd/main_test.go             |  5 +----
 4 files changed, 14 insertions(+), 23 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 5eb6f9331..38ada5640 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -89,9 +89,6 @@ type Daemon struct {
 	MockMode  bool
 	SetupMode bool
 
-	imagesDownloading     map[string]chan bool
-	imagesDownloadingLock sync.Mutex
-
 	tlsConfig *tls.Config
 
 	proxy func(req *http.Request) (*url.URL, error)
@@ -535,8 +532,6 @@ func haveMacAdmin() bool {
 
 func (d *Daemon) Init() error {
 	/* Initialize some variables */
-	d.imagesDownloading = map[string]chan bool{}
-
 	d.readyChan = make(chan bool)
 	d.shutdownChan = make(chan bool)
 
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 97739ab2f..15cc0e12a 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -34,6 +34,9 @@ type imageStreamCacheEntry struct {
 var imageStreamCache = map[string]*imageStreamCacheEntry{}
 var imageStreamCacheLock sync.Mutex
 
+var imagesDownloading = map[string]chan bool{}
+var imagesDownloadingLock sync.Mutex
+
 func imageSaveStreamCache() error {
 	data, err := yaml.Marshal(&imageStreamCache)
 	if err != nil {
@@ -188,10 +191,10 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	}
 
 	// Now check if we already downloading the image
-	d.imagesDownloadingLock.Lock()
-	if waitChannel, ok := d.imagesDownloading[fp]; ok {
+	imagesDownloadingLock.Lock()
+	if waitChannel, ok := imagesDownloading[fp]; ok {
 		// We already download the image
-		d.imagesDownloadingLock.Unlock()
+		imagesDownloadingLock.Unlock()
 
 		shared.LogDebug(
 			"Already downloading the image, waiting for it to succeed",
@@ -218,17 +221,17 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	}
 
 	// Add the download to the queue
-	d.imagesDownloading[fp] = make(chan bool)
-	d.imagesDownloadingLock.Unlock()
+	imagesDownloading[fp] = make(chan bool)
+	imagesDownloadingLock.Unlock()
 
 	// Unlock once this func ends.
 	defer func() {
-		d.imagesDownloadingLock.Lock()
-		if waitChannel, ok := d.imagesDownloading[fp]; ok {
+		imagesDownloadingLock.Lock()
+		if waitChannel, ok := imagesDownloading[fp]; ok {
 			close(waitChannel)
-			delete(d.imagesDownloading, fp)
+			delete(imagesDownloading, fp)
 		}
-		d.imagesDownloadingLock.Unlock()
+		imagesDownloadingLock.Unlock()
 	}()
 
 	// Begin downloading
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index b0d66d278..f91ff6b1c 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -1,8 +1,6 @@
 package main
 
 import (
-	"sync"
-
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 )
@@ -10,9 +8,7 @@ import (
 func cmdActivateIfNeeded() error {
 	// Don't start a full daemon, we just need DB access
 	d := &Daemon{
-		imagesDownloading:     map[string]chan bool{},
-		imagesDownloadingLock: sync.Mutex{},
-		lxcpath:               shared.VarPath("containers"),
+		lxcpath: shared.VarPath("containers"),
 	}
 
 	if !shared.PathExists(shared.VarPath("lxd.db")) {
diff --git a/lxd/main_test.go b/lxd/main_test.go
index e3af4e1a5..40a639eee 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -3,7 +3,6 @@ package main
 import (
 	"io/ioutil"
 	"os"
-	"sync"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -14,9 +13,7 @@ import (
 
 func mockStartDaemon() (*Daemon, error) {
 	d := &Daemon{
-		MockMode:              true,
-		imagesDownloading:     map[string]chan bool{},
-		imagesDownloadingLock: sync.Mutex{},
+		MockMode: true,
 	}
 
 	if err := d.Init(); err != nil {

From dc2a2724efce09a132c6bf8b4a2b0dd08d4bda98 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 13 Feb 2017 18:32:46 -0500
Subject: [PATCH 0679/1193] Fix extraction of fd from UnixConn with go tip
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/devlxd.go | 28 +++++++++++++++++++++++++---
 1 file changed, 25 insertions(+), 3 deletions(-)

diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 1d0acc11f..73597e792 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -247,16 +247,38 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) {
  * I also don't see that golang exports an API to get at the underlying FD, but
  * we need it to get at SO_PEERCRED, so let's grab it.
  */
-func extractUnderlyingFd(unixConnPtr *net.UnixConn) int {
+func extractUnderlyingFd(unixConnPtr *net.UnixConn) (int, error) {
 	conn := reflect.Indirect(reflect.ValueOf(unixConnPtr))
+
 	netFdPtr := conn.FieldByName("fd")
+	if !netFdPtr.IsValid() {
+		return -1, fmt.Errorf("Unable to extract fd from net.UnixConn")
+	}
 	netFd := reflect.Indirect(netFdPtr)
+
 	fd := netFd.FieldByName("sysfd")
-	return int(fd.Int())
+	if !fd.IsValid() {
+		// Try under the new name
+		pfdPtr := netFd.FieldByName("pfd")
+		if !pfdPtr.IsValid() {
+			return -1, fmt.Errorf("Unable to extract pfd from netFD")
+		}
+		pfd := reflect.Indirect(pfdPtr)
+
+		fd = pfd.FieldByName("Sysfd")
+		if !fd.IsValid() {
+			return -1, fmt.Errorf("Unable to extract Sysfd from poll.FD")
+		}
+	}
+
+	return int(fd.Int()), nil
 }
 
 func getCred(conn *net.UnixConn) (*ucred, error) {
-	fd := extractUnderlyingFd(conn)
+	fd, err := extractUnderlyingFd(conn)
+	if err != nil {
+		return nil, err
+	}
 
 	uid, gid, pid, err := getUcred(fd)
 	if err != nil {

From d459b69ffd63ccd7917f2b4c75e9559ae0822605 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 13 Feb 2017 21:18:23 -0500
Subject: [PATCH 0680/1193] tests: Fix typo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/main.sh b/test/main.sh
index ac1dd7fec..724fe01b8 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -401,7 +401,7 @@ fi
 run_test test_check_deps "checking dependencies"
 run_test test_static_analysis "static analysis"
 run_test test_database_update "database schema updates"
-run_test test_remote_url "remote  url handling"
+run_test test_remote_url "remote url handling"
 run_test test_remote_admin "remote administration"
 run_test test_remote_usage "remote usage"
 run_test test_basic_usage "basic usage"

From cf724f2f06abeb778878ed8bf8b82c44d111ec56 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 14 Feb 2017 11:27:48 -0500
Subject: [PATCH 0681/1193] Don't include spaces in translated strings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/info.go |  6 +++---
 po/lxd.pot  | 28 ++++++++++++++--------------
 2 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/lxc/info.go b/lxc/info.go
index 66287460e..6ef30e863 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -140,7 +140,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		}
 
 		if diskInfo != "" {
-			fmt.Println(i18n.G("  Disk usage:"))
+			fmt.Println(fmt.Sprintf("  %s", i18n.G("Disk usage:")))
 			fmt.Printf(diskInfo)
 		}
 
@@ -163,7 +163,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		}
 
 		if memoryInfo != "" {
-			fmt.Println(i18n.G("  Memory usage:"))
+			fmt.Println(fmt.Sprintf("  %s", i18n.G("Memory usage:")))
 			fmt.Printf(memoryInfo)
 		}
 
@@ -180,7 +180,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 		}
 
 		if networkInfo != "" {
-			fmt.Println(i18n.G("  Network usage:"))
+			fmt.Println(fmt.Sprintf("  %s", i18n.G("Network usage:")))
 			fmt.Printf(networkInfo)
 		}
 	}
diff --git a/po/lxd.pot b/po/lxd.pot
index daff8d2f7..42d936023 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-01-23 16:34-0500\n"
+        "POT-Creation-Date: 2017-02-24 20:55-0500\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -16,18 +16,6 @@ msgstr  "Project-Id-Version: lxd\n"
         "Content-Type: text/plain; charset=CHARSET\n"
         "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:143
-msgid   "  Disk usage:"
-msgstr  ""
-
-#: lxc/info.go:166
-msgid   "  Memory usage:"
-msgstr  ""
-
-#: lxc/info.go:183
-msgid   "  Network usage:"
-msgstr  ""
-
 #: lxc/config.go:37
 msgid   "### This is a yaml representation of the configuration.\n"
         "### Any line starting with a '# will be ignored.\n"
@@ -283,6 +271,10 @@ msgstr  ""
 msgid   "Device %s removed from %s"
 msgstr  ""
 
+#: lxc/info.go:143
+msgid   "Disk usage:"
+msgstr  ""
+
 #: lxc/list.go:482
 msgid   "EPHEMERAL"
 msgstr  ""
@@ -713,6 +705,10 @@ msgstr  ""
 msgid   "Memory (peak)"
 msgstr  ""
 
+#: lxc/info.go:166
+msgid   "Memory usage:"
+msgstr  ""
+
 #: lxc/help.go:87
 msgid   "Missing summary."
 msgstr  ""
@@ -765,6 +761,10 @@ msgstr  ""
 msgid   "Name: %s"
 msgstr  ""
 
+#: lxc/info.go:183
+msgid   "Network usage:"
+msgstr  ""
+
 #: lxc/image.go:166 lxc/publish.go:34
 msgid   "New alias to define at target"
 msgstr  ""
@@ -1260,7 +1260,7 @@ msgstr  ""
 msgid   "taken at %s"
 msgstr  ""
 
-#: lxc/exec.go:164
+#: lxc/exec.go:167
 msgid   "unreachable return reached"
 msgstr  ""
 

From a521899f7be351800a21e4ca701219d6cf57dcdb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 14 Feb 2017 11:41:34 -0500
Subject: [PATCH 0682/1193] tests: Add golint
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile                       | 1 +
 test/suites/static_analysis.sh | 9 ++++++---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index 3970a1131..5d092ed0c 100644
--- a/Makefile
+++ b/Makefile
@@ -48,6 +48,7 @@ protobuf:
 check: default
 	go get -v -x github.com/rogpeppe/godeps
 	go get -v -x github.com/remyoudompheng/go-misc/deadcode
+	go get -v -x github.com/golang/lint/golint
 	go test -v $(TAGS) $(DEBUG) ./...
 	cd test && ./main.sh
 
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index d8da11de1..3039abc46 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -25,9 +25,7 @@ test_static_analysis() {
     fi
 
     ## go vet, if it exists
-    have_go_vet=1
-    go help vet > /dev/null 2>&1 || have_go_vet=0
-    if [ "${have_go_vet}" -eq 1 ]; then
+    if go help vet >/dev/null 2>&1; then
       go vet ./...
     fi
 
@@ -36,6 +34,11 @@ test_static_analysis() {
       vet --all .
     fi
 
+    ## golint
+    if which golint >/dev/null 2>&1; then
+      golint -set_exit_status shared/api/
+    fi
+
     ## deadcode
     if which deadcode >/dev/null 2>&1; then
       for path in . fuidshift lxc lxd lxd/types shared shared/api shared/i18n shared/ioprogress shared/logging shared/osarch shared/simplestreams shared/termios shared/version test/lxd-benchmark; do

From 0435071a63e9083f04369fb3ec4c60ca9d14aa0f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 14 Feb 2017 14:27:22 -0500
Subject: [PATCH 0683/1193] Use a tmpfs for shmounts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Avoids some information leakage from the host.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go  | 21 +++++++--------------
 shared/util.go | 36 ------------------------------------
 2 files changed, 7 insertions(+), 50 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 38ada5640..e677b532c 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -378,35 +378,28 @@ var sharedMounted bool
 var sharedMountsLock sync.Mutex
 
 func setupSharedMounts() error {
+	// Check if we already went through this
 	if sharedMounted {
 		return nil
 	}
 
+	// Get a lock to prevent races
 	sharedMountsLock.Lock()
 	defer sharedMountsLock.Unlock()
 
-	if sharedMounted {
-		return nil
-	}
-
+	// Check if already setup
 	path := shared.VarPath("shmounts")
-
-	isShared, err := shared.IsOnSharedMount(path)
-	if err != nil {
-		return err
-	}
-
-	if isShared {
-		// / may already be ms-shared, or shmounts may have
-		// been mounted by a previous lxd run
+	if shared.IsMountPoint(path) {
 		sharedMounted = true
 		return nil
 	}
 
-	if err := syscall.Mount(path, path, "none", syscall.MS_BIND, ""); err != nil {
+	// Mount a new tmpfs
+	if err := syscall.Mount("tmpfs", path, "tmpfs", 0, "size=100k,mode=0711"); err != nil {
 		return err
 	}
 
+	// Mark as MS_SHARED and MS_REC
 	var flags uintptr = syscall.MS_SHARED | syscall.MS_REC
 	if err := syscall.Mount(path, path, "none", flags, ""); err != nil {
 		return err
diff --git a/shared/util.go b/shared/util.go
index f159d5a9a..55764e9cb 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -421,42 +421,6 @@ func IsTrue(value string) bool {
 	return false
 }
 
-func IsOnSharedMount(pathName string) (bool, error) {
-	file, err := os.Open("/proc/self/mountinfo")
-	if err != nil {
-		return false, err
-	}
-	defer file.Close()
-
-	absPath, err := filepath.Abs(pathName)
-	if err != nil {
-		return false, err
-	}
-
-	expPath, err := os.Readlink(absPath)
-	if err != nil {
-		expPath = absPath
-	}
-
-	scanner := bufio.NewScanner(file)
-	for scanner.Scan() {
-		line := scanner.Text()
-		rows := strings.Fields(line)
-
-		if rows[4] != expPath {
-			continue
-		}
-
-		if strings.HasPrefix(rows[6], "shared:") {
-			return true, nil
-		} else {
-			return false, nil
-		}
-	}
-
-	return false, nil
-}
-
 func IsBlockdev(fm os.FileMode) bool {
 	return ((fm&os.ModeDevice != 0) && (fm&os.ModeCharDevice == 0))
 }

From 44f91cb383396481066eff04be21f5655ac978ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 14 Feb 2017 15:01:10 -0500
Subject: [PATCH 0684/1193] Mount a tmpfs under devlxd
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2877

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 26 +++++++++++++++++++-------
 1 file changed, 19 insertions(+), 7 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index e677b532c..71a284ce4 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -816,6 +816,17 @@ func (d *Daemon) Init() error {
 		daemonConfig["core.proxy_ignore_hosts"].Get(),
 	)
 
+	/* Setup some mounts (nice to have) */
+	if !d.MockMode {
+		// Attempt to mount the shmounts tmpfs
+		setupSharedMounts()
+
+		// Attempt to Mount the devlxd tmpfs
+		if !shared.IsMountPoint(shared.VarPath("devlxd")) {
+			syscall.Mount("tmpfs", shared.VarPath("devlxd"), "tmpfs", 0, "size=100k,mode=0755")
+		}
+	}
+
 	/* Setup /dev/lxd */
 	shared.LogInfof("Starting /dev/lxd handler")
 	d.devlxd, err = createAndBindDevLxd()
@@ -1108,23 +1119,24 @@ func (d *Daemon) Stop() error {
 		}
 	}
 
+	shared.LogInfof("Stopping /dev/lxd handler")
+	d.devlxd.Close()
+	shared.LogInfof("Stopped /dev/lxd handler")
+
 	if n, err := d.numRunningContainers(); err != nil || n == 0 {
-		shared.LogInfof("Unmounting shmounts")
+		shared.LogInfof("Unmounting temporary filesystems")
 
+		syscall.Unmount(shared.VarPath("devlxd"), syscall.MNT_DETACH)
 		syscall.Unmount(shared.VarPath("shmounts"), syscall.MNT_DETACH)
 
-		shared.LogInfof("Done unmounting shmounts")
+		shared.LogInfof("Done unmounting temporary filesystems")
 	} else {
-		shared.LogDebugf("Not unmounting shmounts (containers are still running)")
+		shared.LogDebugf("Not unmounting temporary filesystems (containers are still running)")
 	}
 
 	shared.LogInfof("Closing the database")
 	d.db.Close()
 
-	shared.LogInfof("Stopping /dev/lxd handler")
-	d.devlxd.Close()
-	shared.LogInfof("Stopped /dev/lxd handler")
-
 	shared.LogInfof("Saving simplestreams cache")
 	imageSaveStreamCache()
 	shared.LogInfof("Saved simplestreams cache")

From 23fedd56a22b03ec43969460afe815b708461709 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 14 Feb 2017 16:08:46 -0500
Subject: [PATCH 0685/1193] Fix error handling on FileRemove
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 23 ++++++++++++++++++-----
 1 file changed, 18 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 2b2e40059..133804dfb 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3983,6 +3983,8 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 }
 
 func (c *containerLXC) FileRemove(path string) error {
+	var errStr string
+
 	// Setup container storage if needed
 	if !c.IsRunning() {
 		err := c.StorageStart()
@@ -4009,13 +4011,24 @@ func (c *containerLXC) FileRemove(path string) error {
 	}
 
 	// Process forkremovefile response
-	if string(out) != "" {
-		if strings.HasPrefix(string(out), "error:") {
-			return fmt.Errorf(strings.TrimPrefix(strings.TrimSuffix(string(out), "\n"), "error: "))
+	for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
+		if line == "" {
+			continue
 		}
 
-		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.LogDebugf("forkremovefile: %s", line)
+		// Extract errors
+		if strings.HasPrefix(line, "error: ") {
+			errStr = strings.TrimPrefix(line, "error: ")
+			continue
+		}
+
+		if strings.HasPrefix(line, "errno: ") {
+			errno := strings.TrimPrefix(line, "errno: ")
+			if errno == "2" {
+				return os.ErrNotExist
+			}
+
+			return fmt.Errorf(errStr)
 		}
 	}
 

From cb170282ef75a18cda5a9697d7e1ef2a8c767e59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 14 Feb 2017 19:20:57 -0500
Subject: [PATCH 0686/1193] Forward user-agent and other headers on redirect
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We don't use sensitive headers so don't need to be concerned about
crossing domain boundaries. So simply always copy all headers to
subsequent requests in the redirect chain.

Closes #2805

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go     | 9 +++++++++
 lxd/daemon.go | 8 ++++++++
 2 files changed, 17 insertions(+)

diff --git a/client.go b/client.go
index 20bef32cd..4cc52742e 100644
--- a/client.go
+++ b/client.go
@@ -265,6 +265,15 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 		},
 	}
 	c.Name = info.Name
+
+	// Setup redirect policy
+	c.Http.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+		// Replicate the headers
+		req.Header = via[len(via)-1].Header
+
+		return nil
+	}
+
 	var err error
 	if strings.HasPrefix(info.RemoteConfig.Addr, "unix:") {
 		err = connectViaUnix(c, &info.RemoteConfig)
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 71a284ce4..3b3d86773 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -137,6 +137,14 @@ func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
 		Transport: tr,
 	}
 
+	// Setup redirect policy
+	myhttp.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+		// Replicate the headers
+		req.Header = via[len(via)-1].Header
+
+		return nil
+	}
+
 	return &myhttp, nil
 }
 

From 580f7029b94c07146246dbfdd2ebe11d66ed1b6c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 15 Feb 2017 00:03:30 -0500
Subject: [PATCH 0687/1193] Fix concurent read/write to s.conns in exec
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2862

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_exec.go | 36 +++++++++++++++++++++++++++++-------
 1 file changed, 29 insertions(+), 7 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 34c25d99a..9863959db 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -147,7 +147,11 @@ func (s *execWs) Do(op *operation) error {
 			}
 
 			for {
-				mt, r, err := s.conns[-1].NextReader()
+				s.connsLock.Lock()
+				conn := s.conns[-1]
+				s.connsLock.Unlock()
+
+				mt, r, err := conn.NextReader()
 				if mt == websocket.CloseMessage {
 					break
 				}
@@ -193,10 +197,16 @@ func (s *execWs) Do(op *operation) error {
 		}()
 
 		go func() {
-			readDone, writeDone := shared.WebsocketExecMirror(s.conns[0], ptys[0], ptys[0], attachedChildIsDead, int(ptys[0].Fd()))
+			s.connsLock.Lock()
+			conn := s.conns[0]
+			s.connsLock.Unlock()
+
+			readDone, writeDone := shared.WebsocketExecMirror(conn, ptys[0], ptys[0], attachedChildIsDead, int(ptys[0].Fd()))
+
 			<-readDone
 			<-writeDone
-			s.conns[0].Close()
+
+			conn.Close()
 			wgEOF.Done()
 		}()
 
@@ -205,10 +215,18 @@ func (s *execWs) Do(op *operation) error {
 		for i := 0; i < len(ttys); i++ {
 			go func(i int) {
 				if i == 0 {
-					<-shared.WebsocketRecvStream(ttys[i], s.conns[i])
+					s.connsLock.Lock()
+					conn := s.conns[i]
+					s.connsLock.Unlock()
+
+					<-shared.WebsocketRecvStream(ttys[i], conn)
 					ttys[i].Close()
 				} else {
-					<-shared.WebsocketSendStream(s.conns[i], ptys[i], -1)
+					s.connsLock.Lock()
+					conn := s.conns[i]
+					s.connsLock.Unlock()
+
+					<-shared.WebsocketSendStream(conn, ptys[i], -1)
 					ptys[i].Close()
 					wgEOF.Done()
 				}
@@ -221,12 +239,16 @@ func (s *execWs) Do(op *operation) error {
 			tty.Close()
 		}
 
-		if s.conns[-1] == nil {
+		s.connsLock.Lock()
+		conn := s.conns[-1]
+		s.connsLock.Unlock()
+
+		if conn == nil {
 			if s.interactive {
 				controlExit <- true
 			}
 		} else {
-			s.conns[-1].Close()
+			conn.Close()
 		}
 
 		attachedChildIsDead <- true

From 35fd178a30cda0cea7ca2e6be7c935a6cea95454 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 15 Feb 2017 16:01:44 -0500
Subject: [PATCH 0688/1193] list: Fix regression in json output
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2887

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index f01f71074..af45b20fe 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -345,8 +345,8 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 type listContainerItem struct {
 	*api.Container
 
-	State     *api.ContainerState
-	Snapshots []api.ContainerSnapshot
+	State     *api.ContainerState     `json:"state" yaml:"state"`
+	Snapshots []api.ContainerSnapshot `json:"snapshots" yaml:"snapshots"`
 }
 
 func (c *listCmd) run(config *lxd.Config, args []string) error {

From eebb45da70bad9b577f8147a1b806ffec83f9500 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 15 Feb 2017 16:02:19 -0500
Subject: [PATCH 0689/1193] api: Use consistent json and yaml field names
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This will allow us to change the struct members as we see fit without
impacting the output of our commands and on-disk files.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/certificate.go        | 16 +++++------
 shared/api/container.go          | 58 ++++++++++++++++++-------------------
 shared/api/container_exec.go     | 18 ++++++------
 shared/api/container_snapshot.go | 30 +++++++++----------
 shared/api/container_state.go    | 62 ++++++++++++++++++++--------------------
 shared/api/image.go              | 52 ++++++++++++++++-----------------
 shared/api/network.go            |  6 ++--
 shared/api/operation.go          | 20 ++++++-------
 shared/api/profile.go            | 12 ++++----
 shared/api/response.go           | 16 +++++------
 shared/api/server.go             | 42 +++++++++++++--------------
 11 files changed, 166 insertions(+), 166 deletions(-)

diff --git a/shared/api/certificate.go b/shared/api/certificate.go
index 12bcc315b..f5b4c4d88 100644
--- a/shared/api/certificate.go
+++ b/shared/api/certificate.go
@@ -2,16 +2,16 @@ package api
 
 // CertificatesPost represents the fields of a new LXD certificate
 type CertificatesPost struct {
-	Name        string `json:"name"`
-	Type        string `json:"type"`
-	Certificate string `json:"certificate"`
-	Password    string `json:"password"`
+	Name        string `json:"name" yaml:"name"`
+	Type        string `json:"type" yaml:"type"`
+	Certificate string `json:"certificate" yaml:"certificate"`
+	Password    string `json:"password" yaml:"password"`
 }
 
 // Certificate represents a LXD certificate
 type Certificate struct {
-	Name        string `json:"name"`
-	Type        string `json:"type"`
-	Certificate string `json:"certificate"`
-	Fingerprint string `json:"fingerprint"`
+	Name        string `json:"name" yaml:"name"`
+	Type        string `json:"type" yaml:"type"`
+	Certificate string `json:"certificate" yaml:"certificate"`
+	Fingerprint string `json:"fingerprint" yaml:"fingerprint"`
 }
diff --git a/shared/api/container.go b/shared/api/container.go
index 3ef0949cd..64812b4e0 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -8,23 +8,23 @@ import (
 type ContainersPost struct {
 	ContainerPut `yaml:",inline"`
 
-	Name   string          `json:"name"`
-	Source ContainerSource `json:"source"`
+	Name   string          `json:"name" yaml:"name"`
+	Source ContainerSource `json:"source" yaml:"source"`
 }
 
 // ContainerPost represents the fields required to rename/move a LXD container
 type ContainerPost struct {
-	Migration bool   `json:"migration"`
-	Name      string `json:"name"`
+	Migration bool   `json:"migration" yaml:"migration"`
+	Name      string `json:"name" yaml:"name"`
 }
 
 // ContainerPut represents the modifiable fields of a LXD container
 type ContainerPut struct {
-	Architecture string                       `json:"architecture"`
-	Config       map[string]string            `json:"config"`
-	Devices      map[string]map[string]string `json:"devices"`
-	Ephemeral    bool                         `json:"ephemeral"`
-	Profiles     []string                     `json:"profiles"`
+	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"`
 	Restore      string                       `json:"restore,omitempty" yaml:"restore,omitempty"`
 }
 
@@ -32,13 +32,13 @@ type ContainerPut struct {
 type Container struct {
 	ContainerPut `yaml:",inline"`
 
-	CreatedAt       time.Time                    `json:"created_at"`
-	ExpandedConfig  map[string]string            `json:"expanded_config"`
-	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
-	Name            string                       `json:"name"`
-	Stateful        bool                         `json:"stateful"`
-	Status          string                       `json:"status"`
-	StatusCode      StatusCode                   `json:"status_code"`
+	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"`
+	Stateful        bool                         `json:"stateful" yaml:"stateful"`
+	Status          string                       `json:"status" yaml:"status"`
+	StatusCode      StatusCode                   `json:"status_code" yaml:"status_code"`
 }
 
 // Writable converts a full Container struct into a ContainerPut struct (filters read-only fields)
@@ -60,25 +60,25 @@ func (c Container) IsActive() bool {
 
 // ContainerSource represents the creation source for a new container
 type ContainerSource struct {
-	Type        string `json:"type"`
-	Certificate string `json:"certificate"`
+	Type        string `json:"type" yaml:"type"`
+	Certificate string `json:"certificate" yaml:"certificate"`
 
 	// For "image" type
-	Alias       string            `json:"alias,omitempty"`
-	Fingerprint string            `json:"fingerprint,omitempty"`
-	Properties  map[string]string `json:"properties,omitempty"`
-	Server      string            `json:"server,omitempty"`
-	Secret      string            `json:"secret,omitempty"`
-	Protocol    string            `json:"protocol,omitempty"`
+	Alias       string            `json:"alias,omitempty" yaml:"alias,omitempty"`
+	Fingerprint string            `json:"fingerprint,omitempty" yaml:"fingerprint,omitempty"`
+	Properties  map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"`
+	Server      string            `json:"server,omitempty" yaml:"server,omitempty"`
+	Secret      string            `json:"secret,omitempty" yaml:"secret,omitempty"`
+	Protocol    string            `json:"protocol,omitempty" yaml:"protocol,omitempty"`
 
 	// For "migration" and "copy" types
-	BaseImage string `json:"base-image,omitempty"`
+	BaseImage string `json:"base-image,omitempty" yaml:"base-image,omitempty"`
 
 	// For "migration" type
-	Mode       string            `json:"mode,omitempty"`
-	Operation  string            `json:"operation,omitempty"`
-	Websockets map[string]string `json:"secrets,omitempty"`
+	Mode       string            `json:"mode,omitempty" yaml:"mode,omitempty"`
+	Operation  string            `json:"operation,omitempty" yaml:"operation,omitempty"`
+	Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
 
 	// For "copy" type
-	Source string `json:"source,omitempty"`
+	Source string `json:"source,omitempty" yaml:"source,omitempty"`
 }
diff --git a/shared/api/container_exec.go b/shared/api/container_exec.go
index f4cd86944..d1548e603 100644
--- a/shared/api/container_exec.go
+++ b/shared/api/container_exec.go
@@ -2,17 +2,17 @@ package api
 
 // ContainerExecControl represents a message on the container exec "control" socket
 type ContainerExecControl struct {
-	Command string            `json:"command"`
-	Args    map[string]string `json:"args"`
-	Signal  int               `json:"signal"`
+	Command string            `json:"command" yaml:"command"`
+	Args    map[string]string `json:"args" yaml:"args"`
+	Signal  int               `json:"signal" yaml:"signal"`
 }
 
 // ContainerExecPost represents a LXD container exec request
 type ContainerExecPost struct {
-	Command     []string          `json:"command"`
-	WaitForWS   bool              `json:"wait-for-websocket"`
-	Interactive bool              `json:"interactive"`
-	Environment map[string]string `json:"environment"`
-	Width       int               `json:"width"`
-	Height      int               `json:"height"`
+	Command     []string          `json:"command" yaml:"command"`
+	WaitForWS   bool              `json:"wait-for-websocket" yaml:"wait-for-websocket"`
+	Interactive bool              `json:"interactive" yaml:"interactive"`
+	Environment map[string]string `json:"environment" yaml:"environment"`
+	Width       int               `json:"width" yaml:"width"`
+	Height      int               `json:"height" yaml:"height"`
 }
diff --git a/shared/api/container_snapshot.go b/shared/api/container_snapshot.go
index ab65ab7de..17e5d3a8d 100644
--- a/shared/api/container_snapshot.go
+++ b/shared/api/container_snapshot.go
@@ -6,27 +6,27 @@ import (
 
 // ContainerSnapshotsPost represents the fields available for a new LXD container snapshot
 type ContainerSnapshotsPost struct {
-	Name     string `json:"name"`
-	Stateful bool   `json:"stateful"`
+	Name     string `json:"name" yaml:"name"`
+	Stateful bool   `json:"stateful" yaml:"stateful"`
 }
 
 // ContainerSnapshotPost represents the fields required to rename/move a LXD container snapshot
 type ContainerSnapshotPost struct {
-	Name      string `json:"name"`
-	Migration bool   `json:"migration"`
+	Name      string `json:"name" yaml:"name"`
+	Migration bool   `json:"migration" yaml:"migration"`
 }
 
 // ContainerSnapshot represents a LXD conainer snapshot
 type ContainerSnapshot struct {
-	Architecture    string                       `json:"architecture"`
-	Config          map[string]string            `json:"config"`
-	CreationDate    time.Time                    `json:"created_at"`
-	Devices         map[string]map[string]string `json:"devices"`
-	Ephemeral       bool                         `json:"ephemeral"`
-	ExpandedConfig  map[string]string            `json:"expanded_config"`
-	ExpandedDevices map[string]map[string]string `json:"expanded_devices"`
-	LastUsedDate    time.Time                    `json:"last_used_at"`
-	Name            string                       `json:"name"`
-	Profiles        []string                     `json:"profiles"`
-	Stateful        bool                         `json:"stateful"`
+	Architecture    string                       `json:"architecture" yaml:"architecture"`
+	Config          map[string]string            `json:"config" yaml:"config"`
+	CreationDate    time.Time                    `json:"created_at" yaml:"created_at"`
+	Devices         map[string]map[string]string `json:"devices" yaml:"devices"`
+	Ephemeral       bool                         `json:"ephemeral" yaml:"ephemeral"`
+	ExpandedConfig  map[string]string            `json:"expanded_config" yaml:"expanded_config"`
+	ExpandedDevices map[string]map[string]string `json:"expanded_devices" yaml:"expanded_devices"`
+	LastUsedDate    time.Time                    `json:"last_used_at" yaml:"last_used_at"`
+	Name            string                       `json:"name" yaml:"name"`
+	Profiles        []string                     `json:"profiles" yaml:"profiles"`
+	Stateful        bool                         `json:"stateful" yaml:"stateful"`
 }
diff --git a/shared/api/container_state.go b/shared/api/container_state.go
index 955a1278e..4606c2c4c 100644
--- a/shared/api/container_state.go
+++ b/shared/api/container_state.go
@@ -2,59 +2,59 @@ package api
 
 // ContainerStatePut represents the modifiable fields of a LXD container's state
 type ContainerStatePut struct {
-	Action   string `json:"action"`
-	Timeout  int    `json:"timeout"`
-	Force    bool   `json:"force"`
-	Stateful bool   `json:"stateful"`
+	Action   string `json:"action" yaml:"action"`
+	Timeout  int    `json:"timeout" yaml:"timeout"`
+	Force    bool   `json:"force" yaml:"force"`
+	Stateful bool   `json:"stateful" yaml:"stateful"`
 }
 
 // ContainerState represents a LXD container's state
 type ContainerState struct {
-	Status     string                           `json:"status"`
-	StatusCode StatusCode                       `json:"status_code"`
-	Disk       map[string]ContainerStateDisk    `json:"disk"`
-	Memory     ContainerStateMemory             `json:"memory"`
-	Network    map[string]ContainerStateNetwork `json:"network"`
-	Pid        int64                            `json:"pid"`
-	Processes  int64                            `json:"processes"`
+	Status     string                           `json:"status" yaml:"status"`
+	StatusCode StatusCode                       `json:"status_code" yaml:"status_code"`
+	Disk       map[string]ContainerStateDisk    `json:"disk" yaml:"disk"`
+	Memory     ContainerStateMemory             `json:"memory" yaml:"memory"`
+	Network    map[string]ContainerStateNetwork `json:"network" yaml:"network"`
+	Pid        int64                            `json:"pid" yaml:"pid"`
+	Processes  int64                            `json:"processes" yaml:"processes"`
 }
 
 // ContainerStateDisk represents the disk information section of a LXD container's state
 type ContainerStateDisk struct {
-	Usage int64 `json:"usage"`
+	Usage int64 `json:"usage" yaml:"usage"`
 }
 
 // ContainerStateMemory represents the memory information section of a LXD container's state
 type ContainerStateMemory struct {
-	Usage         int64 `json:"usage"`
-	UsagePeak     int64 `json:"usage_peak"`
-	SwapUsage     int64 `json:"swap_usage"`
-	SwapUsagePeak int64 `json:"swap_usage_peak"`
+	Usage         int64 `json:"usage" yaml:"usage"`
+	UsagePeak     int64 `json:"usage_peak" yaml:"usage_peak"`
+	SwapUsage     int64 `json:"swap_usage" yaml:"swap_usage"`
+	SwapUsagePeak int64 `json:"swap_usage_peak" yaml:"swap_usage_peak"`
 }
 
 // ContainerStateNetwork represents the network information section of a LXD container's state
 type ContainerStateNetwork struct {
-	Addresses []ContainerStateNetworkAddress `json:"addresses"`
-	Counters  ContainerStateNetworkCounters  `json:"counters"`
-	Hwaddr    string                         `json:"hwaddr"`
-	HostName  string                         `json:"host_name"`
-	Mtu       int                            `json:"mtu"`
-	State     string                         `json:"state"`
-	Type      string                         `json:"type"`
+	Addresses []ContainerStateNetworkAddress `json:"addresses" yaml:"addresses"`
+	Counters  ContainerStateNetworkCounters  `json:"counters" yaml:"counters"`
+	Hwaddr    string                         `json:"hwaddr" yaml:"hwaddr"`
+	HostName  string                         `json:"host_name" yaml:"host_name"`
+	Mtu       int                            `json:"mtu" yaml:"mtu"`
+	State     string                         `json:"state" yaml:"state"`
+	Type      string                         `json:"type" yaml:"type"`
 }
 
 // ContainerStateNetworkAddress represents a network address as part of the network section of a LXD container's state
 type ContainerStateNetworkAddress struct {
-	Family  string `json:"family"`
-	Address string `json:"address"`
-	Netmask string `json:"netmask"`
-	Scope   string `json:"scope"`
+	Family  string `json:"family" yaml:"family"`
+	Address string `json:"address" yaml:"address"`
+	Netmask string `json:"netmask" yaml:"netmask"`
+	Scope   string `json:"scope" yaml:"scope"`
 }
 
 // ContainerStateNetworkCounters represents packet counters as part of the network section of a LXD container's state
 type ContainerStateNetworkCounters struct {
-	BytesReceived   int64 `json:"bytes_received"`
-	BytesSent       int64 `json:"bytes_sent"`
-	PacketsReceived int64 `json:"packets_received"`
-	PacketsSent     int64 `json:"packets_sent"`
+	BytesReceived   int64 `json:"bytes_received" yaml:"bytes_received"`
+	BytesSent       int64 `json:"bytes_sent" yaml:"bytes_sent"`
+	PacketsReceived int64 `json:"packets_received" yaml:"packets_received"`
+	PacketsSent     int64 `json:"packets_sent" yaml:"packets_sent"`
 }
diff --git a/shared/api/image.go b/shared/api/image.go
index c4456a332..ea8093fe9 100644
--- a/shared/api/image.go
+++ b/shared/api/image.go
@@ -8,33 +8,33 @@ import (
 type ImagesPost struct {
 	ImagePut `yaml:",inline"`
 
-	Filename string            `json:"filename"`
-	Source   map[string]string `json:"source"`
+	Filename string            `json:"filename" yaml:"filename"`
+	Source   map[string]string `json:"source" yaml:"source"`
 }
 
 // ImagePut represents the modifiable fields of a LXD image
 type ImagePut struct {
-	AutoUpdate bool              `json:"auto_update"`
-	Properties map[string]string `json:"properties"`
-	Public     bool              `json:"public"`
+	AutoUpdate bool              `json:"auto_update" yaml:"auto_update"`
+	Properties map[string]string `json:"properties" yaml:"properties"`
+	Public     bool              `json:"public" yaml:"public"`
 }
 
 // Image represents a LXD image
 type Image struct {
 	ImagePut `yaml:",inline"`
 
-	Aliases      []ImageAlias `json:"aliases"`
-	Architecture string       `json:"architecture"`
-	Cached       bool         `json:"cached"`
-	Filename     string       `json:"filename"`
-	Fingerprint  string       `json:"fingerprint"`
-	Size         int64        `json:"size"`
-	UpdateSource *ImageSource `json:"update_source,omitempty"`
+	Aliases      []ImageAlias `json:"aliases" yaml:"aliases"`
+	Architecture string       `json:"architecture" yaml:"architecture"`
+	Cached       bool         `json:"cached" yaml:"cached"`
+	Filename     string       `json:"filename" yaml:"filename"`
+	Fingerprint  string       `json:"fingerprint" yaml:"fingerprint"`
+	Size         int64        `json:"size" yaml:"size"`
+	UpdateSource *ImageSource `json:"update_source,omitempty" yaml:"update_source,omitempty"`
 
-	CreatedAt  time.Time `json:"created_at"`
-	ExpiresAt  time.Time `json:"expires_at"`
-	LastUsedAt time.Time `json:"last_used_at"`
-	UploadedAt time.Time `json:"uploaded_at"`
+	CreatedAt  time.Time `json:"created_at" yaml:"created_at"`
+	ExpiresAt  time.Time `json:"expires_at" yaml:"expires_at"`
+	LastUsedAt time.Time `json:"last_used_at" yaml:"last_used_at"`
+	UploadedAt time.Time `json:"uploaded_at" yaml:"uploaded_at"`
 }
 
 // Writable converts a full Image struct into a ImagePut struct (filters read-only fields)
@@ -44,16 +44,16 @@ func (img *Image) Writable() ImagePut {
 
 // ImageAlias represents an alias from the alias list of a LXD image
 type ImageAlias struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
+	Name        string `json:"name" yaml:"name"`
+	Description string `json:"description" yaml:"description"`
 }
 
 // ImageSource represents the source of a LXD image
 type ImageSource struct {
-	Alias       string `json:"alias"`
-	Certificate string `json:"certificate"`
-	Protocol    string `json:"protocol"`
-	Server      string `json:"server"`
+	Alias       string `json:"alias" yaml:"alias"`
+	Certificate string `json:"certificate" yaml:"certificate"`
+	Protocol    string `json:"protocol" yaml:"protocol"`
+	Server      string `json:"server" yaml:"server"`
 }
 
 // ImageAliasesPost represents a new LXD image alias
@@ -63,18 +63,18 @@ type ImageAliasesPost struct {
 
 // ImageAliasesEntryPost represents the required fields to rename a LXD image alias
 type ImageAliasesEntryPost struct {
-	Name string `json:"name"`
+	Name string `json:"name" yaml:"name"`
 }
 
 // ImageAliasesEntryPut represents the modifiable fields of a LXD image alias
 type ImageAliasesEntryPut struct {
-	Description string `json:"description"`
-	Target      string `json:"target"`
+	Description string `json:"description" yaml:"description"`
+	Target      string `json:"target" yaml:"target"`
 }
 
 // ImageAliasesEntry represents a LXD image alias
 type ImageAliasesEntry struct {
 	ImageAliasesEntryPut `yaml:",inline"`
 
-	Name string `json:"name"`
+	Name string `json:"name" yaml:"name"`
 }
diff --git a/shared/api/network.go b/shared/api/network.go
index 1fb138404..28a8206e4 100644
--- a/shared/api/network.go
+++ b/shared/api/network.go
@@ -2,7 +2,7 @@ package api
 
 // Network represents a LXD network
 type Network struct {
-	Name   string   `json:"name"`
-	Type   string   `json:"type"`
-	UsedBy []string `json:"used_by"`
+	Name   string   `json:"name" yaml:"name"`
+	Type   string   `json:"type" yaml:"type"`
+	UsedBy []string `json:"used_by" yaml:"used_by"`
 }
diff --git a/shared/api/operation.go b/shared/api/operation.go
index 14db8fd0f..787c0cf7a 100644
--- a/shared/api/operation.go
+++ b/shared/api/operation.go
@@ -6,14 +6,14 @@ import (
 
 // Operation represents a LXD background operation
 type Operation struct {
-	ID         string                 `json:"id"`
-	Class      string                 `json:"class"`
-	CreatedAt  time.Time              `json:"created_at"`
-	UpdatedAt  time.Time              `json:"updated_at"`
-	Status     string                 `json:"status"`
-	StatusCode StatusCode             `json:"status_code"`
-	Resources  map[string][]string    `json:"resources"`
-	Metadata   map[string]interface{} `json:"metadata"`
-	MayCancel  bool                   `json:"may_cancel"`
-	Err        string                 `json:"err"`
+	ID         string                 `json:"id" yaml:"id"`
+	Class      string                 `json:"class" yaml:"class"`
+	CreatedAt  time.Time              `json:"created_at" yaml:"created_at"`
+	UpdatedAt  time.Time              `json:"updated_at" yaml:"updated_at"`
+	Status     string                 `json:"status" yaml:"status"`
+	StatusCode StatusCode             `json:"status_code" yaml:"status_code"`
+	Resources  map[string][]string    `json:"resources" yaml:"resources"`
+	Metadata   map[string]interface{} `json:"metadata" yaml:"metadata"`
+	MayCancel  bool                   `json:"may_cancel" yaml:"may_cancel"`
+	Err        string                 `json:"err" yaml:"err"`
 }
diff --git a/shared/api/profile.go b/shared/api/profile.go
index 2ade83ea8..30bdbba3d 100644
--- a/shared/api/profile.go
+++ b/shared/api/profile.go
@@ -4,26 +4,26 @@ package api
 type ProfilesPost struct {
 	ProfilePut `yaml:",inline"`
 
-	Name string `json:"name"`
+	Name string `json:"name" yaml:"name"`
 }
 
 // ProfilePost represents the fields required to rename a LXD profile
 type ProfilePost struct {
-	Name string `json:"name"`
+	Name string `json:"name" yaml:"name"`
 }
 
 // ProfilePut represents the modifiable fields of a LXD profile
 type ProfilePut struct {
-	Config      map[string]string            `json:"config"`
-	Description string                       `json:"description"`
-	Devices     map[string]map[string]string `json:"devices"`
+	Config      map[string]string            `json:"config" yaml:"config"`
+	Description string                       `json:"description" yaml:"description"`
+	Devices     map[string]map[string]string `json:"devices" yaml:"devices"`
 }
 
 // Profile represents a LXD profile
 type Profile struct {
 	ProfilePut `yaml:",inline"`
 
-	Name string `json:"name"`
+	Name string `json:"name" yaml:"name"`
 }
 
 // Writable converts a full Profile struct into a ProfilePut struct (filters read-only fields)
diff --git a/shared/api/response.go b/shared/api/response.go
index a049c4cd1..71c7a0ecb 100644
--- a/shared/api/response.go
+++ b/shared/api/response.go
@@ -8,26 +8,26 @@ import (
 type ResponseRaw struct {
 	Response `yaml:",inline"`
 
-	Metadata interface{} `json:"metadata"`
+	Metadata interface{} `json:"metadata" yaml:"metadata"`
 }
 
 // Response represents a LXD operation
 type Response struct {
-	Type ResponseType `json:"type"`
+	Type ResponseType `json:"type" yaml:"type"`
 
 	// Valid only for Sync responses
-	Status     string `json:"status"`
-	StatusCode int    `json:"status_code"`
+	Status     string `json:"status" yaml:"status"`
+	StatusCode int    `json:"status_code" yaml:"status_code"`
 
 	// Valid only for Async responses
-	Operation string `json:"operation"`
+	Operation string `json:"operation" yaml:"operation"`
 
 	// Valid only for Error responses
-	Code  int    `json:"error_code"`
-	Error string `json:"error"`
+	Code  int    `json:"error_code" yaml:"error_code"`
+	Error string `json:"error" yaml:"error"`
 
 	// Valid for Sync and Error responses
-	Metadata json.RawMessage `json:"metadata"`
+	Metadata json.RawMessage `json:"metadata" yaml:"metadata"`
 }
 
 // MetadataAsMap parses the Response metadata into a map
diff --git a/shared/api/server.go b/shared/api/server.go
index 4790cea47..53a4c81be 100644
--- a/shared/api/server.go
+++ b/shared/api/server.go
@@ -2,34 +2,34 @@ package api
 
 // ServerEnvironment represents the read-only environment fields of a LXD server
 type ServerEnvironment struct {
-	Addresses              []string `json:"addresses"`
-	Architectures          []string `json:"architectures"`
-	Certificate            string   `json:"certificate"`
-	CertificateFingerprint string   `json:"certificate_fingerprint"`
-	Driver                 string   `json:"driver"`
-	DriverVersion          string   `json:"driver_version"`
-	Kernel                 string   `json:"kernel"`
-	KernelArchitecture     string   `json:"kernel_architecture"`
-	KernelVersion          string   `json:"kernel_version"`
-	Server                 string   `json:"server"`
-	ServerPid              int      `json:"server_pid"`
-	ServerVersion          string   `json:"server_version"`
-	Storage                string   `json:"storage"`
-	StorageVersion         string   `json:"storage_version"`
+	Addresses              []string `json:"addresses" yaml:"addresses"`
+	Architectures          []string `json:"architectures" yaml:"architectures"`
+	Certificate            string   `json:"certificate" yaml:"certificate"`
+	CertificateFingerprint string   `json:"certificate_fingerprint" yaml:"certificate_fingerprint"`
+	Driver                 string   `json:"driver" yaml:"driver"`
+	DriverVersion          string   `json:"driver_version" yaml:"driver_version"`
+	Kernel                 string   `json:"kernel" yaml:"kernel"`
+	KernelArchitecture     string   `json:"kernel_architecture" yaml:"kernel_architecture"`
+	KernelVersion          string   `json:"kernel_version" yaml:"kernel_version"`
+	Server                 string   `json:"server" yaml:"server"`
+	ServerPid              int      `json:"server_pid" yaml:"server_pid"`
+	ServerVersion          string   `json:"server_version" yaml:"server_version"`
+	Storage                string   `json:"storage" yaml:"storage"`
+	StorageVersion         string   `json:"storage_version" yaml:"storage_version"`
 }
 
 // ServerPut represents the modifiable fields of a LXD server configuration
 type ServerPut struct {
-	Config map[string]interface{} `json:"config"`
+	Config map[string]interface{} `json:"config" yaml:"config"`
 }
 
 // ServerUntrusted represents a LXD server for an untrusted client
 type ServerUntrusted struct {
-	APIExtensions []string `json:"api_extensions"`
-	APIStatus     string   `json:"api_status"`
-	APIVersion    string   `json:"api_version"`
-	Auth          string   `json:"auth"`
-	Public        bool     `json:"public"`
+	APIExtensions []string `json:"api_extensions" yaml:"api_extensions"`
+	APIStatus     string   `json:"api_status" yaml:"api_status"`
+	APIVersion    string   `json:"api_version" yaml:"api_version"`
+	Auth          string   `json:"auth" yaml:"auth"`
+	Public        bool     `json:"public" yaml:"public"`
 }
 
 // Server represents a LXD server
@@ -37,7 +37,7 @@ type Server struct {
 	ServerPut       `yaml:",inline"`
 	ServerUntrusted `yaml:",inline"`
 
-	Environment ServerEnvironment `json:"environment"`
+	Environment ServerEnvironment `json:"environment" yaml:"environment"`
 }
 
 // Writable converts a full Server struct into a ServerPut struct (filters read-only fields)

From 883e2bab5f4c232baf3ef56024a858ecabdb24fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 15 Feb 2017 18:10:19 -0500
Subject: [PATCH 0690/1193] tests: The monitor can exit on its own
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/main.sh b/test/main.sh
index 724fe01b8..402c257bc 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -328,7 +328,7 @@ wipe() {
 
   # shellcheck disable=SC2009
   ps aux | grep lxc-monitord | grep "${1}" | awk '{print $2}' | while read -r pid; do
-    kill -9 "${pid}"
+    kill -9 "${pid}" || true
   done
 
   if [ -f "${TEST_DIR}/loops" ]; then

From 0c6648088e05bbb322d0474749c198e7f11c2b8b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 16 Feb 2017 01:32:21 +0100
Subject: [PATCH 0691/1193] doc: note LXD assumes full control over the pool

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/storage-backends.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/doc/storage-backends.md b/doc/storage-backends.md
index c3db7802d..1d3039b87 100644
--- a/doc/storage-backends.md
+++ b/doc/storage-backends.md
@@ -72,3 +72,6 @@ rsync is used to transfer the container content across.
    Copying the wanted snapshot into a new container and then deleting
    the old container does however work, at the cost of losing any other
    snapshot the container may have had.
+ - Note that LXD will assume it has full control over the zfs pool or dataset.
+   It is recommended to not maintain any non-LXD owned filesystem entities in
+   a LXD zfs pool or dataset since LXD might delete them.

From 5badbec92cb1408206ce91b073113f1ef69a9ad8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 17 Feb 2017 16:02:12 -0500
Subject: [PATCH 0692/1193] Makefile: Drop repeated calls to "go get"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Not needed anymore.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 18 +++---------------
 1 file changed, 3 insertions(+), 15 deletions(-)

diff --git a/Makefile b/Makefile
index 5d092ed0c..e2c758446 100644
--- a/Makefile
+++ b/Makefile
@@ -13,27 +13,18 @@ TAGS=$(shell test -e /usr/include/sqlite3.h && echo "-tags libsqlite3")
 
 .PHONY: default
 default:
-	# Must a few times due to go get race
-	-go get -t -v -d ./...
-	-go get -t -v -d ./...
-	-go get -t -v -d ./...
+	go get -t -v -d ./...
 	go install -v $(TAGS) $(DEBUG) ./...
 	@echo "LXD built successfully"
 
 .PHONY: client
 client:
-	# Must a few times due to go get race
-	-go get -t -v -d ./...
-	-go get -t -v -d ./...
-	-go get -t -v -d ./...
+	go get -t -v -d ./...
 	go install -v $(TAGS) $(DEBUG) ./lxc
 	@echo "LXD client built successfully"
 
 .PHONY: update
 update:
-	# Must a few times due to go get race
-	-go get -t -v -d -u ./...
-	-go get -t -v -d -u ./...
 	go get -t -v -d -u ./...
 	@echo "Dependencies updated"
 
@@ -68,9 +59,6 @@ dist:
 	ln -s ../../../../lxd-$(VERSION) $(TMP)/dist/src/github.com/lxc/lxd
 	
 	# Download dependencies
-	-cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
-	-cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
-	-cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
 	cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
 	
 	# Assemble tarball
@@ -92,7 +80,7 @@ po/%.po: po/$(DOMAIN).pot
 	msgmerge -U po/$*.po po/$(DOMAIN).pot
 
 update-po:
-	-for lang in $(LINGUAS); do\
+	for lang in $(LINGUAS); do\
 	    msgmerge -U $$lang.po po/$(DOMAIN).pot; \
 	    rm -f $$lang.po~; \
 	done

From e51864c631e5bb9d63df5838b4bc52509ace2294 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 17 Feb 2017 16:04:20 -0500
Subject: [PATCH 0693/1193] Makefile: Always include gorilla/context
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/Makefile b/Makefile
index e2c758446..25d894ef0 100644
--- a/Makefile
+++ b/Makefile
@@ -61,6 +61,9 @@ dist:
 	# Download dependencies
 	cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
 	
+	# Workaround for gorilla/mux on Go < 1.7
+	cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -v -d github.com/gorilla/context
+	
 	# Assemble tarball
 	rm $(TMP)/dist/src/github.com/lxc/lxd
 	ln -s ../../../../ $(TMP)/dist/src/github.com/lxc/lxd

From 6f2332c566af5a8f5c9378bf4eb806b4749b74f2 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 17 Feb 2017 18:20:07 +0100
Subject: [PATCH 0694/1193] lxd/container: path may only be used by one disk

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxd/container.go b/lxd/container.go
index b2fb5a834..133e000e1 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -318,6 +318,7 @@ func containerValidDevices(devices types.Devices, profile bool, expanded bool) e
 		return nil
 	}
 
+	var diskDevicePaths []string
 	// Check each device individually
 	for name, m := range devices {
 		if m["type"] == "" {
@@ -347,6 +348,12 @@ func containerValidDevices(devices types.Devices, profile bool, expanded bool) e
 				return fmt.Errorf("Missing parent for %s type nic.", m["nictype"])
 			}
 		} else if m["type"] == "disk" {
+			if !expanded && !shared.StringInSlice(m["path"], diskDevicePaths) {
+				diskDevicePaths = append(diskDevicePaths, m["path"])
+			} else if !expanded {
+				return fmt.Errorf("More than one disk device uses the same path: %s.", m["path"])
+			}
+
 			if m["path"] == "" {
 				return fmt.Errorf("Disk entry is missing the required \"path\" property.")
 			}

From 24a0ad5bf1d4457625a1e7fc4189b0346ac91516 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 20 Feb 2017 16:29:10 -0500
Subject: [PATCH 0695/1193] config: Always use "simplestreams" for images:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We switched to simplestreams by default as the protocol for the images
remote a while back, yet we're still seeing a majority of users use the
LXD protocol.

This will update existing user configuration to use the right protocol
for that remote and hopefully let us one day deprecate the LXD protocol
from images.linuxcontainers.org.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 config.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/config.go b/config.go
index 63cd67154..a8e70eab6 100644
--- a/config.go
+++ b/config.go
@@ -113,6 +113,14 @@ func LoadConfig(path string) (*Config, error) {
 		c.Remotes[k] = v
 	}
 
+	// NOTE: Remove this once we only see a small fraction of non-simplestreams users
+	// Upgrade users to the "simplestreams" protocol
+	images, ok := c.Remotes["images"]
+	if ok && images.Protocol != ImagesRemote.Protocol && images.Addr == ImagesRemote.Addr {
+		c.Remotes["images"] = ImagesRemote
+		SaveConfig(&c, path)
+	}
+
 	return &c, nil
 }
 

From 9e09a19618b13ceebb0762904dab9e2392c1f47f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Feb 2017 01:37:13 -0500
Subject: [PATCH 0696/1193] tests: Improve performance of deadcode test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/static_analysis.sh | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 3039abc46..d4b5bdf5a 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -41,13 +41,11 @@ test_static_analysis() {
 
     ## deadcode
     if which deadcode >/dev/null 2>&1; then
-      for path in . fuidshift lxc lxd lxd/types shared shared/api shared/i18n shared/ioprogress shared/logging shared/osarch shared/simplestreams shared/termios shared/version test/lxd-benchmark; do
-        OUT=$(deadcode ./${path} 2>&1 | grep -v lxd/migrate.pb.go: | grep -v /C: | grep -vi _cgo | grep -vi _cfunc || true)
-        if [ -n "${OUT}" ]; then
-          echo "${OUT}" >&2
-          false
-        fi
-      done
+      OUT=$(deadcode ./ ./fuidshift ./lxc ./lxd ./lxd/types ./shared ./shared/api ./shared/i18n ./shared/ioprogress ./shared/logging ./shared/osarch ./shared/simplestreams ./shared/termios ./shared/version ./test/lxd-benchmark 2>&1 | grep -v lxd/migrate.pb.go: | grep -v /C: | grep -vi _cgo | grep -vi _cfunc || true)
+      if [ -n "${OUT}" ]; then
+        echo "${OUT}" >&2
+        false
+      fi
     fi
 
     if which godeps >/dev/null 2>&1; then

From 23ebd1ff5004c9061c1aea5bc2af6c7b4de5bd99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Feb 2017 17:29:36 -0500
Subject: [PATCH 0697/1193] tests: Also unmount the devlxd path
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/main.sh b/test/main.sh
index 402c257bc..d24e8afc2 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -228,6 +228,7 @@ kill_lxd() {
 
     # Cleanup shmounts (needed due to the forceful kill)
     find "${daemon_dir}" -name shmounts -exec "umount" "-l" "{}" \; >/dev/null 2>&1 || true
+    find "${daemon_dir}" -name devlxd -exec "umount" "-l" "{}" \; >/dev/null 2>&1 || true
 
     check_leftovers="true"
   fi

From 915a9716e2c4bbee91954742a2e7eb72ac1efc44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Feb 2017 00:10:12 -0500
Subject: [PATCH 0698/1193] simplestreams: Properly handle image rebuilds
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams/simplestreams.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index 6ca766687..76b2e8eb5 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -40,6 +40,10 @@ func (a ssSortImage) Less(i, j int) bool {
 				return false
 			}
 
+			if a[i].CreatedAt == a[j].CreatedAt {
+				return a[i].Properties["serial"] > a[j].Properties["serial"]
+			}
+
 			return a[i].CreatedAt.UTC().Unix() > a[j].CreatedAt.UTC().Unix()
 		}
 

From a8a0f57c5528f8eb8ba413a34e727c9b4c2ed46a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Feb 2017 00:36:04 -0500
Subject: [PATCH 0699/1193] daemon: s/Default map/Available map/
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

As the container maps will be a subset of the available map.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 3b3d86773..551cff6f3 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -763,7 +763,7 @@ func (d *Daemon) Init() error {
 		shared.LogWarn("Error reading idmap", log.Ctx{"err": err.Error()})
 		shared.LogWarnf("Only privileged containers will be able to run")
 	} else {
-		shared.LogInfof("Default uid/gid map:")
+		shared.LogInfof("Available uid/gid map:")
 		for _, lxcmap := range d.IdmapSet.ToLxcString() {
 			shared.LogInfof(strings.TrimRight(" - "+lxcmap, "\n"))
 		}

From b9c69afa746eb4bef9de82c2fe20fb5d69da02ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Feb 2017 01:02:20 -0500
Subject: [PATCH 0700/1193] main: Restrict daemon and activateifneeded to root
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_activateifneeded.go | 8 ++++++++
 lxd/main_daemon.go           | 5 +++++
 2 files changed, 13 insertions(+)

diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index f91ff6b1c..1ff4c8b8b 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -1,11 +1,19 @@
 package main
 
 import (
+	"fmt"
+	"os"
+
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 )
 
 func cmdActivateIfNeeded() error {
+	// Only root should run this
+	if os.Geteuid() != 0 {
+		return fmt.Errorf("This must be run as root")
+	}
+
 	// Don't start a full daemon, we just need DB access
 	d := &Daemon{
 		lxcpath: shared.VarPath("containers"),
diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
index a257b7769..4d6d85157 100644
--- a/lxd/main_daemon.go
+++ b/lxd/main_daemon.go
@@ -14,6 +14,11 @@ import (
 )
 
 func cmdDaemon() error {
+	// Only root should run this
+	if os.Geteuid() != 0 {
+		return fmt.Errorf("This must be run as root")
+	}
+
 	if *argCPUProfile != "" {
 		f, err := os.Create(*argCPUProfile)
 		if err != nil {

From f7f56c16cb976e12d3485a04117afc6a102c95be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Feb 2017 01:11:26 -0500
Subject: [PATCH 0701/1193] main: Fix comment in activateifneeded
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_activateifneeded.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index 1ff4c8b8b..7f831f8e9 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -43,12 +43,13 @@ func cmdActivateIfNeeded() error {
 		return err
 	}
 
-	// Look for auto-started or previously started containers
+	// Load the idmap for unprivileged containers
 	d.IdmapSet, err = shared.DefaultIdmapSet()
 	if err != nil {
 		return err
 	}
 
+	// Look for auto-started or previously started containers
 	result, err := dbContainersList(d.db, cTypeRegular)
 	if err != nil {
 		return err

From 7d86242edcd8c64e000fdbe5cf144f4b4b9b7725 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Feb 2017 01:18:19 -0500
Subject: [PATCH 0702/1193] idmap: DefaultIdmapSet is always for root
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The username check was always wrong, so lets just assume root as nobody
else can start the daemon.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 32 ++++++--------------------------
 1 file changed, 6 insertions(+), 26 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 2f9d847da..81066c19d 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
-	"os/user"
 	"path"
 	"path/filepath"
 	"strconv"
@@ -438,53 +437,34 @@ func getFromMap(fname string, username string) (int, int, error) {
 }
 
 /*
- * Get current username
- */
-func getUsername() (string, error) {
-	me, err := user.Current()
-	if err == nil {
-		return me.Username, nil
-	} else {
-		/* user.Current() requires cgo */
-		username := os.Getenv("USER")
-		if username == "" {
-			return "", err
-		}
-		return username, nil
-	}
-}
-
-/*
  * Create a new default idmap
  */
 func DefaultIdmapSet() (*IdmapSet, error) {
-	myname, err := getUsername()
-	if err != nil {
-		return nil, err
-	}
+	var err error
 
+	// Fallback values
 	umin := 1000000
 	urange := 1000000000
 	gmin := 1000000
 	grange := 1000000000
 
+	// Check if shadow's uidmap tools are installed
 	newuidmap, _ := exec.LookPath("newuidmap")
 	newgidmap, _ := exec.LookPath("newgidmap")
-
 	if newuidmap != "" && newgidmap != "" && PathExists("/etc/subuid") && PathExists("/etc/subgid") {
-		umin, urange, err = getFromMap("/etc/subuid", myname)
+		umin, urange, err = getFromMap("/etc/subuid", "root")
 		if err != nil {
 			return nil, err
 		}
 
-		gmin, grange, err = getFromMap("/etc/subgid", myname)
+		gmin, grange, err = getFromMap("/etc/subgid", "root")
 		if err != nil {
 			return nil, err
 		}
 	}
 
+	// Generate the map
 	m := new(IdmapSet)
-
 	e := IdmapEntry{Isuid: true, Nsid: 0, Hostid: umin, Maprange: urange}
 	m.Idmap = Extend(m.Idmap, e)
 	e = IdmapEntry{Isgid: true, Nsid: 0, Hostid: gmin, Maprange: grange}

From cb8f322e53750bd78896f0f1bf1d038b9276b98e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Feb 2017 01:44:58 -0500
Subject: [PATCH 0703/1193] idmap: Improve parsing of the shadow id files
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 98 ++++++++++++++++++++++++++++--------------------
 1 file changed, 57 insertions(+), 41 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 81066c19d..ee86ba12d 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -383,92 +383,108 @@ func (set *IdmapSet) ShiftFile(p string) error {
 	return set.ShiftRootfs(p)
 }
 
-const (
-	minIDRange = 65536
-)
-
 /*
  * get a uid or gid mapping from /etc/subxid
  */
-func getFromMap(fname string, username string) (int, int, error) {
+func getFromShadow(fname string, username string) ([][]int, error) {
+	entries := [][]int{}
+
 	f, err := os.Open(fname)
-	var min int
-	var idrange int
 	if err != nil {
-		return 0, 0, err
+		return nil, err
 	}
 	defer f.Close()
+
 	scanner := bufio.NewScanner(f)
-	min = 0
-	idrange = 0
 	for scanner.Scan() {
-		/*
-		 * /etc/sub{gu}id allow comments in the files, so ignore
-		 * everything after a '#'
-		 */
+		// Skip comments
 		s := strings.Split(scanner.Text(), "#")
 		if len(s[0]) == 0 {
 			continue
 		}
 
+		// Validate format
 		s = strings.Split(s[0], ":")
 		if len(s) < 3 {
-			return 0, 0, fmt.Errorf("unexpected values in %q: %q", fname, s)
+			return nil, fmt.Errorf("Unexpected values in %q: %q", fname, s)
 		}
+
 		if strings.EqualFold(s[0], username) {
-			bigmin, err := strconv.ParseUint(s[1], 10, 32)
+			// Get range start
+			entryStart, err := strconv.ParseUint(s[1], 10, 32)
 			if err != nil {
 				continue
 			}
-			bigIdrange, err := strconv.ParseUint(s[2], 10, 32)
+
+			// Get range size
+			entrySize, err := strconv.ParseUint(s[2], 10, 32)
 			if err != nil {
 				continue
 			}
-			min = int(bigmin)
-			idrange = int(bigIdrange)
-			if idrange < minIDRange {
-				continue
-			}
-			return min, idrange, nil
+
+			entries = append(entries, []int{int(entryStart), int(entrySize)})
 		}
 	}
 
-	return 0, 0, fmt.Errorf("User %q has no %ss.", username, path.Base(fname))
+	if len(entries) == 0 {
+		return nil, fmt.Errorf("User %q has no %ss.", username, path.Base(fname))
+	}
+
+	return entries, nil
 }
 
 /*
  * Create a new default idmap
  */
 func DefaultIdmapSet() (*IdmapSet, error) {
-	var err error
-
-	// Fallback values
-	umin := 1000000
-	urange := 1000000000
-	gmin := 1000000
-	grange := 1000000000
+	idmapset := new(IdmapSet)
 
 	// Check if shadow's uidmap tools are installed
 	newuidmap, _ := exec.LookPath("newuidmap")
 	newgidmap, _ := exec.LookPath("newgidmap")
 	if newuidmap != "" && newgidmap != "" && PathExists("/etc/subuid") && PathExists("/etc/subgid") {
-		umin, urange, err = getFromMap("/etc/subuid", "root")
+		// Parse the shadow uidmap
+		entries, err := getFromShadow("/etc/subuid", "root")
 		if err != nil {
 			return nil, err
 		}
 
-		gmin, grange, err = getFromMap("/etc/subgid", "root")
+		for _, entry := range entries {
+			// Check that it's big enough to be useful
+			if int(entry[1]) < 65536 {
+				continue
+			}
+
+			e := IdmapEntry{Isuid: true, Nsid: 0, Hostid: entry[0], Maprange: entry[1]}
+			idmapset.Idmap = Extend(idmapset.Idmap, e)
+
+			// NOTE: Remove once LXD can deal with multiple shadow maps
+			break
+		}
+
+		// Parse the shadow gidmap
+		entries, err = getFromShadow("/etc/subgid", "root")
 		if err != nil {
 			return nil, err
 		}
-	}
 
-	// Generate the map
-	m := new(IdmapSet)
-	e := IdmapEntry{Isuid: true, Nsid: 0, Hostid: umin, Maprange: urange}
-	m.Idmap = Extend(m.Idmap, e)
-	e = IdmapEntry{Isgid: true, Nsid: 0, Hostid: gmin, Maprange: grange}
-	m.Idmap = Extend(m.Idmap, e)
+		for _, entry := range entries {
+			// Check that it's big enough to be useful
+			if int(entry[1]) < 65536 {
+				continue
+			}
 
-	return m, nil
+			e := IdmapEntry{Isgid: true, Nsid: 0, Hostid: entry[0], Maprange: entry[1]}
+			idmapset.Idmap = Extend(idmapset.Idmap, e)
+
+			// NOTE: Remove once LXD can deal with multiple shadow maps
+			break
+		}
+	} else {
+		// Fallback map
+		e := IdmapEntry{Isuid: true, Isgid: true, Nsid: 0, Hostid: 1000000, Maprange: 1000000000}
+		idmapset.Idmap = Extend(idmapset.Idmap, e)
+	}
+
+	return idmapset, nil
 }

From 7bfc3149b26aa82d7a5c5e976d0f56db8d241541 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Feb 2017 02:09:33 -0500
Subject: [PATCH 0704/1193] idmap: Implement parsing of kernel id maps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index ee86ba12d..8a3ebf3d9 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -434,6 +434,60 @@ func getFromShadow(fname string, username string) ([][]int, error) {
 }
 
 /*
+ * get a uid or gid mapping from /proc/self/{g,u}id_map
+ */
+func getFromProc(fname string) ([][]int, error) {
+	entries := [][]int{}
+
+	f, err := os.Open(fname)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	scanner := bufio.NewScanner(f)
+	for scanner.Scan() {
+		// Skip comments
+		s := strings.Split(scanner.Text(), "#")
+		if len(s[0]) == 0 {
+			continue
+		}
+
+		// Validate format
+		s = strings.Fields(s[0])
+		if len(s) < 3 {
+			return nil, fmt.Errorf("Unexpected values in %q: %q", fname, s)
+		}
+
+		// Get range start
+		entryStart, err := strconv.ParseUint(s[0], 10, 32)
+		if err != nil {
+			continue
+		}
+
+		// Get range size
+		entryHost, err := strconv.ParseUint(s[1], 10, 32)
+		if err != nil {
+			continue
+		}
+
+		// Get range size
+		entrySize, err := strconv.ParseUint(s[2], 10, 32)
+		if err != nil {
+			continue
+		}
+
+		entries = append(entries, []int{int(entryStart), int(entryHost), int(entrySize)})
+	}
+
+	if len(entries) == 0 {
+		return nil, fmt.Errorf("Namespace doesn't have any map set")
+	}
+
+	return entries, nil
+}
+
+/*
  * Create a new default idmap
  */
 func DefaultIdmapSet() (*IdmapSet, error) {
@@ -488,3 +542,46 @@ func DefaultIdmapSet() (*IdmapSet, error) {
 
 	return idmapset, nil
 }
+
+/*
+ * Create an idmap of the current allocation
+ */
+func CurrentIdmapSet() (*IdmapSet, error) {
+	idmapset := new(IdmapSet)
+
+	if PathExists("/proc/self/uid_map") {
+		// Parse the uidmap
+		entries, err := getFromProc("/proc/self/uid_map")
+		if err != nil {
+			return nil, err
+		}
+
+		for _, entry := range entries {
+			e := IdmapEntry{Isuid: true, Nsid: entry[0], Hostid: entry[1], Maprange: entry[2]}
+			idmapset.Idmap = Extend(idmapset.Idmap, e)
+		}
+	} else {
+		// Fallback map
+		e := IdmapEntry{Isuid: true, Nsid: 0, Hostid: 0, Maprange: 0}
+		idmapset.Idmap = Extend(idmapset.Idmap, e)
+	}
+
+	if PathExists("/proc/self/gid_map") {
+		// Parse the gidmap
+		entries, err := getFromProc("/proc/self/gid_map")
+		if err != nil {
+			return nil, err
+		}
+
+		for _, entry := range entries {
+			e := IdmapEntry{Isgid: true, Nsid: entry[0], Hostid: entry[1], Maprange: entry[2]}
+			idmapset.Idmap = Extend(idmapset.Idmap, e)
+		}
+	} else {
+		// Fallback map
+		e := IdmapEntry{Isgid: true, Nsid: 0, Hostid: 0, Maprange: 0}
+		idmapset.Idmap = Extend(idmapset.Idmap, e)
+	}
+
+	return idmapset, nil
+}

From ed44fad62ef3cec1cce264789d6d6a8d734bf3bd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 22 Feb 2017 14:09:26 +0100
Subject: [PATCH 0705/1193] shared: coding-style pedantry

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/shared/util.go b/shared/util.go
index 55764e9cb..6caf1cd75 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -247,11 +247,13 @@ func WriteAllBuf(w io.Writer, buf *bytes.Buffer) error {
 // FileMove tries to move a file by using os.Rename,
 // if that fails it tries to copy the file and remove the source.
 func FileMove(oldPath string, newPath string) error {
-	if err := os.Rename(oldPath, newPath); err == nil {
+	err := os.Rename(oldPath, newPath)
+	if err == nil {
 		return nil
 	}
 
-	if err := FileCopy(oldPath, newPath); err != nil {
+	err = FileCopy(oldPath, newPath)
+	if err != nil {
 		return err
 	}
 

From aafe5bf32dd8bd3cd66e9973a0449629f5154ccb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Feb 2017 12:10:16 -0500
Subject: [PATCH 0706/1193] Don't ignore snapshot deletion failures
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 133804dfb..26264688b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2417,11 +2417,13 @@ func (c *containerLXC) Delete() error {
 		// Remove the snapshot
 		if err := c.storage.ContainerSnapshotDelete(c); err != nil {
 			shared.LogWarn("Failed to delete snapshot", log.Ctx{"name": c.Name(), "err": err})
+			return err
 		}
 	} else {
 		// Remove all snapshot
 		if err := containerDeleteSnapshots(c.daemon, c.Name()); err != nil {
 			shared.LogWarn("Failed to delete snapshots", log.Ctx{"name": c.Name(), "err": err})
+			return err
 		}
 
 		// Clean things up

From 5b2a4d6896edc0a4c50e1f48df8faec113dcaf84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Feb 2017 17:30:06 -0500
Subject: [PATCH 0707/1193] Add extra validation for unix-block/unix-char
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2941

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/lxd/container.go b/lxd/container.go
index 133e000e1..65bc7c6d8 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -377,6 +377,25 @@ func containerValidDevices(devices types.Devices, profile bool, expanded bool) e
 			if m["path"] == "" {
 				return fmt.Errorf("Unix device entry is missing the required \"path\" property.")
 			}
+
+			if m["major"] == "" || m["minor"] == "" {
+				if !shared.PathExists(m["path"]) {
+					return fmt.Errorf("The device path doesn't exist on the host and major/minor wasn't specified.")
+				}
+
+				dType, _, _, err := deviceGetAttributes(m["path"])
+				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 79d26e456caf49725ce3003000502651302c05d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Feb 2017 18:08:45 -0500
Subject: [PATCH 0708/1193] idmap: Drop GetOwner
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 8a3ebf3d9..b2784abb8 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -319,11 +319,6 @@ func (m IdmapSet) ShiftFromNs(uid int, gid int) (int, int) {
 	return m.doShiftIntoNs(uid, gid, "out")
 }
 
-func GetOwner(path string) (int, int, error) {
-	uid, gid, _, _, _, _, err := GetFileStat(path)
-	return uid, gid, err
-}
-
 func (set *IdmapSet) doUidshiftIntoContainer(dir string, testmode bool, how string) error {
 	// Expand any symlink before the final path component
 	tmp := filepath.Dir(dir)
@@ -335,7 +330,7 @@ func (set *IdmapSet) doUidshiftIntoContainer(dir string, testmode bool, how stri
 	dir = strings.TrimRight(dir, "/")
 
 	convert := func(path string, fi os.FileInfo, err error) (e error) {
-		uid, gid, err := GetOwner(path)
+		uid, gid, _, _, _, _, err := GetFileStat(path)
 		if err != nil {
 			return err
 		}

From 691c079b32be430e19d8b743f0e5fbe6b2732bbd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Feb 2017 18:33:47 -0500
Subject: [PATCH 0709/1193] Remove debugging during idmap changes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 26264688b..7391965b7 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1471,7 +1471,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	}
 
 	if !reflect.DeepEqual(idmap, lastIdmap) {
-		shared.LogDebugf("Container idmap changed, remapping: %s => %s", lastIdmap, idmap)
+		shared.LogDebugf("Container idmap changed, remapping")
 
 		err := c.StorageStart()
 		if err != nil {

From f3173837c6312006ed939c28156edea8b7833376 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Feb 2017 19:44:40 -0500
Subject: [PATCH 0710/1193] idmap: Implement Usable() functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 113 insertions(+)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index b2784abb8..65658423d 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -7,10 +7,22 @@ import (
 	"os/exec"
 	"path"
 	"path/filepath"
+	"sort"
 	"strconv"
 	"strings"
 )
 
+type IdRange struct {
+	Isuid   bool
+	Isgid   bool
+	Startid int
+	Endid   int
+}
+
+func (i *IdRange) Contains(id int) bool {
+	return id >= i.Startid && id <= i.Endid
+}
+
 /*
  * One entry in id mapping set - a single range of either
  * uid or gid mappings.
@@ -83,6 +95,40 @@ func (e *IdmapEntry) Intersects(i IdmapEntry) bool {
 	return false
 }
 
+func (e *IdmapEntry) Usable() error {
+	kernelIdmap, err := CurrentIdmapSet()
+	if err != nil {
+		return err
+	}
+
+	kernelRanges, err := kernelIdmap.ValidRanges()
+	if err != nil {
+		return err
+	}
+
+	valid := false
+	for _, kernelRange := range kernelRanges {
+		if kernelRange.Isuid != e.Isuid {
+			continue
+		}
+
+		if kernelRange.Isgid != e.Isgid {
+			continue
+		}
+
+		if kernelRange.Contains(e.Hostid) && kernelRange.Contains(e.Hostid+e.Maprange-1) {
+			valid = true
+			break
+		}
+	}
+
+	if !valid {
+		return fmt.Errorf("The '%s' map can't work in the current user namespace.", e.ToLxcString())
+	}
+
+	return nil
+}
+
 func (e *IdmapEntry) parse(s string) error {
 	split := strings.Split(s, ":")
 	var err error
@@ -184,6 +230,22 @@ func (m IdmapSet) Len() int {
 	return len(m.Idmap)
 }
 
+func (m IdmapSet) Swap(i, j int) {
+	m.Idmap[i], m.Idmap[j] = m.Idmap[j], m.Idmap[i]
+}
+
+func (m IdmapSet) Less(i, j int) bool {
+	if m.Idmap[i].Isuid != m.Idmap[j].Isuid {
+		return m.Idmap[i].Isuid == true
+	}
+
+	if m.Idmap[i].Isgid != m.Idmap[j].Isgid {
+		return m.Idmap[i].Isgid == true
+	}
+
+	return m.Idmap[i].Nsid < m.Idmap[j].Nsid
+}
+
 func (m IdmapSet) Intersects(i IdmapEntry) bool {
 	for _, e := range m.Idmap {
 		if i.Intersects(e) {
@@ -202,6 +264,57 @@ func (m IdmapSet) HostidsIntersect(i IdmapEntry) bool {
 	return false
 }
 
+func (m IdmapSet) Usable() error {
+	for _, e := range m.Idmap {
+		err := e.Usable()
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (m IdmapSet) ValidRanges() ([]*IdRange, error) {
+	ranges := []*IdRange{}
+
+	// Sort the map
+	idmap := IdmapSet{}
+	err := DeepCopy(&m, &idmap)
+	if err != nil {
+		return nil, err
+	}
+	sort.Sort(idmap)
+
+	for _, mapEntry := range idmap.Idmap {
+		var entry *IdRange
+		for _, idEntry := range ranges {
+			if mapEntry.Isuid != idEntry.Isuid || mapEntry.Isgid != idEntry.Isgid {
+				continue
+			}
+
+			if idEntry.Endid+1 == mapEntry.Nsid {
+				entry = idEntry
+				break
+			}
+		}
+
+		if entry != nil {
+			entry.Endid = entry.Endid + mapEntry.Maprange
+			continue
+		}
+
+		ranges = append(ranges, &IdRange{
+			Isuid:   mapEntry.Isuid,
+			Isgid:   mapEntry.Isgid,
+			Startid: mapEntry.Nsid,
+			Endid:   mapEntry.Nsid + mapEntry.Maprange - 1,
+		})
+	}
+
+	return ranges, nil
+}
+
 /* AddSafe adds an entry to the idmap set, breaking apart any ranges that the
  * new idmap intersects with in the process.
  */

From 721afa1d960daffef6c31c562f217b2033b5c8a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Feb 2017 19:44:54 -0500
Subject: [PATCH 0711/1193] Check for the validity of the id maps at startup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2885

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 32 ++++++++++++++++++++++++++++----
 1 file changed, 28 insertions(+), 4 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 551cff6f3..219e771ca 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -760,12 +760,36 @@ func (d *Daemon) Init() error {
 	/* Read the uid/gid allocation */
 	d.IdmapSet, err = shared.DefaultIdmapSet()
 	if err != nil {
-		shared.LogWarn("Error reading idmap", log.Ctx{"err": err.Error()})
+		shared.LogWarn("Error reading default idmap", log.Ctx{"err": err.Error()})
 		shared.LogWarnf("Only privileged containers will be able to run")
+		d.IdmapSet = nil
 	} else {
-		shared.LogInfof("Available uid/gid map:")
-		for _, lxcmap := range d.IdmapSet.ToLxcString() {
-			shared.LogInfof(strings.TrimRight(" - "+lxcmap, "\n"))
+		kernelIdmapSet, err := shared.CurrentIdmapSet()
+		if err == nil {
+			shared.LogInfof("Kernel uid/gid map:")
+			for _, lxcmap := range kernelIdmapSet.ToLxcString() {
+				shared.LogInfof(strings.TrimRight(" - "+lxcmap, "\n"))
+			}
+		}
+
+		shared.LogInfof("Configured LXD uid/gid map:")
+		for _, lxcmap := range d.IdmapSet.Idmap {
+			suffix := ""
+
+			if lxcmap.Usable() != nil {
+				suffix = " (unusable)"
+			}
+
+			for _, lxcEntry := range lxcmap.ToLxcString() {
+				shared.LogInfof(" - %s%s", strings.TrimRight(lxcEntry, "\n"), suffix)
+			}
+		}
+
+		err = d.IdmapSet.Usable()
+		if err != nil {
+			shared.LogWarnf("One or more uid/gid map entry isn't usable (typically due to nesting)")
+			shared.LogWarnf("Only privileged containers will be able to run")
+			d.IdmapSet = nil
 		}
 	}
 

From ac76a42aafd369e56434b7c1f12d1d692ec06c35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 24 Feb 2017 00:55:42 -0500
Subject: [PATCH 0712/1193] Use int64 for uid and gid everywhere
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The right type on Linux would be uint32 but a number of functions rely
on negative values so int64 will be easier to deal with.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                |  2 +-
 lxd/container.go         |  4 +--
 lxd/container_exec.go    |  4 +--
 lxd/container_lxc.go     | 58 +++++++++++++++++++-------------------
 lxd/container_test.go    | 26 ++++++++---------
 lxd/migrate.go           | 12 ++++----
 shared/idmapset_linux.go | 72 +++++++++++++++++++++++++++++-------------------
 shared/util.go           |  6 ++--
 shared/util_linux.go     |  8 +++---
 9 files changed, 105 insertions(+), 87 deletions(-)

diff --git a/client.go b/client.go
index 4cc52742e..0525c7fcb 100644
--- a/client.go
+++ b/client.go
@@ -1767,7 +1767,7 @@ func (c *Client) PushFile(container string, p string, gid int, uid int, mode str
 	return err
 }
 
-func (c *Client) PullFile(container string, p string) (int, int, int, io.ReadCloser, error) {
+func (c *Client) PullFile(container string, p string) (int64, int64, int, io.ReadCloser, error) {
 	if c.Remote.Public {
 		return 0, 0, 0, nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
diff --git a/lxd/container.go b/lxd/container.go
index 65bc7c6d8..acba1b707 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -467,9 +467,9 @@ type container interface {
 	ConfigKeySet(key string, value string) error
 
 	// File handling
-	FilePull(srcpath string, dstpath string) (int, int, os.FileMode, error)
+	FilePull(srcpath string, dstpath string) (int64, int64, os.FileMode, error)
 	FileExists(path string) error
-	FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error
+	FilePush(srcpath string, dstpath string, uid int64, gid int64, mode int) error
 	FileRemove(path string) error
 
 	/* Command execution:
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 9863959db..08fd309f2 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -25,8 +25,8 @@ type execWs struct {
 	container container
 	env       map[string]string
 
-	rootUid          int
-	rootGid          int
+	rootUid          int64
+	rootGid          int64
 	conns            map[int]*websocket.Conn
 	connsLock        sync.Mutex
 	allConnected     chan bool
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7391965b7..103bfbaee 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -267,7 +267,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 
 	// Setup initial idmap config
 	var idmap *shared.IdmapSet
-	base := 0
+	base := int64(0)
 	if !c.IsPrivileged() {
 		idmap, base, err = findIdmap(
 			d,
@@ -441,13 +441,13 @@ func (c *containerLXC) waitOperation() error {
 	return nil
 }
 
-func idmapSize(daemon *Daemon, isolatedStr string, size string) (int, error) {
+func idmapSize(daemon *Daemon, isolatedStr string, size string) (int64, error) {
 	isolated := false
 	if shared.IsTrue(isolatedStr) {
 		isolated = true
 	}
 
-	var idMapSize int
+	var idMapSize int64
 	if size == "" || size == "auto" {
 		if isolated {
 			idMapSize = 65536
@@ -464,7 +464,7 @@ func idmapSize(daemon *Daemon, isolatedStr string, size string) (int, error) {
 			return 0, err
 		}
 
-		idMapSize = int(size)
+		idMapSize = int64(size)
 	}
 
 	return idMapSize, nil
@@ -473,20 +473,20 @@ func idmapSize(daemon *Daemon, isolatedStr string, size string) (int, error) {
 var idmapLock sync.Mutex
 
 func parseRawIdmap(value string) ([]shared.IdmapEntry, error) {
-	getRange := func(r string) (int, int, error) {
+	getRange := func(r string) (int64, int64, error) {
 		entries := strings.Split(r, "-")
 		if len(entries) > 2 {
 			return -1, -1, fmt.Errorf("invalid raw.idmap range %s", r)
 		}
 
-		base, err := strconv.Atoi(entries[0])
+		base, err := strconv.ParseInt(entries[0], 10, 64)
 		if err != nil {
 			return -1, -1, err
 		}
 
-		size := 1
+		size := int64(1)
 		if len(entries) > 1 {
-			size, err = strconv.Atoi(entries[1])
+			size, err = strconv.ParseInt(entries[1], 10, 64)
 			if err != nil {
 				return -1, -1, err
 			}
@@ -558,7 +558,7 @@ func parseRawIdmap(value string) ([]shared.IdmapEntry, error) {
 	return ret.Idmap, nil
 }
 
-func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string, rawIdmap string) (*shared.IdmapSet, int, error) {
+func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string, rawIdmap string) (*shared.IdmapSet, int64, error) {
 	isolated := false
 	if shared.IsTrue(isolatedStr) {
 		isolated = true
@@ -627,12 +627,12 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 			return nil, 0, err
 		}
 
-		mapentries = append(mapentries, &shared.IdmapEntry{Hostid: int(cBase), Maprange: cSize})
+		mapentries = append(mapentries, &shared.IdmapEntry{Hostid: int64(cBase), Maprange: cSize})
 	}
 
 	sort.Sort(mapentries)
 
-	mkIdmap := func(offset int, size int) *shared.IdmapSet {
+	mkIdmap := func(offset int64, size int64) *shared.IdmapSet {
 		set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
 			{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
 			{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
@@ -1495,8 +1495,8 @@ func (c *containerLXC) startCommon() (string, error) {
 		}
 
 		var mode os.FileMode
-		var uid int
-		var gid int
+		var uid int64
+		var gid int64
 
 		if c.IsPrivileged() {
 			mode = 0700
@@ -1512,7 +1512,7 @@ func (c *containerLXC) startCommon() (string, error) {
 			return "", err
 		}
 
-		err = os.Chown(c.Path(), uid, gid)
+		err = os.Chown(c.Path(), int(uid), int(gid))
 		if err != nil {
 			return "", err
 		}
@@ -2775,7 +2775,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 
 	if shared.StringInSlice("security.idmap.isolated", changedConfig) || shared.StringInSlice("security.idmap.size", changedConfig) || shared.StringInSlice("raw.idmap", changedConfig) || shared.StringInSlice("security.privileged", changedConfig) {
 		var idmap *shared.IdmapSet
-		base := 0
+		base := int64(0)
 		if !c.IsPrivileged() {
 			// update the idmap
 			idmap, base, err = findIdmap(
@@ -3649,8 +3649,8 @@ func (c *containerLXC) templateApplyNow(trigger string) error {
 			}
 		} else {
 			// Create a new one
-			uid := 0
-			gid := 0
+			uid := int64(0)
+			gid := int64(0)
 
 			// Get the right uid and gid for the container
 			if !c.IsPrivileged() {
@@ -3658,7 +3658,7 @@ func (c *containerLXC) templateApplyNow(trigger string) error {
 			}
 
 			// Create the directories leading to the file
-			shared.MkdirAllOwner(path.Dir(fullpath), 0755, uid, gid)
+			shared.MkdirAllOwner(path.Dir(fullpath), 0755, int(uid), int(gid))
 
 			// Create the file itself
 			w, err = os.Create(fullpath)
@@ -3668,7 +3668,7 @@ func (c *containerLXC) templateApplyNow(trigger string) error {
 
 			// Fix ownership and mode
 			if !c.IsPrivileged() {
-				w.Chown(uid, gid)
+				w.Chown(int(uid), int(gid))
 			}
 			w.Chmod(0644)
 		}
@@ -3782,7 +3782,7 @@ func (c *containerLXC) FileExists(path string) error {
 	return nil
 }
 
-func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.FileMode, error) {
+func (c *containerLXC) FilePull(srcpath string, dstpath string) (int64, int64, os.FileMode, error) {
 	// Setup container storage if needed
 	if !c.IsRunning() {
 		err := c.StorageStart()
@@ -3809,8 +3809,8 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 		}
 	}
 
-	uid := -1
-	gid := -1
+	uid := int64(-1)
+	gid := int64(-1)
 	mode := -1
 	var errStr string
 
@@ -3837,7 +3837,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 
 		// Extract the uid
 		if strings.HasPrefix(line, "uid: ") {
-			uid, err = strconv.Atoi(strings.TrimPrefix(line, "uid: "))
+			uid, err = strconv.ParseInt(strings.TrimPrefix(line, "uid: "), 10, 64)
 			if err != nil {
 				return -1, -1, 0, err
 			}
@@ -3847,7 +3847,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 
 		// Extract the gid
 		if strings.HasPrefix(line, "gid: ") {
-			gid, err = strconv.Atoi(strings.TrimPrefix(line, "gid: "))
+			gid, err = strconv.ParseInt(strings.TrimPrefix(line, "gid: "), 10, 64)
 			if err != nil {
 				return -1, -1, 0, err
 			}
@@ -3893,9 +3893,9 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 	return uid, gid, os.FileMode(mode), nil
 }
 
-func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int, mode int) error {
-	var rootUid = 0
-	var rootGid = 0
+func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int64, gid int64, mode int) error {
+	var rootUid int64
+	var rootGid int64
 	var errStr string
 
 	// Map uid and gid if needed
@@ -4295,7 +4295,9 @@ func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *t
 
 	// Unshift the id under /rootfs/ for unpriv containers
 	if !c.IsPrivileged() && strings.HasPrefix(hdr.Name, "/rootfs") {
-		hdr.Uid, hdr.Gid = c.idmapset.ShiftFromNs(hdr.Uid, hdr.Gid)
+		huid, hgid := c.idmapset.ShiftFromNs(int64(hdr.Uid), int64(hdr.Gid))
+		hdr.Uid = int(huid)
+		hdr.Gid = int(hgid)
 		if hdr.Uid == -1 || hdr.Gid == -1 {
 			return nil
 		}
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 54105f141..7eac9ecde 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -255,14 +255,14 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_isolated() {
 
 	for i := 0; i < 2; i++ {
 		suite.Req.Equal(host.Hostid+65536, map1.Idmap[i].Hostid, "hostids don't match %d", i)
-		suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
-		suite.Req.Equal(65536, map1.Idmap[i].Maprange, "incorrect maprange")
+		suite.Req.Equal(int64(0), map1.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(int64(65536), map1.Idmap[i].Maprange, "incorrect maprange")
 	}
 
 	for i := 0; i < 2; i++ {
 		suite.Req.Equal(host.Hostid+65536*2, map2.Idmap[i].Hostid, "hostids don't match")
-		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
-		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
+		suite.Req.Equal(int64(0), map2.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(int64(65536), map2.Idmap[i].Maprange, "incorrect maprange")
 	}
 }
 
@@ -296,14 +296,14 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
 
 	for i := 0; i < 2; i++ {
 		suite.Req.Equal(host.Hostid, map1.Idmap[i].Hostid, "hostids don't match %d", i)
-		suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(int64(0), map1.Idmap[i].Nsid, "nsid nonzero")
 		suite.Req.Equal(host.Maprange, map1.Idmap[i].Maprange, "incorrect maprange")
 	}
 
 	for i := 0; i < 2; i++ {
 		suite.Req.Equal(host.Hostid+65536, map2.Idmap[i].Hostid, "hostids don't match")
-		suite.Req.Equal(0, map2.Idmap[i].Nsid, "nsid nonzero")
-		suite.Req.Equal(65536, map2.Idmap[i].Maprange, "incorrect maprange")
+		suite.Req.Equal(int64(0), map2.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(int64(65536), map2.Idmap[i].Maprange, "incorrect maprange")
 	}
 }
 
@@ -326,19 +326,19 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_raw() {
 
 	for _, i := range []int{0, 3} {
 		suite.Req.Equal(host.Hostid, map1.Idmap[i].Hostid, "hostids don't match")
-		suite.Req.Equal(0, map1.Idmap[i].Nsid, "nsid nonzero")
-		suite.Req.Equal(1000, map1.Idmap[i].Maprange, "incorrect maprange")
+		suite.Req.Equal(int64(0), map1.Idmap[i].Nsid, "nsid nonzero")
+		suite.Req.Equal(int64(1000), map1.Idmap[i].Maprange, "incorrect maprange")
 	}
 
 	for _, i := range []int{1, 4} {
-		suite.Req.Equal(1000, map1.Idmap[i].Hostid, "hostids don't match")
-		suite.Req.Equal(1000, map1.Idmap[i].Nsid, "invalid nsid")
-		suite.Req.Equal(1, map1.Idmap[i].Maprange, "incorrect maprange")
+		suite.Req.Equal(int64(1000), map1.Idmap[i].Hostid, "hostids don't match")
+		suite.Req.Equal(int64(1000), map1.Idmap[i].Nsid, "invalid nsid")
+		suite.Req.Equal(int64(1), map1.Idmap[i].Maprange, "incorrect maprange")
 	}
 
 	for _, i := range []int{2, 5} {
 		suite.Req.Equal(host.Hostid+1001, map1.Idmap[i].Hostid, "hostids don't match")
-		suite.Req.Equal(1001, map1.Idmap[i].Nsid, "invalid nsid")
+		suite.Req.Equal(int64(1001), map1.Idmap[i].Nsid, "invalid nsid")
 		suite.Req.Equal(host.Maprange-1000-1, map1.Idmap[i].Maprange, "incorrect maprange")
 	}
 }
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 6b5aa7f53..5a306f349 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -309,9 +309,9 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 			idmap := IDMapType{
 				Isuid:    proto.Bool(ctnIdmap.Isuid),
 				Isgid:    proto.Bool(ctnIdmap.Isgid),
-				Hostid:   proto.Int(ctnIdmap.Hostid),
-				Nsid:     proto.Int(ctnIdmap.Nsid),
-				Maprange: proto.Int(ctnIdmap.Maprange),
+				Hostid:   proto.Int32(int32(ctnIdmap.Hostid)),
+				Nsid:     proto.Int32(int32(ctnIdmap.Nsid)),
+				Maprange: proto.Int32(int32(ctnIdmap.Maprange)),
 			}
 
 			idmaps = append(idmaps, &idmap)
@@ -647,9 +647,9 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 			e := shared.IdmapEntry{
 				Isuid:    *idmap.Isuid,
 				Isgid:    *idmap.Isgid,
-				Nsid:     int(*idmap.Nsid),
-				Hostid:   int(*idmap.Hostid),
-				Maprange: int(*idmap.Maprange)}
+				Nsid:     int64(*idmap.Nsid),
+				Hostid:   int64(*idmap.Hostid),
+				Maprange: int64(*idmap.Maprange)}
 			srcIdmap.Idmap = shared.Extend(srcIdmap.Idmap, e)
 		}
 
diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 65658423d..9e39f27a4 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -15,11 +15,11 @@ import (
 type IdRange struct {
 	Isuid   bool
 	Isgid   bool
-	Startid int
-	Endid   int
+	Startid int64
+	Endid   int64
 }
 
-func (i *IdRange) Contains(id int) bool {
+func (i *IdRange) Contains(id int64) bool {
 	return id >= i.Startid && id <= i.Endid
 }
 
@@ -30,9 +30,9 @@ func (i *IdRange) Contains(id int) bool {
 type IdmapEntry struct {
 	Isuid    bool
 	Isgid    bool
-	Hostid   int // id as seen on the host - i.e. 100000
-	Nsid     int // id as seen in the ns - i.e. 0
-	Maprange int
+	Hostid   int64 // id as seen on the host - i.e. 100000
+	Nsid     int64 // id as seen in the ns - i.e. 0
+	Maprange int64
 }
 
 func (e *IdmapEntry) ToLxcString() []string {
@@ -50,7 +50,7 @@ func (e *IdmapEntry) ToLxcString() []string {
 	return []string{fmt.Sprintf("g %d %d %d", e.Nsid, e.Hostid, e.Maprange)}
 }
 
-func is_between(x, low, high int) bool {
+func is_between(x, low, high int64) bool {
 	return x >= low && x < high
 }
 
@@ -132,9 +132,11 @@ func (e *IdmapEntry) Usable() error {
 func (e *IdmapEntry) parse(s string) error {
 	split := strings.Split(s, ":")
 	var err error
+
 	if len(split) != 4 {
 		return fmt.Errorf("Bad idmap: %q", s)
 	}
+
 	switch split[0] {
 	case "u":
 		e.Isuid = true
@@ -146,18 +148,24 @@ func (e *IdmapEntry) parse(s string) error {
 	default:
 		return fmt.Errorf("Bad idmap type in %q", s)
 	}
-	e.Nsid, err = strconv.Atoi(split[1])
+
+	nsid, err := strconv.ParseUint(split[1], 10, 32)
 	if err != nil {
 		return err
 	}
-	e.Hostid, err = strconv.Atoi(split[2])
+	e.Nsid = int64(nsid)
+
+	hostid, err := strconv.ParseUint(split[2], 10, 32)
 	if err != nil {
 		return err
 	}
-	e.Maprange, err = strconv.Atoi(split[3])
+	e.Hostid = int64(hostid)
+
+	maprange, err := strconv.ParseUint(split[3], 10, 32)
 	if err != nil {
 		return err
 	}
+	e.Maprange = int64(maprange)
 
 	// wraparound
 	if e.Hostid+e.Maprange < e.Hostid || e.Nsid+e.Maprange < e.Nsid {
@@ -171,10 +179,10 @@ func (e *IdmapEntry) parse(s string) error {
  * Shift a uid from the host into the container
  * I.e. 0 -> 1000 -> 101000
  */
-func (e *IdmapEntry) shift_into_ns(id int) (int, error) {
+func (e *IdmapEntry) shift_into_ns(id int64) (int64, error) {
 	if id < e.Nsid || id >= e.Nsid+e.Maprange {
 		// this mapping doesn't apply
-		return 0, fmt.Errorf("N/A")
+		return 0, fmt.Errorf("ID mapping doesn't apply.")
 	}
 
 	return id - e.Nsid + e.Hostid, nil
@@ -184,10 +192,10 @@ func (e *IdmapEntry) shift_into_ns(id int) (int, error) {
  * Shift a uid from the container back to the host
  * I.e. 101000 -> 1000
  */
-func (e *IdmapEntry) shift_from_ns(id int) (int, error) {
+func (e *IdmapEntry) shift_from_ns(id int64) (int64, error) {
 	if id < e.Hostid || id >= e.Hostid+e.Maprange {
 		// this mapping doesn't apply
-		return 0, fmt.Errorf("N/A")
+		return 0, fmt.Errorf("ID mapping doesn't apply.")
 	}
 
 	return id - e.Hostid + e.Nsid, nil
@@ -391,12 +399,13 @@ func (m IdmapSet) Append(s string) (IdmapSet, error) {
 	return m, nil
 }
 
-func (m IdmapSet) doShiftIntoNs(uid int, gid int, how string) (int, int) {
-	u := -1
-	g := -1
+func (m IdmapSet) doShiftIntoNs(uid int64, gid int64, how string) (int64, int64) {
+	u := int64(-1)
+	g := int64(-1)
+
 	for _, e := range m.Idmap {
 		var err error
-		var tmpu, tmpg int
+		var tmpu, tmpg int64
 		if e.Isuid && u == -1 {
 			switch how {
 			case "in":
@@ -404,10 +413,12 @@ func (m IdmapSet) doShiftIntoNs(uid int, gid int, how string) (int, int) {
 			case "out":
 				tmpu, err = e.shift_from_ns(uid)
 			}
+
 			if err == nil {
 				u = tmpu
 			}
 		}
+
 		if e.Isgid && g == -1 {
 			switch how {
 			case "in":
@@ -415,6 +426,7 @@ func (m IdmapSet) doShiftIntoNs(uid int, gid int, how string) (int, int) {
 			case "out":
 				tmpg, err = e.shift_from_ns(gid)
 			}
+
 			if err == nil {
 				g = tmpg
 			}
@@ -424,11 +436,11 @@ func (m IdmapSet) doShiftIntoNs(uid int, gid int, how string) (int, int) {
 	return u, g
 }
 
-func (m IdmapSet) ShiftIntoNs(uid int, gid int) (int, int) {
+func (m IdmapSet) ShiftIntoNs(uid int64, gid int64) (int64, int64) {
 	return m.doShiftIntoNs(uid, gid, "in")
 }
 
-func (m IdmapSet) ShiftFromNs(uid int, gid int) (int, int) {
+func (m IdmapSet) ShiftFromNs(uid int64, gid int64) (int64, int64) {
 	return m.doShiftIntoNs(uid, gid, "out")
 }
 
@@ -443,17 +455,21 @@ func (set *IdmapSet) doUidshiftIntoContainer(dir string, testmode bool, how stri
 	dir = strings.TrimRight(dir, "/")
 
 	convert := func(path string, fi os.FileInfo, err error) (e error) {
-		uid, gid, _, _, _, _, err := GetFileStat(path)
+		intUid, intGid, _, _, _, _, err := GetFileStat(path)
 		if err != nil {
 			return err
 		}
-		var newuid, newgid int
+		uid := int64(intUid)
+		gid := int64(intGid)
+
+		var newuid, newgid int64
 		switch how {
 		case "in":
 			newuid, newgid = set.ShiftIntoNs(uid, gid)
 		case "out":
 			newuid, newgid = set.ShiftFromNs(uid, gid)
 		}
+
 		if testmode {
 			fmt.Printf("I would shift %q to %d %d\n", path, newuid, newgid)
 		} else {
@@ -494,8 +510,8 @@ func (set *IdmapSet) ShiftFile(p string) error {
 /*
  * get a uid or gid mapping from /etc/subxid
  */
-func getFromShadow(fname string, username string) ([][]int, error) {
-	entries := [][]int{}
+func getFromShadow(fname string, username string) ([][]int64, error) {
+	entries := [][]int64{}
 
 	f, err := os.Open(fname)
 	if err != nil {
@@ -530,7 +546,7 @@ func getFromShadow(fname string, username string) ([][]int, error) {
 				continue
 			}
 
-			entries = append(entries, []int{int(entryStart), int(entrySize)})
+			entries = append(entries, []int64{int64(entryStart), int64(entrySize)})
 		}
 	}
 
@@ -544,8 +560,8 @@ func getFromShadow(fname string, username string) ([][]int, error) {
 /*
  * get a uid or gid mapping from /proc/self/{g,u}id_map
  */
-func getFromProc(fname string) ([][]int, error) {
-	entries := [][]int{}
+func getFromProc(fname string) ([][]int64, error) {
+	entries := [][]int64{}
 
 	f, err := os.Open(fname)
 	if err != nil {
@@ -585,7 +601,7 @@ func getFromProc(fname string) ([][]int, error) {
 			continue
 		}
 
-		entries = append(entries, []int{int(entryStart), int(entryHost), int(entrySize)})
+		entries = append(entries, []int64{int64(entryStart), int64(entryHost), int64(entrySize)})
 	}
 
 	if len(entries) == 0 {
diff --git a/shared/util.go b/shared/util.go
index 6caf1cd75..524429c45 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -122,13 +122,13 @@ func LogPath(path ...string) string {
 	return filepath.Join(items...)
 }
 
-func ParseLXDFileHeaders(headers http.Header) (uid int, gid int, mode int) {
-	uid, err := strconv.Atoi(headers.Get("X-LXD-uid"))
+func ParseLXDFileHeaders(headers http.Header) (uid int64, gid int64, mode int) {
+	uid, err := strconv.ParseInt(headers.Get("X-LXD-uid"), 10, 64)
 	if err != nil {
 		uid = -1
 	}
 
-	gid, err = strconv.Atoi(headers.Get("X-LXD-gid"))
+	gid, err = strconv.ParseInt(headers.Get("X-LXD-gid"), 10, 64)
 	if err != nil {
 		gid = -1
 	}
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 87b44d6e7..95e365df8 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -91,7 +91,7 @@ void configure_pty(int fd) {
 	return;
 }
 
-void create_pty(int *master, int *slave, int uid, int gid) {
+void create_pty(int *master, int *slave, uid_t uid, gid_t gid) {
 	if (openpty(master, slave, NULL, NULL, NULL) < 0) {
 		fprintf(stderr, "Failed to openpty: %s\n", strerror(errno));
 		return;
@@ -244,11 +244,11 @@ func ShiftOwner(basepath string, path string, uid int, gid int) error {
 	return nil
 }
 
-func OpenPty(uid, gid int) (master *os.File, slave *os.File, err error) {
+func OpenPty(uid, gid int64) (master *os.File, slave *os.File, err error) {
 	fd_master := C.int(-1)
 	fd_slave := C.int(-1)
-	rootUid := C.int(uid)
-	rootGid := C.int(gid)
+	rootUid := C.uid_t(uid)
+	rootGid := C.gid_t(gid)
 
 	C.create_pty(&fd_master, &fd_slave, rootUid, rootGid)
 

From bc31484e4a99abb3dd1f0d80035c7a511151c47e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sun, 19 Feb 2017 03:10:12 +0100
Subject: [PATCH 0713/1193] lxd/container: add fun to detect root disk device

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container.go | 51 +++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 43 insertions(+), 8 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index acba1b707..689574fc4 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -312,6 +312,24 @@ func containerValidConfig(config map[string]string, profile bool, expanded bool)
 	return nil
 }
 
+func isRootDiskDevice(device types.Device) bool {
+	if device["type"] == "disk" && device["path"] == "/" && device["source"] == "" {
+		return true
+	}
+
+	return false
+}
+
+func containerGetRootDiskDevice(devices types.Devices) (string, types.Device) {
+	for devName, dev := range devices {
+		if isRootDiskDevice(dev) {
+			return devName, dev
+		}
+	}
+
+	return "", types.Device{}
+}
+
 func containerValidDevices(devices types.Devices, profile bool, expanded bool) error {
 	// Empty device list
 	if devices == nil {
@@ -405,14 +423,8 @@ func containerValidDevices(devices types.Devices, profile bool, expanded bool) e
 
 	// Checks on the expanded config
 	if expanded {
-		foundRootfs := false
-		for _, m := range devices {
-			if m["type"] == "disk" && m["path"] == "/" {
-				foundRootfs = true
-			}
-		}
-
-		if !foundRootfs {
+		k, _ := containerGetRootDiskDevice(devices)
+		if k == "" {
 			return fmt.Errorf("Container is lacking rootfs entry")
 		}
 	}
@@ -737,6 +749,29 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 		}
 	}
 
+	// Check that there are no contradicting root disk devices.
+	var profileRootDiskDevices []string
+	for _, pName := range args.Profiles {
+		_, p, err := dbProfileGet(d.db, pName)
+		if err != nil {
+			return nil, fmt.Errorf("Could not load profile '%s'.", pName)
+		}
+
+		k, v := containerGetRootDiskDevice(p.Devices)
+		if k != "" && !shared.StringInSlice(v["pool"], profileRootDiskDevices) {
+			profileRootDiskDevices = append(profileRootDiskDevices, v["pool"])
+		}
+	}
+
+	_, newLocalRootDiskDevice := containerGetRootDiskDevice(args.Devices)
+	if newLocalRootDiskDevice["pool"] == "" {
+		if len(profileRootDiskDevices) == 0 {
+			return nil, fmt.Errorf("Container relies on profile's root disk device but none was found")
+		} else if len(profileRootDiskDevices) > 1 {
+			return nil, fmt.Errorf("Container relies on profile's root disk device but conflicting devices were found")
+		}
+	}
+
 	// Create the container entry
 	id, err := dbContainerCreate(d.db, args)
 	if err != nil {

From 654309fd0b9fe0a4bb2a66947a8a16419bfce866 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 16 Feb 2017 21:11:06 +0100
Subject: [PATCH 0714/1193] lxd/profiles: verify root disk devices

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/profiles.go       |  95 ++---------------------------------
 lxd/profiles_utils.go | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 140 insertions(+), 90 deletions(-)
 create mode 100644 lxd/profiles_utils.go

diff --git a/lxd/profiles.go b/lxd/profiles.go
index 1aa5c7f06..8902b4aa4 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
-	"reflect"
 	"strings"
 
 	"github.com/gorilla/mux"
@@ -137,103 +136,19 @@ func getContainersWithProfile(d *Daemon, profile string) []container {
 }
 
 func profilePut(d *Daemon, r *http.Request) Response {
+	// Get the profile
 	name := mux.Vars(r)["name"]
-
-	req := api.ProfilePut{}
-	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
-	}
-
-	// Sanity checks
-	err := containerValidConfig(req.Config, true, false)
-	if err != nil {
-		return BadRequest(err)
-	}
-
-	err = containerValidDevices(req.Devices, true, false)
-	if err != nil {
-		return BadRequest(err)
-	}
-
-	// Get the container list
-	containers := getContainersWithProfile(d, name)
-
-	// Update the database
 	id, profile, err := dbProfileGet(d.db, name)
 	if err != nil {
 		return InternalError(fmt.Errorf("Failed to retrieve profile='%s'", name))
 	}
 
-	tx, err := dbBegin(d.db)
-	if err != nil {
-		return InternalError(err)
-	}
-
-	if profile.Description != req.Description {
-		err = dbProfileDescriptionUpdate(tx, id, req.Description)
-		if err != nil {
-			tx.Rollback()
-			return InternalError(err)
-		}
-	}
-
-	// Optimize for description-only changes
-	if reflect.DeepEqual(profile.Config, req.Config) && reflect.DeepEqual(profile.Devices, req.Devices) {
-		err = txCommit(tx)
-		if err != nil {
-			return InternalError(err)
-		}
-
-		return EmptySyncResponse
-	}
-
-	err = dbProfileConfigClear(tx, id)
-	if err != nil {
-		tx.Rollback()
-		return InternalError(err)
-	}
-
-	err = dbProfileConfigAdd(tx, id, req.Config)
-	if err != nil {
-		tx.Rollback()
-		return SmartError(err)
-	}
-
-	err = dbDevicesAdd(tx, "profile", id, req.Devices)
-	if err != nil {
-		tx.Rollback()
-		return SmartError(err)
-	}
-
-	err = txCommit(tx)
-	if err != nil {
-		return InternalError(err)
-	}
-
-	// Update all the containers using the profile. Must be done after txCommit due to DB lock.
-	failures := map[string]error{}
-	for _, c := range containers {
-		err = c.Update(containerArgs{
-			Architecture: c.Architecture(),
-			Ephemeral:    c.IsEphemeral(),
-			Config:       c.LocalConfig(),
-			Devices:      c.LocalDevices(),
-			Profiles:     c.Profiles()}, true)
-
-		if err != nil {
-			failures[c.Name()] = err
-		}
-	}
-
-	if len(failures) != 0 {
-		msg := "The following containers failed to update (profile change still saved):\n"
-		for cname, err := range failures {
-			msg += fmt.Sprintf(" - %s: %s\n", cname, err)
-		}
-		return InternalError(fmt.Errorf("%s", msg))
+	req := api.ProfilePut{}
+	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		return BadRequest(err)
 	}
 
-	return EmptySyncResponse
+	return doProfileUpdate(d, name, id, profile, req)
 }
 
 // The handler for the post operation.
diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
new file mode 100644
index 000000000..58256e1b1
--- /dev/null
+++ b/lxd/profiles_utils.go
@@ -0,0 +1,135 @@
+package main
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req api.ProfilePut) Response {
+	// Sanity checks
+	err := containerValidConfig(req.Config, true, false)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	err = containerValidDevices(req.Devices, true, false)
+	if err != nil {
+		return BadRequest(err)
+	}
+
+	containers := getContainersWithProfile(d, name)
+
+	// Check if the root device is supposed to be changed or removed.
+	oldProfileRootDiskDeviceKey, oldProfileRootDiskDevice := containerGetRootDiskDevice(profile.Devices)
+	_, newProfileRootDiskDevice := containerGetRootDiskDevice(req.Devices)
+	if len(containers) > 0 &&
+		oldProfileRootDiskDevice["pool"] != "" &&
+		newProfileRootDiskDevice["pool"] == "" ||
+		(oldProfileRootDiskDevice["pool"] != newProfileRootDiskDevice["pool"]) {
+		// Check for containers using the device
+		for _, container := range containers {
+			// Check if the device is locally overridden
+			localDevices := container.LocalDevices()
+			k, v := containerGetRootDiskDevice(localDevices)
+			if k != "" && v["pool"] != "" {
+				continue
+			}
+
+			// Check what profile the device comes from
+			profiles := container.Profiles()
+			for i := len(profiles) - 1; i >= 0; i-- {
+				_, profile, err := dbProfileGet(d.db, profiles[i])
+				if err != nil {
+					return InternalError(err)
+				}
+
+				// Check if we find a match for the device
+				_, ok := profile.Devices[oldProfileRootDiskDeviceKey]
+				if ok {
+					// Found the profile
+					if profiles[i] == name {
+						// If it's the current profile, then we can't modify that root device
+						return BadRequest(fmt.Errorf("At least one container relies on this profile's root disk device."))
+					} else {
+						// If it's not, then move on to the next container
+						break
+					}
+				}
+			}
+		}
+	}
+
+	// Update the database
+	tx, err := dbBegin(d.db)
+	if err != nil {
+		return InternalError(err)
+	}
+
+	if profile.Description != req.Description {
+		err = dbProfileDescriptionUpdate(tx, id, req.Description)
+		if err != nil {
+			tx.Rollback()
+			return InternalError(err)
+		}
+	}
+
+	// Optimize for description-only changes
+	if reflect.DeepEqual(profile.Config, req.Config) && reflect.DeepEqual(profile.Devices, req.Devices) {
+		err = txCommit(tx)
+		if err != nil {
+			return InternalError(err)
+		}
+
+		return EmptySyncResponse
+	}
+
+	err = dbProfileConfigClear(tx, id)
+	if err != nil {
+		tx.Rollback()
+		return InternalError(err)
+	}
+
+	err = dbProfileConfigAdd(tx, id, req.Config)
+	if err != nil {
+		tx.Rollback()
+		return SmartError(err)
+	}
+
+	err = dbDevicesAdd(tx, "profile", id, req.Devices)
+	if err != nil {
+		tx.Rollback()
+		return SmartError(err)
+	}
+
+	err = txCommit(tx)
+	if err != nil {
+		return InternalError(err)
+	}
+
+	// Update all the containers using the profile. Must be done after txCommit due to DB lock.
+	failures := map[string]error{}
+	for _, c := range containers {
+		err = c.Update(containerArgs{
+			Architecture: c.Architecture(),
+			Ephemeral:    c.IsEphemeral(),
+			Config:       c.LocalConfig(),
+			Devices:      c.LocalDevices(),
+			Profiles:     c.Profiles()}, true)
+
+		if err != nil {
+			failures[c.Name()] = err
+		}
+	}
+
+	if len(failures) != 0 {
+		msg := "The following containers failed to update (profile change still saved):\n"
+		for cname, err := range failures {
+			msg += fmt.Sprintf(" - %s: %s\n", cname, err)
+		}
+		return InternalError(fmt.Errorf("%s", msg))
+	}
+
+	return EmptySyncResponse
+}

From 33a7e84c05e7f80874f37491e13c6db20a317ee3 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 16 Feb 2017 23:11:55 +0100
Subject: [PATCH 0715/1193] test: add lxd init --auto tests

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/main.sh        | 53 +++++++++++++++++++++++++++++++++++
 test/suites/init.sh | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 134 insertions(+)
 create mode 100644 test/suites/init.sh

diff --git a/test/main.sh b/test/main.sh
index d24e8afc2..05b1b5ac0 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -344,6 +344,58 @@ wipe() {
   rm -Rf "${1}"
 }
 
+configure_loop_device() {
+  lv_loop_file=$(mktemp -p "${TEST_DIR}" XXXX.img)
+  truncate -s 10G "${lv_loop_file}"
+  pvloopdev=$(losetup --show -f "${lv_loop_file}")
+  if [ ! -e "${pvloopdev}" ]; then
+    echo "failed to setup loop"
+    false
+  fi
+
+  # The following code enables to return a value from a shell function by
+  # calling the function as: fun VAR1
+
+  # shellcheck disable=2039
+  local  __tmp1="${1}"
+  # shellcheck disable=2039
+  local  res1="${lv_loop_file}"
+  if [ "${__tmp1}" ]; then
+      eval "${__tmp1}='${res1}'"
+  fi
+
+  # shellcheck disable=2039
+  local  __tmp2="${2}"
+  # shellcheck disable=2039
+  local  res2="${pvloopdev}"
+  if [ "${__tmp2}" ]; then
+      eval "${__tmp2}='${res2}'"
+  fi
+}
+
+deconfigure_loop_device() {
+  lv_loop_file="${1}"
+  loopdev="${2}"
+
+  SUCCESS=0
+  # shellcheck disable=SC2034
+  for i in $(seq 10); do
+    if losetup -d "${loopdev}"; then
+      SUCCESS=1
+      break
+    fi
+
+    sleep 0.5
+  done
+
+  if [ "${SUCCESS}" = "0" ]; then
+    echo "Failed to tear down loop device"
+    false
+  fi
+
+  rm -f "${lv_loop_file}"
+}
+
 # Must be set before cleanup()
 TEST_CURRENT=setup
 TEST_RESULT=failure
@@ -423,5 +475,6 @@ run_test test_migration "migration"
 run_test test_fdleak "fd leak"
 run_test test_cpu_profiling "CPU profiling"
 run_test test_mem_profiling "memory profiling"
+run_test test_lxd_autoinit "lxd init auto"
 
 TEST_RESULT=success
diff --git a/test/suites/init.sh b/test/suites/init.sh
new file mode 100644
index 000000000..8a4184637
--- /dev/null
+++ b/test/suites/init.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_lxd_autoinit() {
+  # lxd init --auto
+  LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD_INIT_DIR}"
+  spawn_lxd "${LXD_INIT_DIR}" false
+
+  ZFS_POOL="lxdtest-$(basename "${LXD_DIR}")-init"
+  LXD_DIR=${LXD_INIT_DIR} lxd init --auto
+
+  kill_lxd "${LXD_INIT_DIR}"
+
+  # lxd init --auto --storage-backend zfs
+  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+    chmod +x "${LXD_INIT_DIR}"
+    spawn_lxd "${LXD_INIT_DIR}" false
+
+    LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs
+
+    kill_lxd "${LXD_INIT_DIR}"
+  fi
+
+  # lxd init --auto --storage-backend zfs --storage-pool <name>
+  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+    chmod +x "${LXD_INIT_DIR}"
+    spawn_lxd "${LXD_INIT_DIR}" false
+
+    configure_loop_device loop_file_1 loop_device_1
+    zpool create "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool" "${loop_device_1}" -m none -O compression=on
+    LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
+
+    kill_lxd "${LXD_INIT_DIR}"
+    deconfigure_loop_device "${loop_file_1}" "${loop_device_1}"
+  fi
+
+  # lxd init --auto --storage-backend zfs --storage-pool <name>/<non-existing-dataset>
+  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+    chmod +x "${LXD_INIT_DIR}"
+    spawn_lxd "${LXD_INIT_DIR}" false
+
+    configure_loop_device loop_file_1 loop_device_1
+    zpool create "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool" "${loop_device_1}" -m none -O compression=on
+    LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/non-existing-dataset"
+
+    kill_lxd "${LXD_INIT_DIR}"
+    deconfigure_loop_device "${loop_file_1}" "${loop_device_1}"
+    zpool destroy -f "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
+  fi
+
+  # lxd init --auto --storage-backend zfs --storage-pool <name>/<existing-dataset>
+  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+    chmod +x "${LXD_INIT_DIR}"
+    spawn_lxd "${LXD_INIT_DIR}" false
+
+    configure_loop_device loop_file_1 loop_device_1
+    zpool create "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool" "${loop_device_1}" -f -m none -O compression=on
+    zfs create -p -o mountpoint=none "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/existing-dataset"
+    LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/existing-dataset"
+
+    kill_lxd "${LXD_INIT_DIR}"
+    deconfigure_loop_device "${loop_file_1}" "${loop_device_1}"
+    zpool destroy -f "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
+  fi
+
+ # lxd init --storage-backend zfs --storage-create-loop 1 --storage-pool <name> --auto
+  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+    chmod +x "${LXD_INIT_DIR}"
+    spawn_lxd "${LXD_INIT_DIR}" false
+
+    ZFS_POOL="lxdtest-$(basename "${LXD_DIR}")-init"
+    LXD_DIR=${LXD_INIT_DIR} lxd init --storage-backend zfs --storage-create-loop 1 --storage-pool "${ZFS_POOL}" --auto
+
+    kill_lxd "${LXD_INIT_DIR}"
+  fi
+}

From 8c5570f19ac28180c29ea05939046f416131a469 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Feb 2017 03:11:57 -0500
Subject: [PATCH 0716/1193] tests: Always cleanup loop devices
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/main.sh b/test/main.sh
index 05b1b5ac0..ec202394d 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -352,6 +352,7 @@ configure_loop_device() {
     echo "failed to setup loop"
     false
   fi
+  echo "${pvloopdev}" >> "${TEST_DIR}/loops"
 
   # The following code enables to return a value from a shell function by
   # calling the function as: fun VAR1
@@ -394,6 +395,7 @@ deconfigure_loop_device() {
   fi
 
   rm -f "${lv_loop_file}"
+  sed -i "\|^${loopdev}|d" "${TEST_DIR}/loops"
 }
 
 # Must be set before cleanup()

From 65a73b6474764cadac3ca4629b9c9f02876b7083 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 22 Feb 2017 16:47:50 +0100
Subject: [PATCH 0717/1193] test: fix lxd auto init test suite

- Recent changes to the test suite causes a kall to "kill_lxd" to deconfigure
  loop devices. So we can't call deconfigure_{lvm}-loop_device afterwards or
  else we'll fail.
- Also, we can't test some auto init cases because they use the hard-coded
  "default" zfs pool name as pool name. This will mess with parallel runs of the
  test-suite on the same runner. It's not a big problem though, since the
  interesting cases should still be testable.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/init.sh | 36 +++++++++---------------------------
 1 file changed, 9 insertions(+), 27 deletions(-)

diff --git a/test/suites/init.sh b/test/suites/init.sh
index 8a4184637..dfc86d28d 100644
--- a/test/suites/init.sh
+++ b/test/suites/init.sh
@@ -1,26 +1,12 @@
 #!/bin/sh
 
 test_lxd_autoinit() {
-  # lxd init --auto
-  LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
-  chmod +x "${LXD_INIT_DIR}"
-  spawn_lxd "${LXD_INIT_DIR}" false
-
-  ZFS_POOL="lxdtest-$(basename "${LXD_DIR}")-init"
-  LXD_DIR=${LXD_INIT_DIR} lxd init --auto
-
-  kill_lxd "${LXD_INIT_DIR}"
-
-  # lxd init --auto --storage-backend zfs
-  if [ "${LXD_BACKEND}" = "zfs" ]; then
-    LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
-    chmod +x "${LXD_INIT_DIR}"
-    spawn_lxd "${LXD_INIT_DIR}" false
-
-    LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs
-
-    kill_lxd "${LXD_INIT_DIR}"
-  fi
+  # - lxd init --auto --storage-backend zfs
+  # and
+  # - lxd init --auto
+  # can't be easily tested on jenkins since it hard-codes "default" as pool
+  # naming. This can cause naming conflicts when multiple test-suites are run on
+  # a single runner.
 
   # lxd init --auto --storage-backend zfs --storage-pool <name>
   if [ "${LXD_BACKEND}" = "zfs" ]; then
@@ -29,11 +15,11 @@ test_lxd_autoinit() {
     spawn_lxd "${LXD_INIT_DIR}" false
 
     configure_loop_device loop_file_1 loop_device_1
+    # shellcheck disable=SC2154
     zpool create "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool" "${loop_device_1}" -m none -O compression=on
     LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
 
     kill_lxd "${LXD_INIT_DIR}"
-    deconfigure_loop_device "${loop_file_1}" "${loop_device_1}"
   fi
 
   # lxd init --auto --storage-backend zfs --storage-pool <name>/<non-existing-dataset>
@@ -43,12 +29,11 @@ test_lxd_autoinit() {
     spawn_lxd "${LXD_INIT_DIR}" false
 
     configure_loop_device loop_file_1 loop_device_1
+    # shellcheck disable=SC2154
     zpool create "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool" "${loop_device_1}" -m none -O compression=on
     LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/non-existing-dataset"
 
     kill_lxd "${LXD_INIT_DIR}"
-    deconfigure_loop_device "${loop_file_1}" "${loop_device_1}"
-    zpool destroy -f "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
   fi
 
   # lxd init --auto --storage-backend zfs --storage-pool <name>/<existing-dataset>
@@ -57,14 +42,11 @@ test_lxd_autoinit() {
     chmod +x "${LXD_INIT_DIR}"
     spawn_lxd "${LXD_INIT_DIR}" false
 
-    configure_loop_device loop_file_1 loop_device_1
-    zpool create "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool" "${loop_device_1}" -f -m none -O compression=on
+    # shellcheck disable=SC2154
     zfs create -p -o mountpoint=none "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/existing-dataset"
     LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/existing-dataset"
 
     kill_lxd "${LXD_INIT_DIR}"
-    deconfigure_loop_device "${loop_file_1}" "${loop_device_1}"
-    zpool destroy -f "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
   fi
 
  # lxd init --storage-backend zfs --storage-create-loop 1 --storage-pool <name> --auto

From 2cd7739a9711cdb69b8372ba2334d63eb6024d72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 24 Feb 2017 02:45:06 -0500
Subject: [PATCH 0718/1193] tests: Don't leak zpools in "lxd init" test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/init.sh | 28 ++++++++++++----------------
 1 file changed, 12 insertions(+), 16 deletions(-)

diff --git a/test/suites/init.sh b/test/suites/init.sh
index dfc86d28d..a186e5a0b 100644
--- a/test/suites/init.sh
+++ b/test/suites/init.sh
@@ -8,11 +8,11 @@ test_lxd_autoinit() {
   # naming. This can cause naming conflicts when multiple test-suites are run on
   # a single runner.
 
-  # lxd init --auto --storage-backend zfs --storage-pool <name>
   if [ "${LXD_BACKEND}" = "zfs" ]; then
+    # lxd init --auto --storage-backend zfs --storage-pool <name>
     LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
     chmod +x "${LXD_INIT_DIR}"
-    spawn_lxd "${LXD_INIT_DIR}" false
+    spawn_lxd "${LXD_INIT_DIR}"
 
     configure_loop_device loop_file_1 loop_device_1
     # shellcheck disable=SC2154
@@ -20,40 +20,36 @@ test_lxd_autoinit() {
     LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
 
     kill_lxd "${LXD_INIT_DIR}"
-  fi
+    sed -i "\|^${loop_device_1}|d" "${TEST_DIR}/loops"
 
-  # lxd init --auto --storage-backend zfs --storage-pool <name>/<non-existing-dataset>
-  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    # lxd init --auto --storage-backend zfs --storage-pool <name>/<non-existing-dataset>
     LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
     chmod +x "${LXD_INIT_DIR}"
-    spawn_lxd "${LXD_INIT_DIR}" false
+    spawn_lxd "${LXD_INIT_DIR}"
 
-    configure_loop_device loop_file_1 loop_device_1
     # shellcheck disable=SC2154
+    configure_loop_device loop_file_1 loop_device_1
     zpool create "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool" "${loop_device_1}" -m none -O compression=on
     LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/non-existing-dataset"
 
     kill_lxd "${LXD_INIT_DIR}"
-  fi
 
-  # lxd init --auto --storage-backend zfs --storage-pool <name>/<existing-dataset>
-  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    # lxd init --auto --storage-backend zfs --storage-pool <name>/<existing-dataset>
     LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
     chmod +x "${LXD_INIT_DIR}"
-    spawn_lxd "${LXD_INIT_DIR}" false
+    spawn_lxd "${LXD_INIT_DIR}"
 
-    # shellcheck disable=SC2154
     zfs create -p -o mountpoint=none "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/existing-dataset"
     LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/existing-dataset"
 
     kill_lxd "${LXD_INIT_DIR}"
-  fi
+    zpool destroy "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
+    sed -i "\|^${loop_device_1}|d" "${TEST_DIR}/loops"
 
- # lxd init --storage-backend zfs --storage-create-loop 1 --storage-pool <name> --auto
-  if [ "${LXD_BACKEND}" = "zfs" ]; then
+    # lxd init --storage-backend zfs --storage-create-loop 1 --storage-pool <name> --auto
     LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
     chmod +x "${LXD_INIT_DIR}"
-    spawn_lxd "${LXD_INIT_DIR}" false
+    spawn_lxd "${LXD_INIT_DIR}"
 
     ZFS_POOL="lxdtest-$(basename "${LXD_DIR}")-init"
     LXD_DIR=${LXD_INIT_DIR} lxd init --storage-backend zfs --storage-create-loop 1 --storage-pool "${ZFS_POOL}" --auto

From 897076b29c8bf2910eb0a28dcd34b950ccf17dac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 25 Feb 2017 00:36:30 -0500
Subject: [PATCH 0719/1193] tests: Update init test for stable branch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/init.sh | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/test/suites/init.sh b/test/suites/init.sh
index a186e5a0b..abb89e9ff 100644
--- a/test/suites/init.sh
+++ b/test/suites/init.sh
@@ -20,25 +20,17 @@ test_lxd_autoinit() {
     LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
 
     kill_lxd "${LXD_INIT_DIR}"
+    zpool destroy "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool"
     sed -i "\|^${loop_device_1}|d" "${TEST_DIR}/loops"
 
-    # lxd init --auto --storage-backend zfs --storage-pool <name>/<non-existing-dataset>
+    # lxd init --auto --storage-backend zfs --storage-pool <name>/<existing-dataset>
     LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
     chmod +x "${LXD_INIT_DIR}"
     spawn_lxd "${LXD_INIT_DIR}"
 
-    # shellcheck disable=SC2154
     configure_loop_device loop_file_1 loop_device_1
+    # shellcheck disable=SC2154
     zpool create "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool" "${loop_device_1}" -m none -O compression=on
-    LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/non-existing-dataset"
-
-    kill_lxd "${LXD_INIT_DIR}"
-
-    # lxd init --auto --storage-backend zfs --storage-pool <name>/<existing-dataset>
-    LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
-    chmod +x "${LXD_INIT_DIR}"
-    spawn_lxd "${LXD_INIT_DIR}"
-
     zfs create -p -o mountpoint=none "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/existing-dataset"
     LXD_DIR=${LXD_INIT_DIR} lxd init --auto --storage-backend zfs --storage-pool "lxdtest-$(basename "${LXD_DIR}")-pool1-existing-pool/existing-dataset"
 
@@ -55,5 +47,6 @@ test_lxd_autoinit() {
     LXD_DIR=${LXD_INIT_DIR} lxd init --storage-backend zfs --storage-create-loop 1 --storage-pool "${ZFS_POOL}" --auto
 
     kill_lxd "${LXD_INIT_DIR}"
+    zpool destroy "${ZFS_POOL}"
   fi
 }

From b393cba1d08953ab468766eda1b4d189d4112486 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Feb 2017 01:46:45 -0500
Subject: [PATCH 0720/1193] Cleanup root device validation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go       | 75 ++++++++++++++++++++++++--------------------------
 lxd/container_lxc.go   | 27 ++----------------
 lxd/containers_post.go | 11 +++++---
 lxd/profiles_utils.go  | 40 ---------------------------
 4 files changed, 45 insertions(+), 108 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 689574fc4..f0fe36c35 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -320,14 +320,28 @@ func isRootDiskDevice(device types.Device) bool {
 	return false
 }
 
-func containerGetRootDiskDevice(devices types.Devices) (string, types.Device) {
-	for devName, dev := range devices {
-		if isRootDiskDevice(dev) {
-			return devName, dev
+func containerGetRootDiskDevice(devices types.Devices) (string, types.Device, error) {
+	var devName string
+	var dev types.Device
+
+	count := 0
+	for n, d := range devices {
+		if isRootDiskDevice(d) {
+			count += 1
+			devName = n
+			dev = d
 		}
 	}
 
-	return "", types.Device{}
+	if count == 1 {
+		return devName, dev, nil
+	}
+
+	if count > 1 {
+		return "", types.Device{}, fmt.Errorf("More than one root device found.")
+	}
+
+	return "", types.Device{}, fmt.Errorf("No root device could be found.")
 }
 
 func containerValidDevices(devices types.Devices, profile bool, expanded bool) error {
@@ -423,9 +437,9 @@ func containerValidDevices(devices types.Devices, profile bool, expanded bool) e
 
 	// Checks on the expanded config
 	if expanded {
-		k, _ := containerGetRootDiskDevice(devices)
-		if k == "" {
-			return fmt.Errorf("Container is lacking rootfs entry")
+		_, _, err := containerGetRootDiskDevice(devices)
+		if err != nil {
+			return err
 		}
 	}
 
@@ -749,29 +763,6 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 		}
 	}
 
-	// Check that there are no contradicting root disk devices.
-	var profileRootDiskDevices []string
-	for _, pName := range args.Profiles {
-		_, p, err := dbProfileGet(d.db, pName)
-		if err != nil {
-			return nil, fmt.Errorf("Could not load profile '%s'.", pName)
-		}
-
-		k, v := containerGetRootDiskDevice(p.Devices)
-		if k != "" && !shared.StringInSlice(v["pool"], profileRootDiskDevices) {
-			profileRootDiskDevices = append(profileRootDiskDevices, v["pool"])
-		}
-	}
-
-	_, newLocalRootDiskDevice := containerGetRootDiskDevice(args.Devices)
-	if newLocalRootDiskDevice["pool"] == "" {
-		if len(profileRootDiskDevices) == 0 {
-			return nil, fmt.Errorf("Container relies on profile's root disk device but none was found")
-		} else if len(profileRootDiskDevices) > 1 {
-			return nil, fmt.Errorf("Container relies on profile's root disk device but conflicting devices were found")
-		}
-	}
-
 	// Create the container entry
 	id, err := dbContainerCreate(d.db, args)
 	if err != nil {
@@ -797,27 +788,33 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	}
 	args.CreationDate = dbArgs.CreationDate
 
-	return containerLXCCreate(d, args)
+	// Setup the container struct and finish creation (storage and idmap)
+	c, err := containerLXCCreate(d, args)
+	if err != nil {
+		return nil, err
+	}
+
+	return c, nil
 }
 
 func containerConfigureInternal(c container) error {
 	// Find the root device
-	for _, m := range c.ExpandedDevices() {
-		if m["type"] != "disk" || m["path"] != "/" || m["size"] == "" {
-			continue
-		}
+	_, rootDiskDevice, err := containerGetRootDiskDevice(c.ExpandedDevices())
+	if err != nil {
+		return err
+	}
 
-		size, err := shared.ParseByteSizeString(m["size"])
+	if rootDiskDevice["size"] != "" {
+		size, err := shared.ParseByteSizeString(rootDiskDevice["size"])
 		if err != nil {
 			return err
 		}
 
+		// Storage is guaranteed to be ready.
 		err = c.Storage().ContainerSetQuota(c, size)
 		if err != nil {
 			return err
 		}
-
-		break
 	}
 
 	return nil
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 103bfbaee..5bdd1b88f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -213,16 +213,8 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Look for a rootfs entry
-	rootfs := false
-	for _, name := range c.expandedDevices.DeviceNames() {
-		m := c.expandedDevices[name]
-		if m["type"] == "disk" && m["path"] == "/" {
-			rootfs = true
-			break
-		}
-	}
-
-	if !rootfs {
+	_, _, err = containerGetRootDiskDevice(c.expandedDevices)
+	if err != nil {
 		deviceName := "root"
 		for {
 			if c.expandedDevices[deviceName] == nil {
@@ -250,21 +242,6 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		}
 	}
 
-	// Validate expanded config
-	err = containerValidConfig(c.expandedConfig, false, true)
-	if err != nil {
-		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
-		return nil, err
-	}
-
-	err = containerValidDevices(c.expandedDevices, false, true)
-	if err != nil {
-		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
-		return nil, err
-	}
-
 	// Setup initial idmap config
 	var idmap *shared.IdmapSet
 	base := int64(0)
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index ba21dc43a..46569fa1c 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -163,15 +163,20 @@ func createFromNone(d *Daemon, req *api.ContainersPost) Response {
 }
 
 func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
+	// Validate migration mode
 	if req.Source.Mode != "pull" {
 		return NotImplemented
 	}
 
+	var c container
+
+	// Parse the architecture name
 	architecture, err := osarch.ArchitectureId(req.Architecture)
 	if err != nil {
-		architecture = 0
+		return BadRequest(err)
 	}
 
+	// Prepare the container creation request
 	args := containerArgs{
 		Architecture: architecture,
 		BaseImage:    req.Source.BaseImage,
@@ -183,9 +188,6 @@ func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
 		Profiles:     req.Profiles,
 	}
 
-	var c container
-	_, _, err = dbImageGet(d.db, req.Source.BaseImage, false, true)
-
 	/* Only create a container from an image if we're going to
 	 * rsync over the top of it. In the case of a better file
 	 * transfer mechanism, let's just use that.
@@ -199,6 +201,7 @@ func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
 	 * point and just negotiate it over the migration control
 	 * socket. Anyway, it'll happen later :)
 	 */
+	_, _, err = dbImageGet(d.db, req.Source.BaseImage, false, true)
 	if err == nil && d.Storage.MigrationType() == MigrationFSType_RSYNC {
 		c, err = containerCreateFromImage(d, args, req.Source.BaseImage)
 		if err != nil {
diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index 58256e1b1..69a694ec3 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -21,46 +21,6 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 
 	containers := getContainersWithProfile(d, name)
 
-	// Check if the root device is supposed to be changed or removed.
-	oldProfileRootDiskDeviceKey, oldProfileRootDiskDevice := containerGetRootDiskDevice(profile.Devices)
-	_, newProfileRootDiskDevice := containerGetRootDiskDevice(req.Devices)
-	if len(containers) > 0 &&
-		oldProfileRootDiskDevice["pool"] != "" &&
-		newProfileRootDiskDevice["pool"] == "" ||
-		(oldProfileRootDiskDevice["pool"] != newProfileRootDiskDevice["pool"]) {
-		// Check for containers using the device
-		for _, container := range containers {
-			// Check if the device is locally overridden
-			localDevices := container.LocalDevices()
-			k, v := containerGetRootDiskDevice(localDevices)
-			if k != "" && v["pool"] != "" {
-				continue
-			}
-
-			// Check what profile the device comes from
-			profiles := container.Profiles()
-			for i := len(profiles) - 1; i >= 0; i-- {
-				_, profile, err := dbProfileGet(d.db, profiles[i])
-				if err != nil {
-					return InternalError(err)
-				}
-
-				// Check if we find a match for the device
-				_, ok := profile.Devices[oldProfileRootDiskDeviceKey]
-				if ok {
-					// Found the profile
-					if profiles[i] == name {
-						// If it's the current profile, then we can't modify that root device
-						return BadRequest(fmt.Errorf("At least one container relies on this profile's root disk device."))
-					} else {
-						// If it's not, then move on to the next container
-						break
-					}
-				}
-			}
-		}
-	}
-
 	// Update the database
 	tx, err := dbBegin(d.db)
 	if err != nil {

From 800081e9d1aa3dcf7ae21f5bb284eb21e0e4e75f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Feb 2017 12:11:57 -0500
Subject: [PATCH 0721/1193] storage: Optimize containerGetRootDiskDevice a bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index f0fe36c35..f8cb14f5b 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -324,23 +324,21 @@ func containerGetRootDiskDevice(devices types.Devices) (string, types.Device, er
 	var devName string
 	var dev types.Device
 
-	count := 0
 	for n, d := range devices {
 		if isRootDiskDevice(d) {
-			count += 1
+			if devName != "" {
+				return "", types.Device{}, fmt.Errorf("More than one root device found.")
+			}
+
 			devName = n
 			dev = d
 		}
 	}
 
-	if count == 1 {
+	if devName != "" {
 		return devName, dev, nil
 	}
 
-	if count > 1 {
-		return "", types.Device{}, fmt.Errorf("More than one root device found.")
-	}
-
 	return "", types.Device{}, fmt.Errorf("No root device could be found.")
 }
 

From 032dc8ef9c173870337a2800180547937fcc5dab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Feb 2017 22:55:35 -0500
Subject: [PATCH 0722/1193] btrfs: Always use the recursive subvol functions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_btrfs.go | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 3f0a8ec68..8f88d0fa7 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -415,7 +415,7 @@ func (s *storageBtrfs) ImageCreate(fingerprint string) error {
 	}
 
 	if err := unpackImage(s.d, imagePath, subvol); err != nil {
-		s.subvolDelete(subvol)
+		s.subvolsDelete(subvol)
 		return err
 	}
 
@@ -804,11 +804,11 @@ func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn) erro
 		}
 
 		btrfsPath := fmt.Sprintf("%s/.root", tmpPath)
-		if err := s.btrfs.subvolSnapshot(s.container.Path(), btrfsPath, true); err != nil {
+		if err := s.btrfs.subvolsSnapshot(s.container.Path(), btrfsPath, true); err != nil {
 			return err
 		}
 
-		defer s.btrfs.subvolDelete(btrfsPath)
+		defer s.btrfs.subvolsDelete(btrfsPath)
 
 		return s.send(conn, btrfsPath, "")
 	}
@@ -834,7 +834,7 @@ func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn) erro
 	}
 
 	s.runningSnapName = fmt.Sprintf("%s/.root", tmpPath)
-	if err := s.btrfs.subvolSnapshot(s.container.Path(), s.runningSnapName, true); err != nil {
+	if err := s.btrfs.subvolsSnapshot(s.container.Path(), s.runningSnapName, true); err != nil {
 		return err
 	}
 
@@ -854,7 +854,7 @@ func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) e
 	}
 
 	s.stoppedSnapName = fmt.Sprintf("%s/.root", tmpPath)
-	if err := s.btrfs.subvolSnapshot(s.container.Path(), s.stoppedSnapName, true); err != nil {
+	if err := s.btrfs.subvolsSnapshot(s.container.Path(), s.stoppedSnapName, true); err != nil {
 		return err
 	}
 
@@ -863,11 +863,11 @@ func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) e
 
 func (s *btrfsMigrationSourceDriver) Cleanup() {
 	if s.stoppedSnapName != "" {
-		s.btrfs.subvolDelete(s.stoppedSnapName)
+		s.btrfs.subvolsDelete(s.stoppedSnapName)
 	}
 
 	if s.runningSnapName != "" {
-		s.btrfs.subvolDelete(s.runningSnapName)
+		s.btrfs.subvolsDelete(s.runningSnapName)
 	}
 }
 
@@ -971,7 +971,7 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 		if !isSnapshot {
 			cPath := containerPath(fmt.Sprintf("%s/.root", cName), true)
 
-			err := s.subvolSnapshot(cPath, targetPath, false)
+			err := s.subvolsSnapshot(cPath, targetPath, false)
 			if err != nil {
 				shared.LogError("problem with btrfs snapshot", log.Ctx{"err": err})
 				return err

From 0fe12a1a08ec6ed9d03b33ca7c0b43759f9b1349 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 21 Feb 2017 23:12:04 -0500
Subject: [PATCH 0723/1193] btrfs: Properly handle nested subvolumes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_btrfs.go | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 8f88d0fa7..a28683030 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -576,6 +576,7 @@ func (s *storageBtrfs) subvolsDelete(subvol string) error {
 	if err != nil {
 		return err
 	}
+	sort.Sort(sort.Reverse(sort.StringSlice(subsubvols)))
 
 	for _, subsubvol := range subsubvols {
 		s.log.Debug(
@@ -659,6 +660,7 @@ func (s *storageBtrfs) subvolsSnapshot(
 	if err != nil {
 		return err
 	}
+	sort.Sort(sort.StringSlice(subsubvols))
 
 	if len(subsubvols) > 0 && readonly {
 		// A root with subvolumes can never be readonly,
@@ -677,11 +679,10 @@ func (s *storageBtrfs) subvolsSnapshot(
 
 	// Now snapshot all subvolumes of the root.
 	for _, subsubvol := range subsubvols {
-		if err := s.subvolSnapshot(
-			path.Join(source, subsubvol),
-			path.Join(dest, subsubvol),
-			readonly); err != nil {
+		// Clear the target for the subvol to use
+		os.Remove(path.Join(dest, subsubvol))
 
+		if err := s.subvolSnapshot(path.Join(source, subsubvol), path.Join(dest, subsubvol), readonly); err != nil {
 			return err
 		}
 	}
@@ -741,8 +742,6 @@ func (s *storageBtrfs) getSubVolumes(path string) ([]string, error) {
 		return nil
 	})
 
-	sort.Sort(sort.Reverse(sort.StringSlice(result)))
-
 	return result, nil
 }
 

From a23e28f11f8f9625cb5352faec9046833a4ad320 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 13:16:06 -0500
Subject: [PATCH 0724/1193] Fix bad root device detection code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3043

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 5bdd1b88f..34b0aac61 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2783,10 +2783,14 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 
 	// Apply disk quota changes
 	for _, m := range addDevices {
+		if m["type"] != "disk" || m["path"] != "/" {
+			continue
+		}
+
 		var oldRootfsSize string
-		for _, m := range oldExpandedDevices {
-			if m["type"] == "disk" && m["path"] == "/" {
-				oldRootfsSize = m["size"]
+		for _, n := range oldExpandedDevices {
+			if n["type"] == "disk" && n["path"] == "/" {
+				oldRootfsSize = n["size"]
 				break
 			}
 		}

From e993128d518b84ba8c590dd98fa8f7a7fd636826 Mon Sep 17 00:00:00 2001
From: codejuan <xh at decbug.com>
Date: Sun, 26 Mar 2017 11:12:10 +0800
Subject: [PATCH 0725/1193] storage: ensure the container directory has the
 right permission

When the container directory was created by other application(such as nova-lxd), the
container directory maybe has the wrong perrmissions, so chmod 755 to ensure the directory has the right permission

Signed-off-by: codejuan <xh at decbug.com>
---
 lxd/storage_dir.go | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index c8dac4d54..e938a0531 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -36,10 +36,15 @@ func (s *storageDir) ContainerCreate(container container) error {
 		return fmt.Errorf("Error creating containers directory")
 	}
 
+	var mode os.FileMode
 	if container.IsPrivileged() {
-		if err := os.Chmod(cPath, 0700); err != nil {
-			return err
-		}
+		mode = 0700
+	} else {
+		mode = 0755
+	}
+
+	if err := os.Chmod(cPath, mode); err != nil {
+		return err
 	}
 
 	return container.TemplateApply("create")
@@ -53,10 +58,15 @@ func (s *storageDir) ContainerCreateFromImage(
 		return fmt.Errorf("Error creating rootfs directory")
 	}
 
+	var mode os.FileMode
 	if container.IsPrivileged() {
-		if err := os.Chmod(container.Path(), 0700); err != nil {
-			return err
-		}
+		mode = 0700
+	} else {
+		mode = 0755
+	}
+
+	if err := os.Chmod(container.Path(), mode); err != nil {
+		return err
 	}
 
 	imagePath := shared.VarPath("images", imageFingerprint)

From 9389497c4fa60d488a9c2e0a132ea36e648259db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 24 Feb 2017 18:20:11 -0500
Subject: [PATCH 0726/1193] Improve formatting in events API
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/events.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/events.go b/lxd/events.go
index 46b7dc6c0..5778ae927 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -26,7 +26,7 @@ func logContextMap(ctx []interface{}) map[string]string {
 		if key == "" {
 			key = entry.(string)
 		} else {
-			ctxMap[key] = fmt.Sprintf("%s", entry)
+			ctxMap[key] = fmt.Sprintf("%v", entry)
 			key = ""
 		}
 	}

From dfc2e255a7116ebe3d96409823d9e88e2af3f648 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 25 Feb 2017 01:15:55 -0500
Subject: [PATCH 0727/1193] Fix some race conditions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - /dev/lxd should be functional by the time we spawn containers
 - Mixed log messages during REST API initialization

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 34 +++++++++++++++++-----------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 219e771ca..a6ff632da 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -866,6 +866,11 @@ func (d *Daemon) Init() error {
 		return err
 	}
 
+	d.tomb.Go(func() error {
+		server := devLxdServer(d)
+		return server.Serve(d.devlxd)
+	})
+
 	if !d.MockMode {
 		/* Start the scheduler */
 		go deviceEventListener(d)
@@ -921,6 +926,7 @@ func (d *Daemon) Init() error {
 		NotFound.Render(w)
 	})
 
+	// Prepare the list of listeners
 	listeners := d.GetListeners()
 	if len(listeners) > 0 {
 		shared.LogInfof("LXD is socket activated")
@@ -1005,25 +1011,19 @@ func (d *Daemon) Init() error {
 		}
 	}
 
-	d.tomb.Go(func() error {
-		shared.LogInfof("REST API daemon:")
-		if d.UnixSocket != nil {
-			shared.LogInfo(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
-			d.tomb.Go(func() error { return http.Serve(d.UnixSocket.Socket, &lxdHttpServer{d.mux, d}) })
-		}
-
-		if d.TCPSocket != nil {
-			shared.LogInfo(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
-			d.tomb.Go(func() error { return http.Serve(d.TCPSocket.Socket, &lxdHttpServer{d.mux, d}) })
-		}
+	// Bind the REST API
+	shared.LogInfof("REST API daemon:")
+	if d.UnixSocket != nil {
+		shared.LogInfo(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
+		d.tomb.Go(func() error { return http.Serve(d.UnixSocket.Socket, &lxdHttpServer{d.mux, d}) })
+	}
 
-		d.tomb.Go(func() error {
-			server := devLxdServer(d)
-			return server.Serve(d.devlxd)
-		})
-		return nil
-	})
+	if d.TCPSocket != nil {
+		shared.LogInfo(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
+		d.tomb.Go(func() error { return http.Serve(d.TCPSocket.Socket, &lxdHttpServer{d.mux, d}) })
+	}
 
+	// Run the post initialization actions
 	if !d.MockMode && !d.SetupMode {
 		err := d.Ready()
 		if err != nil {

From 290691b5122793ac9b882aa388a08f7f3db463d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 25 Feb 2017 01:18:44 -0500
Subject: [PATCH 0728/1193] Fix override of Devices during copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2872

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/rest-api.md        |  7 ++++++-
 lxd/containers_post.go | 18 +++++++++++++++++-
 2 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index c85c690d9..0caa09417 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -495,10 +495,15 @@ Input (using a local container):
 
     {
         "name": "my-new-container",                                                     # 64 chars max, ASCII, no slash, no colon and no comma
-        "architecture": "x86_64",
         "profiles": ["default"],                                                        # List of profiles
         "ephemeral": true,                                                              # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                                  # Config override.
+        "devices": {                                                                    # optional list of devices the container should have
+            "kvm": {
+                "path": "/dev/kvm",
+                "type": "unix-char"
+            },
+        },
         "source": {"type": "copy",                                                      # Can be: "image", "migration", "copy" or "none"
                    "source": "my-old-container"}                                        # Name of the source container
     }
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 46569fa1c..39849f286 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -293,6 +293,22 @@ func createFromCopy(d *Daemon, req *api.ContainersPost) Response {
 		req.Config[key] = value
 	}
 
+	// Devices override
+	sourceDevices := source.LocalDevices()
+
+	if req.Devices == nil {
+		req.Devices = make(map[string]map[string]string)
+	}
+
+	for key, value := range sourceDevices {
+		_, exists := req.Devices[key]
+		if exists {
+			continue
+		}
+
+		req.Devices[key] = value
+	}
+
 	// Profiles override
 	if req.Profiles == nil {
 		req.Profiles = source.Profiles()
@@ -303,7 +319,7 @@ func createFromCopy(d *Daemon, req *api.ContainersPost) Response {
 		BaseImage:    req.Source.BaseImage,
 		Config:       req.Config,
 		Ctype:        cTypeRegular,
-		Devices:      source.LocalDevices(),
+		Devices:      req.Devices,
 		Ephemeral:    req.Ephemeral,
 		Name:         req.Name,
 		Profiles:     req.Profiles,

From 4afbc1073085e76b47f67c2c9ecbee51cced1a41 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 25 Feb 2017 01:49:40 -0500
Subject: [PATCH 0729/1193] doc: Fix badly named example device
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/rest-api.md | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 0caa09417..07bda2004 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -357,7 +357,7 @@ Input (container based on a local image with the "ubuntu/devel" alias):
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
         "devices": {                                                        # optional list of devices the container should have
-            "rootfs": {
+            "kvm": {
                 "path": "/dev/kvm",
                 "type": "unix-char"
             },
@@ -375,7 +375,7 @@ Input (container based on a local image identified by its fingerprint):
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
         "devices": {                                                        # optional list of devices the container should have
-            "rootfs": {
+            "kvm": {
                 "path": "/dev/kvm",
                 "type": "unix-char"
             },
@@ -393,7 +393,7 @@ Input (container based on most recent match based on image properties):
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
         "devices": {                                                        # optional list of devices the container should have
-            "rootfs": {
+            "kvm": {
                 "path": "/dev/kvm",
                 "type": "unix-char"
             },
@@ -415,7 +415,7 @@ Input (container without a pre-populated rootfs, useful when attaching to an exi
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
         "devices": {                                                        # optional list of devices the container should have
-            "rootfs": {
+            "kvm": {
                 "path": "/dev/kvm",
                 "type": "unix-char"
             },
@@ -432,7 +432,7 @@ Input (using a public remote image):
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
         "devices": {                                                        # optional list of devices the container should have
-            "rootfs": {
+            "kvm": {
                 "path": "/dev/kvm",
                 "type": "unix-char"
             },
@@ -454,7 +454,7 @@ Input (using a private remote image after having obtained a secret for that imag
         "ephemeral": true,                                                  # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                      # Config override.
         "devices": {                                                        # optional list of devices the container should have
-            "rootfs": {
+            "kvm": {
                 "path": "/dev/kvm",
                 "type": "unix-char"
             },
@@ -476,7 +476,7 @@ Input (using a remote container, sent over the migration websocket):
         "ephemeral": true,                                                              # Whether to destroy the container on shutdown
         "config": {"limits.cpu": "2"},                                                  # Config override.
         "devices": {                                                                    # optional list of devices the container should have
-            "rootfs": {
+            "kvm": {
                 "path": "/dev/kvm",
                 "type": "unix-char"
             },

From dd51e9abe3e89039d2aa42a647c8efc0d1739123 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Feb 2017 00:12:24 -0500
Subject: [PATCH 0730/1193] Validate the expanded config at container create
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

As only the unexpanded config is validated prior to that.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 34b0aac61..29b8722e4 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -212,6 +212,21 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		return nil, err
 	}
 
+	// Validate expanded config
+	err = containerValidConfig(c.expandedConfig, false, true)
+	if err != nil {
+		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
+		return nil, err
+	}
+
+	err = containerValidDevices(c.expandedDevices, false, true)
+	if err != nil {
+		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
+		return nil, err
+	}
+
 	// Look for a rootfs entry
 	_, _, err = containerGetRootDiskDevice(c.expandedDevices)
 	if err != nil {

From 8d8672eb2102bf7b2e844340ad9efe5f234aa5bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Feb 2017 00:31:24 -0500
Subject: [PATCH 0731/1193] Validate container idmap as early as possible
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go      |  8 ++++++--
 lxd/container_lxc.go  | 10 +++-------
 lxd/profiles.go       |  2 +-
 lxd/profiles_utils.go |  2 +-
 4 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index f8cb14f5b..0d660fffc 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -293,7 +293,7 @@ func containerValidDeviceConfigKey(t, k string) bool {
 	}
 }
 
-func containerValidConfig(config map[string]string, profile bool, expanded bool) error {
+func containerValidConfig(d *Daemon, config map[string]string, profile bool, expanded bool) error {
 	if config == nil {
 		return nil
 	}
@@ -309,6 +309,10 @@ func containerValidConfig(config map[string]string, profile bool, expanded bool)
 		}
 	}
 
+	if expanded && (config["security.privileged"] == "" || !shared.IsTrue(config["security.privileged"])) && d.IdmapSet == nil {
+		return fmt.Errorf("LXD doesn't have a uid/gid allocation. In this mode, only privileged containers are supported.")
+	}
+
 	return nil
 }
 
@@ -732,7 +736,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Validate container config
-	err := containerValidConfig(args.Config, false, false)
+	err := containerValidConfig(d, args.Config, false, false)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 29b8722e4..70e0f1bc0 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -213,7 +213,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Validate expanded config
-	err = containerValidConfig(c.expandedConfig, false, true)
+	err = containerValidConfig(c.daemon, c.expandedConfig, false, true)
 	if err != nil {
 		c.Delete()
 		shared.LogError("Failed creating container", ctxMap)
@@ -680,10 +680,6 @@ func (c *containerLXC) init() error {
 
 	// Setup the Idmap
 	if !c.IsPrivileged() {
-		if c.daemon.IdmapSet == nil {
-			return fmt.Errorf("LXD doesn't have a uid/gid allocation. In this mode, only privileged containers are supported.")
-		}
-
 		c.idmapset, err = c.NextIdmapSet()
 		if err != nil {
 			return err
@@ -2590,7 +2586,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	}
 
 	// Validate the new config
-	err := containerValidConfig(args.Config, false, false)
+	err := containerValidConfig(c.daemon, args.Config, false, false)
 	if err != nil {
 		return err
 	}
@@ -2739,7 +2735,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	removeDevices, addDevices, updateDevices := oldExpandedDevices.Update(c.expandedDevices)
 
 	// Do some validation of the config diff
-	err = containerValidConfig(c.expandedConfig, false, true)
+	err = containerValidConfig(c.daemon, c.expandedConfig, false, true)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 8902b4aa4..2e93a46c5 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -74,7 +74,7 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
 	}
 
-	err := containerValidConfig(req.Config, true, false)
+	err := containerValidConfig(d, req.Config, true, false)
 	if err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index 69a694ec3..89aa1caae 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -9,7 +9,7 @@ import (
 
 func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req api.ProfilePut) Response {
 	// Sanity checks
-	err := containerValidConfig(req.Config, true, false)
+	err := containerValidConfig(d, req.Config, true, false)
 	if err != nil {
 		return BadRequest(err)
 	}

From fceff3f563a5ee75e94831f9233488eaff5b1a99 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Feb 2017 00:51:33 -0500
Subject: [PATCH 0732/1193] container: Initialize idmap on demand
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go      |  2 +-
 lxd/container_exec.go |  8 +++++-
 lxd/container_lxc.go  | 73 +++++++++++++++++++++++++++++++++++++--------------
 lxd/migrate.go        |  6 ++++-
 lxd/storage.go        | 20 ++++++++++----
 5 files changed, 82 insertions(+), 27 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 0d660fffc..3ae393326 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -554,7 +554,7 @@ type container interface {
 	StorageStart() error
 	StorageStop() error
 	Storage() storage
-	IdmapSet() *shared.IdmapSet
+	IdmapSet() (*shared.IdmapSet, error)
 	LastIdmapSet() (*shared.IdmapSet, error)
 	TemplateApply(trigger string) error
 	Daemon() *Daemon
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 08fd309f2..091f5f46f 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -375,10 +375,16 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	if post.WaitForWS {
 		ws := &execWs{}
 		ws.fds = map[int]string{}
-		idmapset := c.IdmapSet()
+
+		idmapset, err := c.IdmapSet()
+		if err != nil {
+			return InternalError(err)
+		}
+
 		if idmapset != nil {
 			ws.rootUid, ws.rootGid = idmapset.ShiftIntoNs(0, 0)
 		}
+
 		ws.conns = map[int]*websocket.Conn{}
 		ws.conns[-1] = nil
 		ws.conns[0] = nil
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 70e0f1bc0..da24a59d8 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -678,14 +678,6 @@ func (c *containerLXC) init() error {
 		return err
 	}
 
-	// Setup the Idmap
-	if !c.IsPrivileged() {
-		c.idmapset, err = c.NextIdmapSet()
-		if err != nil {
-			return err
-		}
-	}
-
 	return nil
 }
 
@@ -950,8 +942,13 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup idmap
-	if c.idmapset != nil {
-		lines := c.idmapset.ToLxcString()
+	idmapset, err := c.IdmapSet()
+	if err != nil {
+		return err
+	}
+
+	if idmapset != nil {
+		lines := idmapset.ToLxcString()
 		for _, line := range lines {
 			err := lxcSetConfigItem(cc, "lxc.id_map", strings.TrimSuffix(line, "\n"))
 			if err != nil {
@@ -1440,7 +1437,10 @@ func (c *containerLXC) startCommon() (string, error) {
 	}
 
 	/* Deal with idmap changes */
-	idmap := c.IdmapSet()
+	idmap, err := c.IdmapSet()
+	if err != nil {
+		return "", err
+	}
 
 	lastIdmap, err := c.LastIdmapSet()
 	if err != nil {
@@ -3479,12 +3479,17 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 		 * namespace.
 		 */
 		if !c.IsPrivileged() {
+			idmapset, err := c.IdmapSet()
+			if err != nil {
+				return err
+			}
+
 			err = c.StorageStart()
 			if err != nil {
 				return err
 			}
 
-			err = c.IdmapSet().ShiftRootfs(stateDir)
+			err = idmapset.ShiftRootfs(stateDir)
 			err2 := c.StorageStop()
 			if err != nil {
 				return err
@@ -3646,7 +3651,12 @@ func (c *containerLXC) templateApplyNow(trigger string) error {
 
 			// Get the right uid and gid for the container
 			if !c.IsPrivileged() {
-				uid, gid = c.idmapset.ShiftIntoNs(0, 0)
+				idmapset, err := c.IdmapSet()
+				if err != nil {
+					return err
+				}
+
+				uid, gid = idmapset.ShiftIntoNs(0, 0)
 			}
 
 			// Create the directories leading to the file
@@ -4287,7 +4297,12 @@ func (c *containerLXC) tarStoreFile(linkmap map[uint64]string, offset int, tw *t
 
 	// Unshift the id under /rootfs/ for unpriv containers
 	if !c.IsPrivileged() && strings.HasPrefix(hdr.Name, "/rootfs") {
-		huid, hgid := c.idmapset.ShiftFromNs(int64(hdr.Uid), int64(hdr.Gid))
+		idmapset, err := c.IdmapSet()
+		if err != nil {
+			return err
+		}
+
+		huid, hgid := idmapset.ShiftFromNs(int64(hdr.Uid), int64(hdr.Gid))
 		hdr.Uid = int(huid)
 		hdr.Gid = int(hgid)
 		if hdr.Uid == -1 || hdr.Gid == -1 {
@@ -4558,8 +4573,13 @@ func (c *containerLXC) createUnixDevice(m types.Device) (string, error) {
 			return "", fmt.Errorf("Failed to chmod device %s: %s", devPath, err)
 		}
 
-		if c.idmapset != nil {
-			if err := c.idmapset.ShiftFile(devPath); err != nil {
+		idmapset, err := c.IdmapSet()
+		if err != nil {
+			return "", err
+		}
+
+		if idmapset != nil {
+			if err := idmapset.ShiftFile(devPath); err != nil {
 				// uidshift failing is weird, but not a big problem.  Log and proceed
 				shared.LogDebugf("Failed to uidshift device %s: %s\n", m["path"], err)
 			}
@@ -5629,8 +5649,23 @@ func (c *containerLXC) Id() int {
 	return c.id
 }
 
-func (c *containerLXC) IdmapSet() *shared.IdmapSet {
-	return c.idmapset
+func (c *containerLXC) IdmapSet() (*shared.IdmapSet, error) {
+	var err error
+
+	if c.idmapset != nil {
+		return c.idmapset, nil
+	}
+
+	if c.IsPrivileged() {
+		return nil, nil
+	}
+
+	c.idmapset, err = c.NextIdmapSet()
+	if err != nil {
+		return nil, err
+	}
+
+	return c.idmapset, nil
 }
 
 func (c *containerLXC) InitPID() int {
@@ -5655,7 +5690,7 @@ func (c *containerLXC) idmapsetFromConfig(k string) (*shared.IdmapSet, error) {
 	lastJsonIdmap := c.LocalConfig()[k]
 
 	if lastJsonIdmap == "" {
-		return c.IdmapSet(), nil
+		return c.IdmapSet()
 	}
 
 	lastIdmap := new(shared.IdmapSet)
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 5a306f349..90ca3fdc3 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -303,7 +303,11 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 
 	idmaps := make([]*IDMapType, 0)
 
-	idmapset := s.container.IdmapSet()
+	idmapset, err := s.container.IdmapSet()
+	if err != nil {
+		return err
+	}
+
 	if idmapset != nil {
 		for _, ctnIdmap := range idmapset.Idmap {
 			idmap := IDMapType{
diff --git a/lxd/storage.go b/lxd/storage.go
index cb93fb3dd..0f9f0ce3c 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -320,13 +320,16 @@ func (ss *storageShared) shiftRootfs(c container) error {
 	shared.LogDebug("Shifting root filesystem",
 		log.Ctx{"container": c.Name(), "rootfs": rpath})
 
-	idmapset := c.IdmapSet()
+	idmapset, err := c.IdmapSet()
+	if err != nil {
+		return err
+	}
 
 	if idmapset == nil {
 		return fmt.Errorf("IdmapSet of container '%s' is nil", c.Name())
 	}
 
-	err := idmapset.ShiftRootfs(rpath)
+	err = idmapset.ShiftRootfs(rpath)
 	if err != nil {
 		shared.LogDebugf("Shift of rootfs %s failed: %s", rpath, err)
 		return err
@@ -339,7 +342,10 @@ func (ss *storageShared) shiftRootfs(c container) error {
 }
 
 func (ss *storageShared) setUnprivUserAcl(c container, destPath string) error {
-	idmapset := c.IdmapSet()
+	idmapset, err := c.IdmapSet()
+	if err != nil {
+		return err
+	}
 
 	// Skip for privileged containers
 	if idmapset == nil {
@@ -357,7 +363,7 @@ func (ss *storageShared) setUnprivUserAcl(c container, destPath string) error {
 
 	// Attempt to set a POSIX ACL first. Fallback to chmod if the fs doesn't support it.
 	acl := fmt.Sprintf("%d:rx", uid)
-	_, err := exec.Command("setfacl", "-m", acl, destPath).CombinedOutput()
+	_, err = exec.Command("setfacl", "-m", acl, destPath).CombinedOutput()
 	if err != nil {
 		_, err := exec.Command("chmod", "+x", destPath).CombinedOutput()
 		if err != nil {
@@ -581,7 +587,11 @@ func (lw *storageLogWrapper) MigrationSink(live bool, container container, objec
 }
 
 func ShiftIfNecessary(container container, srcIdmap *shared.IdmapSet) error {
-	dstIdmap := container.IdmapSet()
+	dstIdmap, err := container.IdmapSet()
+	if err != nil {
+		return err
+	}
+
 	if dstIdmap == nil {
 		dstIdmap = new(shared.IdmapSet)
 	}

From 444675cf19894555f91484baae1ef95bde829cf1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Feb 2017 16:02:49 -0500
Subject: [PATCH 0733/1193] Don't parse id ranges as int32
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index da24a59d8..053202237 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -451,12 +451,12 @@ func idmapSize(daemon *Daemon, isolatedStr string, size string) (int64, error) {
 			idMapSize = daemon.IdmapSet.Idmap[0].Maprange
 		}
 	} else {
-		size, err := strconv.ParseInt(size, 10, 32)
+		size, err := strconv.ParseInt(size, 10, 64)
 		if err != nil {
 			return 0, err
 		}
 
-		idMapSize = int64(size)
+		idMapSize = size
 	}
 
 	return idMapSize, nil
@@ -608,7 +608,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 
 		cBase := int64(0)
 		if container.ExpandedConfig()["volatile.idmap.base"] != "" {
-			cBase, err = strconv.ParseInt(container.ExpandedConfig()["volatile.idmap.base"], 10, 32)
+			cBase, err = strconv.ParseInt(container.ExpandedConfig()["volatile.idmap.base"], 10, 64)
 			if err != nil {
 				return nil, 0, err
 			}

From 6d8556eaf6930418ad49116c8aafb4a4bd02db00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Feb 2017 16:04:19 -0500
Subject: [PATCH 0734/1193] Clarify uid/gid error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 053202237..b81743509 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -663,7 +663,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 		return mkIdmap(offset, size), offset, nil
 	}
 
-	return nil, 0, fmt.Errorf("no map range available")
+	return nil, 0, fmt.Errorf("Not enough uid/gid available for the container.")
 }
 
 func (c *containerLXC) init() error {

From 126c1fe459463adafdde254d10744e7c54dd6c3b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Feb 2017 16:34:08 -0500
Subject: [PATCH 0735/1193] idmap: Make more of an effort to find a default
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go            | 36 ++++++++++++----------
 shared/idmapset_linux.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 96 insertions(+), 17 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index a6ff632da..46f193f12 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -760,7 +760,7 @@ func (d *Daemon) Init() error {
 	/* Read the uid/gid allocation */
 	d.IdmapSet, err = shared.DefaultIdmapSet()
 	if err != nil {
-		shared.LogWarn("Error reading default idmap", log.Ctx{"err": err.Error()})
+		shared.LogWarn("Error reading default uid/gid map", log.Ctx{"err": err.Error()})
 		shared.LogWarnf("Only privileged containers will be able to run")
 		d.IdmapSet = nil
 	} else {
@@ -772,24 +772,30 @@ func (d *Daemon) Init() error {
 			}
 		}
 
-		shared.LogInfof("Configured LXD uid/gid map:")
-		for _, lxcmap := range d.IdmapSet.Idmap {
-			suffix := ""
+		if len(d.IdmapSet.Idmap) == 0 {
+			shared.LogWarnf("No available uid/gid map could be found")
+			shared.LogWarnf("Only privileged containers will be able to run")
+			d.IdmapSet = nil
+		} else {
+			shared.LogInfof("Configured LXD uid/gid map:")
+			for _, lxcmap := range d.IdmapSet.Idmap {
+				suffix := ""
 
-			if lxcmap.Usable() != nil {
-				suffix = " (unusable)"
-			}
+				if lxcmap.Usable() != nil {
+					suffix = " (unusable)"
+				}
 
-			for _, lxcEntry := range lxcmap.ToLxcString() {
-				shared.LogInfof(" - %s%s", strings.TrimRight(lxcEntry, "\n"), suffix)
+				for _, lxcEntry := range lxcmap.ToLxcString() {
+					shared.LogInfof(" - %s%s", strings.TrimRight(lxcEntry, "\n"), suffix)
+				}
 			}
-		}
 
-		err = d.IdmapSet.Usable()
-		if err != nil {
-			shared.LogWarnf("One or more uid/gid map entry isn't usable (typically due to nesting)")
-			shared.LogWarnf("Only privileged containers will be able to run")
-			d.IdmapSet = nil
+			err = d.IdmapSet.Usable()
+			if err != nil {
+				shared.LogWarnf("One or more uid/gid map entry isn't usable (typically due to nesting)")
+				shared.LogWarnf("Only privileged containers will be able to run")
+				d.IdmapSet = nil
+			}
 		}
 	}
 
diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 9e39f27a4..d08970e2f 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -658,10 +658,83 @@ func DefaultIdmapSet() (*IdmapSet, error) {
 			// NOTE: Remove once LXD can deal with multiple shadow maps
 			break
 		}
-	} else {
-		// Fallback map
+
+		return idmapset, nil
+	}
+
+	// No shadow available, figure out a default map
+	kernelMap, err := CurrentIdmapSet()
+	if err != nil {
+		// Hardcoded fallback map
 		e := IdmapEntry{Isuid: true, Isgid: true, Nsid: 0, Hostid: 1000000, Maprange: 1000000000}
 		idmapset.Idmap = Extend(idmapset.Idmap, e)
+		return idmapset, nil
+	}
+
+	// Look for mapped ranges
+	kernelRanges, err := kernelMap.ValidRanges()
+	if err != nil {
+		return nil, err
+	}
+
+	// Find a suitable uid range
+	for _, entry := range kernelRanges {
+		// We only care about uids right now
+		if !entry.Isuid {
+			continue
+		}
+
+		// We want a map that's separate from the system's own POSIX allocation
+		if entry.Endid < 100000 {
+			continue
+		}
+
+		// Don't use the first 65536 ids
+		if entry.Startid < 100000 {
+			entry.Startid = 100000
+		}
+
+		// Check if we have enough ids
+		if entry.Endid-entry.Startid < 65536 {
+			continue
+		}
+
+		// Add the map
+		e := IdmapEntry{Isuid: true, Isgid: false, Nsid: 0, Hostid: entry.Startid, Maprange: entry.Endid - entry.Startid + 1}
+		idmapset.Idmap = Extend(idmapset.Idmap, e)
+
+		// NOTE: Remove once LXD can deal with multiple shadow maps
+		break
+	}
+
+	// Find a suitable gid range
+	for _, entry := range kernelRanges {
+		// We only care about gids right now
+		if !entry.Isgid {
+			continue
+		}
+
+		// We want a map that's separate from the system's own POSIX allocation
+		if entry.Endid < 100000 {
+			continue
+		}
+
+		// Don't use the first 65536 ids
+		if entry.Startid < 100000 {
+			entry.Startid = 100000
+		}
+
+		// Check if we have enough ids
+		if entry.Endid-entry.Startid < 65536 {
+			continue
+		}
+
+		// Add the map
+		e := IdmapEntry{Isuid: false, Isgid: true, Nsid: 0, Hostid: entry.Startid, Maprange: entry.Endid - entry.Startid + 1}
+		idmapset.Idmap = Extend(idmapset.Idmap, e)
+
+		// NOTE: Remove once LXD can deal with multiple shadow maps
+		break
 	}
 
 	return idmapset, nil

From e71a0e58b13e07aaa1f99830b57bc7c344547b43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Feb 2017 16:47:58 -0500
Subject: [PATCH 0736/1193] init: Only show userns message if lacking uid/gid
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_init.go | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/lxd/main_init.go b/lxd/main_init.go
index 172ed35ca..6a2b50c59 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -283,7 +283,14 @@ func cmdInit() error {
 			}
 		}
 
-		if runningInUserns {
+		// Detect lack of uid/gid
+		needPrivileged := false
+		idmapset, err := shared.DefaultIdmapSet()
+		if err != nil || len(idmapset.Idmap) == 0 || idmapset.Usable() != nil {
+			needPrivileged = true
+		}
+
+		if runningInUserns && needPrivileged {
 			fmt.Printf(`
 We detected that you are running inside an unprivileged container.
 This means that unless you manually configured your host otherwise,

From 598275f09bf55a8dd5375c960ff29347c44f12da Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 27 Feb 2017 21:32:47 +0100
Subject: [PATCH 0737/1193] exec: kill forkexec on abnormal websocket closure

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_exec.go | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 091f5f46f..b3f0fa616 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -158,7 +158,23 @@ func (s *execWs) Do(op *operation) error {
 
 				if err != nil {
 					shared.LogDebugf("Got error getting next reader %s", err)
-					break
+					er, ok := err.(*websocket.CloseError)
+					if !ok {
+						break
+					}
+
+					if er.Code != websocket.CloseAbnormalClosure {
+						break
+					}
+
+					// If an abnormal closure occured, kill the attached process.
+					err := syscall.Kill(attachedChildPid, syscall.SIGKILL)
+					if err != nil {
+						shared.LogDebugf("Failed to send SIGKILL to pid %d.", attachedChildPid)
+					} else {
+						shared.LogDebugf("Sent SIGKILL to pid %d.", attachedChildPid)
+					}
+					return
 				}
 
 				buf, err := ioutil.ReadAll(r)

From 7efa40741e54153c84f0dfb48f75b8d84d926b71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 27 Feb 2017 23:48:05 -0500
Subject: [PATCH 0738/1193] Properly validate architectures
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2971

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go       |  4 ++++
 lxd/container_exec.go  |  2 +-
 lxd/containers_post.go | 47 ++++++++++++++++++++++++-----------------------
 3 files changed, 29 insertions(+), 24 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 3ae393326..20ad87b56 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -753,6 +753,10 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 		return nil, err
 	}
 
+	if !shared.IntInSlice(args.Architecture, d.architectures) {
+		return nil, fmt.Errorf("Requested architecture isn't supported by this host")
+	}
+
 	// Validate profiles
 	profiles, err := dbProfiles(d.db)
 	if err != nil {
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index b3f0fa616..ed0d9fbe6 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -137,7 +137,7 @@ func (s *execWs) Do(op *operation) error {
 	if s.interactive {
 		wgEOF.Add(1)
 		go func() {
-			<-attachedChildIsBorn
+			attachedChildPid := <-attachedChildIsBorn
 			select {
 			case <-s.controlConnected:
 				break
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 39849f286..c591eebd4 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -99,20 +99,19 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 
 		hash = imgInfo.Fingerprint
 
-		architecture, err := osarch.ArchitectureId(imgInfo.Architecture)
-		if err != nil {
-			architecture = 0
+		args := containerArgs{
+			BaseImage: hash,
+			Config:    req.Config,
+			Ctype:     cTypeRegular,
+			Devices:   req.Devices,
+			Ephemeral: req.Ephemeral,
+			Name:      req.Name,
+			Profiles:  req.Profiles,
 		}
 
-		args := containerArgs{
-			Architecture: architecture,
-			BaseImage:    hash,
-			Config:       req.Config,
-			Ctype:        cTypeRegular,
-			Devices:      req.Devices,
-			Ephemeral:    req.Ephemeral,
-			Name:         req.Name,
-			Profiles:     req.Profiles,
+		args.Architecture, err = osarch.ArchitectureId(imgInfo.Architecture)
+		if err != nil {
+			return err
 		}
 
 		_, err = containerCreateFromImage(d, args, hash)
@@ -131,19 +130,21 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 }
 
 func createFromNone(d *Daemon, req *api.ContainersPost) Response {
-	architecture, err := osarch.ArchitectureId(req.Architecture)
-	if err != nil {
-		architecture = 0
+	args := containerArgs{
+		Config:    req.Config,
+		Ctype:     cTypeRegular,
+		Devices:   req.Devices,
+		Ephemeral: req.Ephemeral,
+		Name:      req.Name,
+		Profiles:  req.Profiles,
 	}
 
-	args := containerArgs{
-		Architecture: architecture,
-		Config:       req.Config,
-		Ctype:        cTypeRegular,
-		Devices:      req.Devices,
-		Ephemeral:    req.Ephemeral,
-		Name:         req.Name,
-		Profiles:     req.Profiles,
+	if req.Architecture != "" {
+		architecture, err := osarch.ArchitectureId(req.Architecture)
+		if err != nil {
+			return InternalError(err)
+		}
+		args.Architecture = architecture
 	}
 
 	run := func(op *operation) error {

From 820d2f7e2ec3dff2bbe3c0a35248e1ece1ec29c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Feb 2017 15:18:57 -0500
Subject: [PATCH 0739/1193] lxc: Move common functions/types to utils.go
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/exec.go   |  10 ++---
 lxc/help.go   |   2 +-
 lxc/image.go  |  34 ---------------
 lxc/list.go   |  22 ----------
 lxc/main.go   |  38 -----------------
 lxc/remote.go |   4 +-
 lxc/utils.go  | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 138 insertions(+), 102 deletions(-)
 create mode 100644 lxc/utils.go

diff --git a/lxc/exec.go b/lxc/exec.go
index f73257269..44d1a33ea 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -18,15 +18,15 @@ import (
 	"github.com/lxc/lxd/shared/termios"
 )
 
-type envFlag []string
+type envList []string
 
-func (f *envFlag) String() string {
+func (f *envList) String() string {
 	return fmt.Sprint(*f)
 }
 
-func (f *envFlag) Set(value string) error {
+func (f *envList) Set(value string) error {
 	if f == nil {
-		*f = make(envFlag, 1)
+		*f = make(envList, 1)
 	} else {
 		*f = append(*f, value)
 	}
@@ -35,7 +35,7 @@ func (f *envFlag) Set(value string) error {
 
 type execCmd struct {
 	modeFlag string
-	envArgs  envFlag
+	envArgs  envList
 }
 
 func (c *execCmd) showByDefault() bool {
diff --git a/lxc/help.go b/lxc/help.go
index bba74f3e4..589e9621b 100644
--- a/lxc/help.go
+++ b/lxc/help.go
@@ -32,7 +32,7 @@ func (c *helpCmd) flags() {
 	gnuflag.BoolVar(&c.showAll, "all", false, i18n.G("Show all commands (not just interesting ones)"))
 }
 
-func (c *helpCmd) run(_ *lxd.Config, args []string) error {
+func (c *helpCmd) run(config *lxd.Config, args []string) error {
 	if len(args) > 0 {
 		for _, name := range args {
 			cmd, ok := commands[name]
diff --git a/lxc/image.go b/lxc/image.go
index f2c7ecc44..a74e9e334 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -20,40 +20,6 @@ import (
 	"github.com/lxc/lxd/shared/termios"
 )
 
-type SortImage [][]string
-
-func (a SortImage) Len() int {
-	return len(a)
-}
-
-func (a SortImage) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a SortImage) Less(i, j int) bool {
-	if a[i][0] == a[j][0] {
-		if a[i][3] == "" {
-			return false
-		}
-
-		if a[j][3] == "" {
-			return true
-		}
-
-		return a[i][3] < a[j][3]
-	}
-
-	if a[i][0] == "" {
-		return false
-	}
-
-	if a[j][0] == "" {
-		return true
-	}
-
-	return a[i][0] < a[j][0]
-}
-
 type aliasList []string
 
 func (f *aliasList) String() string {
diff --git a/lxc/list.go b/lxc/list.go
index af45b20fe..b945a29de 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -27,28 +27,6 @@ type column struct {
 
 type columnData func(api.Container, *api.ContainerState, []api.ContainerSnapshot) string
 
-type byName [][]string
-
-func (a byName) Len() int {
-	return len(a)
-}
-
-func (a byName) Swap(i, j int) {
-	a[i], a[j] = a[j], a[i]
-}
-
-func (a byName) Less(i, j int) bool {
-	if a[i][0] == "" {
-		return false
-	}
-
-	if a[j][0] == "" {
-		return true
-	}
-
-	return a[i][0] < a[j][0]
-}
-
 const (
 	listFormatTable = "table"
 	listFormatJSON  = "json"
diff --git a/lxc/main.go b/lxc/main.go
index 43bd4992b..89b607250 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -269,41 +269,3 @@ func execIfAliases(config *lxd.Config, origArgs []string) {
 	fmt.Fprintf(os.Stderr, i18n.G("processing aliases failed %s\n"), ret)
 	os.Exit(5)
 }
-
-type ProgressRenderer struct {
-	Format string
-
-	maxLength int
-}
-
-func (p *ProgressRenderer) Done(msg string) {
-	if msg != "" {
-		msg += "\n"
-	}
-
-	if len(msg) > p.maxLength {
-		p.maxLength = len(msg)
-	} else {
-		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
-	}
-
-	fmt.Print("\r")
-	fmt.Print(msg)
-}
-
-func (p *ProgressRenderer) Update(status string) {
-	msg := "%s"
-	if p.Format != "" {
-		msg = p.Format
-	}
-
-	msg = fmt.Sprintf("\r"+msg, status)
-
-	if len(msg) > p.maxLength {
-		p.maxLength = len(msg)
-	} else {
-		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
-	}
-
-	fmt.Print(msg)
-}
diff --git a/lxc/remote.go b/lxc/remote.go
index 36df6c56d..4f7d60b91 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -54,7 +54,7 @@ func (c *remoteCmd) flags() {
 	gnuflag.BoolVar(&c.public, "public", false, i18n.G("Public image server"))
 }
 
-func getRemoteCertificate(address string) (*x509.Certificate, error) {
+func (c *remoteCmd) getRemoteCertificate(address string) (*x509.Certificate, error) {
 	// Setup a permissive TLS config
 	tlsConfig, err := shared.GetTLSConfig("", "", nil)
 	if err != nil {
@@ -181,7 +181,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 	_, err = d.GetServerConfig()
 	if err != nil {
 		// Failed to connect using the system CA, so retrieve the remote certificate
-		certificate, err = getRemoteCertificate(addr)
+		certificate, err = c.getRemoteCertificate(addr)
 		if err != nil {
 			return err
 		}
diff --git a/lxc/utils.go b/lxc/utils.go
new file mode 100644
index 000000000..daaff837e
--- /dev/null
+++ b/lxc/utils.go
@@ -0,0 +1,130 @@
+package main
+
+import (
+	"fmt"
+	"strings"
+)
+
+// Progress tracking
+type ProgressRenderer struct {
+	Format string
+
+	maxLength int
+}
+
+func (p *ProgressRenderer) Done(msg string) {
+	if msg != "" {
+		msg += "\n"
+	}
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print("\r")
+	fmt.Print(msg)
+}
+
+func (p *ProgressRenderer) Update(status string) {
+	msg := "%s"
+	if p.Format != "" {
+		msg = p.Format
+	}
+
+	msg = fmt.Sprintf("\r"+msg, status)
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print(msg)
+}
+
+// Image fingerprint and alias sorting
+type SortImage [][]string
+
+func (a SortImage) Len() int {
+	return len(a)
+}
+
+func (a SortImage) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a SortImage) Less(i, j int) bool {
+	if a[i][0] == a[j][0] {
+		if a[i][3] == "" {
+			return false
+		}
+
+		if a[j][3] == "" {
+			return true
+		}
+
+		return a[i][3] < a[j][3]
+	}
+
+	if a[i][0] == "" {
+		return false
+	}
+
+	if a[j][0] == "" {
+		return true
+	}
+
+	return a[i][0] < a[j][0]
+}
+
+// Container name sorting
+type byName [][]string
+
+func (a byName) Len() int {
+	return len(a)
+}
+
+func (a byName) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a byName) Less(i, j int) bool {
+	if a[i][0] == "" {
+		return false
+	}
+
+	if a[j][0] == "" {
+		return true
+	}
+
+	return a[i][0] < a[j][0]
+}
+
+// Storage volume sorting
+type byNameAndType [][]string
+
+func (a byNameAndType) Len() int {
+	return len(a)
+}
+
+func (a byNameAndType) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a byNameAndType) Less(i, j int) bool {
+	if a[i][0] != a[j][0] {
+		return a[i][0] < a[j][0]
+	}
+
+	if a[i][1] == "" {
+		return false
+	}
+
+	if a[j][1] == "" {
+		return true
+	}
+
+	return a[i][1] < a[j][1]
+}

From 9c9309c2042becd2eecc0cf39721153fefe12945 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Feb 2017 15:42:35 -0500
Subject: [PATCH 0740/1193] lxc: Improve batch mode
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2966

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/action.go | 99 +++++++++++++++++++++++++++++++++++++++--------------------
 lxc/utils.go  | 23 ++++++++++++++
 2 files changed, 89 insertions(+), 33 deletions(-)

diff --git a/lxc/action.go b/lxc/action.go
index 238cad92f..3f465aa57 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -2,6 +2,8 @@ package main
 
 import (
 	"fmt"
+	"os"
+	"strings"
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
@@ -47,11 +49,7 @@ func (c *actionCmd) flags() {
 	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only for start)"))
 }
 
-func (c *actionCmd) run(config *lxd.Config, args []string) error {
-	if len(args) == 0 {
-		return errArgs
-	}
-
+func (c *actionCmd) doAction(config *lxd.Config, nameArg string) error {
 	state := false
 
 	// Only store state if asked to
@@ -59,46 +57,81 @@ func (c *actionCmd) run(config *lxd.Config, args []string) error {
 		state = true
 	}
 
-	for _, nameArg := range args {
-		remote, name := config.ParseRemoteAndContainer(nameArg)
-		d, err := lxd.NewClient(config, remote)
+	remote, name := config.ParseRemoteAndContainer(nameArg)
+	d, err := lxd.NewClient(config, remote)
+	if err != nil {
+		return err
+	}
+
+	if name == "" {
+		return fmt.Errorf(i18n.G("Must supply container name for: ")+"\"%s\"", nameArg)
+	}
+
+	if c.action == shared.Start {
+		current, err := d.ContainerInfo(name)
 		if err != nil {
 			return err
 		}
 
-		if name == "" {
-			return fmt.Errorf(i18n.G("Must supply container name for: ")+"\"%s\"", nameArg)
+		// "start" for a frozen container means "unfreeze"
+		if current.StatusCode == api.Frozen {
+			c.action = shared.Unfreeze
 		}
 
-		if c.action == shared.Start {
-			current, err := d.ContainerInfo(name)
-			if err != nil {
-				return err
-			}
-
-			// "start" for a frozen container means "unfreeze"
-			if current.StatusCode == api.Frozen {
-				c.action = shared.Unfreeze
-			}
-
-			// Always restore state (if present) unless asked not to
-			if c.action == shared.Start && current.Stateful && !c.stateless {
-				state = true
-			}
+		// Always restore state (if present) unless asked not to
+		if c.action == shared.Start && current.Stateful && !c.stateless {
+			state = true
 		}
+	}
 
-		resp, err := d.Action(name, c.action, c.timeout, c.force, state)
-		if err != nil {
-			return err
-		}
+	resp, err := d.Action(name, c.action, c.timeout, c.force, state)
+	if err != nil {
+		return err
+	}
+
+	if resp.Type != api.AsyncResponse {
+		return fmt.Errorf(i18n.G("bad result type from action"))
+	}
 
-		if resp.Type != api.AsyncResponse {
-			return fmt.Errorf(i18n.G("bad result type from action"))
+	if err := d.WaitForSuccess(resp.Operation); err != nil {
+		return fmt.Errorf("%s\n"+i18n.G("Try `lxc info --show-log %s` for more info"), err, nameArg)
+	}
+
+	return nil
+}
+
+func (c *actionCmd) run(config *lxd.Config, args []string) error {
+	if len(args) == 0 {
+		return errArgs
+	}
+
+	// Run the action for every listed container
+	results := runBatch(args, func(name string) error { return c.doAction(config, name) })
+
+	// Single container is easy
+	if len(results) == 1 {
+		return results[0].err
+	}
+
+	// Do fancier rendering for batches
+	success := true
+
+	for _, result := range results {
+		if result.err == nil {
+			continue
 		}
 
-		if err := d.WaitForSuccess(resp.Operation); err != nil {
-			return fmt.Errorf("%s\n"+i18n.G("Try `lxc info --show-log %s` for more info"), err, nameArg)
+		success = false
+		msg := fmt.Sprintf(i18n.G("error: %v"), result.err)
+		for _, line := range strings.Split(msg, "\n") {
+			fmt.Fprintln(os.Stderr, fmt.Sprintf("%s: %s", result.name, line))
 		}
 	}
+
+	if !success {
+		fmt.Fprintln(os.Stderr, "")
+		return fmt.Errorf(i18n.G("Some containers failed to %s"), c.name)
+	}
+
 	return nil
 }
diff --git a/lxc/utils.go b/lxc/utils.go
index daaff837e..00672e345 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -128,3 +128,26 @@ func (a byNameAndType) Less(i, j int) bool {
 
 	return a[i][1] < a[j][1]
 }
+
+// Batch operations
+type batchResult struct {
+	err  error
+	name string
+}
+
+func runBatch(names []string, action func(name string) error) []batchResult {
+	chResult := make(chan batchResult, len(names))
+
+	for _, name := range names {
+		go func(name string) {
+			chResult <- batchResult{action(name), name}
+		}(name)
+	}
+
+	results := []batchResult{}
+	for range names {
+		results = append(results, <-chResult)
+	}
+
+	return results
+}

From c9a8fb0eae9f63c549f72cab581f543414536b32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Feb 2017 16:40:54 -0500
Subject: [PATCH 0741/1193] Detect downgrades with newer DB and fail
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/db.go b/lxd/db.go
index 2d9c777d5..944926b27 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -246,6 +246,11 @@ func initializeDbObject(d *Daemon, path string) (err error) {
 		return fmt.Errorf("Error creating database: %s", err)
 	}
 
+	// Detect LXD downgrades
+	if dbGetSchema(d.db) > dbGetLatestSchema() {
+		return fmt.Errorf("The database schema is more recent than LXD's schema.")
+	}
+
 	// Apply any update
 	err = dbUpdatesApplyAll(d)
 	if err != nil {

From 2b6dea03a29baa11d8d4d08de10ead3d6a4f04b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Feb 2017 17:59:42 -0500
Subject: [PATCH 0742/1193] lxc/publish: Wait for the conainer to be running
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/publish.go | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lxc/publish.go b/lxc/publish.go
index 544724a86..400356c62 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -111,7 +111,15 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 			if op.StatusCode == api.Failure {
 				return fmt.Errorf(i18n.G("Stopping container failed!"))
 			}
-			defer s.Action(cName, shared.Start, -1, true, false)
+
+			defer func() {
+				resp, err := s.Action(cName, shared.Start, -1, true, false)
+				if err != nil {
+					return
+				}
+
+				s.WaitFor(resp.Operation)
+			}()
 
 			if wasEphemeral {
 				ct.Ephemeral = true

From 40f09ca37adc9bc0649df8c3be23dc5e36801aa1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Feb 2017 23:15:50 -0500
Subject: [PATCH 0743/1193] Escape markdown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/cloud-init.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/cloud-init.md b/doc/cloud-init.md
index 948066748..dcad9dd4b 100644
--- a/doc/cloud-init.md
+++ b/doc/cloud-init.md
@@ -125,7 +125,7 @@ A default cloud-init-network.tpl provided with images from the "ubuntu:" image
 source looks like this:
 
 ```
-{% if config_get("user.network-config", "") == "" %}version: 1
+{% if config\_get("user.network-config", "") == "" %}version: 1
 config:
     - type: physical
       name: eth0

From cfa3ee69bbcc17c1504f4534abdbe75e6a970d94 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 2 Mar 2017 01:44:53 +0100
Subject: [PATCH 0744/1193] test: give more time to reboot

LVM seems to take longer due to the amount of setup we need to do.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/basic.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index ad989dbc7..990b5a4fc 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -338,7 +338,7 @@ test_basic_usage() {
   REBOOTED="false"
 
   # shellcheck disable=SC2034
-  for i in $(seq 10); do
+  for i in $(seq 20); do
     NEW_INIT=$(lxc info foo | grep ^Pid || true)
 
     if [ -n "${NEW_INIT}" ] && [ "${OLD_INIT}" != "${NEW_INIT}" ]; then

From 297076ddfdda76bd8b23a545d48f7a999a69a65e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 1 Mar 2017 21:12:10 -0500
Subject: [PATCH 0745/1193] shared/simplestreams: Improve error handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                             |  9 +++++++--
 lxd/daemon_images.go                  |  2 +-
 shared/simplestreams/simplestreams.go | 36 ++++++++++++++++++++++-------------
 3 files changed, 31 insertions(+), 16 deletions(-)

diff --git a/client.go b/client.go
index 0525c7fcb..d7d4d6bc8 100644
--- a/client.go
+++ b/client.go
@@ -1091,7 +1091,7 @@ func (c *Client) PostImage(imageFile string, rootfsFile string, properties []str
 
 func (c *Client) GetImageInfo(image string) (*api.Image, error) {
 	if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil {
-		return c.simplestreams.GetImageInfo(image)
+		return c.simplestreams.GetImage(image)
 	}
 
 	resp, err := c.get(fmt.Sprintf("images/%s", image))
@@ -1249,7 +1249,12 @@ func (c *Client) IsAlias(alias string) (bool, error) {
 
 func (c *Client) GetAlias(alias string) string {
 	if c.Remote.Protocol == "simplestreams" && c.simplestreams != nil {
-		return c.simplestreams.GetAlias(alias)
+		alias, err := c.simplestreams.GetAlias(alias)
+		if err != nil {
+			return ""
+		}
+
+		return alias.Target
 	}
 
 	resp, err := c.get(fmt.Sprintf("images/aliases/%s", alias))
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 15cc0e12a..287a3e802 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -318,7 +318,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			return "", err
 		}
 
-		info, err := ss.GetImageInfo(fp)
+		info, err := ss.GetImage(fp)
 		if err != nil {
 			return "", err
 		}
diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index 76b2e8eb5..e4251e80a 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -294,7 +294,8 @@ func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) {
 		return s.cachedIndex, nil
 	}
 
-	req, err := http.NewRequest("GET", fmt.Sprintf("%s/streams/v1/index.json", s.url), nil)
+	url := fmt.Sprintf("%s/streams/v1/index.json", s.url)
+	req, err := http.NewRequest("GET", url, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -309,6 +310,10 @@ func (s *SimpleStreams) parseIndex() (*SimpleStreamsIndex, error) {
 	}
 	defer r.Body.Close()
 
+	if r.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("Unable to fetch %s: %s", url, r.Status)
+	}
+
 	body, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		return nil, err
@@ -331,7 +336,8 @@ func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, erro
 		return s.cachedManifest[path], nil
 	}
 
-	req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", s.url, path), nil)
+	url := fmt.Sprintf("%s/%s", s.url, path)
+	req, err := http.NewRequest("GET", url, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -346,6 +352,10 @@ func (s *SimpleStreams) parseManifest(path string) (*SimpleStreamsManifest, erro
 	}
 	defer r.Body.Close()
 
+	if r.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("Unable to fetch %s: %s", url, r.Status)
+	}
+
 	body, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		return nil, err
@@ -531,20 +541,20 @@ func (s *SimpleStreams) downloadFile(path string, hash string, target string, pr
 			req.Header.Set("User-Agent", s.useragent)
 		}
 
-		resp, err := s.http.Do(req)
+		r, err := s.http.Do(req)
 		if err != nil {
 			return err
 		}
-		defer resp.Body.Close()
+		defer r.Body.Close()
 
-		if resp.StatusCode != http.StatusOK {
-			return fmt.Errorf("invalid simplestreams source: got %d looking for %s", resp.StatusCode, path)
+		if r.StatusCode != http.StatusOK {
+			return fmt.Errorf("Unable to fetch %s: %s", url, r.Status)
 		}
 
 		body := &ioprogress.ProgressReader{
-			ReadCloser: resp.Body,
+			ReadCloser: r.Body,
 			Tracker: &ioprogress.ProgressTracker{
-				Length:  resp.ContentLength,
+				Length:  r.ContentLength,
 				Handler: progress,
 			},
 		}
@@ -600,21 +610,21 @@ func (s *SimpleStreams) ListImages() ([]api.Image, error) {
 	return images, err
 }
 
-func (s *SimpleStreams) GetAlias(name string) string {
+func (s *SimpleStreams) GetAlias(name string) (*api.ImageAliasesEntry, error) {
 	_, aliasesMap, err := s.getImages()
 	if err != nil {
-		return ""
+		return nil, err
 	}
 
 	alias, ok := aliasesMap[name]
 	if !ok {
-		return ""
+		return nil, fmt.Errorf("Alias '%s' doesn't exist", name)
 	}
 
-	return alias.Target
+	return alias, nil
 }
 
-func (s *SimpleStreams) GetImageInfo(fingerprint string) (*api.Image, error) {
+func (s *SimpleStreams) GetImage(fingerprint string) (*api.Image, error) {
 	images, _, err := s.getImages()
 	if err != nil {
 		return nil, err

From 198046055100f2cd062ac0409e0cdf5ef1a524a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 2 Mar 2017 13:05:49 -0500
Subject: [PATCH 0746/1193] Fix error handling in all filepath.Walk calls
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go     | 7 ++++++-
 shared/idmapset_linux.go | 6 ++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b81743509..f1a2a345a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3222,7 +3222,12 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 	offset := len(cDir) + 1
 
 	writeToTar := func(path string, fi os.FileInfo, err error) error {
-		if err := c.tarStoreFile(linkmap, offset, tw, path, fi); err != nil {
+		if err != nil {
+			return err
+		}
+
+		err = c.tarStoreFile(linkmap, offset, tw, path, fi)
+		if err != nil {
 			shared.LogDebugf("Error tarring up %s: %s", path, err)
 			return err
 		}
diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index d08970e2f..85e991f2a 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -455,10 +455,15 @@ func (set *IdmapSet) doUidshiftIntoContainer(dir string, testmode bool, how stri
 	dir = strings.TrimRight(dir, "/")
 
 	convert := func(path string, fi os.FileInfo, err error) (e error) {
+		if err != nil {
+			return err
+		}
+
 		intUid, intGid, _, _, _, _, err := GetFileStat(path)
 		if err != nil {
 			return err
 		}
+
 		uid := int64(intUid)
 		gid := int64(intGid)
 
@@ -484,6 +489,7 @@ func (set *IdmapSet) doUidshiftIntoContainer(dir string, testmode bool, how stri
 	if !PathExists(dir) {
 		return fmt.Errorf("No such file or directory: %q", dir)
 	}
+
 	return filepath.Walk(dir, convert)
 }
 

From 873ef014cd2f5c0f965a42349837631be923e027 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 3 Mar 2017 18:38:06 -0500
Subject: [PATCH 0747/1193] doc: Add instructions to grow ZFS loop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Launchpad: https://bugs.launchpad.net/ubuntu/+source/lxd/+bug/1648995
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/storage-backends.md | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/doc/storage-backends.md b/doc/storage-backends.md
index 1d3039b87..09387481e 100644
--- a/doc/storage-backends.md
+++ b/doc/storage-backends.md
@@ -75,3 +75,13 @@ rsync is used to transfer the container content across.
  - Note that LXD will assume it has full control over the zfs pool or dataset.
    It is recommended to not maintain any non-LXD owned filesystem entities in
    a LXD zfs pool or dataset since LXD might delete them.
+
+#### Growing a loop backed ZFS pool
+LXD doesn't let you directly grow a loop backed ZFS pool, but you can do so with:
+
+```
+sudo truncate -s +5G /var/lib/lxd/zfs.img
+sudo zpool set autoexpand=on lxd
+sudo zpool online -e lxd /var/lib/lxd/zfs.img
+sudo zpool set autoexpand=off lxd
+```

From c1f399fc6f1ecc4f34a9652c8348b7389b6ec884 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 2 Mar 2017 16:52:58 -0500
Subject: [PATCH 0748/1193] Fix base image tracking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2999

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go       | 3 +++
 lxd/containers_post.go | 9 +++------
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 20ad87b56..fa5250d36 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -601,6 +601,9 @@ func containerCreateEmptySnapshot(d *Daemon, args containerArgs) (container, err
 }
 
 func containerCreateFromImage(d *Daemon, args containerArgs, hash string) (container, error) {
+	// Set the BaseImage field (regardless of previous value)
+	args.BaseImage = hash
+
 	// Create the container
 	c, err := containerCreateInternal(d, args)
 	if err != nil {
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index c591eebd4..44596de7b 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -48,8 +48,8 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 
 		var image *api.Image
 
-		for _, hash := range hashes {
-			_, img, err := dbImageGet(d.db, hash, false, true)
+		for _, imageHash := range hashes {
+			_, img, err := dbImageGet(d.db, imageHash, false, true)
 			if err != nil {
 				continue
 			}
@@ -97,10 +97,7 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 			return err
 		}
 
-		hash = imgInfo.Fingerprint
-
 		args := containerArgs{
-			BaseImage: hash,
 			Config:    req.Config,
 			Ctype:     cTypeRegular,
 			Devices:   req.Devices,
@@ -114,7 +111,7 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 			return err
 		}
 
-		_, err = containerCreateFromImage(d, args, hash)
+		_, err = containerCreateFromImage(d, args, imgInfo.Fingerprint)
 		return err
 	}
 

From eee516c7e0889b1cbecf19d80a168a8e9eb2e300 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 3 Mar 2017 18:49:06 -0500
Subject: [PATCH 0749/1193] lxc: Properly implement "profile unset"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Launchpad: https://bugs.launchpad.net/ubuntu/+source/lxd/+bug/1669772
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/profile.go | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/lxc/profile.go b/lxc/profile.go
index ab190f42c..7521e298a 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -126,7 +126,7 @@ func (c *profileCmd) run(config *lxd.Config, args []string) error {
 	case "set":
 		return c.doProfileSet(client, profile, args[2:])
 	case "unset":
-		return c.doProfileSet(client, profile, args[2:])
+		return c.doProfileUnset(client, profile, args[2:])
 	case "copy":
 		return c.doProfileCopy(config, client, profile, args[2:])
 	case "show":
@@ -338,6 +338,15 @@ func (c *profileCmd) doProfileSet(client *lxd.Client, p string, args []string) e
 	return err
 }
 
+func (c *profileCmd) doProfileUnset(client *lxd.Client, p string, args []string) error {
+	// we shifted @args so so it should read "<key> [<value>]"
+	if len(args) != 1 {
+		return errArgs
+	}
+
+	return c.doProfileSet(client, p, args)
+}
+
 func (c *profileCmd) doProfileList(config *lxd.Config, args []string) error {
 	var remote string
 	if len(args) > 1 {

From dcaa5380658c1a30b10e998f1d7c56818e204556 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 4 Mar 2017 01:46:49 -0500
Subject: [PATCH 0750/1193] rsync: Tweak logging a bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/rsync.go | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/lxd/rsync.go b/lxd/rsync.go
index ba646ee35..fe5770c04 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -89,23 +89,24 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 // directory pointed to by path over the websocket.
 func RsyncSend(path string, conn *websocket.Conn) error {
 	cmd, dataSocket, stderr, err := rsyncSendSetup(path)
-	if dataSocket != nil {
-		defer dataSocket.Close()
-	}
 	if err != nil {
 		return err
 	}
 
+	if dataSocket != nil {
+		defer dataSocket.Close()
+	}
+
 	readDone, writeDone := shared.WebsocketMirror(conn, dataSocket, dataSocket, nil, nil)
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
-		shared.LogDebugf("problem reading rsync stderr %s", err)
+		return err
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.LogDebugf("problem with rsync send of %s: %s: %s", path, err, string(output))
+		shared.LogErrorf("Rsync send failed: %s: %s: %s", path, err, string(output))
 	}
 
 	<-readDone
@@ -147,15 +148,14 @@ func RsyncRecv(path string, conn *websocket.Conn) error {
 	}
 
 	readDone, writeDone := shared.WebsocketMirror(conn, stdin, stdout, nil, nil)
-	data, err2 := ioutil.ReadAll(stderr)
-	if err2 != nil {
-		shared.LogDebugf("error reading rsync stderr: %s", err2)
-		return err2
+	output, err := ioutil.ReadAll(stderr)
+	if err != nil {
+		return err
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.LogDebugf("rsync recv error for path %s: %s: %s", path, err, string(data))
+		shared.LogErrorf("Rsync receive failed: %s: %s: %s", path, err, string(output))
 	}
 
 	<-readDone

From 16a09050a6fa9f5da21241a82a0f53f94661279b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 4 Mar 2017 18:19:43 -0500
Subject: [PATCH 0751/1193] Properly revert memory limits on failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3017

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f1a2a345a..fa8dd9993 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2919,21 +2919,57 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 					memory = fmt.Sprintf("%d", valueInt)
 				}
 
+				// Store the old values for revert
+				oldMemswLimit := ""
+				if cgSwapAccounting {
+					oldMemswLimit, err = c.CGroupGet("memory.memsw.limit_in_bytes")
+					if err != nil {
+						oldMemswLimit = ""
+					}
+				}
+
+				oldLimit, err := c.CGroupGet("memory.limit_in_bytes")
+				if err != nil {
+					oldLimit = ""
+				}
+
+				oldSoftLimit, err := c.CGroupGet("memory.soft_limit_in_bytes")
+				if err != nil {
+					oldSoftLimit = ""
+				}
+
+				revertMemory := func() {
+					if oldSoftLimit != "" {
+						c.CGroupSet("memory.soft_limit_in_bytes", oldSoftLimit)
+					}
+
+					if oldLimit != "" {
+						c.CGroupSet("memory.limit_in_bytes", oldLimit)
+					}
+
+					if oldMemswLimit != "" {
+						c.CGroupSet("memory.memsw.limit_in_bytes", oldMemswLimit)
+					}
+				}
+
 				// Reset everything
 				if cgSwapAccounting {
 					err = c.CGroupSet("memory.memsw.limit_in_bytes", "-1")
 					if err != nil {
+						revertMemory()
 						return err
 					}
 				}
 
 				err = c.CGroupSet("memory.limit_in_bytes", "-1")
 				if err != nil {
+					revertMemory()
 					return err
 				}
 
 				err = c.CGroupSet("memory.soft_limit_in_bytes", "-1")
 				if err != nil {
+					revertMemory()
 					return err
 				}
 
@@ -2942,21 +2978,26 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 					// Set new limit
 					err = c.CGroupSet("memory.soft_limit_in_bytes", memory)
 					if err != nil {
+						revertMemory()
 						return err
 					}
 				} else {
 					if cgSwapAccounting && (memorySwap == "" || shared.IsTrue(memorySwap)) {
 						err = c.CGroupSet("memory.limit_in_bytes", memory)
 						if err != nil {
+							revertMemory()
 							return err
 						}
+
 						err = c.CGroupSet("memory.memsw.limit_in_bytes", memory)
 						if err != nil {
+							revertMemory()
 							return err
 						}
 					} else {
 						err = c.CGroupSet("memory.limit_in_bytes", memory)
 						if err != nil {
+							revertMemory()
 							return err
 						}
 					}

From dab211afb334bc8057a1a998fae6db65a8d387cc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 3 Mar 2017 23:41:15 -0500
Subject: [PATCH 0752/1193] lxc: Make help/usage a bit more consistent
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Make sure that we always exit 0 when passed --help
 - Make sure that when exiting 0, the usage goes to stdout.
 - On error, make sure the usage goes to stderr.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index 89b607250..7eb811197 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -105,8 +105,12 @@ func run() error {
 	}
 	cmd.flags()
 	gnuflag.Usage = func() {
-		fmt.Fprintf(os.Stderr, i18n.G("Usage: %s")+"\n\n"+i18n.G("Options:")+"\n\n", strings.TrimSpace(cmd.usage()))
+		fmt.Print(cmd.usage())
+		fmt.Printf("\n\n%s\n", i18n.G("Options:"))
+
+		gnuflag.SetOut(os.Stdout)
 		gnuflag.PrintDefaults()
+		os.Exit(0)
 	}
 
 	os.Args = os.Args[1:]
@@ -142,7 +146,15 @@ func run() error {
 		if !*noAlias {
 			execIfAliases(config, origArgs)
 		}
-		fmt.Fprintf(os.Stderr, "%s\n\n"+i18n.G("error: %v")+"\n", cmd.usage(), err)
+
+		fmt.Fprintf(os.Stderr, i18n.G("error: %v"), err)
+		fmt.Fprintf(os.Stderr, "\n\n")
+		fmt.Fprint(os.Stderr, cmd.usage())
+		fmt.Fprintf(os.Stderr, "\n\n%s\n", i18n.G("Options:"))
+
+		gnuflag.SetOut(os.Stderr)
+		gnuflag.PrintDefaults()
+
 		os.Exit(1)
 	}
 	return err

From bc964a7575d30a6ebe7c782bd67d522e9be4923b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 3 Mar 2017 23:45:48 -0500
Subject: [PATCH 0753/1193] lxc: Allow --version to be passed with any command
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index 7eb811197..8f6401bed 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -62,8 +62,8 @@ func run() error {
 		os.Args = append(os.Args, "--all")
 	}
 
-	if len(os.Args) == 2 && os.Args[1] == "--version" {
-		os.Args[1] = "version"
+	if shared.StringInSlice("--version", os.Args) {
+		os.Args = []string{os.Args[0], "version"}
 	}
 
 	if len(os.Args) < 2 {

From fdd6f936cbfc15b85f709034a52c8d9643ebdc03 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Apr 2017 16:24:02 -0400
Subject: [PATCH 0754/1193] Validate expanded configuration after root setup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fa8dd9993..3076cce35 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -212,21 +212,6 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		return nil, err
 	}
 
-	// Validate expanded config
-	err = containerValidConfig(c.daemon, c.expandedConfig, false, true)
-	if err != nil {
-		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
-		return nil, err
-	}
-
-	err = containerValidDevices(c.expandedDevices, false, true)
-	if err != nil {
-		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
-		return nil, err
-	}
-
 	// Look for a rootfs entry
 	_, _, err = containerGetRootDiskDevice(c.expandedDevices)
 	if err != nil {
@@ -257,6 +242,21 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		}
 	}
 
+	// Validate expanded config
+	err = containerValidConfig(c.daemon, c.expandedConfig, false, true)
+	if err != nil {
+		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
+		return nil, err
+	}
+
+	err = containerValidDevices(c.expandedDevices, false, true)
+	if err != nil {
+		c.Delete()
+		shared.LogError("Failed creating container", ctxMap)
+		return nil, err
+	}
+
 	// Setup initial idmap config
 	var idmap *shared.IdmapSet
 	base := int64(0)

From d519c4d13483fc2409f070a5fb995eb6cbafa29b Mon Sep 17 00:00:00 2001
From: Carlos Neira <cneirabustos at gmail.com>
Date: Fri, 26 Aug 2016 18:50:11 -0300
Subject: [PATCH 0755/1193] lxc: Add a manpage command

Closes #2280
Closes #2325

Signed-off-by: Carlos <cneirabustos at gmail.com>
---
 lxc/manpage.go | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 lxc/manpage.go

diff --git a/lxc/manpage.go b/lxc/manpage.go
new file mode 100644
index 000000000..8bc9b610c
--- /dev/null
+++ b/lxc/manpage.go
@@ -0,0 +1,30 @@
+package main
+
+import (
+	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/shared/i18n"
+)
+
+type manpageCmd struct{}
+
+func (c *manpageCmd) showByDefault() bool {
+	return false
+}
+
+func (c *manpageCmd) usage() string {
+	return i18n.G(
+		`Prints all subcommands help to create a lxd manpage`)
+}
+
+func (c *manpageCmd) flags() {
+}
+
+func (c *manpageCmd) run(_ *lxd.Config, args []string) error {
+	if len(args) > 0 {
+		return errArgs
+	}
+	for k, _ := range commands {
+		commands["help"].run(nil, []string{k})
+	}
+	return nil
+}

From bea4f1747024de71276d511dc1b029b9154dafe8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 30 Aug 2016 17:59:28 -0400
Subject: [PATCH 0756/1193] lxc: Tweak to --man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Update help string
 - Add headers for each command
 - Sort the command alphabetically
 - Update translation template

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/manpage.go |  20 ++++++-
 po/lxd.pot     | 168 +++++++++++++++++++++++++++++----------------------------
 2 files changed, 105 insertions(+), 83 deletions(-)

diff --git a/lxc/manpage.go b/lxc/manpage.go
index 8bc9b610c..db74cbe91 100644
--- a/lxc/manpage.go
+++ b/lxc/manpage.go
@@ -1,6 +1,9 @@
 package main
 
 import (
+	"fmt"
+	"sort"
+
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -13,7 +16,7 @@ func (c *manpageCmd) showByDefault() bool {
 
 func (c *manpageCmd) usage() string {
 	return i18n.G(
-		`Prints all subcommands help to create a lxd manpage`)
+		`Prints all the subcommands help.`)
 }
 
 func (c *manpageCmd) flags() {
@@ -23,8 +26,23 @@ func (c *manpageCmd) run(_ *lxd.Config, args []string) error {
 	if len(args) > 0 {
 		return errArgs
 	}
+
+	keys := []string{}
 	for k, _ := range commands {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
+	header := false
+	for _, k := range keys {
+		if header {
+			fmt.Printf("\n\n")
+		}
+
+		fmt.Printf("### lxc %s\n", k)
 		commands["help"].run(nil, []string{k})
+		header = true
 	}
+
 	return nil
 }
diff --git a/po/lxd.pot b/po/lxd.pot
index 42d936023..54b411226 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-02-24 20:55-0500\n"
+        "POT-Creation-Date: 2017-04-17 16:35-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -36,7 +36,7 @@ msgid   "### This is a yaml representation of the configuration.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:84
+#: lxc/image.go:50
 msgid   "### This is a yaml representation of the image properties.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -65,7 +65,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:614
+#: lxc/image.go:580
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -78,15 +78,15 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:635 lxc/image.go:664
+#: lxc/image.go:601 lxc/image.go:630
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:639
+#: lxc/image.go:605
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:396
+#: lxc/list.go:374
 msgid   "ARCHITECTURE"
 msgstr  ""
 
@@ -99,16 +99,16 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:373
+#: lxc/image.go:339
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:351 lxc/info.go:93
+#: lxc/image.go:317 lxc/info.go:93
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:377
+#: lxc/image.go:343
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
@@ -129,7 +129,7 @@ msgstr  ""
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:397
+#: lxc/list.go:375
 msgid   "CREATED AT"
 msgstr  ""
 
@@ -143,7 +143,7 @@ msgstr  ""
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:347
+#: lxc/profile.go:356
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
@@ -152,7 +152,7 @@ msgstr  ""
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
 
-#: lxc/action.go:34
+#: lxc/action.go:36
 #, c-format
 msgid   "Change state of one or more containers to %s.\n"
         "\n"
@@ -169,7 +169,7 @@ msgstr  ""
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
-#: lxc/list.go:100 lxc/list.go:101
+#: lxc/list.go:78 lxc/list.go:79
 msgid   "Columns"
 msgstr  ""
 
@@ -177,7 +177,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:718 lxc/profile.go:190
+#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:684 lxc/profile.go:190
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -195,12 +195,12 @@ msgstr  ""
 msgid   "Container name is: %s"
 msgstr  ""
 
-#: lxc/publish.go:150 lxc/publish.go:165
+#: lxc/publish.go:158 lxc/publish.go:173
 #, c-format
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:164
+#: lxc/image.go:130
 msgid   "Copy aliases from source"
 msgstr  ""
 
@@ -210,7 +210,7 @@ msgid   "Copy containers within or in between LXD instances.\n"
         "lxc copy [<remote>:]<source>[/<snapshot>] [<remote>:]<destination> [--ephemeral|e]"
 msgstr  ""
 
-#: lxc/image.go:276
+#: lxc/image.go:242
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
@@ -235,7 +235,7 @@ msgid   "Create a read-only snapshot of a container.\n"
         "    lxc snapshot u1 snap0"
 msgstr  ""
 
-#: lxc/image.go:356 lxc/info.go:95
+#: lxc/image.go:322 lxc/info.go:95
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -249,7 +249,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:638 lxc/image.go:666
+#: lxc/image.go:604 lxc/image.go:632
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -275,7 +275,7 @@ msgstr  ""
 msgid   "Disk usage:"
 msgstr  ""
 
-#: lxc/list.go:482
+#: lxc/list.go:460
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -315,29 +315,29 @@ msgid   "Execute the specified command in a container.\n"
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
 
-#: lxc/image.go:360
+#: lxc/image.go:326
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:362
+#: lxc/image.go:328
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:273 lxc/image.go:636 lxc/image.go:665
+#: lxc/config.go:273 lxc/image.go:602 lxc/image.go:631
 msgid   "FINGERPRINT"
 msgstr  ""
 
-#: lxc/list.go:103
+#: lxc/list.go:81
 msgid   "Fast mode (same as --columns=nsacPt"
 msgstr  ""
 
-#: lxc/image.go:349
+#: lxc/image.go:315
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
 
-#: lxc/action.go:43 lxc/action.go:44
+#: lxc/action.go:45 lxc/action.go:46
 msgid   "Force the container to shutdown"
 msgstr  ""
 
@@ -349,11 +349,11 @@ msgstr  ""
 msgid   "Force using the local unix socket"
 msgstr  ""
 
-#: lxc/list.go:102
+#: lxc/list.go:80
 msgid   "Format"
 msgstr  ""
 
-#: lxc/main.go:124
+#: lxc/main.go:128
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
@@ -363,11 +363,11 @@ msgid   "Help page for the LXD client.\n"
         "lxc help [--all]"
 msgstr  ""
 
-#: lxc/list.go:394
+#: lxc/list.go:372
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:395
+#: lxc/list.go:373
 msgid   "IPV6"
 msgstr  ""
 
@@ -375,7 +375,7 @@ msgstr  ""
 msgid   "ISSUE DATE"
 msgstr  ""
 
-#: lxc/main.go:132
+#: lxc/main.go:136
 msgid   "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr  ""
 
@@ -383,20 +383,20 @@ msgstr  ""
 msgid   "Ignore aliases when determining what command to run"
 msgstr  ""
 
-#: lxc/action.go:47
+#: lxc/action.go:49
 msgid   "Ignore the container state (only for start)"
 msgstr  ""
 
-#: lxc/image.go:279
+#: lxc/image.go:245
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:432 lxc/image.go:444
+#: lxc/image.go:398 lxc/image.go:410
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:429
+#: lxc/image.go:395
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
@@ -442,7 +442,7 @@ msgstr  ""
 msgid   "Ips:"
 msgstr  ""
 
-#: lxc/image.go:165
+#: lxc/image.go:131
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
@@ -450,12 +450,12 @@ msgstr  ""
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/image.go:365
+#: lxc/image.go:331
 #, c-format
 msgid   "Last used: %s"
 msgstr  ""
 
-#: lxc/image.go:367
+#: lxc/image.go:333
 msgid   "Last used: never"
 msgstr  ""
 
@@ -483,7 +483,7 @@ msgid   "List information on LXD servers and containers.\n"
         "    lxc info [<remote:>]"
 msgstr  ""
 
-#: lxc/list.go:68
+#: lxc/list.go:46
 msgid   "Lists the containers.\n"
         "\n"
         "lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]\n"
@@ -518,7 +518,7 @@ msgstr  ""
 msgid   "Log:"
 msgstr  ""
 
-#: lxc/image.go:163
+#: lxc/image.go:129
 msgid   "Make image public"
 msgstr  ""
 
@@ -629,7 +629,7 @@ msgid   "Manage remote LXD servers.\n"
         "lxc remote get-default                                                      Print the default remote."
 msgstr  ""
 
-#: lxc/image.go:94
+#: lxc/image.go:60
 msgid   "Manipulate container images.\n"
         "\n"
         "In LXD containers are created from images. Those images were themselves\n"
@@ -744,11 +744,11 @@ msgid   "Move containers within or in between lxd instances.\n"
         "    Rename a snapshot."
 msgstr  ""
 
-#: lxc/action.go:70
+#: lxc/action.go:67
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:398 lxc/remote.go:351
+#: lxc/list.go:376 lxc/remote.go:351
 msgid   "NAME"
 msgstr  ""
 
@@ -765,7 +765,7 @@ msgstr  ""
 msgid   "Network usage:"
 msgstr  ""
 
-#: lxc/image.go:166 lxc/publish.go:34
+#: lxc/image.go:132 lxc/publish.go:34
 msgid   "New alias to define at target"
 msgstr  ""
 
@@ -781,15 +781,15 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:435
+#: lxc/image.go:401
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
-#: lxc/help.go:63 lxc/main.go:108
+#: lxc/help.go:63 lxc/main.go:109 lxc/main.go:153
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:537
+#: lxc/image.go:503
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -798,15 +798,15 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:484
+#: lxc/list.go:462
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:399
+#: lxc/list.go:377
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:400
+#: lxc/list.go:378
 msgid   "PROFILES"
 msgstr  ""
 
@@ -814,7 +814,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:637 lxc/remote.go:354
+#: lxc/image.go:603 lxc/remote.go:354
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -847,7 +847,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:719
+#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:685
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -863,6 +863,10 @@ msgstr  ""
 msgid   "Print verbose information"
 msgstr  ""
 
+#: lxc/manpage.go:18
+msgid   "Prints all the subcommands help."
+msgstr  ""
+
 #: lxc/version.go:18
 msgid   "Prints the version number of this client tool.\n"
         "\n"
@@ -898,7 +902,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:369
+#: lxc/image.go:335
 msgid   "Properties:"
 msgstr  ""
 
@@ -906,7 +910,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:352
+#: lxc/image.go:318
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -960,15 +964,15 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:640
+#: lxc/image.go:606
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:401
+#: lxc/list.go:379
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:402
+#: lxc/list.go:380
 msgid   "STATE"
 msgstr  ""
 
@@ -1016,7 +1020,7 @@ msgstr  ""
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:350
+#: lxc/image.go:316
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -1025,7 +1029,12 @@ msgstr  ""
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/image.go:379
+#: lxc/action.go:133
+#, c-format
+msgid   "Some containers failed to %s"
+msgstr  ""
+
+#: lxc/image.go:345
 msgid   "Source:"
 msgstr  ""
 
@@ -1047,7 +1056,7 @@ msgstr  ""
 msgid   "Stopping container failed!"
 msgstr  ""
 
-#: lxc/action.go:46
+#: lxc/action.go:48
 msgid   "Store the container state (only for stop)"
 msgstr  ""
 
@@ -1059,7 +1068,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:403
+#: lxc/list.go:381
 msgid   "TYPE"
 msgstr  ""
 
@@ -1080,7 +1089,7 @@ msgstr  ""
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
 
-#: lxc/main.go:176
+#: lxc/main.go:188
 msgid   "The opposite of `lxc pause` is `lxc start`."
 msgstr  ""
 
@@ -1088,24 +1097,24 @@ msgstr  ""
 msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
 
-#: lxc/action.go:42
+#: lxc/action.go:44
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:353
+#: lxc/image.go:319
 msgid   "Timestamps:"
 msgstr  ""
 
-#: lxc/main.go:133
+#: lxc/main.go:137
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:437
+#: lxc/image.go:403
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:100 lxc/launch.go:133
+#: lxc/action.go:97 lxc/launch.go:133
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -1118,7 +1127,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:641
+#: lxc/image.go:607
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1130,16 +1139,11 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:358
+#: lxc/image.go:324
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
 
-#: lxc/main.go:108
-#, c-format
-msgid   "Usage: %s"
-msgstr  ""
-
 #: lxc/help.go:48
 msgid   "Usage: lxc <command> [options]"
 msgstr  ""
@@ -1168,7 +1172,7 @@ msgstr  ""
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
-#: lxc/action.go:96
+#: lxc/action.go:93
 msgid   "bad result type from action"
 msgstr  ""
 
@@ -1188,15 +1192,15 @@ msgstr  ""
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:344
+#: lxc/image.go:310
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:346
+#: lxc/image.go:312
 msgid   "enabled"
 msgstr  ""
 
-#: lxc/main.go:22 lxc/main.go:145
+#: lxc/action.go:125 lxc/main.go:22 lxc/main.go:150
 #, c-format
 msgid   "error: %v"
 msgstr  ""
@@ -1210,7 +1214,7 @@ msgstr  ""
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:339 lxc/image.go:617
+#: lxc/image.go:305 lxc/image.go:583
 msgid   "no"
 msgstr  ""
 
@@ -1222,7 +1226,7 @@ msgstr  ""
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:265 lxc/main.go:269
+#: lxc/main.go:277 lxc/main.go:281
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
@@ -1264,11 +1268,11 @@ msgstr  ""
 msgid   "unreachable return reached"
 msgstr  ""
 
-#: lxc/main.go:205
+#: lxc/main.go:217
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:46 lxc/image.go:341 lxc/image.go:621
+#: lxc/delete.go:46 lxc/image.go:307 lxc/image.go:587
 msgid   "yes"
 msgstr  ""
 

From 0da7295c4f2b24ac82911421878aefe3b7b28e36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 12:22:20 -0500
Subject: [PATCH 0757/1193] manpage: Update help
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/manpage.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/manpage.go b/lxc/manpage.go
index db74cbe91..101dc2cc9 100644
--- a/lxc/manpage.go
+++ b/lxc/manpage.go
@@ -16,7 +16,7 @@ func (c *manpageCmd) showByDefault() bool {
 
 func (c *manpageCmd) usage() string {
 	return i18n.G(
-		`Prints all the subcommands help.`)
+		`Print all the subcommands help.`)
 }
 
 func (c *manpageCmd) flags() {

From e69fe85980210cb62e9f9c10c60e68436c6a66f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Apr 2017 16:38:29 -0400
Subject: [PATCH 0758/1193] "gofmt -s" run
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/manpage.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/manpage.go b/lxc/manpage.go
index 101dc2cc9..084ae0a34 100644
--- a/lxc/manpage.go
+++ b/lxc/manpage.go
@@ -28,7 +28,7 @@ func (c *manpageCmd) run(_ *lxd.Config, args []string) error {
 	}
 
 	keys := []string{}
-	for k, _ := range commands {
+	for k := range commands {
 		keys = append(keys, k)
 	}
 	sort.Strings(keys)

From 4c30d3b7db83d679b1d3b45835e8e8784f67c517 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:13:48 -0500
Subject: [PATCH 0759/1193] lxc: Rework for better manpages
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This commit and the following ones for each individual command are
designed to improve the help messages from the lxc command line tool so
that help2man will generate proper manpages for us.

This includes adding a bunch of spacing to generate proper options,
better marking help sub-sections, ...

It also modifies "lxc manpage" so that instead of spitting out a mostly
useless wall of text, it will instead now generate proper manpages in
the specified directory.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go    | 74 ++++++++++++++++++++++++++++++++++++----------------------
 lxc/manpage.go | 47 ++++++++++++++++++++++++++-----------
 2 files changed, 79 insertions(+), 42 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index 8f6401bed..eddda95aa 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -16,8 +16,11 @@ import (
 )
 
 var configPath string
+var execName string
 
 func main() {
+	execName = os.Args[0]
+
 	if err := run(); err != nil {
 		msg := fmt.Sprintf(i18n.G("error: %v"), err)
 
@@ -139,24 +142,35 @@ func run() error {
 	}
 
 	err = cmd.run(config, gnuflag.Args())
-	if err == errArgs {
-		/* If we got an error about invalid arguments, let's try to
-		 * expand this as an alias
-		 */
-		if !*noAlias {
-			execIfAliases(config, origArgs)
+	if err == errArgs || err == errUsage {
+		out := os.Stdout
+		if err == errArgs {
+			/* If we got an error about invalid arguments, let's try to
+			 * expand this as an alias
+			 */
+			if !*noAlias {
+				execIfAliases(config, origArgs)
+			}
+
+			out = os.Stderr
 		}
+		gnuflag.SetOut(out)
 
-		fmt.Fprintf(os.Stderr, i18n.G("error: %v"), err)
-		fmt.Fprintf(os.Stderr, "\n\n")
-		fmt.Fprint(os.Stderr, cmd.usage())
-		fmt.Fprintf(os.Stderr, "\n\n%s\n", i18n.G("Options:"))
+		if err == errArgs {
+			fmt.Fprintf(out, i18n.G("error: %v"), err)
+			fmt.Fprintf(out, "\n\n")
+		}
+		fmt.Fprint(out, cmd.usage())
+		fmt.Fprintf(out, "\n\n%s\n", i18n.G("Options:"))
 
-		gnuflag.SetOut(os.Stderr)
 		gnuflag.PrintDefaults()
 
-		os.Exit(1)
+		if err == errArgs {
+			os.Exit(1)
+		}
+		os.Exit(0)
 	}
+
 	return err
 }
 
@@ -183,38 +197,42 @@ var commands = map[string]command{
 	"monitor": &monitorCmd{},
 	"move":    &moveCmd{},
 	"pause": &actionCmd{
-		action:         shared.Freeze,
-		name:           "pause",
-		additionalHelp: i18n.G("The opposite of `lxc pause` is `lxc start`."),
+		action:      shared.Freeze,
+		description: i18n.G("Pause containers."),
+		name:        "pause",
 	},
 	"profile": &profileCmd{},
 	"publish": &publishCmd{},
 	"remote":  &remoteCmd{},
 	"restart": &actionCmd{
-		action:     shared.Restart,
-		hasTimeout: true,
-		visible:    true,
-		name:       "restart",
-		timeout:    -1,
+		action:      shared.Restart,
+		description: i18n.G("Restart containers."),
+		hasTimeout:  true,
+		visible:     true,
+		name:        "restart",
+		timeout:     -1,
 	},
 	"restore":  &restoreCmd{},
 	"snapshot": &snapshotCmd{},
 	"start": &actionCmd{
-		action:  shared.Start,
-		visible: true,
-		name:    "start",
+		action:      shared.Start,
+		description: i18n.G("Start containers."),
+		visible:     true,
+		name:        "start",
 	},
 	"stop": &actionCmd{
-		action:     shared.Stop,
-		hasTimeout: true,
-		visible:    true,
-		name:       "stop",
-		timeout:    -1,
+		action:      shared.Stop,
+		description: i18n.G("Stop containers."),
+		hasTimeout:  true,
+		visible:     true,
+		name:        "stop",
+		timeout:     -1,
 	},
 	"version": &versionCmd{},
 }
 
 var errArgs = fmt.Errorf(i18n.G("wrong number of subcommand arguments"))
+var errUsage = fmt.Errorf("show usage")
 
 func expandAlias(config *lxd.Config, origArgs []string) ([]string, bool) {
 	foundAlias := false
diff --git a/lxc/manpage.go b/lxc/manpage.go
index 084ae0a34..5dc07c1d0 100644
--- a/lxc/manpage.go
+++ b/lxc/manpage.go
@@ -2,7 +2,9 @@ package main
 
 import (
 	"fmt"
-	"sort"
+	"os"
+	"os/exec"
+	"path/filepath"
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared/i18n"
@@ -16,32 +18,49 @@ func (c *manpageCmd) showByDefault() bool {
 
 func (c *manpageCmd) usage() string {
 	return i18n.G(
-		`Print all the subcommands help.`)
+		`Usage: lxc manpage <directory>
+
+Generate all the LXD manpages.`)
 }
 
 func (c *manpageCmd) flags() {
 }
 
 func (c *manpageCmd) run(_ *lxd.Config, args []string) error {
-	if len(args) > 0 {
+	if len(args) != 1 {
 		return errArgs
 	}
 
-	keys := []string{}
-	for k := range commands {
-		keys = append(keys, k)
+	_, err := exec.LookPath("help2man")
+	if err != nil {
+		return fmt.Errorf(i18n.G("Unable to find help2man."))
 	}
-	sort.Strings(keys)
 
-	header := false
-	for _, k := range keys {
-		if header {
-			fmt.Printf("\n\n")
+	help2man := func(command string, title string, path string) error {
+		target, err := os.Create(path)
+		if err != nil {
+			return err
 		}
+		defer target.Close()
+
+		cmd := exec.Command("help2man", command, "-n", title, "--no-info")
+		cmd.Stdout = target
+
+		return cmd.Run()
+	}
 
-		fmt.Printf("### lxc %s\n", k)
-		commands["help"].run(nil, []string{k})
-		header = true
+	// Generate the main manpage
+	err = help2man(execName, "LXD - client", filepath.Join(args[0], fmt.Sprintf("lxc.1")))
+	if err != nil {
+		return fmt.Errorf(i18n.G("Failed to generate 'lxc.1': %v"), err)
+	}
+
+	// Generate the pages for the subcommands
+	for k, cmd := range commands {
+		err := help2man(fmt.Sprintf("%s %s", execName, k), summaryLine(cmd.usage()), filepath.Join(args[0], fmt.Sprintf("lxc.%s.1", k)))
+		if err != nil {
+			return fmt.Errorf(i18n.G("Failed to generate 'lxc.%s.1': %v"), k, err)
+		}
 	}
 
 	return nil

From 410e71907b4d6a022d6bcc22ba3c0de82f665dd3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:41 -0500
Subject: [PATCH 0760/1193] lxc/action: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/action.go | 27 ++++++++++++++-------------
 1 file changed, 14 insertions(+), 13 deletions(-)

diff --git a/lxc/action.go b/lxc/action.go
index 3f465aa57..d545cbb5c 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -13,15 +13,15 @@ import (
 )
 
 type actionCmd struct {
-	action         shared.ContainerAction
-	hasTimeout     bool
-	visible        bool
-	name           string
-	timeout        int
-	force          bool
-	stateful       bool
-	stateless      bool
-	additionalHelp string
+	action      shared.ContainerAction
+	description string
+	hasTimeout  bool
+	visible     bool
+	name        string
+	timeout     int
+	force       bool
+	stateful    bool
+	stateless   bool
 }
 
 func (c *actionCmd) showByDefault() bool {
@@ -29,14 +29,15 @@ func (c *actionCmd) showByDefault() bool {
 }
 
 func (c *actionCmd) usage() string {
-	if c.additionalHelp != "" {
-		c.additionalHelp = fmt.Sprintf("\n\n%s", c.additionalHelp)
+	extra := ""
+	if c.name == "pause" {
+		extra = "\n" + i18n.G("The opposite of \"lxc pause\" is \"lxc start\".")
 	}
 
 	return fmt.Sprintf(i18n.G(
-		`Change state of one or more containers to %s.
+		`Usage: lxc %s [<remote>:]<container> [[<remote>:]<container>...]
 
-lxc %s [<remote>:]<container> [[<remote>:]<container>...]%s`), c.name, c.name, c.additionalHelp)
+%s%s`), c.name, c.description, extra)
 }
 
 func (c *actionCmd) flags() {

From de97d4de983264f0a7037d420d0084f6fe4880c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:45 -0500
Subject: [PATCH 0761/1193] lxc/delete: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/delete.go | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/lxc/delete.go b/lxc/delete.go
index 6900fb3b6..efe565fb9 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -24,11 +24,9 @@ func (c *deleteCmd) showByDefault() bool {
 
 func (c *deleteCmd) usage() string {
 	return i18n.G(
-		`Delete containers or snapshots.
+		`Usage: lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]
 
-lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]
-
-Destroy containers or snapshots with any attached data (configuration, snapshots, ...).`)
+Delete containers and snapshots.`)
 }
 
 func (c *deleteCmd) flags() {

From 807d9b7cf96dbfb269a7a97f22a59e33e8f28667 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:45 -0500
Subject: [PATCH 0762/1193] lxc/finger: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/finger.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/finger.go b/lxc/finger.go
index af1e28bac..ed192954f 100644
--- a/lxc/finger.go
+++ b/lxc/finger.go
@@ -13,9 +13,9 @@ func (c *fingerCmd) showByDefault() bool {
 
 func (c *fingerCmd) usage() string {
 	return i18n.G(
-		`Check if the LXD instance is up.
+		`Usage: lxc finger [<remote>:]
 
-lxc finger [<remote>:]`)
+Check if the LXD server is alive.`)
 }
 
 func (c *fingerCmd) flags() {}

From 9208704c02b64cf512ffb49f3fc9ea69e668f68a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:45 -0500
Subject: [PATCH 0763/1193] lxc/help: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/help.go | 60 +++++++++++++++++++++++++++---------------------------------
 1 file changed, 27 insertions(+), 33 deletions(-)

diff --git a/lxc/help.go b/lxc/help.go
index 589e9621b..27f0a82d7 100644
--- a/lxc/help.go
+++ b/lxc/help.go
@@ -1,12 +1,9 @@
 package main
 
 import (
-	"bufio"
-	"bytes"
 	"fmt"
 	"os"
 	"sort"
-	"strings"
 
 	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -23,9 +20,9 @@ func (c *helpCmd) showByDefault() bool {
 
 func (c *helpCmd) usage() string {
 	return i18n.G(
-		`Help page for the LXD client.
+		`Usage: lxc help [--all]
 
-lxc help [--all]`)
+Help page for the LXD client.`)
 }
 
 func (c *helpCmd) flags() {
@@ -46,43 +43,40 @@ func (c *helpCmd) run(config *lxd.Config, args []string) error {
 	}
 
 	fmt.Println(i18n.G("Usage: lxc <command> [options]"))
-	fmt.Println(i18n.G("Available commands:"))
+	fmt.Println()
+	fmt.Println(i18n.G(`This is the LXD command line client.
+
+All of LXD's features can be driven through the various commands below.
+For help with any of those, simply call them with --help.`))
+	fmt.Println()
+
+	fmt.Println(i18n.G("Commands:"))
 	var names []string
 	for name := range commands {
 		names = append(names, name)
 	}
 	sort.Strings(names)
 	for _, name := range names {
+		if name == "help" {
+			continue
+		}
+
 		cmd := commands[name]
 		if c.showAll || cmd.showByDefault() {
-			fmt.Printf("\t%-10s - %s\n", name, c.summaryLine(cmd.usage()))
+			fmt.Printf("  %-16s %s\n", name, summaryLine(cmd.usage()))
 		}
 	}
-	if !c.showAll {
-		fmt.Println()
-		fmt.Println(i18n.G("Options:"))
-		fmt.Println("  --all              " + i18n.G("Print less common commands"))
-		fmt.Println("  --debug            " + i18n.G("Print debug information"))
-		fmt.Println("  --verbose          " + i18n.G("Print verbose information"))
-		fmt.Println("  --version          " + i18n.G("Show client version"))
-		fmt.Println()
-		fmt.Println(i18n.G("Environment:"))
-		fmt.Println("  LXD_CONF           " + i18n.G("Path to an alternate client configuration directory"))
-		fmt.Println("  LXD_DIR            " + i18n.G("Path to an alternate server directory"))
-	}
-	return nil
-}
 
-// summaryLine returns the first line of the help text. Conventionally, this
-// should be a one-line command summary, potentially followed by a longer
-// explanation.
-func (c *helpCmd) summaryLine(usage string) string {
-	usage = strings.TrimSpace(usage)
-	s := bufio.NewScanner(bytes.NewBufferString(usage))
-	if s.Scan() {
-		if len(s.Text()) > 1 {
-			return s.Text()
-		}
-	}
-	return i18n.G("Missing summary.")
+	fmt.Println()
+	fmt.Println(i18n.G("Options:"))
+	fmt.Println("  --all            " + i18n.G("Print less common commands"))
+	fmt.Println("  --debug          " + i18n.G("Print debug information"))
+	fmt.Println("  --verbose        " + i18n.G("Print verbose information"))
+	fmt.Println("  --version        " + i18n.G("Show client version"))
+	fmt.Println()
+	fmt.Println(i18n.G("Environment:"))
+	fmt.Println("  LXD_CONF         " + i18n.G("Path to an alternate client configuration directory"))
+	fmt.Println("  LXD_DIR          " + i18n.G("Path to an alternate server directory"))
+
+	return nil
 }

From 3df2c57401a47083c27c5e9e22869ad8245f7396 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:46 -0500
Subject: [PATCH 0764/1193] lxc/info: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/info.go | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/lxc/info.go b/lxc/info.go
index 6ef30e863..357fd567d 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -23,13 +23,15 @@ func (c *infoCmd) showByDefault() bool {
 
 func (c *infoCmd) usage() string {
 	return i18n.G(
-		`List information on LXD servers and containers.
+		`Usage: lxc info [<remote>:][<container>] [--show-log]
 
-For a container:
-    lxc info [<remote:>]<container> [--show-log]
+Show container or server information.
 
-For a server:
-    lxc info [<remote:>]`)
+lxc info [<remote>:]<container> [--show-log]
+    For container information.
+
+lxc info [<remote>:]
+    For LXD server information.`)
 }
 
 func (c *infoCmd) flags() {

From d710997a59cbcb6d88600ffb56013a90951b5376 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:46 -0500
Subject: [PATCH 0765/1193] lxc/monitor: Rework usage to be parsable by
 help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/monitor.go | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/lxc/monitor.go b/lxc/monitor.go
index 74e3306e8..3b5d1b8c7 100644
--- a/lxc/monitor.go
+++ b/lxc/monitor.go
@@ -39,17 +39,17 @@ func (c *monitorCmd) showByDefault() bool {
 
 func (c *monitorCmd) usage() string {
 	return i18n.G(
-		`Monitor activity on the LXD server.
+		`Usage: lxc monitor [<remote>:] [--type=TYPE...]
 
-lxc monitor [<remote>:] [--type=TYPE...]
+Monitor a local or remote LXD server.
 
-Connects to the monitoring interface of the specified LXD server.
+By default the monitor will listen to all message types.
 
-By default will listen to all message types.
-Specific types to listen to can be specified with --type.
+Message types to listen for can be specified with --type.
 
-Example:
-    lxc monitor --type=logging`)
+*Examples*
+lxc monitor --type=logging
+    Only show log message.`)
 }
 
 func (c *monitorCmd) flags() {

From 85c7f572185cb972e0c5d5ea099b54e0474e4bf5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:47 -0500
Subject: [PATCH 0766/1193] lxc/move: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/move.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lxc/move.go b/lxc/move.go
index d740db569..112d2a7da 100644
--- a/lxc/move.go
+++ b/lxc/move.go
@@ -14,7 +14,9 @@ func (c *moveCmd) showByDefault() bool {
 
 func (c *moveCmd) usage() string {
 	return i18n.G(
-		`Move containers within or in between lxd instances.
+		`Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]]
+
+Move containers within or in between LXD instances.
 
 lxc move [<remote>:]<source container> [<remote>:][<destination container>]
     Move a container between two hosts, renaming it if destination name differs.

From 7519b6c9d25f4c3bb5746f1172fd5eaae6dedc75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:47 -0500
Subject: [PATCH 0767/1193] lxc/publish: Rework usage to be parsable by
 help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/publish.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/publish.go b/lxc/publish.go
index 400356c62..846d41764 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -24,9 +24,9 @@ func (c *publishCmd) showByDefault() bool {
 
 func (c *publishCmd) usage() string {
 	return i18n.G(
-		`Publish containers as images.
+		`Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]
 
-lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]`)
+Publish containers as images.`)
 }
 
 func (c *publishCmd) flags() {

From 8590d050de826dd696f1e12ac2a4dff1c44a1b20 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:47 -0500
Subject: [PATCH 0768/1193] lxc/restore: Rework usage to be parsable by
 help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/restore.go | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/lxc/restore.go b/lxc/restore.go
index b37b3eff0..cd4024b44 100644
--- a/lxc/restore.go
+++ b/lxc/restore.go
@@ -19,19 +19,18 @@ func (c *restoreCmd) showByDefault() bool {
 
 func (c *restoreCmd) usage() string {
 	return i18n.G(
-		`Restore a container's state to a previous snapshot.
+		`Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]
 
-lxc restore [<remote>:]<container> <snapshot> [--stateful]
+Restore containers from snapshots.
 
-Restores a container from a snapshot (optionally with running state, see
-snapshot help for details).
+If --stateful is passed, then the running state will be restored too.
 
-Examples:
-Create the snapshot:
-    lxc snapshot u1 snap0
+*Examples*
+lxc snapshot u1 snap0
+    Create the snapshot.
 
-Restore the snapshot:
-    lxc restore u1 snap0`)
+lxc restore u1 snap0
+    Restore the snapshot.`)
 }
 
 func (c *restoreCmd) flags() {

From 2cf52aa428d583147104157a961ef3d09f0c86c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:47 -0500
Subject: [PATCH 0769/1193] lxc/snapshot: Rework usage to be parsable by
 help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/snapshot.go | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/lxc/snapshot.go b/lxc/snapshot.go
index 50a01ad2c..7f92b0432 100644
--- a/lxc/snapshot.go
+++ b/lxc/snapshot.go
@@ -19,19 +19,16 @@ func (c *snapshotCmd) showByDefault() bool {
 
 func (c *snapshotCmd) usage() string {
 	return i18n.G(
-		`Create a read-only snapshot of a container.
+		`Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]
 
-lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]
+Create container snapshots.
 
-Creates a snapshot of the container (optionally with the container's memory
-state). When --stateful is used, LXD attempts to checkpoint the container's
-running state, including process memory state, TCP connections, etc. so that it
-can be restored (via lxc restore) at a later time (although some things, e.g.
-TCP connections after the TCP timeout window has expired, may not be restored
-successfully).
+When --stateful is used, LXD attempts to checkpoint the container's
+running state, including process memory state, TCP connections, ...
 
-Example:
-    lxc snapshot u1 snap0`)
+*Examples*
+lxc snapshot u1 snap0
+    Create a snapshot of "u1" called "snap0".`)
 }
 
 func (c *snapshotCmd) flags() {

From ac8109a196e0f4c4daa7a8e6b3e5187fbf1059c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:48 -0500
Subject: [PATCH 0770/1193] lxc/utils: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/utils.go | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/lxc/utils.go b/lxc/utils.go
index 00672e345..0611f9b6c 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -3,6 +3,8 @@ package main
 import (
 	"fmt"
 	"strings"
+
+	"github.com/lxc/lxd/shared/i18n"
 )
 
 // Progress tracking
@@ -151,3 +153,22 @@ func runBatch(names []string, action func(name string) error) []batchResult {
 
 	return results
 }
+
+// summaryLine returns the first line of the help text. Conventionally, this
+// should be a one-line command summary, potentially followed by a longer
+// explanation.
+func summaryLine(usage string) string {
+	for _, line := range strings.Split(usage, "\n") {
+		if strings.HasPrefix(line, "Usage:") {
+			continue
+		}
+
+		if len(line) == 0 {
+			continue
+		}
+
+		return strings.TrimSuffix(line, ".")
+	}
+
+	return i18n.G("Missing summary.")
+}

From 25af380efd7ba59ee3e6862612e4829fa1f85dbf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:48 -0500
Subject: [PATCH 0771/1193] lxc/version: Rework usage to be parsable by
 help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/version.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/version.go b/lxc/version.go
index 57e9919b4..f07bf6842 100644
--- a/lxc/version.go
+++ b/lxc/version.go
@@ -16,9 +16,9 @@ func (c *versionCmd) showByDefault() bool {
 
 func (c *versionCmd) usage() string {
 	return i18n.G(
-		`Prints the version number of this client tool.
+		`Usage: lxc version
 
-lxc version`)
+Print the version number of this client tool.`)
 }
 
 func (c *versionCmd) flags() {

From c53448daa45ac76c43cfe96f2ffadfe223a733fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:45 -0500
Subject: [PATCH 0772/1193] lxc/config: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config.go | 96 ++++++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 66 insertions(+), 30 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index ce79c9ef4..7be59aca8 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -56,41 +56,77 @@ func (c *configCmd) configEditHelp() string {
 
 func (c *configCmd) usage() string {
 	return i18n.G(
-		`Manage configuration.
-
-lxc config device add [<remote>:]<container> <device> <type> [key=value...]   Add a device to a container.
-lxc config device get [<remote>:]<container> <device> <key>                   Get a device property.
-lxc config device set [<remote>:]<container> <device> <key> <value>           Set a device property.
-lxc config device unset [<remote>:]<container> <device> <key>                 Unset a device property.
-lxc config device list [<remote>:]<container>                                 List devices for container.
-lxc config device show [<remote>:]<container>                                 Show full device details for container.
-lxc config device remove [<remote>:]<container> <name>                        Remove device from container.
-
-lxc config get [<remote>:][container] <key>                                   Get container or server configuration key.
-lxc config set [<remote>:][container] <key> <value>                           Set container or server configuration key.
-lxc config unset [<remote>:][container] <key>                                 Unset container or server configuration key.
-lxc config show [<remote>:][container] [--expanded]                           Show container or server configuration.
-lxc config edit [<remote>:][container]                                        Edit container or server configuration in external editor.
+		`Usage: lxc config <subcommand> [options]
+
+Change container or server configuration options.
+
+*Container configuration*
+
+lxc config get [<remote>:][container] <key>
+    Get container or server configuration key.
+
+lxc config set [<remote>:][container] <key> <value>
+    Set container or server configuration key.
+
+lxc config unset [<remote>:][container] <key>
+    Unset container or server configuration key.
+
+lxc config show [<remote>:][container] [--expanded]
+    Show container or server configuration.
+
+lxc config edit [<remote>:][container]
     Edit configuration, either by launching external editor or reading STDIN.
-    Example: lxc config edit <container> # launch editor
-             cat config.yaml | lxc config edit <container> # read from config.yaml
 
-lxc config trust list [<remote>:]                                             List all trusted certs.
-lxc config trust add [<remote>:] <certfile.crt>                               Add certfile.crt to trusted hosts.
-lxc config trust remove [<remote>:] [hostname|fingerprint]                    Remove the cert from trusted hosts.
+*Device management*
+
+lxc config device add [<remote>:]<container> <device> <type> [key=value...]
+    Add a device to a container.
+
+lxc config device get [<remote>:]<container> <device> <key>
+    Get a device property.
+
+lxc config device set [<remote>:]<container> <device> <key> <value>
+    Set a device property.
+
+lxc config device unset [<remote>:]<container> <device> <key>
+    Unset a device property.
 
-Examples:
-To mount host's /share/c1 onto /opt in the container:
-    lxc config device add [<remote>:]container1 <device-name> disk source=/share/c1 path=opt
+lxc config device list [<remote>:]<container>
+    List devices for container.
 
-To set an lxc config value:
-    lxc config set [<remote>:]<container> raw.lxc 'lxc.aa_allow_incomplete = 1'
+lxc config device show [<remote>:]<container>
+    Show full device details for container.
 
-To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the default):
-    lxc config set core.https_address [::]:8443
+lxc config device remove [<remote>:]<container> <name>
+    Remove device from container.
 
-To set the server trust password:
-    lxc config set core.trust_password blah`)
+*Client trust store management*
+
+lxc config trust list [<remote>:]
+    List all trusted certs.
+
+lxc config trust add [<remote>:] <certfile.crt>
+    Add certfile.crt to trusted hosts.
+
+lxc config trust remove [<remote>:] [hostname|fingerprint]
+    Remove the cert from trusted hosts.
+
+*Examples*
+
+cat config.yaml | lxc config edit <container>
+    Update the container configuration from config.yaml.
+
+lxc config device add [<remote>:]container1 <device-name> disk source=/share/c1 path=opt
+    Will mount the host's /share/c1 onto /opt in the container.
+
+lxc config set [<remote>:]<container> limits.cpu 2
+    Will set a CPU limit of "2" for the container.
+
+lxc config set core.https_address [::]:8443
+    Will have LXD listen on IPv4 and IPv6 port 8443.
+
+lxc config set core.trust_password blah
+    Will set the server's trust password to blah.`)
 }
 
 func (c *configCmd) doSet(config *lxd.Config, args []string, unset bool) error {
@@ -133,7 +169,7 @@ func (c *configCmd) doSet(config *lxd.Config, args []string, unset bool) error {
 
 func (c *configCmd) run(config *lxd.Config, args []string) error {
 	if len(args) < 1 {
-		return errArgs
+		return errUsage
 	}
 
 	switch args[0] {

From 781650eee556f3d87356b1ae9befb9e2b7e45b57 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:45 -0500
Subject: [PATCH 0773/1193] lxc/copy: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/copy.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/copy.go b/lxc/copy.go
index da28c73d8..b25c3fb24 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -21,9 +21,9 @@ func (c *copyCmd) showByDefault() bool {
 
 func (c *copyCmd) usage() string {
 	return i18n.G(
-		`Copy containers within or in between LXD instances.
+		`Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e]
 
-lxc copy [<remote>:]<source>[/<snapshot>] [<remote>:]<destination> [--ephemeral|e]`)
+Copy containers within or in between LXD instances.`)
 }
 
 func (c *copyCmd) flags() {

From c875836a03bc4d6d1f3687eedff3210c4b5f7085 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:45 -0500
Subject: [PATCH 0774/1193] lxc/exec: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/exec.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/exec.go b/lxc/exec.go
index 44d1a33ea..4c6c4ce3c 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -44,9 +44,9 @@ func (c *execCmd) showByDefault() bool {
 
 func (c *execCmd) usage() string {
 	return i18n.G(
-		`Execute the specified command in a container.
+		`Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>
 
-lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>
+Execute commands in containers.
 
 Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored).`)
 }

From 583df309f024732bd44dc4ffbbdcd8c3693f7e0a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:45 -0500
Subject: [PATCH 0775/1193] lxc/file: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/file.go | 27 ++++++++++++++++-----------
 1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/lxc/file.go b/lxc/file.go
index c1d121490..d0a859f88 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -30,20 +30,25 @@ func (c *fileCmd) showByDefault() bool {
 
 func (c *fileCmd) usage() string {
 	return i18n.G(
-		`Manage files in a container.
+		`Usage: lxc file <subcommand> [options]
 
-lxc file pull [<remote>:]<container> [[<remote>:]<container>...] <target path>
-lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source path>...] [<remote>:]<container>
-lxc file edit [<remote>:]<container>/<path>
+Manage files in containers.
+
+lxc file pull [<remote>:]<container>/<path> [[<remote>:]<container>/<path>...] <target path>
+    Pull files from containers.
 
-<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>
+lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source path>...] [<remote>:]<container>/<path>
+    Push files into containers.
 
-Examples:
-To push /etc/hosts into the container foo:
-    lxc file push /etc/hosts foo/etc/hosts
+lxc file edit [<remote>:]<container>/<path>
+    Edit files in containers using the default text editor.
 
-To pull /etc/hosts from the container:
-    lxc file pull foo/etc/hosts .`)
+*Examples*
+lxc file push /etc/hosts foo/etc/hosts
+   To push /etc/hosts into the container "foo".
+
+lxc file pull foo/etc/hosts .
+   To pull /etc/hosts from the container and write it to the current directory.`)
 }
 
 func (c *fileCmd) flags() {
@@ -278,7 +283,7 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
 
 func (c *fileCmd) run(config *lxd.Config, args []string) error {
 	if len(args) < 1 {
-		return errArgs
+		return errUsage
 	}
 
 	switch args[0] {

From 460808095d9c36bd73981011723188fce7e474c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:46 -0500
Subject: [PATCH 0776/1193] lxc/image: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index a74e9e334..71ab3d15f 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -58,7 +58,9 @@ func (c *imageCmd) imageEditHelp() string {
 
 func (c *imageCmd) usage() string {
 	return i18n.G(
-		`Manipulate container images.
+		`Usage: lxc image <subcommand> [options]
+
+Manipulate container images.
 
 In LXD containers are created from images. Those images were themselves
 either generated from an existing container or downloaded from an image
@@ -203,7 +205,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 	var remote string
 
 	if len(args) < 1 {
-		return errArgs
+		return errUsage
 	}
 
 	switch args[0] {

From c23811466b37623ef3cbf3e14fded2e1aa930135 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:46 -0500
Subject: [PATCH 0777/1193] lxc/init: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/init.go | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index bcc3713ab..089f319a6 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -71,16 +71,14 @@ func (c *initCmd) showByDefault() bool {
 
 func (c *initCmd) usage() string {
 	return i18n.G(
-		`Initialize a container from a particular image.
+		`Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
 
-lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
-
-Initializes a container using the specified image and name.
+Create containers from images.
 
 Not specifying -p will result in the default profile.
 Specifying "-p" with no argument will result in no profile.
 
-Example:
+Examples:
     lxc init ubuntu:16.04 u1`)
 }
 

From d1f5bbf55b6ca41a791926424b0cc6a5a4d4700d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:46 -0500
Subject: [PATCH 0778/1193] lxc/launch: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/launch.go | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/lxc/launch.go b/lxc/launch.go
index 05532e9bb..f6e18dcce 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -21,16 +21,14 @@ func (c *launchCmd) showByDefault() bool {
 
 func (c *launchCmd) usage() string {
 	return i18n.G(
-		`Launch a container from a particular image.
+		`Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
 
-lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
-
-Launches a container using the specified image and name.
+Create and start containers from images.
 
 Not specifying -p will result in the default profile.
 Specifying "-p" with no argument will result in no profile.
 
-Example:
+Examples:
     lxc launch ubuntu:16.04 u1`)
 }
 

From 5a1699c05f9ec76aa16c52f7fa66acb55d45666e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:46 -0500
Subject: [PATCH 0779/1193] lxc/list: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 98 ++++++++++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 71 insertions(+), 27 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index b945a29de..177e760d1 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -44,41 +44,85 @@ func (c *listCmd) showByDefault() bool {
 
 func (c *listCmd) usage() string {
 	return i18n.G(
-		`Lists the containers.
-
-lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]
-
-The filters are:
-* A single keyword like "web" which will list any container with a name starting by "web".
-* A regular expression on the container name. (e.g. .*web.*01$)
-* A key/value pair referring to a configuration item. For those, the namespace can be abbreviated to the smallest unambiguous identifier:
- * "user.blah=abc" will list all containers with the "blah" user property set to "abc".
- * "u.blah=abc" will do the same
- * "security.privileged=1" will list all privileged containers
- * "s.privileged=1" will do the same
-* A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*)
-
-Columns for table format are:
-* 4 - IPv4 address
-* 6 - IPv6 address
-* a - architecture
-* c - creation date
-* n - name
-* p - pid of container init process
-* P - profiles
-* s - state
-* S - number of snapshots
-* t - type (persistent or ephemeral)
+		`Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]
+
+List the existing containers.
 
 Default column layout: ns46tS
-Fast column layout: nsacPt`)
+Fast column layout: nsacPt
+
+*Filters*
+A single keyword like "web" which will list any container with a name starting by "web".
+
+A regular expression on the container name. (e.g. .*web.*01$).
+
+A key/value pair referring to a configuration item. For those, the namespace can be abbreviated to the smallest unambiguous identifier.
+    - "user.blah=abc" will list all containers with the "blah" user property set to "abc".
+
+    - "u.blah=abc" will do the same
+
+    - "security.privileged=1" will list all privileged containers
+
+    - "s.privileged=1" will do the same
+
+A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*).
+
+*Columns*
+The -c option takes a comma separated list of arguments that control
+which container attributes to output when displaying in table format.
+
+Column arguments are either pre-defined shorthand chars (see below),
+or (extended) config keys.
+
+Commas between consecutive shorthand chars are optional.
+
+Pre-defined column shorthand chars:
+
+    4 - IPv4 address
+
+    6 - IPv6 address
+
+    a - Architecture
+
+    c - Creation date
+
+    l - Last used date
+
+    n - Name
+
+    p - PID of the container's init process
+
+    P - Profiles
+
+    s - State
+
+    S - Number of snapshots
+
+    t - Type (persistent or ephemeral)
+
+Custom columns are defined with "key[:name][:maxWidth]":
+
+    KEY: The (extended) config key to display
+
+    NAME: Name to display in the column header.
+    Defaults to the key if not specified or empty.
+
+    MAXWIDTH: Max width of the column (longer results are truncated).
+    Defaults to -1 (unlimited). Use 0 to limit to the column header size.
+
+*Examples*
+lxc list -c n,volatile.base_image:"BASE IMAGE":0,s46,volatile.eth0.hwaddr:MAC
+    Shows a list of containers using the "NAME", "BASE IMAGE", "STATE", "IPV4",
+    "IPV6" and "MAC" columns.
+
+    "BASE IMAGE" and "MAC" are custom columns generated from container configuration keys.`)
 }
 
 func (c *listCmd) flags() {
 	gnuflag.StringVar(&c.chosenColumnRunes, "c", "ns46tS", i18n.G("Columns"))
 	gnuflag.StringVar(&c.chosenColumnRunes, "columns", "ns46tS", i18n.G("Columns"))
 	gnuflag.StringVar(&c.format, "format", "table", i18n.G("Format"))
-	gnuflag.BoolVar(&c.fast, "fast", false, i18n.G("Fast mode (same as --columns=nsacPt"))
+	gnuflag.BoolVar(&c.fast, "fast", false, i18n.G("Fast mode (same as --columns=nsacPt)"))
 }
 
 // This seems a little excessive.

From 5d348abbff893baae8b2157fd100e20e676329fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:47 -0500
Subject: [PATCH 0780/1193] lxc/profile: Rework usage to be parsable by
 help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/profile.go | 96 ++++++++++++++++++++++++++++++++++++++++------------------
 1 file changed, 67 insertions(+), 29 deletions(-)

diff --git a/lxc/profile.go b/lxc/profile.go
index 7521e298a..21b6752d5 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -46,45 +46,83 @@ func (c *profileCmd) profileEditHelp() string {
 
 func (c *profileCmd) usage() string {
 	return i18n.G(
-		`Manage configuration profiles.
-
-lxc profile list [<remote>:]                                    List available profiles.
-lxc profile show [<remote>:]<profile>                           Show details of a profile.
-lxc profile create [<remote>:]<profile>                         Create a profile.
-lxc profile copy [<remote>:]<profile> [<remote>:]<profile>      Copy the profile.
-lxc profile get [<remote>:]<profile> <key>                      Get profile configuration.
-lxc profile set [<remote>:]<profile> <key> <value>              Set profile configuration.
-lxc profile unset [<remote>:]<profile> <key>                    Unset profile configuration.
-lxc profile delete [<remote>:]<profile>                         Delete a profile.
+		`Usage: lxc profile <subcommand> [options]
+
+Manage container configuration profiles.
+
+*Profile configuration*
+lxc profile list [<remote>:]
+    List available profiles.
+
+lxc profile show [<remote>:]<profile>
+    Show details of a profile.
+
+lxc profile create [<remote>:]<profile>
+    Create a profile.
+
+lxc profile copy [<remote>:]<profile> [<remote>:]<profile>
+    Copy the profile.
+
+lxc profile get [<remote>:]<profile> <key>
+    Get profile configuration.
+
+lxc profile set [<remote>:]<profile> <key> <value>
+    Set profile configuration.
+
+lxc profile unset [<remote>:]<profile> <key>
+    Unset profile configuration.
+
+lxc profile delete [<remote>:]<profile>
+    Delete a profile.
+
 lxc profile edit [<remote>:]<profile>
     Edit profile, either by launching external editor or reading STDIN.
-    Example: lxc profile edit <profile> # launch editor
-             cat profile.yaml | lxc profile edit <profile> # read from profile.yaml
+
+*Profile assignment*
 lxc profile apply [<remote>:]<container> <profiles>
-    Apply a comma-separated list of profiles to a container, in order.
-    All profiles passed in this call (and only those) will be applied
-    to the specified container.
-    Example: lxc profile apply foo default,bar # Apply default and bar
-             lxc profile apply foo default # Only default is active
-             lxc profile apply '' # no profiles are applied anymore
-             lxc profile apply bar,default # Apply default second now
-
-Devices:
-lxc profile device list [<remote>:]<profile>                                List devices in the given profile.
-lxc profile device show [<remote>:]<profile>                                Show full device details in the given profile.
-lxc profile device remove [<remote>:]<profile> <name>                       Remove a device from a profile.
-lxc profile device get [<remote>:]<profile> <name> <key>                    Get a device property.
-lxc profile device set [<remote>:]<profile> <name> <key> <value>            Set a device property.
-lxc profile device unset [<remote>:]<profile> <name> <key>                  Unset a device property.
+    Replace the current set of profiles for the container by the one provided.
+
+*Device management*
+lxc profile device list [<remote>:]<profile>
+    List devices in the given profile.
+
+lxc profile device show [<remote>:]<profile>
+    Show full device details in the given profile.
+
+lxc profile device remove [<remote>:]<profile> <name>
+    Remove a device from a profile.
+
+lxc profile device get [<remote>:]<profile> <name> <key>
+    Get a device property.
+
+lxc profile device set [<remote>:]<profile> <name> <key> <value>
+    Set a device property.
+
+lxc profile device unset [<remote>:]<profile> <name> <key>
+    Unset a device property.
+
 lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]
-    Add a profile device, such as a disk or a nic, to the containers using the specified profile.`)
+    Add a profile device, such as a disk or a nic, to the containers using the specified profile.
+
+*Examples*
+cat profile.yaml | lxc profile edit <profile>
+    Update a profile using the content of profile.yaml
+
+lxc profile apply foo default,bar
+    Set the profiles for "foo" to "default" and "bar".
+
+lxc profile apply foo default
+    Reset "foo" to only using the "default" profile.
+
+lxc profile apply foo ''
+    Remove all profile from "foo"`)
 }
 
 func (c *profileCmd) flags() {}
 
 func (c *profileCmd) run(config *lxd.Config, args []string) error {
 	if len(args) < 1 {
-		return errArgs
+		return errUsage
 	}
 
 	if args[0] == "list" {

From 4dd28f4484cfb2eb30381f89686bd216430b72f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:17:47 -0500
Subject: [PATCH 0781/1193] lxc/remote: Rework usage to be parsable by help2man
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/remote.go | 36 +++++++++++++++++++++++++-----------
 1 file changed, 25 insertions(+), 11 deletions(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index 4f7d60b91..c25d8f659 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -35,16 +35,30 @@ func (c *remoteCmd) showByDefault() bool {
 
 func (c *remoteCmd) usage() string {
 	return i18n.G(
-		`Manage remote LXD servers.
-
-lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD]
-                                      [--public] [--protocol=PROTOCOL]      Add the remote <remote> at <url>.
-lxc remote remove <remote>                                                  Remove the remote <remote>.
-lxc remote list                                                             List all remotes.
-lxc remote rename <old name> <new name>                                     Rename remote <old name> to <new name>.
-lxc remote set-url <remote> <url>                                           Update <remote>'s url to <url>.
-lxc remote set-default <remote>                                             Set the default remote.
-lxc remote get-default                                                      Print the default remote.`)
+		`Usage: lxc remote <subcommand> [options]
+
+Manage the list of remote LXD servers.
+
+lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD] [--public] [--protocol=PROTOCOL]
+    Add the remote <remote> at <url>.
+
+lxc remote remove <remote>
+    Remove the remote <remote>.
+
+lxc remote list
+    List all remotes.
+
+lxc remote rename <old name> <new name>
+    Rename remote <old name> to <new name>.
+
+lxc remote set-url <remote> <url>
+    Update <remote>'s url to <url>.
+
+lxc remote set-default <remote>
+    Set the default remote.
+
+lxc remote get-default
+    Print the default remote.`)
 }
 
 func (c *remoteCmd) flags() {
@@ -277,7 +291,7 @@ func (c *remoteCmd) removeCertificate(config *lxd.Config, remote string) {
 
 func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 	if len(args) < 1 {
-		return errArgs
+		return errUsage
 	}
 
 	switch args[0] {

From dc43f776a0fec50b0979aa69475f480940dfc5a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 5 Mar 2017 02:22:02 -0500
Subject: [PATCH 0782/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 2092 ++++++++++++++++++++++++++++---------------------
 po/fr.po   | 1540 ++++++++++++++++++++----------------
 po/ja.po   | 2560 ++++++++++++++++++++++++++++++++++--------------------------
 po/lxd.pot | 1266 +++++++++++++++++-------------
 4 files changed, 4223 insertions(+), 3235 deletions(-)

diff --git a/po/de.po b/po/de.po
index e4ca002c0..4767e3ebe 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-10-26 17:06-0400\n"
+"POT-Creation-Date: 2017-04-17 17:27-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -16,19 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:143
-msgid "  Disk usage:"
-msgstr ""
-
-#: lxc/info.go:166
-msgid "  Memory usage:"
-msgstr ""
-
-#: lxc/info.go:183
-msgid "  Network usage:"
-msgstr ""
-
-#: lxc/config.go:36
+#: lxc/config.go:37
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the configuration.\n"
@@ -67,7 +55,7 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:83
+#: lxc/image.go:50
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the image properties.\n"
@@ -84,7 +72,7 @@ msgstr ""
 "### Zum Beispiel:\n"
 "###  description: Mein eigenes Abbild\n"
 
-#: lxc/profile.go:26
+#: lxc/profile.go:27
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the profile.\n"
@@ -123,258 +111,204 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:597
+#: lxc/image.go:582
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/snapshot.go:61
+#: lxc/snapshot.go:58
 #, fuzzy
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 
-#: lxc/profile.go:226
+#: lxc/profile.go:264
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:618 lxc/image.go:647
+#: lxc/image.go:603 lxc/image.go:632
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:622
+#: lxc/image.go:607
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:394
+#: lxc/list.go:418
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:52
+#: lxc/remote.go:65
 msgid "Accept certificate"
 msgstr "Akzeptiere Zertifikat"
 
-#: lxc/remote.go:245
+#: lxc/remote.go:258
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
 
-#: lxc/image.go:356
+#: lxc/image.go:341
 #, fuzzy
 msgid "Aliases:"
 msgstr "Aliasse:\n"
 
-#: lxc/exec.go:54
-msgid "An environment variable of the form HOME=/home/foo"
-msgstr ""
-
-#: lxc/image.go:339 lxc/info.go:93
+#: lxc/image.go:319 lxc/info.go:95
 #, fuzzy, c-format
 msgid "Architecture: %s"
 msgstr "Architektur: %s\n"
 
-#: lxc/image.go:360
+#: lxc/image.go:345
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/help.go:49
-msgid "Available commands:"
-msgstr ""
-
-#: lxc/info.go:175
+#: lxc/info.go:177
 msgid "Bytes received"
 msgstr ""
 
-#: lxc/info.go:176
+#: lxc/info.go:178
 msgid "Bytes sent"
 msgstr ""
 
-#: lxc/config.go:273
+#: lxc/config.go:310
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:395
+#: lxc/list.go:419
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:113
+#: lxc/config.go:150
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr ""
 
-#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181
+#: lxc/config.go:163 lxc/config.go:196 lxc/config.go:218
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:343
+#: lxc/profile.go:394
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:195
+#: lxc/remote.go:208
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %x"
 msgstr "Fingerabdruck des Zertifikats: % x\n"
 
-#: lxc/action.go:33
-#, fuzzy, c-format
-msgid ""
-"Changes state of one or more containers to %s.\n"
-"\n"
-"lxc %s <name> [<name>...]%s"
-msgstr ""
-"Ändert den Laufzustand eines Containers in %s.\n"
-"\n"
-"lxd %s <Name>\n"
-
-#: lxc/remote.go:268
+#: lxc/remote.go:281
 msgid "Client certificate stored at server: "
 msgstr "Gespeichertes Nutzerzertifikat auf dem Server: "
 
-#: lxc/list.go:99 lxc/list.go:100
+#: lxc/list.go:122 lxc/list.go:123
 msgid "Columns"
 msgstr ""
 
-#: lxc/init.go:134 lxc/init.go:135 lxc/launch.go:40 lxc/launch.go:41
+#: lxc/help.go:53
+msgid "Commands:"
+msgstr ""
+
+#: lxc/init.go:133 lxc/init.go:134
 #, fuzzy
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:701 lxc/profile.go:190
+#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:686 lxc/profile.go:228
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
 
-#: lxc/main.go:29
+#: lxc/main.go:32
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
-#: lxc/publish.go:59
+#: lxc/publish.go:60
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:210
+#: lxc/init.go:211
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:141 lxc/publish.go:156
+#: lxc/publish.go:158 lxc/publish.go:173
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/image.go:164
+#: lxc/image.go:132
 msgid "Copy aliases from source"
 msgstr "Kopiere Aliasse von der Quelle"
 
-#: lxc/copy.go:22
-#, fuzzy
-msgid ""
-"Copy containers within or in between lxd instances.\n"
-"\n"
-"lxc copy [remote:]<source container> [remote:]<destination container> [--"
-"ephemeral|e]"
-msgstr ""
-"Kopiert Container innerhalb einer oder zwischen lxd Instanzen\n"
-"\n"
-"lxc copy <Quelle> <Ziel>\n"
-
-#: lxc/image.go:277
+#: lxc/image.go:244
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:210
+#: lxc/remote.go:223
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
-#: lxc/snapshot.go:21
-msgid ""
-"Create a read-only snapshot of a container.\n"
-"\n"
-"lxc snapshot [remote:]<source> <snapshot name> [--stateful]\n"
-"\n"
-"Creates a snapshot of the container (optionally with the container's memory\n"
-"state). When --stateful is used, LXD attempts to checkpoint the container's\n"
-"running state, including process memory state, TCP connections, etc. so that "
-"it\n"
-"can be restored (via lxc restore) at a later time (although some things, e."
-"g.\n"
-"TCP connections after the TCP timeout window has expired, may not be "
-"restored\n"
-"successfully).\n"
-"\n"
-"Example:\n"
-"lxc snapshot u1 snap0"
-msgstr ""
-
-#: lxc/image.go:344 lxc/info.go:95
+#: lxc/image.go:324 lxc/info.go:97
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:177 lxc/launch.go:118
+#: lxc/init.go:176 lxc/launch.go:111
 #, c-format
 msgid "Creating %s"
 msgstr ""
 
-#: lxc/init.go:175
+#: lxc/init.go:174
 #, fuzzy
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:621 lxc/image.go:649
+#: lxc/image.go:606 lxc/image.go:634
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/delete.go:25
-#, fuzzy
-msgid ""
-"Delete containers or container snapshots.\n"
-"\n"
-"lxc delete [remote:]<container>[/<snapshot>] [remote:][<container>[/"
-"<snapshot>]...]\n"
-"\n"
-"Destroy containers or snapshots with any attached data (configuration, "
-"snapshots, ...)."
-msgstr ""
-"Löscht einen Container oder Container Sicherungspunkt.\n"
-"\n"
-"Entfernt einen Container (oder Sicherungspunkt) und alle dazugehörigen\n"
-"Daten (Konfiguration, Sicherungspunkte, ...).\n"
-
-#: lxc/config.go:647
+#: lxc/config.go:688
 #, fuzzy, c-format
 msgid "Device %s added to %s"
 msgstr "Gerät %s wurde zu %s hinzugefügt\n"
 
-#: lxc/config.go:834
+#: lxc/config.go:875
 #, fuzzy, c-format
 msgid "Device %s removed from %s"
 msgstr "Gerät %s wurde von %s entfernt\n"
 
-#: lxc/list.go:478
+#: lxc/info.go:145
+msgid "Disk usage:"
+msgstr ""
+
+#: lxc/list.go:504
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:275
+#: lxc/config.go:312
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:41
-msgid "Enables debug mode."
+#: lxc/main.go:44
+#, fuzzy
+msgid "Enable debug mode"
 msgstr "Aktiviert Debug Modus"
 
-#: lxc/main.go:40
-msgid "Enables verbose mode."
+#: lxc/main.go:43
+#, fuzzy
+msgid "Enable verbose mode"
 msgstr "Aktiviert ausführliche Ausgabe"
 
-#: lxc/help.go:69
+#: lxc/exec.go:55
+msgid "Environment variable to set (e.g. HOME=/home/foo)"
+msgstr ""
+
+#: lxc/help.go:77
 msgid "Environment:"
 msgstr ""
 
-#: lxc/copy.go:29 lxc/copy.go:30 lxc/init.go:138 lxc/init.go:139
-#: lxc/launch.go:44 lxc/launch.go:45
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:137 lxc/init.go:138
 msgid "Ephemeral container"
 msgstr "Flüchtiger Container"
 
@@ -382,149 +316,106 @@ msgstr "Flüchtiger Container"
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/exec.go:45
-#, fuzzy
-msgid ""
-"Execute the specified command in a container.\n"
-"\n"
-"lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
-"EDITOR=/usr/bin/vim]... [--] <command line>\n"
-"\n"
-"Mode defaults to non-interactive, interactive mode is selected if both stdin "
-"AND stdout are terminals (stderr is ignored)."
-msgstr ""
-"Führt den angegebenen Befehl in einem Container aus.\n"
-"\n"
-"lxc exec <Container> [--env EDITOR=/usr/bin/vim]... <Befehl>\n"
-
-#: lxc/image.go:348
+#: lxc/image.go:328
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:350
+#: lxc/image.go:330
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:272 lxc/image.go:619 lxc/image.go:648
+#: lxc/config.go:309 lxc/image.go:604 lxc/image.go:633
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/list.go:102
-msgid "Fast mode (same as --columns=nsacPt"
+#: lxc/manpage.go:62
+#, c-format
+msgid "Failed to generate 'lxc.%s.1': %v"
+msgstr ""
+
+#: lxc/manpage.go:55
+#, c-format
+msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/image.go:337
+#: lxc/list.go:125
+msgid "Fast mode (same as --columns=nsacPt)"
+msgstr ""
+
+#: lxc/image.go:317
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Fingerabdruck: %s\n"
 
-#: lxc/finger.go:15
+#: lxc/action.go:46 lxc/action.go:47
 #, fuzzy
-msgid ""
-"Fingers the LXD instance to check if it is up and working.\n"
-"\n"
-"lxc finger <remote>"
-msgstr ""
-"Fingert die LXD Instanz zum überprüfen ob diese funktionsfähig ist.\n"
-"\n"
-"lxc finger <remote>\n"
-
-#: lxc/action.go:42 lxc/action.go:43
-msgid "Force the container to shutdown."
+msgid "Force the container to shutdown"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/delete.go:34 lxc/delete.go:35
-msgid "Force the removal of stopped containers."
+#: lxc/delete.go:33 lxc/delete.go:34
+msgid "Force the removal of stopped containers"
 msgstr ""
 
-#: lxc/main.go:42
-msgid "Force using the local unix socket."
+#: lxc/main.go:45
+msgid "Force using the local unix socket"
 msgstr ""
 
-#: lxc/list.go:101
+#: lxc/list.go:124
 msgid "Format"
 msgstr ""
 
-#: lxc/main.go:124
+#: lxc/main.go:131
 #, fuzzy
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Generiere Nutzerzertifikat. Dies kann wenige Minuten dauern...\n"
 
-#: lxc/list.go:392
+#: lxc/list.go:416
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:393
+#: lxc/list.go:417
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:274
+#: lxc/config.go:311
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:132
+#: lxc/main.go:139
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:43
-msgid "Ignore aliases when determining what command to run."
+#: lxc/main.go:46
+msgid "Ignore aliases when determining what command to run"
 msgstr ""
 
-#: lxc/action.go:46
+#: lxc/action.go:50
 #, fuzzy
-msgid "Ignore the container state (only for start)."
+msgid "Ignore the container state (only for start)"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/image.go:282
+#: lxc/image.go:247
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:433
+#: lxc/image.go:400 lxc/image.go:412
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/image.go:420
+#: lxc/image.go:397
 #, c-format
 msgid "Importing the image: %s"
 msgstr ""
 
-#: lxc/init.go:73
-#, fuzzy
-msgid ""
-"Initialize a container from a particular image.\n"
-"\n"
-"lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
-"\n"
-"Initializes a container using the specified image and name.\n"
-"\n"
-"Not specifying -p will result in the default profile.\n"
-"Specifying \"-p\" with no argument will result in no profile.\n"
-"\n"
-"Example:\n"
-"lxc init ubuntu u1"
-msgstr ""
-"Starte Container von gegebenem Abbild.\n"
-"\n"
-"lxc launch <Abbild> [<Name>] [—ephemeral|-e] [—profile|-p <Profile>…]\n"
-"\n"
-"Startet einen Container von gegebenem Abbild und mit Namen\n"
-"\n"
-"Ohne den -p Parameter wird das default Profil benutzt.\n"
-"Wird -p ohne Argument angegeben, wird kein Profil verwendet\n"
-"\n"
-"Beispiel:\n"
-"lxc launch ubuntu u1\n"
-
-#: lxc/remote.go:121
+#: lxc/remote.go:134
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
 
-#: lxc/config.go:253
+#: lxc/config.go:290
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "Akzeptiere Zertifikat"
@@ -533,924 +424,1144 @@ msgstr "Akzeptiere Zertifikat"
 msgid "Invalid configuration key"
 msgstr ""
 
-#: lxc/file.go:195
+#: lxc/file.go:207
 #, c-format
 msgid "Invalid source %s"
 msgstr "Ungültige Quelle %s"
 
-#: lxc/file.go:57
+#: lxc/file.go:69
 #, c-format
 msgid "Invalid target %s"
 msgstr "Ungültiges Ziel %s"
 
-#: lxc/info.go:124
+#: lxc/info.go:126
 msgid "Ips:"
 msgstr ""
 
-#: lxc/image.go:165
+#: lxc/image.go:133
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:27
+#: lxc/main.go:30
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/launch.go:22
-#, fuzzy
-msgid ""
-"Launch a container from a particular image.\n"
-"\n"
-"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
-"\n"
-"Launches a container using the specified image and name.\n"
-"\n"
-"Not specifying -p will result in the default profile.\n"
-"Specifying \"-p\" with no argument will result in no profile.\n"
-"\n"
-"Example:\n"
-"lxc launch ubuntu:16.04 u1"
-msgstr ""
-"Starte Container von gegebenem Abbild.\n"
-"\n"
-"lxc launch <Abbild> [<Name>] [—ephemeral|-e] [—profile|-p <Profile>…]\n"
-"\n"
-"Startet einen Container von gegebenem Abbild und mit Namen\n"
-"\n"
-"Ohne den -p Parameter wird das default Profil benutzt.\n"
-"Wird -p ohne Argument angegeben, wird kein Profil verwendet\n"
-"\n"
-"Beispiel:\n"
-"lxc launch ubuntu u1\n"
-
-#: lxc/info.go:25
-#, fuzzy
-msgid ""
-"List information on LXD servers and containers.\n"
-"\n"
-"For a container:\n"
-" lxc info [<remote>:]container [--show-log]\n"
-"\n"
-"For a server:\n"
-" lxc info [<remote>:]"
+#: lxc/image.go:333
+#, c-format
+msgid "Last used: %s"
 msgstr ""
-"Listet Informationen über Container.\n"
-"\n"
-"Dies wird entfernte Instanzen und Abbilder unterstützen, \n"
-"zur Zeit jedoch nur Container.\n"
-"\n"
-"lxc info [<remote>:]Container [--show-log]\n"
 
-#: lxc/list.go:67
-#, fuzzy
-msgid ""
-"Lists the available resources.\n"
-"\n"
-"lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
-"\n"
-"The filters are:\n"
-"* A single keyword like \"web\" which will list any container with a name "
-"starting by \"web\".\n"
-"* A regular expression on the container name. (e.g. .*web.*01$)\n"
-"* A key/value pair referring to a configuration item. For those, the "
-"namespace can be abbreviated to the smallest unambiguous identifier:\n"
-" * \"user.blah=abc\" will list all containers with the \"blah\" user "
-"property set to \"abc\".\n"
-" * \"u.blah=abc\" will do the same\n"
-" * \"security.privileged=1\" will list all privileged containers\n"
-" * \"s.privileged=1\" will do the same\n"
-"* A regular expression matching a configuration item or its value. (e.g. "
-"volatile.eth0.hwaddr=00:16:3e:.*)\n"
-"\n"
-"Columns for table format are:\n"
-"* 4 - IPv4 address\n"
-"* 6 - IPv6 address\n"
-"* a - architecture\n"
-"* c - creation date\n"
-"* n - name\n"
-"* p - pid of container init process\n"
-"* P - profiles\n"
-"* s - state\n"
-"* S - number of snapshots\n"
-"* t - type (persistent or ephemeral)\n"
-"\n"
-"Default column layout: ns46tS\n"
-"Fast column layout: nsacPt"
+#: lxc/image.go:335
+msgid "Last used: never"
 msgstr ""
-"Listet vorhandene Ressourcen.\n"
-"\n"
-"lxc list [Resource] [Filter]\n"
-"\n"
-"Filter sind:\n"
-"* Ein einzelnes Schlüsselwort wie \"web“, was alle Container mit \"web\" im "
-"Namen listet.\n"
-"* Ein key/value Paar bezüglich eines Konfigurationsparameters. Dafür kann "
-"der Namensraum, solange eindeutig, abgekürzt werden:\n"
-"* \"user.blah=abc\" listet alle Container mit der \"blah\" Benutzer "
-"Eigenschaft \"abc\"\n"
-"* \"u.blah=abc\" ebenfalls\n"
-"* \"security.privileged=1\" listet alle privilegierten Container\n"
-"* \"s.privileged=1\" ebenfalls\n"
 
-#: lxc/info.go:228
+#: lxc/info.go:230
 msgid "Log:"
 msgstr ""
 
-#: lxc/image.go:163
+#: lxc/image.go:131
 msgid "Make image public"
 msgstr "Veröffentliche Abbild"
 
-#: lxc/publish.go:32
+#: lxc/publish.go:33
 #, fuzzy
 msgid "Make the image public"
 msgstr "Veröffentliche Abbild"
 
-#: lxc/profile.go:47
-#, fuzzy
-msgid ""
-"Manage configuration profiles.\n"
-"\n"
-"lxc profile list [filters]                     List available profiles.\n"
-"lxc profile show <profile>                     Show details of a profile.\n"
-"lxc profile create <profile>                   Create a profile.\n"
-"lxc profile copy <profile> <remote>            Copy the profile to the "
-"specified remote.\n"
-"lxc profile get <profile> <key>                Get profile configuration.\n"
-"lxc profile set <profile> <key> <value>        Set profile configuration.\n"
-"lxc profile unset <profile> <key>              Unset profile configuration.\n"
-"lxc profile delete <profile>                   Delete a profile.\n"
-"lxc profile edit <profile>\n"
-"    Edit profile, either by launching external editor or reading STDIN.\n"
-"    Example: lxc profile edit <profile> # launch editor\n"
-"             cat profile.yaml | lxc profile edit <profile> # read from "
-"profile.yaml\n"
-"lxc profile apply <container> <profiles>\n"
-"    Apply a comma-separated list of profiles to a container, in order.\n"
-"    All profiles passed in this call (and only those) will be applied\n"
-"    to the specified container.\n"
-"    Example: lxc profile apply foo default,bar # Apply default and bar\n"
-"             lxc profile apply foo default # Only default is active\n"
-"             lxc profile apply '' # no profiles are applied anymore\n"
-"             lxc profile apply bar,default # Apply default second now\n"
-"\n"
-"Devices:\n"
-"lxc profile device list <profile>                                   List "
-"devices in the given profile.\n"
-"lxc profile device show <profile>                                   Show "
-"full device details in the given profile.\n"
-"lxc profile device remove <profile> <name>                          Remove a "
-"device from a profile.\n"
-"lxc profile device get <[remote:]profile> <name> <key>              Get a "
-"device property.\n"
-"lxc profile device set <[remote:]profile> <name> <key> <value>      Set a "
-"device property.\n"
-"lxc profile device unset <[remote:]profile> <name> <key>            Unset a "
-"device property.\n"
-"lxc profile device add <profile name> <device name> <device type> "
-"[key=value]...\n"
-"    Add a profile device, such as a disk or a nic, to the containers\n"
-"    using the specified profile."
+#: lxc/info.go:152
+msgid "Memory (current)"
 msgstr ""
-"Verwalte Konfigurationsprofile.\n"
-"\n"
-"lxc profile list [Filter]                     Listet verfügbare Profile\n"
-"lxc profile show <Profil>                     Zeigt Details zu einem Profil\n"
-"lxc profile create <Profil>                   Erstellt ein Profil\n"
-"lxc profile edit <Profil>                     Bearbeitet das Profil in "
-"externem Editor\n"
-"lxc profile copy <Profil> <remote>            Kopiert das Profil zur "
-"entfernten Instanz\n"
-"lxc profile set <Profil> <key> <value>        Setzt eine "
-"Profilkonfiguration\n"
-"lxc profile delete <Profil>                   Löscht ein Profil\n"
-"lxc profile apply <Container> <Profil>\n"
-"    Wendet eine durch Kommata getrennte Liste von Profilen,\n"
-"    in gegeben Reihenfolge, auf einen Container an.\n"
-"    Alle angegeben, und nur diese, werden auf den Container angewandt.\n"
-"    Beispiel: lxc profile apply foo default,bar # Wende default und bar an\n"
-"             lxc profile apply foo default # Nur default ist aktiv\n"
-"             lxc profile apply '' # keine Profile werden angewandt\n"
-"             lxc profile apply bar,default # Wende nun default als zweites "
-"an\n"
-"\n"
-"Geräte:\n"
-"lxc profile device list <Profil>              Listet Geräte im Profil\n"
-"lxc profile device show <Profil>              Zeigt alle Geräte Details im "
-"gegebenen Profil.\n"
-"lxc profile device remove <Profil> <name>     Entfernt ein Gerät von dem "
-"Profil.\n"
-"lxc profile device add <Profil name> <Gerätename> <Gerätetype> "
-"[key=value]...\n"
-"    Fügt ein Gerät, wie zum Beispiel eine Festplatte oder Netzwerkkarte, den "
-"Containern hinzu,\n"
-"    die dieses Profil verwenden.\n"
 
-#: lxc/config.go:57
-#, fuzzy
-msgid ""
-"Manage configuration.\n"
-"\n"
-"lxc config device add <[remote:]container> <name> <type> [key=value]...     "
-"Add a device to a container.\n"
-"lxc config device get <[remote:]container> <name> <key>                     "
-"Get a device property.\n"
-"lxc config device set <[remote:]container> <name> <key> <value>             "
-"Set a device property.\n"
-"lxc config device unset <[remote:]container> <name> <key>                   "
-"Unset a device property.\n"
-"lxc config device list <[remote:]container>                                 "
-"List devices for container.\n"
-"lxc config device show <[remote:]container>                                 "
-"Show full device details for container.\n"
-"lxc config device remove <[remote:]container> <name>                        "
-"Remove device from container.\n"
-"\n"
-"lxc config get [remote:][container] <key>                                   "
-"Get container or server configuration key.\n"
-"lxc config set [remote:][container] <key> <value>                           "
-"Set container or server configuration key.\n"
-"lxc config unset [remote:][container] <key>                                 "
-"Unset container or server configuration key.\n"
-"lxc config show [remote:][container] [--expanded]                           "
-"Show container or server configuration.\n"
-"lxc config edit [remote:][container]                                        "
-"Edit container or server configuration in external editor.\n"
-"    Edit configuration, either by launching external editor or reading "
-"STDIN.\n"
-"    Example: lxc config edit <container> # launch editor\n"
-"             cat config.yaml | lxc config edit <config> # read from config."
-"yaml\n"
-"\n"
-"lxc config trust list [remote]                                              "
-"List all trusted certs.\n"
-"lxc config trust add [remote] <certfile.crt>                                "
-"Add certfile.crt to trusted hosts.\n"
-"lxc config trust remove [remote] [hostname|fingerprint]                     "
-"Remove the cert from trusted hosts.\n"
-"\n"
-"Examples:\n"
-"To mount host's /share/c1 onto /opt in the container:\n"
-"    lxc config device add [remote:]container1 <device-name> disk source=/"
-"share/c1 path=opt\n"
-"\n"
-"To set an lxc config value:\n"
-"    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete = "
-"1'\n"
-"\n"
-"To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the "
-"default):\n"
-"    lxc config set core.https_address [::]:8443\n"
-"\n"
-"To set the server trust password:\n"
-"    lxc config set core.trust_password blah"
-msgstr ""
-"Verwalte Konfiguration.\n"
-"\n"
-"lxc config device add <Container> <Name> <Type> [key=value]...\n"
-"               Füge ein Gerät zu einem Container hinzu\n"
-"lxc config device list <Container>                     Listet die Geräte des "
-"Containers\n"
-"lxc config device show <Container>                     Zeigt alle Geräte "
-"Details für den Container\n"
-"lxc config device remove <Container> <Name>            Entfernt Gerät vom "
-"Container\n"
-"lxc config edit <Container>                            Bearbeite Container "
-"Konfiguration in externem Editor\n"
-"lxc config get <Container> key                         Holt "
-"Konfigurationsschlüssel\n"
-"lxc config set <Container> key [value]                 Setzt Container "
-"Konfigurationsschlüssel\n"
-"lxc config show <Container>                            Zeigt Konfiguration "
-"des Containers\n"
-"lxc config trust list [remote]                         Liste alle "
-"vertrauenswürdigen Zertifikate.\n"
-"lxc config trust add [remote] [certfile.crt]           Füge certfile.crt zu "
-"vertrauenden Instanzen hinzu.\n"
-"lxc config trust remove [remote] [hostname|fingerprint]\n"
-"               Löscht das Zertifikat aus der Liste der vertrauenswürdigen.\n"
-"\n"
-"Beispiele:\n"
-"Zum mounten von /share/c1 des Hosts nach /opt im Container:\n"
-"\tlxc config device add container1 mntdir disk source=/share/c1 path=opt\n"
-"Um einen lxc config Wert zu setzen:\n"
-"\tlxc config set <container> raw.lxc 'lxc.aa_allow_incomplete = 1'\n"
-"Um das Server Passwort zur authentifizierung zu setzen:\n"
-"\tlxc config set core.trust_password blah\n"
+#: lxc/info.go:156
+msgid "Memory (peak)"
+msgstr ""
 
-#: lxc/file.go:32
+#: lxc/info.go:168
+msgid "Memory usage:"
+msgstr ""
+
+#: lxc/utils.go:173
+msgid "Missing summary."
+msgstr "Fehlende Zusammenfassung."
+
+#: lxc/file.go:195
+msgid "More than one file to download, but target is not a directory"
+msgstr ""
+"Mehr als eine Datei herunterzuladen, aber das Ziel ist kein Verzeichnis"
+
+#: lxc/action.go:68
 #, fuzzy
-msgid ""
-"Manage files on a container.\n"
-"\n"
-"lxc file pull <source> [<source>...] <target>\n"
-"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] "
-"<target>\n"
-"lxc file edit <file>\n"
-"\n"
-"<source> in the case of pull, <target> in the case of push and <file> in the "
-"case of edit are <container name>/<path>"
+msgid "Must supply container name for: "
+msgstr "der Name des Ursprung Containers muss angegeben werden"
+
+#: lxc/list.go:420 lxc/remote.go:365
+msgid "NAME"
+msgstr ""
+
+#: lxc/remote.go:339 lxc/remote.go:344
+msgid "NO"
+msgstr ""
+
+#: lxc/info.go:91
+#, c-format
+msgid "Name: %s"
+msgstr ""
+
+#: lxc/info.go:185
+msgid "Network usage:"
+msgstr ""
+
+#: lxc/image.go:134 lxc/publish.go:34
+msgid "New alias to define at target"
 msgstr ""
-"Verwaltet Dateien in einem Container.\n"
-"\n"
-"lxc file pull <Quelle> [<Quelle>...] <Ziel>\n"
-"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <Quelle> [<Quelle>...] "
-"<Ziel>\n"
-"\n"
-"<Quelle> bei pull und <Ziel> bei push sind jeweils von der Form <Container "
-"Name>/<Pfad>\n"
 
-#: lxc/remote.go:38
+#: lxc/config.go:321
 #, fuzzy
-msgid ""
-"Manage remote LXD servers.\n"
-"\n"
-"lxc remote add <name> <url> [--accept-certificate] [--password=PASSWORD]\n"
-"                            [--public] [--protocol=PROTOCOL]                "
-"Add the remote <name> at <url>.\n"
-"lxc remote remove <name>                                                    "
-"Remove the remote <name>.\n"
-"lxc remote list                                                             "
-"List all remotes.\n"
-"lxc remote rename <old> <new>                                               "
-"Rename remote <old> to <new>.\n"
-"lxc remote set-url <name> <url>                                             "
-"Update <name>'s url to <url>.\n"
-"lxc remote set-default <name>                                               "
-"Set the default remote.\n"
-"lxc remote get-default                                                      "
-"Print the default remote."
+msgid "No certificate provided to add"
+msgstr "Kein Zertifikat zum hinzufügen bereitgestellt"
+
+#: lxc/config.go:344
+msgid "No fingerprint specified."
+msgstr "Kein Fingerabdruck angegeben."
+
+#: lxc/remote.go:119
+msgid "Only https URLs are supported for simplestreams"
 msgstr ""
-"Verwalte entfernte LXD Server.\n"
-"\n"
-"lxc remote add <Name> <url> [--accept-certificate] [--password=PASSWORT]  "
-"Fügt die Instanz <Name> auf <URL> hinzu.\n"
-"lxc remote remove <Name>                                                  "
-"Entfernt die Instanz <Name>.\n"
-"lxc remote list                                                           "
-"Listet alle entfernte Instanzen.\n"
-"lxc remote rename <alt> <neu>                                             "
-"Benennt Instanz von <alt> nach <neu> um.\n"
-"lxc remote set-url <Name> <url>                                           "
-"Setzt die URL von <Name> auf <url>.\n"
-"lxc remote set-default <Name>                                             "
-"Setzt die Standard Instanz.\n"
-"lxc remote get-default                                                    "
-"Gibt die Standard Instanz aus.\n"
 
-#: lxc/image.go:93
-msgid ""
-"Manipulate container images.\n"
-"\n"
-"In LXD containers are created from images. Those images were themselves\n"
-"either generated from an existing container or downloaded from an image\n"
-"server.\n"
-"\n"
-"When using remote images, LXD will automatically cache images for you\n"
-"and remove them upon expiration.\n"
-"\n"
-"The image unique identifier is the hash (sha-256) of its representation\n"
-"as a compressed tarball (or for split images, the concatenation of the\n"
-"metadata and rootfs tarballs).\n"
-"\n"
-"Images can be referenced by their full hash, shortest unique partial\n"
-"hash or alias name (if one is set).\n"
-"\n"
-"\n"
-"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
-"alias=ALIAS].. [prop=value]\n"
-"    Import an image tarball (or tarballs) into the LXD image store.\n"
-"\n"
-"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
-"[--public] [--auto-update]\n"
-"    Copy an image from one LXD daemon to another over the network.\n"
-"\n"
-"    The auto-update flag instructs the server to keep this image up to\n"
-"    date. It requires the source to be an alias and for it to be public.\n"
-"\n"
-"lxc image delete [remote:]<image>\n"
-"    Delete an image from the LXD image store.\n"
-"\n"
-"lxc image export [remote:]<image> [target]\n"
-"    Export an image from the LXD image store into a distributable tarball.\n"
-"\n"
-"    The output target is optional and defaults to the working directory.\n"
-"    The target may be an existing directory, file name, or \"-\" to specify\n"
-"    stdout.  The target MUST be a directory when exporting a split image.\n"
-"    If the target is a directory, the image's name (each part's name for\n"
-"    split images) as found in the database will be used for the exported\n"
-"    image.  If the target is a file (not a directory and not stdout), then\n"
-"    the appropriate extension will be appended to the provided file name\n"
-"    based on the algorithm used to compress the image. \n"
-"\n"
-"lxc image info [remote:]<image>\n"
-"    Print everything LXD knows about a given image.\n"
-"\n"
-"lxc image list [remote:] [filter]\n"
-"    List images in the LXD image store. Filters may be of the\n"
-"    <key>=<value> form for property based filtering, or part of the image\n"
-"    hash or part of the image alias name.\n"
-"\n"
-"lxc image show [remote:]<image>\n"
-"    Yaml output of the user modifiable properties of an image.\n"
-"\n"
-"lxc image edit [remote:]<image>\n"
-"    Edit image, either by launching external editor or reading STDIN.\n"
-"    Example: lxc image edit <image> # launch editor\n"
-"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
-"\n"
-"lxc image alias create [remote:]<alias> <fingerprint>\n"
-"    Create a new alias for an existing image.\n"
-"\n"
-"lxc image alias delete [remote:]<alias>\n"
-"    Delete an alias.\n"
-"\n"
-"lxc image alias list [remote:] [filter]\n"
-"    List the aliases. Filters may be part of the image hash or part of the "
-"image alias name.\n"
-msgstr ""
-
-#: lxc/info.go:150
-msgid "Memory (current)"
-msgstr ""
-
-#: lxc/info.go:154
-msgid "Memory (peak)"
-msgstr ""
-
-#: lxc/help.go:87
-msgid "Missing summary."
-msgstr "Fehlende Zusammenfassung."
-
-#: lxc/monitor.go:41
-msgid ""
-"Monitor activity on the LXD server.\n"
-"\n"
-"lxc monitor [remote:] [--type=TYPE...]\n"
-"\n"
-"Connects to the monitoring interface of the specified LXD server.\n"
-"\n"
-"By default will listen to all message types.\n"
-"Specific types to listen to can be specified with --type.\n"
-"\n"
-"Example:\n"
-"lxc monitor --type=logging"
-msgstr ""
-
-#: lxc/file.go:183
-msgid "More than one file to download, but target is not a directory"
-msgstr ""
-"Mehr als eine Datei herunterzuladen, aber das Ziel ist kein Verzeichnis"
-
-#: lxc/move.go:16
-#, fuzzy
-msgid ""
-"Move containers within or in between lxd instances.\n"
-"\n"
-"lxc move [remote:]<source container> [remote:]<destination container>\n"
-"    Move a container between two hosts, renaming it if destination name "
-"differs.\n"
-"\n"
-"lxc move <old name> <new name>\n"
-"    Rename a local container.\n"
-msgstr ""
-"Verschiebt Container innerhalb einer oder zwischen lxd Instanzen\n"
-"\n"
-"lxc move <Quelle> <Ziel>\n"
-
-#: lxc/action.go:69
-#, fuzzy
-msgid "Must supply container name for: "
-msgstr "der Name des Ursprung Containers muss angegeben werden"
-
-#: lxc/list.go:396 lxc/remote.go:352
-msgid "NAME"
-msgstr ""
-
-#: lxc/remote.go:326 lxc/remote.go:331
-msgid "NO"
-msgstr ""
-
-#: lxc/info.go:89
-#, c-format
-msgid "Name: %s"
-msgstr ""
-
-#: lxc/image.go:166 lxc/publish.go:33
-msgid "New alias to define at target"
-msgstr ""
-
-#: lxc/config.go:284
-#, fuzzy
-msgid "No certificate provided to add"
-msgstr "Kein Zertifikat zum hinzufügen bereitgestellt"
-
-#: lxc/config.go:307
-msgid "No fingerprint specified."
-msgstr "Kein Fingerabdruck angegeben."
-
-#: lxc/remote.go:106
-msgid "Only https URLs are supported for simplestreams"
-msgstr ""
-
-#: lxc/image.go:425
+#: lxc/image.go:403
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
-#: lxc/help.go:63 lxc/main.go:108
+#: lxc/help.go:71 lxc/main.go:112 lxc/main.go:164
 msgid "Options:"
 msgstr ""
 
-#: lxc/image.go:520
+#: lxc/image.go:505
 #, c-format
 msgid "Output is in %s"
 msgstr ""
 
-#: lxc/exec.go:55
+#: lxc/exec.go:56
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:480
+#: lxc/list.go:506
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:397
+#: lxc/list.go:421
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:398
+#: lxc/list.go:422
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:354
+#: lxc/remote.go:367
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:620 lxc/remote.go:355
+#: lxc/image.go:605 lxc/remote.go:368
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:177
+#: lxc/info.go:179
 msgid "Packets received"
 msgstr ""
 
-#: lxc/info.go:178
+#: lxc/info.go:180
 msgid "Packets sent"
 msgstr ""
 
-#: lxc/help.go:70
+#: lxc/help.go:78
 #, fuzzy
-msgid "Path to an alternate client configuration directory."
+msgid "Path to an alternate client configuration directory"
 msgstr "Alternatives config Verzeichnis."
 
-#: lxc/help.go:71
+#: lxc/help.go:79
 #, fuzzy
-msgid "Path to an alternate server directory."
+msgid "Path to an alternate server directory"
 msgstr "Alternatives config Verzeichnis."
 
-#: lxc/main.go:31
+#: lxc/main.go:201
+#, fuzzy
+msgid "Pause containers."
+msgstr "kann nicht zum selben Container Namen kopieren"
+
+#: lxc/main.go:34
 msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
-#: lxc/info.go:106
+#: lxc/info.go:108
 #, c-format
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/help.go:25
-#, fuzzy
-msgid ""
-"Presents details on how to use LXD.\n"
-"\n"
-"lxd help [--all]"
-msgstr ""
-"Zeigt Details über die Benutzung von LXD an.\n"
-"\n"
-"lxd help [—all]\n"
-
-#: lxc/profile.go:191
+#: lxc/profile.go:229
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:702
+#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:687
 msgid "Press enter to start the editor again"
 msgstr ""
 
-#: lxc/help.go:65
-msgid "Print debug information."
+#: lxc/help.go:73
+msgid "Print debug information"
 msgstr ""
 
-#: lxc/help.go:64
-msgid "Print less common commands."
+#: lxc/help.go:72
+msgid "Print less common commands"
 msgstr ""
 
-#: lxc/help.go:66
-msgid "Print verbose information."
+#: lxc/help.go:74
+msgid "Print verbose information"
 msgstr ""
 
-#: lxc/version.go:18
-#, fuzzy
-msgid ""
-"Prints the version number of this client tool.\n"
-"\n"
-"lxc version"
-msgstr ""
-"Zeigt die Versionsnummer von LXD.\n"
-"\n"
-"lxc version\n"
-
-#: lxc/info.go:130
+#: lxc/info.go:132
 #, fuzzy, c-format
 msgid "Processes: %d"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:228
+#: lxc/profile.go:266
 #, fuzzy, c-format
 msgid "Profile %s applied to %s"
 msgstr "Profil %s wurde auf %s angewandt\n"
 
-#: lxc/profile.go:142
+#: lxc/profile.go:180
 #, fuzzy, c-format
 msgid "Profile %s created"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:212
+#: lxc/profile.go:250
 #, fuzzy, c-format
 msgid "Profile %s deleted"
 msgstr "Profil %s gelöscht\n"
 
-#: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
+#: lxc/init.go:135 lxc/init.go:136
 #, fuzzy
 msgid "Profile to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/info.go:104
+#: lxc/info.go:106
 #, fuzzy, c-format
 msgid "Profiles: %s"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/image.go:352
+#: lxc/image.go:337
 #, fuzzy
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
 
-#: lxc/remote.go:55
+#: lxc/remote.go:68
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:340
+#: lxc/image.go:320
 #, fuzzy, c-format
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
 
-#: lxc/publish.go:25
-msgid ""
-"Publish containers as images.\n"
-"\n"
-"lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
-"value]..."
-msgstr ""
-
-#: lxc/remote.go:53
+#: lxc/remote.go:66
 msgid "Remote admin password"
 msgstr "Entferntes Administrator Passwort"
 
-#: lxc/info.go:91
+#: lxc/info.go:93
 #, c-format
 msgid "Remote: %s"
 msgstr ""
 
-#: lxc/delete.go:42
+#: lxc/delete.go:41
 #, c-format
 msgid "Remove %s (yes/no): "
 msgstr ""
 
-#: lxc/delete.go:36 lxc/delete.go:37
-msgid "Require user confirmation."
+#: lxc/delete.go:35 lxc/delete.go:36
+msgid "Require user confirmation"
 msgstr ""
 
-#: lxc/info.go:127
+#: lxc/info.go:129
 msgid "Resources:"
 msgstr ""
 
-#: lxc/init.go:247
+#: lxc/main.go:209
+#, fuzzy
+msgid "Restart containers."
+msgstr "kann nicht zum selben Container Namen kopieren"
+
+#: lxc/init.go:218
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:623
+#: lxc/image.go:608
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:399
+#: lxc/list.go:423
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:400
+#: lxc/list.go:424
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:356
+#: lxc/remote.go:369
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:203
+#: lxc/remote.go:216
 msgid "Server certificate NACKed by user"
 msgstr "Server Zertifikat vom Benutzer nicht akzeptiert"
 
-#: lxc/remote.go:265
+#: lxc/remote.go:278
 msgid "Server doesn't trust us after adding our cert"
 msgstr ""
 "Der Server vertraut uns nicht nachdem er unser Zertifikat hinzugefügt hat"
 
-#: lxc/remote.go:54
+#: lxc/remote.go:67
 msgid "Server protocol (lxd or simplestreams)"
 msgstr ""
 
-#: lxc/restore.go:21
-msgid ""
-"Set the current state of a resource back to a snapshot.\n"
-"\n"
-"lxc restore [remote:]<container> <snapshot name> [--stateful]\n"
-"\n"
-"Restores a container from a snapshot (optionally with running state, see\n"
-"snapshot help for details).\n"
-"\n"
-"For example:\n"
-"lxc snapshot u1 snap0 # create the snapshot\n"
-"lxc restore u1 snap0 # restore the snapshot"
-msgstr ""
-
-#: lxc/file.go:44
+#: lxc/file.go:56
 msgid "Set the file's gid on push"
 msgstr "Setzt die gid der Datei beim Übertragen"
 
-#: lxc/file.go:45
+#: lxc/file.go:57
 msgid "Set the file's perms on push"
 msgstr "Setzt die Dateiberechtigungen beim Übertragen"
 
-#: lxc/file.go:43
+#: lxc/file.go:55
 msgid "Set the file's uid on push"
 msgstr "Setzt die uid der Datei beim Übertragen"
 
-#: lxc/help.go:32
+#: lxc/help.go:29
 msgid "Show all commands (not just interesting ones)"
 msgstr "Zeigt alle Befehle (nicht nur die interessanten)"
 
-#: lxc/help.go:67
-msgid "Show client version."
+#: lxc/help.go:75
+msgid "Show client version"
 msgstr ""
 
-#: lxc/info.go:36
+#: lxc/info.go:38
 msgid "Show the container's last 100 log lines?"
 msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 
-#: lxc/image.go:338
+#: lxc/config.go:33
+msgid "Show the expanded configuration"
+msgstr ""
+
+#: lxc/image.go:318
 #, fuzzy, c-format
 msgid "Size: %.2fMB"
 msgstr "Größe: %.2vMB\n"
 
-#: lxc/info.go:197
+#: lxc/info.go:199
 msgid "Snapshots:"
 msgstr ""
 
-#: lxc/image.go:362
+#: lxc/action.go:134
+#, fuzzy, c-format
+msgid "Some containers failed to %s"
+msgstr "Anhalten des Containers fehlgeschlagen!"
+
+#: lxc/image.go:347
 msgid "Source:"
 msgstr ""
 
-#: lxc/launch.go:124
+#: lxc/main.go:219
+#, fuzzy
+msgid "Start containers."
+msgstr "kann nicht zum selben Container Namen kopieren"
+
+#: lxc/launch.go:118
 #, c-format
 msgid "Starting %s"
 msgstr ""
 
-#: lxc/info.go:98
+#: lxc/info.go:100
 #, c-format
 msgid "Status: %s"
 msgstr ""
 
-#: lxc/publish.go:34 lxc/publish.go:35
+#: lxc/main.go:225
+#, fuzzy
+msgid "Stop containers."
+msgstr "Anhalten des Containers fehlgeschlagen!"
+
+#: lxc/publish.go:35 lxc/publish.go:36
 msgid "Stop the container if currently running"
 msgstr ""
 
-#: lxc/delete.go:106 lxc/publish.go:111
+#: lxc/delete.go:105 lxc/publish.go:112
 msgid "Stopping container failed!"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/action.go:45
+#: lxc/action.go:49
 #, fuzzy
-msgid "Store the container state (only for stop)."
+msgid "Store the container state (only for stop)"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/info.go:158
+#: lxc/info.go:160
 msgid "Swap (current)"
 msgstr ""
 
-#: lxc/info.go:162
+#: lxc/info.go:164
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:401
+#: lxc/list.go:425
 msgid "TYPE"
 msgstr ""
 
-#: lxc/delete.go:92
+#: lxc/delete.go:91
 msgid "The container is currently running, stop it first or pass --force."
 msgstr ""
 
-#: lxc/publish.go:89
+#: lxc/publish.go:90
 msgid ""
 "The container is currently running. Use --force to have it stopped and "
 "restarted."
 msgstr ""
 
-#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738
-#: lxc/config.go:776 lxc/config.go:794
+#: lxc/config.go:716 lxc/config.go:728 lxc/config.go:761 lxc/config.go:779
+#: lxc/config.go:817 lxc/config.go:835
 #, fuzzy
 msgid "The device doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/init.go:277
+#: lxc/init.go:275
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
 
-#: lxc/main.go:176
-msgid "The opposite of `lxc pause` is `lxc start`."
+#: lxc/action.go:34
+msgid "The opposite of \"lxc pause\" is \"lxc start\"."
 msgstr ""
 
-#: lxc/publish.go:62
+#: lxc/publish.go:63
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
 
-#: lxc/action.go:41
-msgid "Time to wait for the container before killing it."
+#: lxc/help.go:47
+msgid ""
+"This is the LXD command line client.\n"
+"\n"
+"All of LXD's features can be driven through the various commands below.\n"
+"For help with any of those, simply call them with --help."
+msgstr ""
+
+#: lxc/action.go:45
+#, fuzzy
+msgid "Time to wait for the container before killing it"
 msgstr "Wartezeit bevor der Container gestoppt wird."
 
-#: lxc/image.go:341
+#: lxc/image.go:321
 #, fuzzy
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
 
-#: lxc/main.go:133
+#: lxc/main.go:140
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:412
+#: lxc/image.go:405
 #, c-format
-msgid "Transferring image: %d%%"
+msgid "Transferring image: %s"
+msgstr ""
+
+#: lxc/action.go:98 lxc/launch.go:131
+#, c-format
+msgid "Try `lxc info --show-log %s` for more info"
+msgstr ""
+
+#: lxc/info.go:102
+msgid "Type: ephemeral"
+msgstr ""
+
+#: lxc/info.go:104
+msgid "Type: persistent"
+msgstr ""
+
+#: lxc/image.go:609
+msgid "UPLOAD DATE"
+msgstr ""
+
+#: lxc/remote.go:366
+msgid "URL"
+msgstr ""
+
+#: lxc/manpage.go:36
+msgid "Unable to find help2man."
+msgstr ""
+
+#: lxc/remote.go:94
+msgid "Unable to read remote TLS certificate"
+msgstr ""
+
+#: lxc/image.go:326
+#, c-format
+msgid "Uploaded: %s"
+msgstr ""
+
+#: lxc/action.go:37
+#, c-format
+msgid ""
+"Usage: lxc %s [<remote>:]<container> [[<remote>:]<container>...]\n"
+"\n"
+"%s%s"
+msgstr ""
+
+#: lxc/help.go:45
+#, fuzzy
+msgid "Usage: lxc <command> [options]"
+msgstr ""
+"Benutzung: lxc [Unterbefehl] [Optionen]\n"
+"Verfügbare Befehle:\n"
+
+#: lxc/config.go:58
+msgid ""
+"Usage: lxc config <subcommand> [options]\n"
+"\n"
+"Change container or server configuration options.\n"
+"\n"
+"*Container configuration*\n"
+"\n"
+"lxc config get [<remote>:][container] <key>\n"
+"    Get container or server configuration key.\n"
+"\n"
+"lxc config set [<remote>:][container] <key> <value>\n"
+"    Set container or server configuration key.\n"
+"\n"
+"lxc config unset [<remote>:][container] <key>\n"
+"    Unset container or server configuration key.\n"
+"\n"
+"lxc config show [<remote>:][container] [--expanded]\n"
+"    Show container or server configuration.\n"
+"\n"
+"lxc config edit [<remote>:][container]\n"
+"    Edit configuration, either by launching external editor or reading "
+"STDIN.\n"
+"\n"
+"*Device management*\n"
+"\n"
+"lxc config device add [<remote>:]<container> <device> <type> [key=value...]\n"
+"    Add a device to a container.\n"
+"\n"
+"lxc config device get [<remote>:]<container> <device> <key>\n"
+"    Get a device property.\n"
+"\n"
+"lxc config device set [<remote>:]<container> <device> <key> <value>\n"
+"    Set a device property.\n"
+"\n"
+"lxc config device unset [<remote>:]<container> <device> <key>\n"
+"    Unset a device property.\n"
+"\n"
+"lxc config device list [<remote>:]<container>\n"
+"    List devices for container.\n"
+"\n"
+"lxc config device show [<remote>:]<container>\n"
+"    Show full device details for container.\n"
+"\n"
+"lxc config device remove [<remote>:]<container> <name>\n"
+"    Remove device from container.\n"
+"\n"
+"*Client trust store management*\n"
+"\n"
+"lxc config trust list [<remote>:]\n"
+"    List all trusted certs.\n"
+"\n"
+"lxc config trust add [<remote>:] <certfile.crt>\n"
+"    Add certfile.crt to trusted hosts.\n"
+"\n"
+"lxc config trust remove [<remote>:] [hostname|fingerprint]\n"
+"    Remove the cert from trusted hosts.\n"
+"\n"
+"*Examples*\n"
+"\n"
+"cat config.yaml | lxc config edit <container>\n"
+"    Update the container configuration from config.yaml.\n"
+"\n"
+"lxc config device add [<remote>:]container1 <device-name> disk source=/share/"
+"c1 path=opt\n"
+"    Will mount the host's /share/c1 onto /opt in the container.\n"
+"\n"
+"lxc config set [<remote>:]<container> limits.cpu 2\n"
+"    Will set a CPU limit of \"2\" for the container.\n"
+"\n"
+"lxc config set core.https_address [::]:8443\n"
+"    Will have LXD listen on IPv4 and IPv6 port 8443.\n"
+"\n"
+"lxc config set core.trust_password blah\n"
+"    Will set the server's trust password to blah."
+msgstr ""
+
+#: lxc/copy.go:23
+msgid ""
+"Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
+"[--ephemeral|e]\n"
+"\n"
+"Copy containers within or in between LXD instances."
+msgstr ""
+
+#: lxc/delete.go:26
+#, fuzzy
+msgid ""
+"Usage: lxc delete [<remote>:]<container>[/<snapshot>] "
+"[[<remote>:]<container>[/<snapshot>]...]\n"
+"\n"
+"Delete containers and snapshots."
+msgstr ""
+"Löscht einen Container oder Container Sicherungspunkt.\n"
+"\n"
+"Entfernt einen Container (oder Sicherungspunkt) und alle dazugehörigen\n"
+"Daten (Konfiguration, Sicherungspunkte, ...).\n"
+
+#: lxc/exec.go:46
+#, fuzzy
+msgid ""
+"Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-"
+"interactive] [--env KEY=VALUE...] [--] <command line>\n"
+"\n"
+"Execute commands in containers.\n"
+"\n"
+"Mode defaults to non-interactive, interactive mode is selected if both stdin "
+"AND stdout are terminals (stderr is ignored)."
+msgstr ""
+"Führt den angegebenen Befehl in einem Container aus.\n"
+"\n"
+"lxc exec <Container> [--env EDITOR=/usr/bin/vim]... <Befehl>\n"
+
+#: lxc/file.go:32
+msgid ""
+"Usage: lxc file <subcommand> [options]\n"
+"\n"
+"Manage files in containers.\n"
+"\n"
+"lxc file pull [<remote>:]<container>/<path> [[<remote>:]<container>/"
+"<path>...] <target path>\n"
+"    Pull files from containers.\n"
+"\n"
+"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source "
+"path>...] [<remote>:]<container>/<path>\n"
+"    Push files into containers.\n"
+"\n"
+"lxc file edit [<remote>:]<container>/<path>\n"
+"    Edit files in containers using the default text editor.\n"
+"\n"
+"*Examples*\n"
+"lxc file push /etc/hosts foo/etc/hosts\n"
+"   To push /etc/hosts into the container \"foo\".\n"
+"\n"
+"lxc file pull foo/etc/hosts .\n"
+"   To pull /etc/hosts from the container and write it to the current "
+"directory."
+msgstr ""
+
+#: lxc/finger.go:15
+msgid ""
+"Usage: lxc finger [<remote>:]\n"
+"\n"
+"Check if the LXD server is alive."
+msgstr ""
+
+#: lxc/help.go:22
+msgid ""
+"Usage: lxc help [--all]\n"
+"\n"
+"Help page for the LXD client."
+msgstr ""
+
+#: lxc/image.go:60
+msgid ""
+"Usage: lxc image <subcommand> [options]\n"
+"\n"
+"Manipulate container images.\n"
+"\n"
+"In LXD containers are created from images. Those images were themselves\n"
+"either generated from an existing container or downloaded from an image\n"
+"server.\n"
+"\n"
+"When using remote images, LXD will automatically cache images for you\n"
+"and remove them upon expiration.\n"
+"\n"
+"The image unique identifier is the hash (sha-256) of its representation\n"
+"as a compressed tarball (or for split images, the concatenation of the\n"
+"metadata and rootfs tarballs).\n"
+"\n"
+"Images can be referenced by their full hash, shortest unique partial\n"
+"hash or alias name (if one is set).\n"
+"\n"
+"\n"
+"lxc image import <tarball> [<rootfs tarball>|<URL>] [<remote>:] [--public] "
+"[--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] "
+"[--alias=ALIAS...] [prop=value]\n"
+"    Import an image tarball (or tarballs) into the LXD image store.\n"
+"\n"
+"lxc image copy [<remote>:]<image> <remote>: [--alias=ALIAS...] [--copy-"
+"aliases] [--public] [--auto-update]\n"
+"    Copy an image from one LXD daemon to another over the network.\n"
+"\n"
+"    The auto-update flag instructs the server to keep this image up to\n"
+"    date. It requires the source to be an alias and for it to be public.\n"
+"\n"
+"lxc image delete [<remote>:]<image> [[<remote>:]<image>...]\n"
+"    Delete one or more images from the LXD image store.\n"
+"\n"
+"lxc image export [<remote>:]<image> [target]\n"
+"    Export an image from the LXD image store into a distributable tarball.\n"
+"\n"
+"    The output target is optional and defaults to the working directory.\n"
+"    The target may be an existing directory, file name, or \"-\" to specify\n"
+"    stdout.  The target MUST be a directory when exporting a split image.\n"
+"    If the target is a directory, the image's name (each part's name for\n"
+"    split images) as found in the database will be used for the exported\n"
+"    image.  If the target is a file (not a directory and not stdout), then\n"
+"    the appropriate extension will be appended to the provided file name\n"
+"    based on the algorithm used to compress the image. \n"
+"\n"
+"lxc image info [<remote>:]<image>\n"
+"    Print everything LXD knows about a given image.\n"
+"\n"
+"lxc image list [<remote>:] [filter]\n"
+"    List images in the LXD image store. Filters may be of the\n"
+"    <key>=<value> form for property based filtering, or part of the image\n"
+"    hash or part of the image alias name.\n"
+"\n"
+"lxc image show [<remote>:]<image>\n"
+"    Yaml output of the user modifiable properties of an image.\n"
+"\n"
+"lxc image edit [<remote>:]<image>\n"
+"    Edit image, either by launching external editor or reading STDIN.\n"
+"    Example: lxc image edit <image> # launch editor\n"
+"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
+"\n"
+"lxc image alias create [<remote>:]<alias> <fingerprint>\n"
+"    Create a new alias for an existing image.\n"
+"\n"
+"lxc image alias delete [<remote>:]<alias>\n"
+"    Delete an alias.\n"
+"\n"
+"lxc image alias list [<remote>:] [filter]\n"
+"    List the aliases. Filters may be part of the image hash or part of the "
+"image alias name."
+msgstr ""
+
+#: lxc/info.go:25
+msgid ""
+"Usage: lxc info [<remote>:][<container>] [--show-log]\n"
+"\n"
+"Show container or server information.\n"
+"\n"
+"lxc info [<remote>:]<container> [--show-log]\n"
+"    For container information.\n"
+"\n"
+"lxc info [<remote>:]\n"
+"    For LXD server information."
+msgstr ""
+
+#: lxc/init.go:73
+#, fuzzy
+msgid ""
+"Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
+"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"\n"
+"Create containers from images.\n"
+"\n"
+"Not specifying -p will result in the default profile.\n"
+"Specifying \"-p\" with no argument will result in no profile.\n"
+"\n"
+"Examples:\n"
+"    lxc init ubuntu:16.04 u1"
+msgstr ""
+"lxc init <Abbild> [<Name>] [--ephemeral|-e] [--profile|-p <Profil>...]\n"
+"\n"
+"Initialisiert einen Container mit Namen von dem angegebenen Abbild .\n"
+"\n"
+"Ohne den -p Parameter wird das default Profil benutzt.\n"
+"Wird -p ohne Argument angegeben, wird kein Profil verwendet\n"
+"\n"
+"Beispiel:\n"
+"lxc init ubuntu u1\n"
+
+#: lxc/launch.go:23
+#, fuzzy
+msgid ""
+"Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
+"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"\n"
+"Create and start containers from images.\n"
+"\n"
+"Not specifying -p will result in the default profile.\n"
+"Specifying \"-p\" with no argument will result in no profile.\n"
+"\n"
+"Examples:\n"
+"    lxc launch ubuntu:16.04 u1"
+msgstr ""
+"Starte Container von gegebenem Abbild.\n"
+"\n"
+"lxc launch <Abbild> [<Name>] [—ephemeral|-e] [—profile|-p <Profile>…]\n"
+"\n"
+"Startet einen Container von gegebenem Abbild und mit Namen\n"
+"\n"
+"Ohne den -p Parameter wird das default Profil benutzt.\n"
+"Wird -p ohne Argument angegeben, wird kein Profil verwendet\n"
+"\n"
+"Beispiel:\n"
+"lxc launch ubuntu u1\n"
+
+#: lxc/list.go:46
+#, fuzzy
+msgid ""
+"Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
+"[--fast]\n"
+"\n"
+"List the existing containers.\n"
+"\n"
+"Default column layout: ns46tS\n"
+"Fast column layout: nsacPt\n"
+"\n"
+"*Filters*\n"
+"A single keyword like \"web\" which will list any container with a name "
+"starting by \"web\".\n"
+"\n"
+"A regular expression on the container name. (e.g. .*web.*01$).\n"
+"\n"
+"A key/value pair referring to a configuration item. For those, the namespace "
+"can be abbreviated to the smallest unambiguous identifier.\n"
+"    - \"user.blah=abc\" will list all containers with the \"blah\" user "
+"property set to \"abc\".\n"
+"\n"
+"    - \"u.blah=abc\" will do the same\n"
+"\n"
+"    - \"security.privileged=1\" will list all privileged containers\n"
+"\n"
+"    - \"s.privileged=1\" will do the same\n"
+"\n"
+"A regular expression matching a configuration item or its value. (e.g. "
+"volatile.eth0.hwaddr=00:16:3e:.*).\n"
+"\n"
+"*Columns*\n"
+"The -c option takes a comma separated list of arguments that control\n"
+"which container attributes to output when displaying in table format.\n"
+"\n"
+"Column arguments are either pre-defined shorthand chars (see below),\n"
+"or (extended) config keys.\n"
+"\n"
+"Commas between consecutive shorthand chars are optional.\n"
+"\n"
+"Pre-defined column shorthand chars:\n"
+"\n"
+"    4 - IPv4 address\n"
+"\n"
+"    6 - IPv6 address\n"
+"\n"
+"    a - Architecture\n"
+"\n"
+"    c - Creation date\n"
+"\n"
+"    l - Last used date\n"
+"\n"
+"    n - Name\n"
+"\n"
+"    p - PID of the container's init process\n"
+"\n"
+"    P - Profiles\n"
+"\n"
+"    s - State\n"
+"\n"
+"    S - Number of snapshots\n"
+"\n"
+"    t - Type (persistent or ephemeral)\n"
+"\n"
+"Custom columns are defined with \"key[:name][:maxWidth]\":\n"
+"\n"
+"    KEY: The (extended) config key to display\n"
+"\n"
+"    NAME: Name to display in the column header.\n"
+"    Defaults to the key if not specified or empty.\n"
+"\n"
+"    MAXWIDTH: Max width of the column (longer results are truncated).\n"
+"    Defaults to -1 (unlimited). Use 0 to limit to the column header size.\n"
+"\n"
+"*Examples*\n"
+"lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:"
+"MAC\n"
+"    Shows a list of containers using the \"NAME\", \"BASE IMAGE\", \"STATE"
+"\", \"IPV4\",\n"
+"    \"IPV6\" and \"MAC\" columns.\n"
+"\n"
+"    \"BASE IMAGE\" and \"MAC\" are custom columns generated from container "
+"configuration keys."
 msgstr ""
+"Listet vorhandene Ressourcen.\n"
+"\n"
+"lxc list [Resource] [Filter]\n"
+"\n"
+"Filter sind:\n"
+"* Ein einzelnes Schlüsselwort wie \"web“, was alle Container mit \"web\" im "
+"Namen listet.\n"
+"* Ein key/value Paar bezüglich eines Konfigurationsparameters. Dafür kann "
+"der Namensraum, solange eindeutig, abgekürzt werden:\n"
+"* \"user.blah=abc\" listet alle Container mit der \"blah\" Benutzer "
+"Eigenschaft \"abc\"\n"
+"* \"u.blah=abc\" ebenfalls\n"
+"* \"security.privileged=1\" listet alle privilegierten Container\n"
+"* \"s.privileged=1\" ebenfalls\n"
 
-#: lxc/action.go:99 lxc/launch.go:137
-#, c-format
-msgid "Try `lxc info --show-log %s` for more info"
+#: lxc/manpage.go:20
+msgid ""
+"Usage: lxc manpage <directory>\n"
+"\n"
+"Generate all the LXD manpages."
 msgstr ""
 
-#: lxc/info.go:100
-msgid "Type: ephemeral"
+#: lxc/monitor.go:41
+msgid ""
+"Usage: lxc monitor [<remote>:] [--type=TYPE...]\n"
+"\n"
+"Monitor a local or remote LXD server.\n"
+"\n"
+"By default the monitor will listen to all message types.\n"
+"\n"
+"Message types to listen for can be specified with --type.\n"
+"\n"
+"*Examples*\n"
+"lxc monitor --type=logging\n"
+"    Only show log message."
 msgstr ""
 
-#: lxc/info.go:102
-msgid "Type: persistent"
+#: lxc/move.go:16
+#, fuzzy
+msgid ""
+"Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
+"<snapshot>]]\n"
+"\n"
+"Move containers within or in between LXD instances.\n"
+"\n"
+"lxc move [<remote>:]<source container> [<remote>:][<destination container>]\n"
+"    Move a container between two hosts, renaming it if destination name "
+"differs.\n"
+"\n"
+"lxc move <old name> <new name>\n"
+"    Rename a local container.\n"
+"\n"
+"lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
+"    Rename a snapshot."
 msgstr ""
+"Verschiebt Container innerhalb einer oder zwischen lxd Instanzen\n"
+"\n"
+"lxc move <Quelle> <Ziel>\n"
 
-#: lxc/image.go:624
-msgid "UPLOAD DATE"
+#: lxc/profile.go:48
+#, fuzzy
+msgid ""
+"Usage: lxc profile <subcommand> [options]\n"
+"\n"
+"Manage container configuration profiles.\n"
+"\n"
+"*Profile configuration*\n"
+"lxc profile list [<remote>:]\n"
+"    List available profiles.\n"
+"\n"
+"lxc profile show [<remote>:]<profile>\n"
+"    Show details of a profile.\n"
+"\n"
+"lxc profile create [<remote>:]<profile>\n"
+"    Create a profile.\n"
+"\n"
+"lxc profile copy [<remote>:]<profile> [<remote>:]<profile>\n"
+"    Copy the profile.\n"
+"\n"
+"lxc profile get [<remote>:]<profile> <key>\n"
+"    Get profile configuration.\n"
+"\n"
+"lxc profile set [<remote>:]<profile> <key> <value>\n"
+"    Set profile configuration.\n"
+"\n"
+"lxc profile unset [<remote>:]<profile> <key>\n"
+"    Unset profile configuration.\n"
+"\n"
+"lxc profile delete [<remote>:]<profile>\n"
+"    Delete a profile.\n"
+"\n"
+"lxc profile edit [<remote>:]<profile>\n"
+"    Edit profile, either by launching external editor or reading STDIN.\n"
+"\n"
+"*Profile assignment*\n"
+"lxc profile apply [<remote>:]<container> <profiles>\n"
+"    Replace the current set of profiles for the container by the one "
+"provided.\n"
+"\n"
+"*Device management*\n"
+"lxc profile device list [<remote>:]<profile>\n"
+"    List devices in the given profile.\n"
+"\n"
+"lxc profile device show [<remote>:]<profile>\n"
+"    Show full device details in the given profile.\n"
+"\n"
+"lxc profile device remove [<remote>:]<profile> <name>\n"
+"    Remove a device from a profile.\n"
+"\n"
+"lxc profile device get [<remote>:]<profile> <name> <key>\n"
+"    Get a device property.\n"
+"\n"
+"lxc profile device set [<remote>:]<profile> <name> <key> <value>\n"
+"    Set a device property.\n"
+"\n"
+"lxc profile device unset [<remote>:]<profile> <name> <key>\n"
+"    Unset a device property.\n"
+"\n"
+"lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]\n"
+"    Add a profile device, such as a disk or a nic, to the containers using "
+"the specified profile.\n"
+"\n"
+"*Examples*\n"
+"cat profile.yaml | lxc profile edit <profile>\n"
+"    Update a profile using the content of profile.yaml\n"
+"\n"
+"lxc profile apply foo default,bar\n"
+"    Set the profiles for \"foo\" to \"default\" and \"bar\".\n"
+"\n"
+"lxc profile apply foo default\n"
+"    Reset \"foo\" to only using the \"default\" profile.\n"
+"\n"
+"lxc profile apply foo ''\n"
+"    Remove all profile from \"foo\""
 msgstr ""
+"Verwalte Konfigurationsprofile.\n"
+"\n"
+"lxc profile list [Filter]                     Listet verfügbare Profile\n"
+"lxc profile show <Profil>                     Zeigt Details zu einem Profil\n"
+"lxc profile create <Profil>                   Erstellt ein Profil\n"
+"lxc profile edit <Profil>                     Bearbeitet das Profil in "
+"externem Editor\n"
+"lxc profile copy <Profil> <remote>            Kopiert das Profil zur "
+"entfernten Instanz\n"
+"lxc profile set <Profil> <key> <value>        Setzt eine "
+"Profilkonfiguration\n"
+"lxc profile delete <Profil>                   Löscht ein Profil\n"
+"lxc profile apply <Container> <Profil>\n"
+"    Wendet eine durch Kommata getrennte Liste von Profilen,\n"
+"    in gegeben Reihenfolge, auf einen Container an.\n"
+"    Alle angegeben, und nur diese, werden auf den Container angewandt.\n"
+"    Beispiel: lxc profile apply foo default,bar # Wende default und bar an\n"
+"             lxc profile apply foo default # Nur default ist aktiv\n"
+"             lxc profile apply '' # keine Profile werden angewandt\n"
+"             lxc profile apply bar,default # Wende nun default als zweites "
+"an\n"
+"\n"
+"Geräte:\n"
+"lxc profile device list <Profil>              Listet Geräte im Profil\n"
+"lxc profile device show <Profil>              Zeigt alle Geräte Details im "
+"gegebenen Profil.\n"
+"lxc profile device remove <Profil> <name>     Entfernt ein Gerät von dem "
+"Profil.\n"
+"lxc profile device add <Profil name> <Gerätename> <Gerätetype> "
+"[key=value]...\n"
+"    Fügt ein Gerät, wie zum Beispiel eine Festplatte oder Netzwerkkarte, den "
+"Containern hinzu,\n"
+"    die dieses Profil verwenden.\n"
 
-#: lxc/remote.go:353
-msgid "URL"
+#: lxc/publish.go:26
+msgid ""
+"Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--"
+"alias=ALIAS...] [prop-key=prop-value...]\n"
+"\n"
+"Publish containers as images."
 msgstr ""
 
-#: lxc/remote.go:81
-msgid "Unable to read remote TLS certificate"
+#: lxc/remote.go:37
+#, fuzzy
+msgid ""
+"Usage: lxc remote <subcommand> [options]\n"
+"\n"
+"Manage the list of remote LXD servers.\n"
+"\n"
+"lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--"
+"password=PASSWORD] [--public] [--protocol=PROTOCOL]\n"
+"    Add the remote <remote> at <url>.\n"
+"\n"
+"lxc remote remove <remote>\n"
+"    Remove the remote <remote>.\n"
+"\n"
+"lxc remote list\n"
+"    List all remotes.\n"
+"\n"
+"lxc remote rename <old name> <new name>\n"
+"    Rename remote <old name> to <new name>.\n"
+"\n"
+"lxc remote set-url <remote> <url>\n"
+"    Update <remote>'s url to <url>.\n"
+"\n"
+"lxc remote set-default <remote>\n"
+"    Set the default remote.\n"
+"\n"
+"lxc remote get-default\n"
+"    Print the default remote."
 msgstr ""
+"Verwalte entfernte LXD Server.\n"
+"\n"
+"lxc remote add <Name> <url> [--accept-certificate] [--password=PASSWORT]  "
+"Fügt die Instanz <Name> auf <URL> hinzu.\n"
+"lxc remote remove <Name>                                                  "
+"Entfernt die Instanz <Name>.\n"
+"lxc remote list                                                           "
+"Listet alle entfernte Instanzen.\n"
+"lxc remote rename <alt> <neu>                                             "
+"Benennt Instanz von <alt> nach <neu> um.\n"
+"lxc remote set-url <Name> <url>                                           "
+"Setzt die URL von <Name> auf <url>.\n"
+"lxc remote set-default <Name>                                             "
+"Setzt die Standard Instanz.\n"
+"lxc remote get-default                                                    "
+"Gibt die Standard Instanz aus.\n"
 
-#: lxc/image.go:346
-#, c-format
-msgid "Uploaded: %s"
+#: lxc/restore.go:21
+msgid ""
+"Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
+"\n"
+"Restore containers from snapshots.\n"
+"\n"
+"If --stateful is passed, then the running state will be restored too.\n"
+"\n"
+"*Examples*\n"
+"lxc snapshot u1 snap0\n"
+"    Create the snapshot.\n"
+"\n"
+"lxc restore u1 snap0\n"
+"    Restore the snapshot."
 msgstr ""
 
-#: lxc/main.go:108
-#, fuzzy, c-format
-msgid "Usage: %s"
-msgstr ""
-"Benutzung: %s\n"
+#: lxc/snapshot.go:21
+msgid ""
+"Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
 "\n"
-"Optionen:\n"
+"Create container snapshots.\n"
 "\n"
+"When --stateful is used, LXD attempts to checkpoint the container's\n"
+"running state, including process memory state, TCP connections, ...\n"
+"\n"
+"*Examples*\n"
+"lxc snapshot u1 snap0\n"
+"    Create a snapshot of \"u1\" called \"snap0\"."
+msgstr ""
 
-#: lxc/help.go:48
+#: lxc/version.go:18
 #, fuzzy
-msgid "Usage: lxc [subcommand] [options]"
+msgid ""
+"Usage: lxc version\n"
+"\n"
+"Print the version number of this client tool."
 msgstr ""
-"Benutzung: lxc [Unterbefehl] [Optionen]\n"
-"Verfügbare Befehle:\n"
+"Zeigt die Versionsnummer von LXD.\n"
+"\n"
+"lxc version\n"
 
-#: lxc/delete.go:46
+#: lxc/delete.go:45
 msgid "User aborted delete operation."
 msgstr ""
 
-#: lxc/restore.go:35
+#: lxc/restore.go:37
 msgid ""
 "Whether or not to restore the container's running state from snapshot (if "
 "available)"
@@ -1458,137 +1569,347 @@ msgstr ""
 "Laufenden Zustand des Containers aus dem Sicherungspunkt (falls vorhanden) "
 "wiederherstellen oder nicht"
 
-#: lxc/snapshot.go:38
+#: lxc/snapshot.go:35
 msgid "Whether or not to snapshot the container's running state"
 msgstr "Zustand des laufenden Containers sichern oder nicht"
 
-#: lxc/config.go:32
-msgid "Whether to show the expanded configuration"
-msgstr ""
-
-#: lxc/remote.go:328 lxc/remote.go:333
+#: lxc/remote.go:341 lxc/remote.go:346
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:52
+#: lxc/main.go:55
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:111
+#: lxc/launch.go:104
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "Falsche Anzahl an Objekten im Abbild, Container oder Sicherungspunkt gelesen."
 
-#: lxc/action.go:95
+#: lxc/action.go:94
 msgid "bad result type from action"
 msgstr ""
 
-#: lxc/copy.go:99
+#: lxc/copy.go:100
 msgid "can't copy to the same container name"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/remote.go:316
+#: lxc/remote.go:329
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:342
+#: lxc/remote.go:355
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
+#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:88 lxc/launch.go:93
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
-#: lxc/image.go:332
+#: lxc/image.go:312
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:334
+#: lxc/image.go:314
 msgid "enabled"
 msgstr ""
 
-#: lxc/main.go:22 lxc/main.go:145
+#: lxc/action.go:126 lxc/main.go:25 lxc/main.go:160
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "Fehler: %v\n"
 
-#: lxc/help.go:40 lxc/main.go:103
+#: lxc/help.go:37 lxc/main.go:106
 #, fuzzy, c-format
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/launch.go:115
+#: lxc/launch.go:108
 msgid "got bad version"
 msgstr "Versionskonflikt"
 
-#: lxc/image.go:327 lxc/image.go:600
+#: lxc/image.go:307 lxc/image.go:585
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:122
+#: lxc/copy.go:123
 msgid "not all the profiles from the source exist on the target"
 msgstr "nicht alle Profile der Quelle sind am Ziel vorhanden."
 
-#: lxc/remote.go:196
+#: lxc/remote.go:209
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "OK (y/n)? "
 
-#: lxc/main.go:265 lxc/main.go:269
+#: lxc/main.go:295 lxc/main.go:299
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:378
+#: lxc/remote.go:391
 #, c-format
 msgid "remote %s already exists"
 msgstr "entfernte Instanz %s existiert bereits"
 
-#: lxc/remote.go:308 lxc/remote.go:370 lxc/remote.go:405 lxc/remote.go:421
+#: lxc/remote.go:321 lxc/remote.go:383 lxc/remote.go:418 lxc/remote.go:434
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/remote.go:291
+#: lxc/remote.go:304
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "entfernte Instanz %s existiert als <%s>"
 
-#: lxc/remote.go:312 lxc/remote.go:374 lxc/remote.go:409
+#: lxc/remote.go:325 lxc/remote.go:387 lxc/remote.go:422
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
 
-#: lxc/info.go:208
+#: lxc/info.go:210
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:210
+#: lxc/info.go:212
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:204
+#: lxc/info.go:206
 #, c-format
 msgid "taken at %s"
 msgstr ""
 
-#: lxc/exec.go:163
+#: lxc/exec.go:167
 msgid "unreachable return reached"
 msgstr ""
 
-#: lxc/main.go:205
+#: lxc/main.go:234
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:604
+#: lxc/delete.go:44 lxc/image.go:309 lxc/image.go:589
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:38
+#: lxc/copy.go:39
 msgid "you must specify a source container name"
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
 #, fuzzy
+#~ msgid ""
+#~ "Changes state of one or more containers to %s.\n"
+#~ "\n"
+#~ "lxc %s <name> [<name>...]%s"
+#~ msgstr ""
+#~ "Ändert den Laufzustand eines Containers in %s.\n"
+#~ "\n"
+#~ "lxd %s <Name>\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "Copy containers within or in between lxd instances.\n"
+#~ "\n"
+#~ "lxc copy [remote:]<source container> [remote:]<destination container> [--"
+#~ "ephemeral|e]"
+#~ msgstr ""
+#~ "Kopiert Container innerhalb einer oder zwischen lxd Instanzen\n"
+#~ "\n"
+#~ "lxc copy <Quelle> <Ziel>\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "Fingers the LXD instance to check if it is up and working.\n"
+#~ "\n"
+#~ "lxc finger <remote>"
+#~ msgstr ""
+#~ "Fingert die LXD Instanz zum überprüfen ob diese funktionsfähig ist.\n"
+#~ "\n"
+#~ "lxc finger <remote>\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "Initialize a container from a particular image.\n"
+#~ "\n"
+#~ "lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-"
+#~ "p <profile>...] [--config|-c <key=value>...]\n"
+#~ "\n"
+#~ "Initializes a container using the specified image and name.\n"
+#~ "\n"
+#~ "Not specifying -p will result in the default profile.\n"
+#~ "Specifying \"-p\" with no argument will result in no profile.\n"
+#~ "\n"
+#~ "Example:\n"
+#~ "lxc init ubuntu u1"
+#~ msgstr ""
+#~ "Starte Container von gegebenem Abbild.\n"
+#~ "\n"
+#~ "lxc launch <Abbild> [<Name>] [—ephemeral|-e] [—profile|-p <Profile>…]\n"
+#~ "\n"
+#~ "Startet einen Container von gegebenem Abbild und mit Namen\n"
+#~ "\n"
+#~ "Ohne den -p Parameter wird das default Profil benutzt.\n"
+#~ "Wird -p ohne Argument angegeben, wird kein Profil verwendet\n"
+#~ "\n"
+#~ "Beispiel:\n"
+#~ "lxc launch ubuntu u1\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "List information on LXD servers and containers.\n"
+#~ "\n"
+#~ "For a container:\n"
+#~ " lxc info [<remote>:]container [--show-log]\n"
+#~ "\n"
+#~ "For a server:\n"
+#~ " lxc info [<remote>:]"
+#~ msgstr ""
+#~ "Listet Informationen über Container.\n"
+#~ "\n"
+#~ "Dies wird entfernte Instanzen und Abbilder unterstützen, \n"
+#~ "zur Zeit jedoch nur Container.\n"
+#~ "\n"
+#~ "lxc info [<remote>:]Container [--show-log]\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "Manage configuration.\n"
+#~ "\n"
+#~ "lxc config device add <[remote:]container> <name> <type> "
+#~ "[key=value]...     Add a device to a container.\n"
+#~ "lxc config device get <[remote:]container> <name> "
+#~ "<key>                     Get a device property.\n"
+#~ "lxc config device set <[remote:]container> <name> <key> "
+#~ "<value>             Set a device property.\n"
+#~ "lxc config device unset <[remote:]container> <name> "
+#~ "<key>                   Unset a device property.\n"
+#~ "lxc config device list "
+#~ "<[remote:]container>                                 List devices for "
+#~ "container.\n"
+#~ "lxc config device show "
+#~ "<[remote:]container>                                 Show full device "
+#~ "details for container.\n"
+#~ "lxc config device remove <[remote:]container> "
+#~ "<name>                        Remove device from container.\n"
+#~ "\n"
+#~ "lxc config get [remote:][container] "
+#~ "<key>                                   Get container or server "
+#~ "configuration key.\n"
+#~ "lxc config set [remote:][container] <key> "
+#~ "<value>                           Set container or server configuration "
+#~ "key.\n"
+#~ "lxc config unset [remote:][container] "
+#~ "<key>                                 Unset container or server "
+#~ "configuration key.\n"
+#~ "lxc config show [remote:][container] [--"
+#~ "expanded]                           Show container or server "
+#~ "configuration.\n"
+#~ "lxc config edit [remote:]"
+#~ "[container]                                        Edit container or "
+#~ "server configuration in external editor.\n"
+#~ "    Edit configuration, either by launching external editor or reading "
+#~ "STDIN.\n"
+#~ "    Example: lxc config edit <container> # launch editor\n"
+#~ "             cat config.yaml | lxc config edit <config> # read from "
+#~ "config.yaml\n"
+#~ "\n"
+#~ "lxc config trust list "
+#~ "[remote]                                              List all trusted "
+#~ "certs.\n"
+#~ "lxc config trust add [remote] <certfile."
+#~ "crt>                                Add certfile.crt to trusted hosts.\n"
+#~ "lxc config trust remove [remote] [hostname|"
+#~ "fingerprint]                     Remove the cert from trusted hosts.\n"
+#~ "\n"
+#~ "Examples:\n"
+#~ "To mount host's /share/c1 onto /opt in the container:\n"
+#~ "    lxc config device add [remote:]container1 <device-name> disk source=/"
+#~ "share/c1 path=opt\n"
+#~ "\n"
+#~ "To set an lxc config value:\n"
+#~ "    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete "
+#~ "= 1'\n"
+#~ "\n"
+#~ "To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the "
+#~ "default):\n"
+#~ "    lxc config set core.https_address [::]:8443\n"
+#~ "\n"
+#~ "To set the server trust password:\n"
+#~ "    lxc config set core.trust_password blah"
+#~ msgstr ""
+#~ "Verwalte Konfiguration.\n"
+#~ "\n"
+#~ "lxc config device add <Container> <Name> <Type> [key=value]...\n"
+#~ "               Füge ein Gerät zu einem Container hinzu\n"
+#~ "lxc config device list <Container>                     Listet die Geräte "
+#~ "des Containers\n"
+#~ "lxc config device show <Container>                     Zeigt alle Geräte "
+#~ "Details für den Container\n"
+#~ "lxc config device remove <Container> <Name>            Entfernt Gerät vom "
+#~ "Container\n"
+#~ "lxc config edit <Container>                            Bearbeite "
+#~ "Container Konfiguration in externem Editor\n"
+#~ "lxc config get <Container> key                         Holt "
+#~ "Konfigurationsschlüssel\n"
+#~ "lxc config set <Container> key [value]                 Setzt Container "
+#~ "Konfigurationsschlüssel\n"
+#~ "lxc config show <Container>                            Zeigt "
+#~ "Konfiguration des Containers\n"
+#~ "lxc config trust list [remote]                         Liste alle "
+#~ "vertrauenswürdigen Zertifikate.\n"
+#~ "lxc config trust add [remote] [certfile.crt]           Füge certfile.crt "
+#~ "zu vertrauenden Instanzen hinzu.\n"
+#~ "lxc config trust remove [remote] [hostname|fingerprint]\n"
+#~ "               Löscht das Zertifikat aus der Liste der "
+#~ "vertrauenswürdigen.\n"
+#~ "\n"
+#~ "Beispiele:\n"
+#~ "Zum mounten von /share/c1 des Hosts nach /opt im Container:\n"
+#~ "\tlxc config device add container1 mntdir disk source=/share/c1 path=opt\n"
+#~ "Um einen lxc config Wert zu setzen:\n"
+#~ "\tlxc config set <container> raw.lxc 'lxc.aa_allow_incomplete = 1'\n"
+#~ "Um das Server Passwort zur authentifizierung zu setzen:\n"
+#~ "\tlxc config set core.trust_password blah\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "Manage files on a container.\n"
+#~ "\n"
+#~ "lxc file pull <source> [<source>...] <target>\n"
+#~ "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> "
+#~ "[<source>...] <target>\n"
+#~ "lxc file edit <file>\n"
+#~ "\n"
+#~ "<source> in the case of pull, <target> in the case of push and <file> in "
+#~ "the case of edit are <container name>/<path>"
+#~ msgstr ""
+#~ "Verwaltet Dateien in einem Container.\n"
+#~ "\n"
+#~ "lxc file pull <Quelle> [<Quelle>...] <Ziel>\n"
+#~ "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <Quelle> "
+#~ "[<Quelle>...] <Ziel>\n"
+#~ "\n"
+#~ "<Quelle> bei pull und <Ziel> bei push sind jeweils von der Form "
+#~ "<Container Name>/<Pfad>\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "Presents details on how to use LXD.\n"
+#~ "\n"
+#~ "lxd help [--all]"
+#~ msgstr ""
+#~ "Zeigt Details über die Benutzung von LXD an.\n"
+#~ "\n"
+#~ "lxd help [—all]\n"
+
+#, fuzzy
+#~ msgid "Usage: %s"
+#~ msgstr ""
+#~ "Benutzung: %s\n"
+#~ "\n"
+#~ "Optionen:\n"
+#~ "\n"
+
+#, fuzzy
 #~ msgid "Profile %s added to %s"
 #~ msgstr "Profil %s wurde auf %s angewandt\n"
 
@@ -1786,29 +2107,6 @@ msgstr "der Name des Ursprung Containers muss angegeben werden"
 #~ "Fehler: %v\n"
 #~ "%s"
 
-#, fuzzy
-#~ msgid ""
-#~ "lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-"
-#~ "p <profile>...]\n"
-#~ "\n"
-#~ "Initializes a container using the specified image and name.\n"
-#~ "\n"
-#~ "Not specifying -p will result in the default profile.\n"
-#~ "Specifying \"-p\" with no argument will result in no profile.\n"
-#~ "\n"
-#~ "Example:\n"
-#~ "lxc init ubuntu u1\n"
-#~ msgstr ""
-#~ "lxc init <Abbild> [<Name>] [--ephemeral|-e] [--profile|-p <Profil>...]\n"
-#~ "\n"
-#~ "Initialisiert einen Container mit Namen von dem angegebenen Abbild .\n"
-#~ "\n"
-#~ "Ohne den -p Parameter wird das default Profil benutzt.\n"
-#~ "Wird -p ohne Argument angegeben, wird kein Profil verwendet\n"
-#~ "\n"
-#~ "Beispiel:\n"
-#~ "lxc init ubuntu u1\n"
-
 #~ msgid ""
 #~ "Changing the name of a running container during copy isn't supported."
 #~ msgstr ""
diff --git a/po/fr.po b/po/fr.po
index 77c01e87a..775ae18fd 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-10-26 17:06-0400\n"
+"POT-Creation-Date: 2017-04-17 17:27-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -16,19 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:143
-msgid "  Disk usage:"
-msgstr ""
-
-#: lxc/info.go:166
-msgid "  Memory usage:"
-msgstr ""
-
-#: lxc/info.go:183
-msgid "  Network usage:"
-msgstr ""
-
-#: lxc/config.go:36
+#: lxc/config.go:37
 msgid ""
 "### This is a yaml representation of the configuration.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -49,7 +37,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:83
+#: lxc/image.go:50
 msgid ""
 "### This is a yaml representation of the image properties.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -59,7 +47,7 @@ msgid ""
 "###  description: My custom image"
 msgstr ""
 
-#: lxc/profile.go:26
+#: lxc/profile.go:27
 msgid ""
 "### This is a yaml representation of the profile.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -80,243 +68,201 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:597
+#: lxc/image.go:582
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/snapshot.go:61
+#: lxc/snapshot.go:58
 #, fuzzy
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 
-#: lxc/profile.go:226
+#: lxc/profile.go:264
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:618 lxc/image.go:647
+#: lxc/image.go:603 lxc/image.go:632
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:622
+#: lxc/image.go:607
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:394
+#: lxc/list.go:418
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:52
+#: lxc/remote.go:65
 msgid "Accept certificate"
 msgstr ""
 
-#: lxc/remote.go:245
+#: lxc/remote.go:258
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
 
-#: lxc/image.go:356
+#: lxc/image.go:341
 msgid "Aliases:"
 msgstr ""
 
-#: lxc/exec.go:54
-msgid "An environment variable of the form HOME=/home/foo"
-msgstr ""
-
-#: lxc/image.go:339 lxc/info.go:93
+#: lxc/image.go:319 lxc/info.go:95
 #, c-format
 msgid "Architecture: %s"
 msgstr ""
 
-#: lxc/image.go:360
+#: lxc/image.go:345
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/help.go:49
-msgid "Available commands:"
-msgstr ""
-
-#: lxc/info.go:175
+#: lxc/info.go:177
 msgid "Bytes received"
 msgstr ""
 
-#: lxc/info.go:176
+#: lxc/info.go:178
 msgid "Bytes sent"
 msgstr ""
 
-#: lxc/config.go:273
+#: lxc/config.go:310
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:395
+#: lxc/list.go:419
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:113
+#: lxc/config.go:150
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr ""
 
-#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181
+#: lxc/config.go:163 lxc/config.go:196 lxc/config.go:218
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:343
+#: lxc/profile.go:394
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:195
+#: lxc/remote.go:208
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %x"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/action.go:33
-#, fuzzy, c-format
-msgid ""
-"Changes state of one or more containers to %s.\n"
-"\n"
-"lxc %s <name> [<name>...]%s"
-msgstr "Change l'état du conteneur à %s.\n"
-
-#: lxc/remote.go:268
+#: lxc/remote.go:281
 msgid "Client certificate stored at server: "
 msgstr "Certificat client enregistré avec le serveur: "
 
-#: lxc/list.go:99 lxc/list.go:100
+#: lxc/list.go:122 lxc/list.go:123
 msgid "Columns"
 msgstr ""
 
-#: lxc/init.go:134 lxc/init.go:135 lxc/launch.go:40 lxc/launch.go:41
+#: lxc/help.go:53
+msgid "Commands:"
+msgstr ""
+
+#: lxc/init.go:133 lxc/init.go:134
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:701 lxc/profile.go:190
+#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:686 lxc/profile.go:228
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
 
-#: lxc/main.go:29
+#: lxc/main.go:32
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
-#: lxc/publish.go:59
+#: lxc/publish.go:60
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:210
+#: lxc/init.go:211
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:141 lxc/publish.go:156
+#: lxc/publish.go:158 lxc/publish.go:173
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/image.go:164
+#: lxc/image.go:132
 msgid "Copy aliases from source"
 msgstr ""
 
-#: lxc/copy.go:22
-msgid ""
-"Copy containers within or in between lxd instances.\n"
-"\n"
-"lxc copy [remote:]<source container> [remote:]<destination container> [--"
-"ephemeral|e]"
-msgstr ""
-
-#: lxc/image.go:277
+#: lxc/image.go:244
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:210
+#: lxc/remote.go:223
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
-#: lxc/snapshot.go:21
-msgid ""
-"Create a read-only snapshot of a container.\n"
-"\n"
-"lxc snapshot [remote:]<source> <snapshot name> [--stateful]\n"
-"\n"
-"Creates a snapshot of the container (optionally with the container's memory\n"
-"state). When --stateful is used, LXD attempts to checkpoint the container's\n"
-"running state, including process memory state, TCP connections, etc. so that "
-"it\n"
-"can be restored (via lxc restore) at a later time (although some things, e."
-"g.\n"
-"TCP connections after the TCP timeout window has expired, may not be "
-"restored\n"
-"successfully).\n"
-"\n"
-"Example:\n"
-"lxc snapshot u1 snap0"
-msgstr ""
-
-#: lxc/image.go:344 lxc/info.go:95
+#: lxc/image.go:324 lxc/info.go:97
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:177 lxc/launch.go:118
+#: lxc/init.go:176 lxc/launch.go:111
 #, c-format
 msgid "Creating %s"
 msgstr ""
 
-#: lxc/init.go:175
+#: lxc/init.go:174
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:621 lxc/image.go:649
+#: lxc/image.go:606 lxc/image.go:634
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/delete.go:25
-msgid ""
-"Delete containers or container snapshots.\n"
-"\n"
-"lxc delete [remote:]<container>[/<snapshot>] [remote:][<container>[/"
-"<snapshot>]...]\n"
-"\n"
-"Destroy containers or snapshots with any attached data (configuration, "
-"snapshots, ...)."
-msgstr ""
-
-#: lxc/config.go:647
+#: lxc/config.go:688
 #, c-format
 msgid "Device %s added to %s"
 msgstr ""
 
-#: lxc/config.go:834
+#: lxc/config.go:875
 #, c-format
 msgid "Device %s removed from %s"
 msgstr ""
 
-#: lxc/list.go:478
+#: lxc/info.go:145
+msgid "Disk usage:"
+msgstr ""
+
+#: lxc/list.go:504
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:275
+#: lxc/config.go:312
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:41
-msgid "Enables debug mode."
+#: lxc/main.go:44
+#, fuzzy
+msgid "Enable debug mode"
 msgstr "Active le mode de déboguage."
 
-#: lxc/main.go:40
-msgid "Enables verbose mode."
+#: lxc/main.go:43
+#, fuzzy
+msgid "Enable verbose mode"
 msgstr "Active le mode verbeux."
 
-#: lxc/help.go:69
+#: lxc/exec.go:55
+msgid "Environment variable to set (e.g. HOME=/home/foo)"
+msgstr ""
+
+#: lxc/help.go:77
 msgid "Environment:"
 msgstr ""
 
-#: lxc/copy.go:29 lxc/copy.go:30 lxc/init.go:138 lxc/init.go:139
-#: lxc/launch.go:44 lxc/launch.go:45
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:137 lxc/init.go:138
 msgid "Ephemeral container"
 msgstr ""
 
@@ -324,131 +270,106 @@ msgstr ""
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/exec.go:45
-#, fuzzy
-msgid ""
-"Execute the specified command in a container.\n"
-"\n"
-"lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
-"EDITOR=/usr/bin/vim]... [--] <command line>\n"
-"\n"
-"Mode defaults to non-interactive, interactive mode is selected if both stdin "
-"AND stdout are terminals (stderr is ignored)."
-msgstr "Exécute la commande spécifiée dans un conteneur.\n"
-
-#: lxc/image.go:348
+#: lxc/image.go:328
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:350
+#: lxc/image.go:330
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:272 lxc/image.go:619 lxc/image.go:648
+#: lxc/config.go:309 lxc/image.go:604 lxc/image.go:633
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/list.go:102
-msgid "Fast mode (same as --columns=nsacPt"
+#: lxc/manpage.go:62
+#, c-format
+msgid "Failed to generate 'lxc.%s.1': %v"
+msgstr ""
+
+#: lxc/manpage.go:55
+#, c-format
+msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/image.go:337
+#: lxc/list.go:125
+msgid "Fast mode (same as --columns=nsacPt)"
+msgstr ""
+
+#: lxc/image.go:317
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/finger.go:15
+#: lxc/action.go:46 lxc/action.go:47
 #, fuzzy
-msgid ""
-"Fingers the LXD instance to check if it is up and working.\n"
-"\n"
-"lxc finger <remote>"
-msgstr "Contacte LXD pour voir s'il est fonctionel.\n"
-
-#: lxc/action.go:42 lxc/action.go:43
-msgid "Force the container to shutdown."
+msgid "Force the container to shutdown"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/delete.go:34 lxc/delete.go:35
-msgid "Force the removal of stopped containers."
+#: lxc/delete.go:33 lxc/delete.go:34
+msgid "Force the removal of stopped containers"
 msgstr ""
 
-#: lxc/main.go:42
-msgid "Force using the local unix socket."
+#: lxc/main.go:45
+msgid "Force using the local unix socket"
 msgstr ""
 
-#: lxc/list.go:101
+#: lxc/list.go:124
 msgid "Format"
 msgstr ""
 
-#: lxc/main.go:124
+#: lxc/main.go:131
 #, fuzzy
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Géneration d'un certificat client. Ceci peut prendre une minute...\n"
 
-#: lxc/list.go:392
+#: lxc/list.go:416
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:393
+#: lxc/list.go:417
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:274
+#: lxc/config.go:311
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:132
+#: lxc/main.go:139
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:43
-msgid "Ignore aliases when determining what command to run."
+#: lxc/main.go:46
+msgid "Ignore aliases when determining what command to run"
 msgstr ""
 
-#: lxc/action.go:46
+#: lxc/action.go:50
 #, fuzzy
-msgid "Ignore the container state (only for start)."
+msgid "Ignore the container state (only for start)"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/image.go:282
+#: lxc/image.go:247
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:433
+#: lxc/image.go:400 lxc/image.go:412
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/image.go:420
+#: lxc/image.go:397
 #, c-format
 msgid "Importing the image: %s"
 msgstr ""
 
-#: lxc/init.go:73
-msgid ""
-"Initialize a container from a particular image.\n"
-"\n"
-"lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
-"\n"
-"Initializes a container using the specified image and name.\n"
-"\n"
-"Not specifying -p will result in the default profile.\n"
-"Specifying \"-p\" with no argument will result in no profile.\n"
-"\n"
-"Example:\n"
-"lxc init ubuntu u1"
-msgstr ""
-
-#: lxc/remote.go:121
+#: lxc/remote.go:134
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
 
-#: lxc/config.go:253
+#: lxc/config.go:290
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "Gérer la configuration.\n"
@@ -458,782 +379,1036 @@ msgstr "Gérer la configuration.\n"
 msgid "Invalid configuration key"
 msgstr "Gérer la configuration.\n"
 
-#: lxc/file.go:195
+#: lxc/file.go:207
 #, c-format
 msgid "Invalid source %s"
 msgstr "Source invalide %s"
 
-#: lxc/file.go:57
+#: lxc/file.go:69
 #, c-format
 msgid "Invalid target %s"
 msgstr "Destination invalide %s"
 
-#: lxc/info.go:124
+#: lxc/info.go:126
 msgid "Ips:"
 msgstr ""
 
-#: lxc/image.go:165
+#: lxc/image.go:133
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:27
+#: lxc/main.go:30
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/launch.go:22
-msgid ""
-"Launch a container from a particular image.\n"
-"\n"
-"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
-"\n"
-"Launches a container using the specified image and name.\n"
-"\n"
-"Not specifying -p will result in the default profile.\n"
-"Specifying \"-p\" with no argument will result in no profile.\n"
-"\n"
-"Example:\n"
-"lxc launch ubuntu:16.04 u1"
-msgstr ""
-
-#: lxc/info.go:25
-msgid ""
-"List information on LXD servers and containers.\n"
-"\n"
-"For a container:\n"
-" lxc info [<remote>:]container [--show-log]\n"
-"\n"
-"For a server:\n"
-" lxc info [<remote>:]"
+#: lxc/image.go:333
+#, c-format
+msgid "Last used: %s"
 msgstr ""
 
-#: lxc/list.go:67
-msgid ""
-"Lists the available resources.\n"
-"\n"
-"lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
-"\n"
-"The filters are:\n"
-"* A single keyword like \"web\" which will list any container with a name "
-"starting by \"web\".\n"
-"* A regular expression on the container name. (e.g. .*web.*01$)\n"
-"* A key/value pair referring to a configuration item. For those, the "
-"namespace can be abbreviated to the smallest unambiguous identifier:\n"
-" * \"user.blah=abc\" will list all containers with the \"blah\" user "
-"property set to \"abc\".\n"
-" * \"u.blah=abc\" will do the same\n"
-" * \"security.privileged=1\" will list all privileged containers\n"
-" * \"s.privileged=1\" will do the same\n"
-"* A regular expression matching a configuration item or its value. (e.g. "
-"volatile.eth0.hwaddr=00:16:3e:.*)\n"
-"\n"
-"Columns for table format are:\n"
-"* 4 - IPv4 address\n"
-"* 6 - IPv6 address\n"
-"* a - architecture\n"
-"* c - creation date\n"
-"* n - name\n"
-"* p - pid of container init process\n"
-"* P - profiles\n"
-"* s - state\n"
-"* S - number of snapshots\n"
-"* t - type (persistent or ephemeral)\n"
-"\n"
-"Default column layout: ns46tS\n"
-"Fast column layout: nsacPt"
+#: lxc/image.go:335
+msgid "Last used: never"
 msgstr ""
 
-#: lxc/info.go:228
+#: lxc/info.go:230
 msgid "Log:"
 msgstr ""
 
-#: lxc/image.go:163
+#: lxc/image.go:131
 msgid "Make image public"
 msgstr ""
 
-#: lxc/publish.go:32
+#: lxc/publish.go:33
 msgid "Make the image public"
 msgstr ""
 
-#: lxc/profile.go:47
-msgid ""
-"Manage configuration profiles.\n"
-"\n"
-"lxc profile list [filters]                     List available profiles.\n"
-"lxc profile show <profile>                     Show details of a profile.\n"
-"lxc profile create <profile>                   Create a profile.\n"
-"lxc profile copy <profile> <remote>            Copy the profile to the "
-"specified remote.\n"
-"lxc profile get <profile> <key>                Get profile configuration.\n"
-"lxc profile set <profile> <key> <value>        Set profile configuration.\n"
-"lxc profile unset <profile> <key>              Unset profile configuration.\n"
-"lxc profile delete <profile>                   Delete a profile.\n"
-"lxc profile edit <profile>\n"
-"    Edit profile, either by launching external editor or reading STDIN.\n"
-"    Example: lxc profile edit <profile> # launch editor\n"
-"             cat profile.yaml | lxc profile edit <profile> # read from "
-"profile.yaml\n"
-"lxc profile apply <container> <profiles>\n"
-"    Apply a comma-separated list of profiles to a container, in order.\n"
-"    All profiles passed in this call (and only those) will be applied\n"
-"    to the specified container.\n"
-"    Example: lxc profile apply foo default,bar # Apply default and bar\n"
-"             lxc profile apply foo default # Only default is active\n"
-"             lxc profile apply '' # no profiles are applied anymore\n"
-"             lxc profile apply bar,default # Apply default second now\n"
-"\n"
-"Devices:\n"
-"lxc profile device list <profile>                                   List "
-"devices in the given profile.\n"
-"lxc profile device show <profile>                                   Show "
-"full device details in the given profile.\n"
-"lxc profile device remove <profile> <name>                          Remove a "
-"device from a profile.\n"
-"lxc profile device get <[remote:]profile> <name> <key>              Get a "
-"device property.\n"
-"lxc profile device set <[remote:]profile> <name> <key> <value>      Set a "
-"device property.\n"
-"lxc profile device unset <[remote:]profile> <name> <key>            Unset a "
-"device property.\n"
-"lxc profile device add <profile name> <device name> <device type> "
-"[key=value]...\n"
-"    Add a profile device, such as a disk or a nic, to the containers\n"
-"    using the specified profile."
-msgstr ""
-
-#: lxc/config.go:57
-msgid ""
-"Manage configuration.\n"
-"\n"
-"lxc config device add <[remote:]container> <name> <type> [key=value]...     "
-"Add a device to a container.\n"
-"lxc config device get <[remote:]container> <name> <key>                     "
-"Get a device property.\n"
-"lxc config device set <[remote:]container> <name> <key> <value>             "
-"Set a device property.\n"
-"lxc config device unset <[remote:]container> <name> <key>                   "
-"Unset a device property.\n"
-"lxc config device list <[remote:]container>                                 "
-"List devices for container.\n"
-"lxc config device show <[remote:]container>                                 "
-"Show full device details for container.\n"
-"lxc config device remove <[remote:]container> <name>                        "
-"Remove device from container.\n"
-"\n"
-"lxc config get [remote:][container] <key>                                   "
-"Get container or server configuration key.\n"
-"lxc config set [remote:][container] <key> <value>                           "
-"Set container or server configuration key.\n"
-"lxc config unset [remote:][container] <key>                                 "
-"Unset container or server configuration key.\n"
-"lxc config show [remote:][container] [--expanded]                           "
-"Show container or server configuration.\n"
-"lxc config edit [remote:][container]                                        "
-"Edit container or server configuration in external editor.\n"
-"    Edit configuration, either by launching external editor or reading "
-"STDIN.\n"
-"    Example: lxc config edit <container> # launch editor\n"
-"             cat config.yaml | lxc config edit <config> # read from config."
-"yaml\n"
-"\n"
-"lxc config trust list [remote]                                              "
-"List all trusted certs.\n"
-"lxc config trust add [remote] <certfile.crt>                                "
-"Add certfile.crt to trusted hosts.\n"
-"lxc config trust remove [remote] [hostname|fingerprint]                     "
-"Remove the cert from trusted hosts.\n"
-"\n"
-"Examples:\n"
-"To mount host's /share/c1 onto /opt in the container:\n"
-"    lxc config device add [remote:]container1 <device-name> disk source=/"
-"share/c1 path=opt\n"
-"\n"
-"To set an lxc config value:\n"
-"    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete = "
-"1'\n"
-"\n"
-"To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the "
-"default):\n"
-"    lxc config set core.https_address [::]:8443\n"
-"\n"
-"To set the server trust password:\n"
-"    lxc config set core.trust_password blah"
-msgstr ""
-
-#: lxc/file.go:32
-msgid ""
-"Manage files on a container.\n"
-"\n"
-"lxc file pull <source> [<source>...] <target>\n"
-"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] "
-"<target>\n"
-"lxc file edit <file>\n"
-"\n"
-"<source> in the case of pull, <target> in the case of push and <file> in the "
-"case of edit are <container name>/<path>"
-msgstr ""
-
-#: lxc/remote.go:38
-msgid ""
-"Manage remote LXD servers.\n"
-"\n"
-"lxc remote add <name> <url> [--accept-certificate] [--password=PASSWORD]\n"
-"                            [--public] [--protocol=PROTOCOL]                "
-"Add the remote <name> at <url>.\n"
-"lxc remote remove <name>                                                    "
-"Remove the remote <name>.\n"
-"lxc remote list                                                             "
-"List all remotes.\n"
-"lxc remote rename <old> <new>                                               "
-"Rename remote <old> to <new>.\n"
-"lxc remote set-url <name> <url>                                             "
-"Update <name>'s url to <url>.\n"
-"lxc remote set-default <name>                                               "
-"Set the default remote.\n"
-"lxc remote get-default                                                      "
-"Print the default remote."
-msgstr ""
-
-#: lxc/image.go:93
-msgid ""
-"Manipulate container images.\n"
-"\n"
-"In LXD containers are created from images. Those images were themselves\n"
-"either generated from an existing container or downloaded from an image\n"
-"server.\n"
-"\n"
-"When using remote images, LXD will automatically cache images for you\n"
-"and remove them upon expiration.\n"
-"\n"
-"The image unique identifier is the hash (sha-256) of its representation\n"
-"as a compressed tarball (or for split images, the concatenation of the\n"
-"metadata and rootfs tarballs).\n"
-"\n"
-"Images can be referenced by their full hash, shortest unique partial\n"
-"hash or alias name (if one is set).\n"
-"\n"
-"\n"
-"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
-"alias=ALIAS].. [prop=value]\n"
-"    Import an image tarball (or tarballs) into the LXD image store.\n"
-"\n"
-"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
-"[--public] [--auto-update]\n"
-"    Copy an image from one LXD daemon to another over the network.\n"
-"\n"
-"    The auto-update flag instructs the server to keep this image up to\n"
-"    date. It requires the source to be an alias and for it to be public.\n"
-"\n"
-"lxc image delete [remote:]<image>\n"
-"    Delete an image from the LXD image store.\n"
-"\n"
-"lxc image export [remote:]<image> [target]\n"
-"    Export an image from the LXD image store into a distributable tarball.\n"
-"\n"
-"    The output target is optional and defaults to the working directory.\n"
-"    The target may be an existing directory, file name, or \"-\" to specify\n"
-"    stdout.  The target MUST be a directory when exporting a split image.\n"
-"    If the target is a directory, the image's name (each part's name for\n"
-"    split images) as found in the database will be used for the exported\n"
-"    image.  If the target is a file (not a directory and not stdout), then\n"
-"    the appropriate extension will be appended to the provided file name\n"
-"    based on the algorithm used to compress the image. \n"
-"\n"
-"lxc image info [remote:]<image>\n"
-"    Print everything LXD knows about a given image.\n"
-"\n"
-"lxc image list [remote:] [filter]\n"
-"    List images in the LXD image store. Filters may be of the\n"
-"    <key>=<value> form for property based filtering, or part of the image\n"
-"    hash or part of the image alias name.\n"
-"\n"
-"lxc image show [remote:]<image>\n"
-"    Yaml output of the user modifiable properties of an image.\n"
-"\n"
-"lxc image edit [remote:]<image>\n"
-"    Edit image, either by launching external editor or reading STDIN.\n"
-"    Example: lxc image edit <image> # launch editor\n"
-"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
-"\n"
-"lxc image alias create [remote:]<alias> <fingerprint>\n"
-"    Create a new alias for an existing image.\n"
-"\n"
-"lxc image alias delete [remote:]<alias>\n"
-"    Delete an alias.\n"
-"\n"
-"lxc image alias list [remote:] [filter]\n"
-"    List the aliases. Filters may be part of the image hash or part of the "
-"image alias name.\n"
-msgstr ""
-
-#: lxc/info.go:150
+#: lxc/info.go:152
 msgid "Memory (current)"
 msgstr ""
 
-#: lxc/info.go:154
+#: lxc/info.go:156
 msgid "Memory (peak)"
 msgstr ""
 
-#: lxc/help.go:87
+#: lxc/info.go:168
+msgid "Memory usage:"
+msgstr ""
+
+#: lxc/utils.go:173
 msgid "Missing summary."
 msgstr "Sommaire manquant."
 
-#: lxc/monitor.go:41
-msgid ""
-"Monitor activity on the LXD server.\n"
-"\n"
-"lxc monitor [remote:] [--type=TYPE...]\n"
-"\n"
-"Connects to the monitoring interface of the specified LXD server.\n"
-"\n"
-"By default will listen to all message types.\n"
-"Specific types to listen to can be specified with --type.\n"
-"\n"
-"Example:\n"
-"lxc monitor --type=logging"
-msgstr ""
-
-#: lxc/file.go:183
+#: lxc/file.go:195
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "Plusieurs fichiers à télécharger mais la destination n'est pas un dossier"
 
-#: lxc/move.go:16
-msgid ""
-"Move containers within or in between lxd instances.\n"
-"\n"
-"lxc move [remote:]<source container> [remote:]<destination container>\n"
-"    Move a container between two hosts, renaming it if destination name "
-"differs.\n"
-"\n"
-"lxc move <old name> <new name>\n"
-"    Rename a local container.\n"
-msgstr ""
-
-#: lxc/action.go:69
+#: lxc/action.go:68
 msgid "Must supply container name for: "
 msgstr ""
 
-#: lxc/list.go:396 lxc/remote.go:352
+#: lxc/list.go:420 lxc/remote.go:365
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:326 lxc/remote.go:331
+#: lxc/remote.go:339 lxc/remote.go:344
 msgid "NO"
 msgstr ""
 
-#: lxc/info.go:89
+#: lxc/info.go:91
 #, c-format
 msgid "Name: %s"
 msgstr ""
 
-#: lxc/image.go:166 lxc/publish.go:33
+#: lxc/info.go:185
+msgid "Network usage:"
+msgstr ""
+
+#: lxc/image.go:134 lxc/publish.go:34
 msgid "New alias to define at target"
 msgstr ""
 
-#: lxc/config.go:284
+#: lxc/config.go:321
 #, fuzzy
 msgid "No certificate provided to add"
 msgstr "Un certificat n'a pas été fournis"
 
-#: lxc/config.go:307
+#: lxc/config.go:344
 msgid "No fingerprint specified."
 msgstr "Aucune empreinte n'a été spécifié."
 
-#: lxc/remote.go:106
+#: lxc/remote.go:119
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:425
+#: lxc/image.go:403
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
-#: lxc/help.go:63 lxc/main.go:108
+#: lxc/help.go:71 lxc/main.go:112 lxc/main.go:164
 #, fuzzy
 msgid "Options:"
 msgstr "Opération %s"
 
-#: lxc/image.go:520
+#: lxc/image.go:505
 #, c-format
 msgid "Output is in %s"
 msgstr ""
 
-#: lxc/exec.go:55
+#: lxc/exec.go:56
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:480
+#: lxc/list.go:506
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:397
+#: lxc/list.go:421
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:398
+#: lxc/list.go:422
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:354
+#: lxc/remote.go:367
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:620 lxc/remote.go:355
+#: lxc/image.go:605 lxc/remote.go:368
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:177
+#: lxc/info.go:179
 msgid "Packets received"
 msgstr ""
 
-#: lxc/info.go:178
+#: lxc/info.go:180
 msgid "Packets sent"
 msgstr ""
 
-#: lxc/help.go:70
+#: lxc/help.go:78
 #, fuzzy
-msgid "Path to an alternate client configuration directory."
+msgid "Path to an alternate client configuration directory"
 msgstr "Dossier de configuration alternatif."
 
-#: lxc/help.go:71
+#: lxc/help.go:79
 #, fuzzy
-msgid "Path to an alternate server directory."
+msgid "Path to an alternate server directory"
 msgstr "Dossier de configuration alternatif."
 
-#: lxc/main.go:31
+#: lxc/main.go:201
+#, fuzzy
+msgid "Pause containers."
+msgstr "Mauvaise URL pour le conteneur %s"
+
+#: lxc/main.go:34
 msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
-#: lxc/info.go:106
+#: lxc/info.go:108
 #, c-format
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/help.go:25
-#, fuzzy
-msgid ""
-"Presents details on how to use LXD.\n"
-"\n"
-"lxd help [--all]"
-msgstr "Explique comment utiliser LXD.\n"
-
-#: lxc/profile.go:191
+#: lxc/profile.go:229
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:702
+#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:687
 msgid "Press enter to start the editor again"
 msgstr ""
 
-#: lxc/help.go:65
-msgid "Print debug information."
+#: lxc/help.go:73
+msgid "Print debug information"
 msgstr ""
 
-#: lxc/help.go:64
-msgid "Print less common commands."
+#: lxc/help.go:72
+msgid "Print less common commands"
 msgstr ""
 
-#: lxc/help.go:66
-msgid "Print verbose information."
+#: lxc/help.go:74
+msgid "Print verbose information"
 msgstr ""
 
-#: lxc/version.go:18
-#, fuzzy
-msgid ""
-"Prints the version number of this client tool.\n"
-"\n"
-"lxc version"
-msgstr "Montre le numéro de version de LXD.\n"
-
-#: lxc/info.go:130
+#: lxc/info.go:132
 #, fuzzy, c-format
 msgid "Processes: %d"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:228
+#: lxc/profile.go:266
 #, fuzzy, c-format
 msgid "Profile %s applied to %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:142
+#: lxc/profile.go:180
 #, c-format
 msgid "Profile %s created"
 msgstr ""
 
-#: lxc/profile.go:212
+#: lxc/profile.go:250
 #, c-format
 msgid "Profile %s deleted"
 msgstr ""
 
-#: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
+#: lxc/init.go:135 lxc/init.go:136
 msgid "Profile to apply to the new container"
 msgstr ""
 
-#: lxc/info.go:104
+#: lxc/info.go:106
 #, fuzzy, c-format
 msgid "Profiles: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:352
+#: lxc/image.go:337
 msgid "Properties:"
 msgstr ""
 
-#: lxc/remote.go:55
+#: lxc/remote.go:68
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:340
+#: lxc/image.go:320
 #, c-format
 msgid "Public: %s"
 msgstr ""
 
-#: lxc/publish.go:25
-msgid ""
-"Publish containers as images.\n"
-"\n"
-"lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
-"value]..."
-msgstr ""
-
-#: lxc/remote.go:53
+#: lxc/remote.go:66
 msgid "Remote admin password"
 msgstr ""
 
-#: lxc/info.go:91
+#: lxc/info.go:93
 #, c-format
 msgid "Remote: %s"
 msgstr ""
 
-#: lxc/delete.go:42
+#: lxc/delete.go:41
 #, c-format
 msgid "Remove %s (yes/no): "
 msgstr ""
 
-#: lxc/delete.go:36 lxc/delete.go:37
-msgid "Require user confirmation."
+#: lxc/delete.go:35 lxc/delete.go:36
+msgid "Require user confirmation"
 msgstr ""
 
-#: lxc/info.go:127
+#: lxc/info.go:129
 msgid "Resources:"
 msgstr ""
 
-#: lxc/init.go:247
+#: lxc/main.go:209
+#, fuzzy
+msgid "Restart containers."
+msgstr "Liste de l'information sur les conteneurs.\n"
+
+#: lxc/init.go:218
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:623
+#: lxc/image.go:608
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:399
+#: lxc/list.go:423
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:400
+#: lxc/list.go:424
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:356
+#: lxc/remote.go:369
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:203
+#: lxc/remote.go:216
 msgid "Server certificate NACKed by user"
 msgstr "Le certificat serveur a été rejeté par l'utilisateur"
 
-#: lxc/remote.go:265
+#: lxc/remote.go:278
 msgid "Server doesn't trust us after adding our cert"
 msgstr "Identification refuse après l'ajout du certificat client"
 
-#: lxc/remote.go:54
+#: lxc/remote.go:67
 msgid "Server protocol (lxd or simplestreams)"
 msgstr ""
 
-#: lxc/restore.go:21
-msgid ""
-"Set the current state of a resource back to a snapshot.\n"
-"\n"
-"lxc restore [remote:]<container> <snapshot name> [--stateful]\n"
-"\n"
-"Restores a container from a snapshot (optionally with running state, see\n"
-"snapshot help for details).\n"
-"\n"
-"For example:\n"
-"lxc snapshot u1 snap0 # create the snapshot\n"
-"lxc restore u1 snap0 # restore the snapshot"
-msgstr ""
-
-#: lxc/file.go:44
+#: lxc/file.go:56
 msgid "Set the file's gid on push"
 msgstr "Définit le gid lors de l'envoi"
 
-#: lxc/file.go:45
+#: lxc/file.go:57
 msgid "Set the file's perms on push"
 msgstr "Définit les permissions lors de l'envoi"
 
-#: lxc/file.go:43
+#: lxc/file.go:55
 msgid "Set the file's uid on push"
 msgstr "Définit le uid lors de l'envoi"
 
-#: lxc/help.go:32
+#: lxc/help.go:29
 msgid "Show all commands (not just interesting ones)"
 msgstr "Affiche toutes les comandes (pas seulement les intéresantes)"
 
-#: lxc/help.go:67
-msgid "Show client version."
+#: lxc/help.go:75
+msgid "Show client version"
 msgstr ""
 
-#: lxc/info.go:36
+#: lxc/info.go:38
 msgid "Show the container's last 100 log lines?"
 msgstr ""
 
-#: lxc/image.go:338
+#: lxc/config.go:33
+msgid "Show the expanded configuration"
+msgstr ""
+
+#: lxc/image.go:318
 #, c-format
 msgid "Size: %.2fMB"
 msgstr ""
 
-#: lxc/info.go:197
+#: lxc/info.go:199
 msgid "Snapshots:"
 msgstr ""
 
-#: lxc/image.go:362
+#: lxc/action.go:134
+#, fuzzy, c-format
+msgid "Some containers failed to %s"
+msgstr "L'arrêt du conteneur a échoué!"
+
+#: lxc/image.go:347
 msgid "Source:"
 msgstr ""
 
-#: lxc/launch.go:124
+#: lxc/main.go:219
+#, fuzzy
+msgid "Start containers."
+msgstr "Mauvaise URL pour le conteneur %s"
+
+#: lxc/launch.go:118
 #, c-format
 msgid "Starting %s"
 msgstr ""
 
-#: lxc/info.go:98
+#: lxc/info.go:100
 #, c-format
 msgid "Status: %s"
 msgstr ""
 
-#: lxc/publish.go:34 lxc/publish.go:35
+#: lxc/main.go:225
+#, fuzzy
+msgid "Stop containers."
+msgstr "L'arrêt du conteneur a échoué!"
+
+#: lxc/publish.go:35 lxc/publish.go:36
 msgid "Stop the container if currently running"
 msgstr ""
 
-#: lxc/delete.go:106 lxc/publish.go:111
+#: lxc/delete.go:105 lxc/publish.go:112
 msgid "Stopping container failed!"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/action.go:45
+#: lxc/action.go:49
 #, fuzzy
-msgid "Store the container state (only for stop)."
+msgid "Store the container state (only for stop)"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/info.go:158
+#: lxc/info.go:160
 msgid "Swap (current)"
 msgstr ""
 
-#: lxc/info.go:162
+#: lxc/info.go:164
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:401
+#: lxc/list.go:425
 msgid "TYPE"
 msgstr ""
 
-#: lxc/delete.go:92
+#: lxc/delete.go:91
 msgid "The container is currently running, stop it first or pass --force."
 msgstr ""
 
-#: lxc/publish.go:89
+#: lxc/publish.go:90
 msgid ""
 "The container is currently running. Use --force to have it stopped and "
 "restarted."
 msgstr ""
 
-#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738
-#: lxc/config.go:776 lxc/config.go:794
+#: lxc/config.go:716 lxc/config.go:728 lxc/config.go:761 lxc/config.go:779
+#: lxc/config.go:817 lxc/config.go:835
 #, fuzzy
 msgid "The device doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/init.go:277
+#: lxc/init.go:275
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
 
-#: lxc/main.go:176
-msgid "The opposite of `lxc pause` is `lxc start`."
+#: lxc/action.go:34
+msgid "The opposite of \"lxc pause\" is \"lxc start\"."
 msgstr ""
 
-#: lxc/publish.go:62
+#: lxc/publish.go:63
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
 
-#: lxc/action.go:41
-msgid "Time to wait for the container before killing it."
+#: lxc/help.go:47
+msgid ""
+"This is the LXD command line client.\n"
+"\n"
+"All of LXD's features can be driven through the various commands below.\n"
+"For help with any of those, simply call them with --help."
+msgstr ""
+
+#: lxc/action.go:45
+#, fuzzy
+msgid "Time to wait for the container before killing it"
 msgstr "Temps d'attente avant de tuer le conteneur."
 
-#: lxc/image.go:341
+#: lxc/image.go:321
 msgid "Timestamps:"
 msgstr ""
 
-#: lxc/main.go:133
+#: lxc/main.go:140
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:412
+#: lxc/image.go:405
 #, c-format
-msgid "Transferring image: %d%%"
+msgid "Transferring image: %s"
 msgstr ""
 
-#: lxc/action.go:99 lxc/launch.go:137
+#: lxc/action.go:98 lxc/launch.go:131
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
 
-#: lxc/info.go:100
+#: lxc/info.go:102
 msgid "Type: ephemeral"
 msgstr ""
 
-#: lxc/info.go:102
+#: lxc/info.go:104
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:624
+#: lxc/image.go:609
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:353
+#: lxc/remote.go:366
 msgid "URL"
 msgstr ""
 
-#: lxc/remote.go:81
-msgid "Unable to read remote TLS certificate"
+#: lxc/manpage.go:36
+msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/image.go:346
+#: lxc/remote.go:94
+msgid "Unable to read remote TLS certificate"
+msgstr ""
+
+#: lxc/image.go:326
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
 
-#: lxc/main.go:108
-#, fuzzy, c-format
-msgid "Usage: %s"
-msgstr ""
-"Utilisation: %s\n"
-"\n"
-"Options:\n"
+#: lxc/action.go:37
+#, c-format
+msgid ""
+"Usage: lxc %s [<remote>:]<container> [[<remote>:]<container>...]\n"
 "\n"
+"%s%s"
+msgstr ""
 
-#: lxc/help.go:48
+#: lxc/help.go:45
 #, fuzzy
-msgid "Usage: lxc [subcommand] [options]"
+msgid "Usage: lxc <command> [options]"
 msgstr ""
 "Utilisation: lxc [sous commande] [options]\n"
 "Comande disponibles:\n"
 
-#: lxc/delete.go:46
+#: lxc/config.go:58
+msgid ""
+"Usage: lxc config <subcommand> [options]\n"
+"\n"
+"Change container or server configuration options.\n"
+"\n"
+"*Container configuration*\n"
+"\n"
+"lxc config get [<remote>:][container] <key>\n"
+"    Get container or server configuration key.\n"
+"\n"
+"lxc config set [<remote>:][container] <key> <value>\n"
+"    Set container or server configuration key.\n"
+"\n"
+"lxc config unset [<remote>:][container] <key>\n"
+"    Unset container or server configuration key.\n"
+"\n"
+"lxc config show [<remote>:][container] [--expanded]\n"
+"    Show container or server configuration.\n"
+"\n"
+"lxc config edit [<remote>:][container]\n"
+"    Edit configuration, either by launching external editor or reading "
+"STDIN.\n"
+"\n"
+"*Device management*\n"
+"\n"
+"lxc config device add [<remote>:]<container> <device> <type> [key=value...]\n"
+"    Add a device to a container.\n"
+"\n"
+"lxc config device get [<remote>:]<container> <device> <key>\n"
+"    Get a device property.\n"
+"\n"
+"lxc config device set [<remote>:]<container> <device> <key> <value>\n"
+"    Set a device property.\n"
+"\n"
+"lxc config device unset [<remote>:]<container> <device> <key>\n"
+"    Unset a device property.\n"
+"\n"
+"lxc config device list [<remote>:]<container>\n"
+"    List devices for container.\n"
+"\n"
+"lxc config device show [<remote>:]<container>\n"
+"    Show full device details for container.\n"
+"\n"
+"lxc config device remove [<remote>:]<container> <name>\n"
+"    Remove device from container.\n"
+"\n"
+"*Client trust store management*\n"
+"\n"
+"lxc config trust list [<remote>:]\n"
+"    List all trusted certs.\n"
+"\n"
+"lxc config trust add [<remote>:] <certfile.crt>\n"
+"    Add certfile.crt to trusted hosts.\n"
+"\n"
+"lxc config trust remove [<remote>:] [hostname|fingerprint]\n"
+"    Remove the cert from trusted hosts.\n"
+"\n"
+"*Examples*\n"
+"\n"
+"cat config.yaml | lxc config edit <container>\n"
+"    Update the container configuration from config.yaml.\n"
+"\n"
+"lxc config device add [<remote>:]container1 <device-name> disk source=/share/"
+"c1 path=opt\n"
+"    Will mount the host's /share/c1 onto /opt in the container.\n"
+"\n"
+"lxc config set [<remote>:]<container> limits.cpu 2\n"
+"    Will set a CPU limit of \"2\" for the container.\n"
+"\n"
+"lxc config set core.https_address [::]:8443\n"
+"    Will have LXD listen on IPv4 and IPv6 port 8443.\n"
+"\n"
+"lxc config set core.trust_password blah\n"
+"    Will set the server's trust password to blah."
+msgstr ""
+
+#: lxc/copy.go:23
+msgid ""
+"Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
+"[--ephemeral|e]\n"
+"\n"
+"Copy containers within or in between LXD instances."
+msgstr ""
+
+#: lxc/delete.go:26
+msgid ""
+"Usage: lxc delete [<remote>:]<container>[/<snapshot>] "
+"[[<remote>:]<container>[/<snapshot>]...]\n"
+"\n"
+"Delete containers and snapshots."
+msgstr ""
+
+#: lxc/exec.go:46
+#, fuzzy
+msgid ""
+"Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-"
+"interactive] [--env KEY=VALUE...] [--] <command line>\n"
+"\n"
+"Execute commands in containers.\n"
+"\n"
+"Mode defaults to non-interactive, interactive mode is selected if both stdin "
+"AND stdout are terminals (stderr is ignored)."
+msgstr "Exécute la commande spécifiée dans un conteneur.\n"
+
+#: lxc/file.go:32
+msgid ""
+"Usage: lxc file <subcommand> [options]\n"
+"\n"
+"Manage files in containers.\n"
+"\n"
+"lxc file pull [<remote>:]<container>/<path> [[<remote>:]<container>/"
+"<path>...] <target path>\n"
+"    Pull files from containers.\n"
+"\n"
+"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source "
+"path>...] [<remote>:]<container>/<path>\n"
+"    Push files into containers.\n"
+"\n"
+"lxc file edit [<remote>:]<container>/<path>\n"
+"    Edit files in containers using the default text editor.\n"
+"\n"
+"*Examples*\n"
+"lxc file push /etc/hosts foo/etc/hosts\n"
+"   To push /etc/hosts into the container \"foo\".\n"
+"\n"
+"lxc file pull foo/etc/hosts .\n"
+"   To pull /etc/hosts from the container and write it to the current "
+"directory."
+msgstr ""
+
+#: lxc/finger.go:15
+msgid ""
+"Usage: lxc finger [<remote>:]\n"
+"\n"
+"Check if the LXD server is alive."
+msgstr ""
+
+#: lxc/help.go:22
+msgid ""
+"Usage: lxc help [--all]\n"
+"\n"
+"Help page for the LXD client."
+msgstr ""
+
+#: lxc/image.go:60
+msgid ""
+"Usage: lxc image <subcommand> [options]\n"
+"\n"
+"Manipulate container images.\n"
+"\n"
+"In LXD containers are created from images. Those images were themselves\n"
+"either generated from an existing container or downloaded from an image\n"
+"server.\n"
+"\n"
+"When using remote images, LXD will automatically cache images for you\n"
+"and remove them upon expiration.\n"
+"\n"
+"The image unique identifier is the hash (sha-256) of its representation\n"
+"as a compressed tarball (or for split images, the concatenation of the\n"
+"metadata and rootfs tarballs).\n"
+"\n"
+"Images can be referenced by their full hash, shortest unique partial\n"
+"hash or alias name (if one is set).\n"
+"\n"
+"\n"
+"lxc image import <tarball> [<rootfs tarball>|<URL>] [<remote>:] [--public] "
+"[--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] "
+"[--alias=ALIAS...] [prop=value]\n"
+"    Import an image tarball (or tarballs) into the LXD image store.\n"
+"\n"
+"lxc image copy [<remote>:]<image> <remote>: [--alias=ALIAS...] [--copy-"
+"aliases] [--public] [--auto-update]\n"
+"    Copy an image from one LXD daemon to another over the network.\n"
+"\n"
+"    The auto-update flag instructs the server to keep this image up to\n"
+"    date. It requires the source to be an alias and for it to be public.\n"
+"\n"
+"lxc image delete [<remote>:]<image> [[<remote>:]<image>...]\n"
+"    Delete one or more images from the LXD image store.\n"
+"\n"
+"lxc image export [<remote>:]<image> [target]\n"
+"    Export an image from the LXD image store into a distributable tarball.\n"
+"\n"
+"    The output target is optional and defaults to the working directory.\n"
+"    The target may be an existing directory, file name, or \"-\" to specify\n"
+"    stdout.  The target MUST be a directory when exporting a split image.\n"
+"    If the target is a directory, the image's name (each part's name for\n"
+"    split images) as found in the database will be used for the exported\n"
+"    image.  If the target is a file (not a directory and not stdout), then\n"
+"    the appropriate extension will be appended to the provided file name\n"
+"    based on the algorithm used to compress the image. \n"
+"\n"
+"lxc image info [<remote>:]<image>\n"
+"    Print everything LXD knows about a given image.\n"
+"\n"
+"lxc image list [<remote>:] [filter]\n"
+"    List images in the LXD image store. Filters may be of the\n"
+"    <key>=<value> form for property based filtering, or part of the image\n"
+"    hash or part of the image alias name.\n"
+"\n"
+"lxc image show [<remote>:]<image>\n"
+"    Yaml output of the user modifiable properties of an image.\n"
+"\n"
+"lxc image edit [<remote>:]<image>\n"
+"    Edit image, either by launching external editor or reading STDIN.\n"
+"    Example: lxc image edit <image> # launch editor\n"
+"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
+"\n"
+"lxc image alias create [<remote>:]<alias> <fingerprint>\n"
+"    Create a new alias for an existing image.\n"
+"\n"
+"lxc image alias delete [<remote>:]<alias>\n"
+"    Delete an alias.\n"
+"\n"
+"lxc image alias list [<remote>:] [filter]\n"
+"    List the aliases. Filters may be part of the image hash or part of the "
+"image alias name."
+msgstr ""
+
+#: lxc/info.go:25
+msgid ""
+"Usage: lxc info [<remote>:][<container>] [--show-log]\n"
+"\n"
+"Show container or server information.\n"
+"\n"
+"lxc info [<remote>:]<container> [--show-log]\n"
+"    For container information.\n"
+"\n"
+"lxc info [<remote>:]\n"
+"    For LXD server information."
+msgstr ""
+
+#: lxc/init.go:73
+msgid ""
+"Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
+"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"\n"
+"Create containers from images.\n"
+"\n"
+"Not specifying -p will result in the default profile.\n"
+"Specifying \"-p\" with no argument will result in no profile.\n"
+"\n"
+"Examples:\n"
+"    lxc init ubuntu:16.04 u1"
+msgstr ""
+
+#: lxc/launch.go:23
+msgid ""
+"Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
+"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"\n"
+"Create and start containers from images.\n"
+"\n"
+"Not specifying -p will result in the default profile.\n"
+"Specifying \"-p\" with no argument will result in no profile.\n"
+"\n"
+"Examples:\n"
+"    lxc launch ubuntu:16.04 u1"
+msgstr ""
+
+#: lxc/list.go:46
+msgid ""
+"Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
+"[--fast]\n"
+"\n"
+"List the existing containers.\n"
+"\n"
+"Default column layout: ns46tS\n"
+"Fast column layout: nsacPt\n"
+"\n"
+"*Filters*\n"
+"A single keyword like \"web\" which will list any container with a name "
+"starting by \"web\".\n"
+"\n"
+"A regular expression on the container name. (e.g. .*web.*01$).\n"
+"\n"
+"A key/value pair referring to a configuration item. For those, the namespace "
+"can be abbreviated to the smallest unambiguous identifier.\n"
+"    - \"user.blah=abc\" will list all containers with the \"blah\" user "
+"property set to \"abc\".\n"
+"\n"
+"    - \"u.blah=abc\" will do the same\n"
+"\n"
+"    - \"security.privileged=1\" will list all privileged containers\n"
+"\n"
+"    - \"s.privileged=1\" will do the same\n"
+"\n"
+"A regular expression matching a configuration item or its value. (e.g. "
+"volatile.eth0.hwaddr=00:16:3e:.*).\n"
+"\n"
+"*Columns*\n"
+"The -c option takes a comma separated list of arguments that control\n"
+"which container attributes to output when displaying in table format.\n"
+"\n"
+"Column arguments are either pre-defined shorthand chars (see below),\n"
+"or (extended) config keys.\n"
+"\n"
+"Commas between consecutive shorthand chars are optional.\n"
+"\n"
+"Pre-defined column shorthand chars:\n"
+"\n"
+"    4 - IPv4 address\n"
+"\n"
+"    6 - IPv6 address\n"
+"\n"
+"    a - Architecture\n"
+"\n"
+"    c - Creation date\n"
+"\n"
+"    l - Last used date\n"
+"\n"
+"    n - Name\n"
+"\n"
+"    p - PID of the container's init process\n"
+"\n"
+"    P - Profiles\n"
+"\n"
+"    s - State\n"
+"\n"
+"    S - Number of snapshots\n"
+"\n"
+"    t - Type (persistent or ephemeral)\n"
+"\n"
+"Custom columns are defined with \"key[:name][:maxWidth]\":\n"
+"\n"
+"    KEY: The (extended) config key to display\n"
+"\n"
+"    NAME: Name to display in the column header.\n"
+"    Defaults to the key if not specified or empty.\n"
+"\n"
+"    MAXWIDTH: Max width of the column (longer results are truncated).\n"
+"    Defaults to -1 (unlimited). Use 0 to limit to the column header size.\n"
+"\n"
+"*Examples*\n"
+"lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:"
+"MAC\n"
+"    Shows a list of containers using the \"NAME\", \"BASE IMAGE\", \"STATE"
+"\", \"IPV4\",\n"
+"    \"IPV6\" and \"MAC\" columns.\n"
+"\n"
+"    \"BASE IMAGE\" and \"MAC\" are custom columns generated from container "
+"configuration keys."
+msgstr ""
+
+#: lxc/manpage.go:20
+msgid ""
+"Usage: lxc manpage <directory>\n"
+"\n"
+"Generate all the LXD manpages."
+msgstr ""
+
+#: lxc/monitor.go:41
+msgid ""
+"Usage: lxc monitor [<remote>:] [--type=TYPE...]\n"
+"\n"
+"Monitor a local or remote LXD server.\n"
+"\n"
+"By default the monitor will listen to all message types.\n"
+"\n"
+"Message types to listen for can be specified with --type.\n"
+"\n"
+"*Examples*\n"
+"lxc monitor --type=logging\n"
+"    Only show log message."
+msgstr ""
+
+#: lxc/move.go:16
+msgid ""
+"Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
+"<snapshot>]]\n"
+"\n"
+"Move containers within or in between LXD instances.\n"
+"\n"
+"lxc move [<remote>:]<source container> [<remote>:][<destination container>]\n"
+"    Move a container between two hosts, renaming it if destination name "
+"differs.\n"
+"\n"
+"lxc move <old name> <new name>\n"
+"    Rename a local container.\n"
+"\n"
+"lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
+"    Rename a snapshot."
+msgstr ""
+
+#: lxc/profile.go:48
+msgid ""
+"Usage: lxc profile <subcommand> [options]\n"
+"\n"
+"Manage container configuration profiles.\n"
+"\n"
+"*Profile configuration*\n"
+"lxc profile list [<remote>:]\n"
+"    List available profiles.\n"
+"\n"
+"lxc profile show [<remote>:]<profile>\n"
+"    Show details of a profile.\n"
+"\n"
+"lxc profile create [<remote>:]<profile>\n"
+"    Create a profile.\n"
+"\n"
+"lxc profile copy [<remote>:]<profile> [<remote>:]<profile>\n"
+"    Copy the profile.\n"
+"\n"
+"lxc profile get [<remote>:]<profile> <key>\n"
+"    Get profile configuration.\n"
+"\n"
+"lxc profile set [<remote>:]<profile> <key> <value>\n"
+"    Set profile configuration.\n"
+"\n"
+"lxc profile unset [<remote>:]<profile> <key>\n"
+"    Unset profile configuration.\n"
+"\n"
+"lxc profile delete [<remote>:]<profile>\n"
+"    Delete a profile.\n"
+"\n"
+"lxc profile edit [<remote>:]<profile>\n"
+"    Edit profile, either by launching external editor or reading STDIN.\n"
+"\n"
+"*Profile assignment*\n"
+"lxc profile apply [<remote>:]<container> <profiles>\n"
+"    Replace the current set of profiles for the container by the one "
+"provided.\n"
+"\n"
+"*Device management*\n"
+"lxc profile device list [<remote>:]<profile>\n"
+"    List devices in the given profile.\n"
+"\n"
+"lxc profile device show [<remote>:]<profile>\n"
+"    Show full device details in the given profile.\n"
+"\n"
+"lxc profile device remove [<remote>:]<profile> <name>\n"
+"    Remove a device from a profile.\n"
+"\n"
+"lxc profile device get [<remote>:]<profile> <name> <key>\n"
+"    Get a device property.\n"
+"\n"
+"lxc profile device set [<remote>:]<profile> <name> <key> <value>\n"
+"    Set a device property.\n"
+"\n"
+"lxc profile device unset [<remote>:]<profile> <name> <key>\n"
+"    Unset a device property.\n"
+"\n"
+"lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]\n"
+"    Add a profile device, such as a disk or a nic, to the containers using "
+"the specified profile.\n"
+"\n"
+"*Examples*\n"
+"cat profile.yaml | lxc profile edit <profile>\n"
+"    Update a profile using the content of profile.yaml\n"
+"\n"
+"lxc profile apply foo default,bar\n"
+"    Set the profiles for \"foo\" to \"default\" and \"bar\".\n"
+"\n"
+"lxc profile apply foo default\n"
+"    Reset \"foo\" to only using the \"default\" profile.\n"
+"\n"
+"lxc profile apply foo ''\n"
+"    Remove all profile from \"foo\""
+msgstr ""
+
+#: lxc/publish.go:26
+msgid ""
+"Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--"
+"alias=ALIAS...] [prop-key=prop-value...]\n"
+"\n"
+"Publish containers as images."
+msgstr ""
+
+#: lxc/remote.go:37
+msgid ""
+"Usage: lxc remote <subcommand> [options]\n"
+"\n"
+"Manage the list of remote LXD servers.\n"
+"\n"
+"lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--"
+"password=PASSWORD] [--public] [--protocol=PROTOCOL]\n"
+"    Add the remote <remote> at <url>.\n"
+"\n"
+"lxc remote remove <remote>\n"
+"    Remove the remote <remote>.\n"
+"\n"
+"lxc remote list\n"
+"    List all remotes.\n"
+"\n"
+"lxc remote rename <old name> <new name>\n"
+"    Rename remote <old name> to <new name>.\n"
+"\n"
+"lxc remote set-url <remote> <url>\n"
+"    Update <remote>'s url to <url>.\n"
+"\n"
+"lxc remote set-default <remote>\n"
+"    Set the default remote.\n"
+"\n"
+"lxc remote get-default\n"
+"    Print the default remote."
+msgstr ""
+
+#: lxc/restore.go:21
+msgid ""
+"Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
+"\n"
+"Restore containers from snapshots.\n"
+"\n"
+"If --stateful is passed, then the running state will be restored too.\n"
+"\n"
+"*Examples*\n"
+"lxc snapshot u1 snap0\n"
+"    Create the snapshot.\n"
+"\n"
+"lxc restore u1 snap0\n"
+"    Restore the snapshot."
+msgstr ""
+
+#: lxc/snapshot.go:21
+msgid ""
+"Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
+"\n"
+"Create container snapshots.\n"
+"\n"
+"When --stateful is used, LXD attempts to checkpoint the container's\n"
+"running state, including process memory state, TCP connections, ...\n"
+"\n"
+"*Examples*\n"
+"lxc snapshot u1 snap0\n"
+"    Create a snapshot of \"u1\" called \"snap0\"."
+msgstr ""
+
+#: lxc/version.go:18
+#, fuzzy
+msgid ""
+"Usage: lxc version\n"
+"\n"
+"Print the version number of this client tool."
+msgstr "Montre le numéro de version de LXD.\n"
+
+#: lxc/delete.go:45
 msgid "User aborted delete operation."
 msgstr ""
 
-#: lxc/restore.go:35
+#: lxc/restore.go:37
 #, fuzzy
 msgid ""
 "Whether or not to restore the container's running state from snapshot (if "
@@ -1242,140 +1417,165 @@ msgstr ""
 "Est-ce que l'état de fonctionement du conteneur doit être inclus dans "
 "l'instantané (snapshot)"
 
-#: lxc/snapshot.go:38
+#: lxc/snapshot.go:35
 msgid "Whether or not to snapshot the container's running state"
 msgstr ""
 "Est-ce que l'état de fonctionement du conteneur doit être inclus dans "
 "l'instantané (snapshot)"
 
-#: lxc/config.go:32
-msgid "Whether to show the expanded configuration"
-msgstr ""
-
-#: lxc/remote.go:328 lxc/remote.go:333
+#: lxc/remote.go:341 lxc/remote.go:346
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:52
+#: lxc/main.go:55
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:111
+#: lxc/launch.go:104
 #, fuzzy
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr "nombre de propriété invalide pour la ressource"
 
-#: lxc/action.go:95
+#: lxc/action.go:94
 msgid "bad result type from action"
 msgstr "mauvais type de réponse pour l'action!"
 
-#: lxc/copy.go:99
+#: lxc/copy.go:100
 msgid "can't copy to the same container name"
 msgstr ""
 
-#: lxc/remote.go:316
+#: lxc/remote.go:329
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:342
+#: lxc/remote.go:355
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
+#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:88 lxc/launch.go:93
 #, fuzzy
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
 
-#: lxc/image.go:332
+#: lxc/image.go:312
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:334
+#: lxc/image.go:314
 msgid "enabled"
 msgstr ""
 
-#: lxc/main.go:22 lxc/main.go:145
+#: lxc/action.go:126 lxc/main.go:25 lxc/main.go:160
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "erreur: %v\n"
 
-#: lxc/help.go:40 lxc/main.go:103
+#: lxc/help.go:37 lxc/main.go:106
 #, fuzzy, c-format
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/launch.go:115
+#: lxc/launch.go:108
 msgid "got bad version"
 msgstr "reçu une version invalide"
 
-#: lxc/image.go:327 lxc/image.go:600
+#: lxc/image.go:307 lxc/image.go:585
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:122
+#: lxc/copy.go:123
 msgid "not all the profiles from the source exist on the target"
 msgstr ""
 
-#: lxc/remote.go:196
+#: lxc/remote.go:209
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:265 lxc/main.go:269
+#: lxc/main.go:295 lxc/main.go:299
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:378
+#: lxc/remote.go:391
 #, c-format
 msgid "remote %s already exists"
 msgstr "le serveur distant %s existe déjà"
 
-#: lxc/remote.go:308 lxc/remote.go:370 lxc/remote.go:405 lxc/remote.go:421
+#: lxc/remote.go:321 lxc/remote.go:383 lxc/remote.go:418 lxc/remote.go:434
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/remote.go:291
+#: lxc/remote.go:304
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "le serveur distant %s existe en tant que <%s>"
 
-#: lxc/remote.go:312 lxc/remote.go:374 lxc/remote.go:409
+#: lxc/remote.go:325 lxc/remote.go:387 lxc/remote.go:422
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
 
-#: lxc/info.go:208
+#: lxc/info.go:210
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:210
+#: lxc/info.go:212
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:204
+#: lxc/info.go:206
 #, c-format
 msgid "taken at %s"
 msgstr ""
 
-#: lxc/exec.go:163
+#: lxc/exec.go:167
 msgid "unreachable return reached"
 msgstr "Un retour inacessible à été atteint"
 
-#: lxc/main.go:205
+#: lxc/main.go:234
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:604
+#: lxc/delete.go:44 lxc/image.go:309 lxc/image.go:589
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:38
+#: lxc/copy.go:39
 msgid "you must specify a source container name"
 msgstr ""
 
 #, fuzzy
+#~ msgid ""
+#~ "Changes state of one or more containers to %s.\n"
+#~ "\n"
+#~ "lxc %s <name> [<name>...]%s"
+#~ msgstr "Change l'état du conteneur à %s.\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "Fingers the LXD instance to check if it is up and working.\n"
+#~ "\n"
+#~ "lxc finger <remote>"
+#~ msgstr "Contacte LXD pour voir s'il est fonctionel.\n"
+
+#, fuzzy
+#~ msgid ""
+#~ "Presents details on how to use LXD.\n"
+#~ "\n"
+#~ "lxd help [--all]"
+#~ msgstr "Explique comment utiliser LXD.\n"
+
+#, fuzzy
+#~ msgid "Usage: %s"
+#~ msgstr ""
+#~ "Utilisation: %s\n"
+#~ "\n"
+#~ "Options:\n"
+#~ "\n"
+
+#, fuzzy
 #~ msgid "Bad image property: %s"
 #~ msgstr "(Image invalide: %s\n"
 
@@ -1492,9 +1692,6 @@ msgstr ""
 #~ msgid "(Bad alias entry: %s\n"
 #~ msgstr "(Alias invalide: %s\n"
 
-#~ msgid "bad container url %s"
-#~ msgstr "Mauvaise URL pour le conteneur %s"
-
 #~ msgid "bad version in container url"
 #~ msgstr "version invalide dans l'URL du conteneur"
 
@@ -1546,9 +1743,6 @@ msgstr ""
 #~ msgid "Delete a container or container snapshot.\n"
 #~ msgstr "Supprime un conteneur ou l'instantané (snapshot) d'un conteneur.\n"
 
-#~ msgid "List information on containers.\n"
-#~ msgstr "Liste de l'information sur les conteneurs.\n"
-
 #~ msgid "Lists the available resources.\n"
 #~ msgstr "Liste des ressources disponibles.\n"
 
diff --git a/po/ja.po b/po/ja.po
index 80decff81..e372886ba 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2016-10-26 17:06-0400\n"
+"POT-Creation-Date: 2017-04-17 17:27-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -16,19 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/info.go:143
-msgid "  Disk usage:"
-msgstr "  ディスク使用量:"
-
-#: lxc/info.go:166
-msgid "  Memory usage:"
-msgstr "  メモリ消費量:"
-
-#: lxc/info.go:183
-msgid "  Network usage:"
-msgstr "  ネットワーク使用状況:"
-
-#: lxc/config.go:36
+#: lxc/config.go:37
 msgid ""
 "### This is a yaml representation of the configuration.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -49,7 +37,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:83
+#: lxc/image.go:50
 msgid ""
 "### This is a yaml representation of the image properties.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -59,7 +47,7 @@ msgid ""
 "###  description: My custom image"
 msgstr ""
 
-#: lxc/profile.go:26
+#: lxc/profile.go:27
 msgid ""
 "### This is a yaml representation of the profile.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -80,268 +68,202 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:597
+#: lxc/image.go:582
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/snapshot.go:61
+#: lxc/snapshot.go:58
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' はスナップショットの名前には使用できません。"
 
-#: lxc/profile.go:226
+#: lxc/profile.go:264
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:618 lxc/image.go:647
+#: lxc/image.go:603 lxc/image.go:632
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:622
+#: lxc/image.go:607
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:394
+#: lxc/list.go:418
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:52
+#: lxc/remote.go:65
 msgid "Accept certificate"
 msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
 
-#: lxc/remote.go:245
+#: lxc/remote.go:258
 #, c-format
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
 
-#: lxc/image.go:356
+#: lxc/image.go:341
 msgid "Aliases:"
 msgstr "エイリアス:"
 
-#: lxc/exec.go:54
-msgid "An environment variable of the form HOME=/home/foo"
-msgstr "環境変数を HOME=/home/foo の形式で指定します"
-
-#: lxc/image.go:339 lxc/info.go:93
+#: lxc/image.go:319 lxc/info.go:95
 #, c-format
 msgid "Architecture: %s"
 msgstr "アーキテクチャ: %s"
 
-#: lxc/image.go:360
+#: lxc/image.go:345
 #, c-format
 msgid "Auto update: %s"
 msgstr "自動更新: %s"
 
-#: lxc/help.go:49
-msgid "Available commands:"
-msgstr "使用可能なコマンド:"
-
-#: lxc/info.go:175
+#: lxc/info.go:177
 msgid "Bytes received"
 msgstr "受信バイト数"
 
-#: lxc/info.go:176
+#: lxc/info.go:178
 msgid "Bytes sent"
 msgstr "送信バイト数"
 
-#: lxc/config.go:273
+#: lxc/config.go:310
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:395
+#: lxc/list.go:419
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:113
+#: lxc/config.go:150
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr "標準入力から読み込めません: %s"
 
-#: lxc/config.go:126 lxc/config.go:159 lxc/config.go:181
+#: lxc/config.go:163 lxc/config.go:196 lxc/config.go:218
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr "キー '%s' が指定されていないので削除できません。"
 
-#: lxc/profile.go:343
+#: lxc/profile.go:394
 msgid "Cannot provide container name to list"
 msgstr "コンテナ名を取得できません"
 
-#: lxc/remote.go:195
+#: lxc/remote.go:208
 #, c-format
 msgid "Certificate fingerprint: %x"
 msgstr "証明書のフィンガープリント: %x"
 
-#: lxc/action.go:33
-#, fuzzy, c-format
-msgid ""
-"Changes state of one or more containers to %s.\n"
-"\n"
-"lxc %s <name> [<name>...]%s"
-msgstr ""
-"1つまたは複数のコンテナの状態を %s に変更します。\n"
-"\n"
-"lxc %s <name> [<name>...]"
-
-#: lxc/remote.go:268
+#: lxc/remote.go:281
 msgid "Client certificate stored at server: "
 msgstr "クライアント証明書がサーバに格納されました: "
 
-#: lxc/list.go:99 lxc/list.go:100
+#: lxc/list.go:122 lxc/list.go:123
 msgid "Columns"
 msgstr "カラムレイアウト"
 
-#: lxc/init.go:134 lxc/init.go:135 lxc/launch.go:40 lxc/launch.go:41
+#: lxc/help.go:53
+msgid "Commands:"
+msgstr ""
+
+#: lxc/init.go:133 lxc/init.go:134
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:530 lxc/config.go:595 lxc/image.go:701 lxc/profile.go:190
+#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:686 lxc/profile.go:228
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
 
-#: lxc/main.go:29
+#: lxc/main.go:32
 msgid "Connection refused; is LXD running?"
 msgstr "接続が拒否されました。LXDが実行されていますか?"
 
-#: lxc/publish.go:59
+#: lxc/publish.go:60
 msgid "Container name is mandatory"
 msgstr "コンテナ名を指定する必要があります"
 
-#: lxc/init.go:210
+#: lxc/init.go:211
 #, c-format
 msgid "Container name is: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/publish.go:141 lxc/publish.go:156
+#: lxc/publish.go:158 lxc/publish.go:173
 #, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "コンテナは以下のフィンガープリントで publish されます: %s"
 
-#: lxc/image.go:164
+#: lxc/image.go:132
 msgid "Copy aliases from source"
 msgstr "ソースからエイリアスをコピーしました"
 
-#: lxc/copy.go:22
-msgid ""
-"Copy containers within or in between lxd instances.\n"
-"\n"
-"lxc copy [remote:]<source container> [remote:]<destination container> [--"
-"ephemeral|e]"
-msgstr ""
-"LXDインスタンス内もしくはLXDインスタンス間でコンテナをコピーします。\n"
-"\n"
-"lxc copy [remote:]<source container> [remote:]<destination container> [--"
-"ephemeral|e]"
-
-#: lxc/image.go:277
+#: lxc/image.go:244
 #, c-format
 msgid "Copying the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/remote.go:210
+#: lxc/remote.go:223
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
-#: lxc/snapshot.go:21
-msgid ""
-"Create a read-only snapshot of a container.\n"
-"\n"
-"lxc snapshot [remote:]<source> <snapshot name> [--stateful]\n"
-"\n"
-"Creates a snapshot of the container (optionally with the container's memory\n"
-"state). When --stateful is used, LXD attempts to checkpoint the container's\n"
-"running state, including process memory state, TCP connections, etc. so that "
-"it\n"
-"can be restored (via lxc restore) at a later time (although some things, e."
-"g.\n"
-"TCP connections after the TCP timeout window has expired, may not be "
-"restored\n"
-"successfully).\n"
-"\n"
-"Example:\n"
-"lxc snapshot u1 snap0"
-msgstr ""
-"コンテナの読み取り専用のスナップショットを作成します。\n"
-"\n"
-"lxc snapshot [remote:]<source> <snapshot name> [--stateful]\n"
-"\n"
-"コンテナのスナップショットを作成します (オプションでコンテナのメモリ状態を\n"
-"含めて)。--statefulが指定された場合、LXDはコンテナプロセスのメモリ状態、TCP\n"
-"接続などの実行状態を、あとでlxc restoreを使ってリストアできるように、コンテ\n"
-"ナのチェックポイントを取得しようとします (しかし、タイムアウトウィンドウが\n"
-"expireしたあとのTCP接続のように正常にリストアできないものもあります)\n"
-"\n"
-"例:\n"
-"lxc snapshot u1 snap0"
-
-#: lxc/image.go:344 lxc/info.go:95
+#: lxc/image.go:324 lxc/info.go:97
 #, c-format
 msgid "Created: %s"
 msgstr "作成日時: %s"
 
-#: lxc/init.go:177 lxc/launch.go:118
+#: lxc/init.go:176 lxc/launch.go:111
 #, c-format
 msgid "Creating %s"
 msgstr "%s を作成中"
 
-#: lxc/init.go:175
+#: lxc/init.go:174
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:621 lxc/image.go:649
+#: lxc/image.go:606 lxc/image.go:634
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/delete.go:25
-msgid ""
-"Delete containers or container snapshots.\n"
-"\n"
-"lxc delete [remote:]<container>[/<snapshot>] [remote:][<container>[/"
-"<snapshot>]...]\n"
-"\n"
-"Destroy containers or snapshots with any attached data (configuration, "
-"snapshots, ...)."
-msgstr ""
-"コンテナもしくはコンテナのスナップショットを消去します。\n"
-"\n"
-"lxc delete [remote:]<container>[/<snapshot>] [remote:]"
-"[<container>[<snapshot>]...]\n"
-"\n"
-"付属するデータ (設定、スナップショット、...) と一緒にコンテナもしくはコンテ\n"
-"ナのスナップショットを消去します。"
-
-#: lxc/config.go:647
+#: lxc/config.go:688
 #, c-format
 msgid "Device %s added to %s"
 msgstr "デバイス %s が %s に追加されました"
 
-#: lxc/config.go:834
+#: lxc/config.go:875
 #, c-format
 msgid "Device %s removed from %s"
 msgstr "デバイス %s が %s から削除されました"
 
-#: lxc/list.go:478
+#: lxc/info.go:145
+#, fuzzy
+msgid "Disk usage:"
+msgstr "  ディスク使用量:"
+
+#: lxc/list.go:504
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:275
+#: lxc/config.go:312
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:41
-msgid "Enables debug mode."
+#: lxc/main.go:44
+#, fuzzy
+msgid "Enable debug mode"
 msgstr "デバッグモードを有効にします。"
 
-#: lxc/main.go:40
-msgid "Enables verbose mode."
+#: lxc/main.go:43
+#, fuzzy
+msgid "Enable verbose mode"
 msgstr "詳細モードを有効にします。"
 
-#: lxc/help.go:69
+#: lxc/exec.go:55
+#, fuzzy
+msgid "Environment variable to set (e.g. HOME=/home/foo)"
+msgstr "環境変数を HOME=/home/foo の形式で指定します"
+
+#: lxc/help.go:77
 msgid "Environment:"
 msgstr "環境変数:"
 
-#: lxc/copy.go:29 lxc/copy.go:30 lxc/init.go:138 lxc/init.go:139
-#: lxc/launch.go:44 lxc/launch.go:45
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:137 lxc/init.go:138
 msgid "Ephemeral container"
 msgstr "Ephemeral コンテナ"
 
@@ -349,150 +271,109 @@ msgstr "Ephemeral コンテナ"
 msgid "Event type to listen for"
 msgstr "Listenするイベントタイプ"
 
-#: lxc/exec.go:45
-#, fuzzy
-msgid ""
-"Execute the specified command in a container.\n"
-"\n"
-"lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
-"EDITOR=/usr/bin/vim]... [--] <command line>\n"
-"\n"
-"Mode defaults to non-interactive, interactive mode is selected if both stdin "
-"AND stdout are terminals (stderr is ignored)."
-msgstr ""
-"指定したコマンドをコンテナ内で実行します。\n"
-"\n"
-"lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
-"EDITOR=/usr/bin/vim]... <command>\n"
-"\n"
-"デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナ\n"
-"ルの場合は interactive モードが選択されます (標準エラー出力は無視されます)。"
-
-#: lxc/image.go:348
+#: lxc/image.go:328
 #, c-format
 msgid "Expires: %s"
 msgstr "失効日時: %s"
 
-#: lxc/image.go:350
+#: lxc/image.go:330
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/config.go:272 lxc/image.go:619 lxc/image.go:648
+#: lxc/config.go:309 lxc/image.go:604 lxc/image.go:633
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/list.go:102
-msgid "Fast mode (same as --columns=nsacPt"
+#: lxc/manpage.go:62
+#, c-format
+msgid "Failed to generate 'lxc.%s.1': %v"
+msgstr ""
+
+#: lxc/manpage.go:55
+#, c-format
+msgid "Failed to generate 'lxc.1': %v"
+msgstr ""
+
+#: lxc/list.go:125
+#, fuzzy
+msgid "Fast mode (same as --columns=nsacPt)"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
-#: lxc/image.go:337
+#: lxc/image.go:317
 #, c-format
 msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
 
-#: lxc/finger.go:15
-msgid ""
-"Fingers the LXD instance to check if it is up and working.\n"
-"\n"
-"lxc finger <remote>"
-msgstr ""
-"LXDインスタンスが稼働中かを確認します。\n"
-"\n"
-"lxc finger <remote>"
-
-#: lxc/action.go:42 lxc/action.go:43
-msgid "Force the container to shutdown."
+#: lxc/action.go:46 lxc/action.go:47
+#, fuzzy
+msgid "Force the container to shutdown"
 msgstr "コンテナを強制シャットダウンします。"
 
-#: lxc/delete.go:34 lxc/delete.go:35
-msgid "Force the removal of stopped containers."
+#: lxc/delete.go:33 lxc/delete.go:34
+#, fuzzy
+msgid "Force the removal of stopped containers"
 msgstr "停止したコンテナを強制的に削除します。"
 
-#: lxc/main.go:42
-msgid "Force using the local unix socket."
+#: lxc/main.go:45
+#, fuzzy
+msgid "Force using the local unix socket"
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
-#: lxc/list.go:101
+#: lxc/list.go:124
 msgid "Format"
 msgstr ""
 
-#: lxc/main.go:124
+#: lxc/main.go:131
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "クライアント証明書を生成します。1分ぐらいかかります..."
 
-#: lxc/list.go:392
+#: lxc/list.go:416
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:393
+#: lxc/list.go:417
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:274
+#: lxc/config.go:311
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:132
+#: lxc/main.go:139
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要があります"
 
-#: lxc/main.go:43
-msgid "Ignore aliases when determining what command to run."
+#: lxc/main.go:46
+#, fuzzy
+msgid "Ignore aliases when determining what command to run"
 msgstr "どのコマンドを実行するか決める際にエイリアスを無視します。"
 
-#: lxc/action.go:46
-msgid "Ignore the container state (only for start)."
+#: lxc/action.go:50
+#, fuzzy
+msgid "Ignore the container state (only for start)"
 msgstr "コンテナの状態を無視します (startのみ)。"
 
-#: lxc/image.go:282
+#: lxc/image.go:247
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:433
+#: lxc/image.go:400 lxc/image.go:412
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
 
-#: lxc/image.go:420
+#: lxc/image.go:397
 #, fuzzy, c-format
 msgid "Importing the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/init.go:73
-msgid ""
-"Initialize a container from a particular image.\n"
-"\n"
-"lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
-"\n"
-"Initializes a container using the specified image and name.\n"
-"\n"
-"Not specifying -p will result in the default profile.\n"
-"Specifying \"-p\" with no argument will result in no profile.\n"
-"\n"
-"Example:\n"
-"lxc init ubuntu u1"
-msgstr ""
-"指定したイメージからコンテナを初期化します。\n"
-"\n"
-"lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
-"\n"
-"指定したイメージとコンテナ名を使ってコンテナを初期化します。\n"
-"\n"
-"-p を指定しない場合はデフォルトのプロファイルを使います。\n"
-"\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n"
-"\n"
-"例:\n"
-"lxc init ubuntu u1"
-
-#: lxc/remote.go:121
+#: lxc/remote.go:134
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr "不正な URL スキーム \"%s\" (\"%s\" 内)"
 
-#: lxc/config.go:253
+#: lxc/config.go:290
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
@@ -501,886 +382,1256 @@ msgstr "証明書のフィンガープリントの確認なしで証明書を受
 msgid "Invalid configuration key"
 msgstr "正しくない設定項目 (key) です"
 
-#: lxc/file.go:195
+#: lxc/file.go:207
 #, c-format
 msgid "Invalid source %s"
 msgstr "不正なソース %s"
 
-#: lxc/file.go:57
+#: lxc/file.go:69
 #, c-format
 msgid "Invalid target %s"
 msgstr "不正な送り先 %s"
 
-#: lxc/info.go:124
+#: lxc/info.go:126
 msgid "Ips:"
 msgstr "IPアドレス:"
 
-#: lxc/image.go:165
+#: lxc/image.go:133
 msgid "Keep the image up to date after initial copy"
 msgstr "最初にコピーした後も常にイメージを最新の状態に保つ"
 
-#: lxc/main.go:27
+#: lxc/main.go:30
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
-#: lxc/launch.go:22
-msgid ""
-"Launch a container from a particular image.\n"
-"\n"
-"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
-"\n"
-"Launches a container using the specified image and name.\n"
-"\n"
-"Not specifying -p will result in the default profile.\n"
-"Specifying \"-p\" with no argument will result in no profile.\n"
-"\n"
-"Example:\n"
-"lxc launch ubuntu:16.04 u1"
-msgstr ""
-"指定したイメージからコンテナを起動します。\n"
-"\n"
-"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
-"\n"
-"指定したイメージと名前を使ってコンテナを起動します。\n"
-"\n"
-"-p を指定しない場合はデフォルトのプロファイルを使います。\n"
-"\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n"
-"\n"
-"例:\n"
-"lxc launch ubuntu:16.04 u1"
-
-#: lxc/info.go:25
-msgid ""
-"List information on LXD servers and containers.\n"
-"\n"
-"For a container:\n"
-" lxc info [<remote>:]container [--show-log]\n"
-"\n"
-"For a server:\n"
-" lxc info [<remote>:]"
-msgstr ""
-"LXD サーバとコンテナの情報を一覧表示します。\n"
-"\n"
-"コンテナ情報:\n"
-" lxc info [<remote>:]container [--show-log]\n"
-"\n"
-"サーバ情報:\n"
-" lxc info [<remote>:]"
+#: lxc/image.go:333
+#, fuzzy, c-format
+msgid "Last used: %s"
+msgstr "状態: %s"
 
-#: lxc/list.go:67
-#, fuzzy
-msgid ""
-"Lists the available resources.\n"
-"\n"
-"lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
-"\n"
-"The filters are:\n"
-"* A single keyword like \"web\" which will list any container with a name "
-"starting by \"web\".\n"
-"* A regular expression on the container name. (e.g. .*web.*01$)\n"
-"* A key/value pair referring to a configuration item. For those, the "
-"namespace can be abbreviated to the smallest unambiguous identifier:\n"
-" * \"user.blah=abc\" will list all containers with the \"blah\" user "
-"property set to \"abc\".\n"
-" * \"u.blah=abc\" will do the same\n"
-" * \"security.privileged=1\" will list all privileged containers\n"
-" * \"s.privileged=1\" will do the same\n"
-"* A regular expression matching a configuration item or its value. (e.g. "
-"volatile.eth0.hwaddr=00:16:3e:.*)\n"
-"\n"
-"Columns for table format are:\n"
-"* 4 - IPv4 address\n"
-"* 6 - IPv6 address\n"
-"* a - architecture\n"
-"* c - creation date\n"
-"* n - name\n"
-"* p - pid of container init process\n"
-"* P - profiles\n"
-"* s - state\n"
-"* S - number of snapshots\n"
-"* t - type (persistent or ephemeral)\n"
-"\n"
-"Default column layout: ns46tS\n"
-"Fast column layout: nsacPt"
+#: lxc/image.go:335
+msgid "Last used: never"
 msgstr ""
-"利用可能なリソースを一覧表示します。\n"
-"\n"
-"lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
-"\n"
-"フィルタの指定:\n"
-"* 単一の \"web\" のようなキーワードを指定すると、名前が \"web\" ではじまるコ"
-"ンテ\n"
-"  ナが一覧表示されます。\n"
-"* コンテナ名の正規表現 (例: .*web.*01$)\n"
-"* 設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することが"
-"で\n"
-"  きます:\n"
-" * \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定され"
-"ている\n"
-"  コンテナをすべて一覧表示します。\n"
-" * \"u.blah=abc\" は上記と同じ意味になります。\n"
-" * \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
-" * \"s.privilaged=1\" は上記と同じ意味になります。\n"
-" * 設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:."
-"*)\n"
-"\n"
-"表のカラムの指定:\n"
-"* 4 - IPv4 アドレス\n"
-"* 6 - IPv6 アドレス\n"
-"* a - アーキテクチャ\n"
-"* c - 作成日\n"
-"* n - 名前\n"
-"* p - コンテナの init プロセスの pid\n"
-"* P - プロファイル\n"
-"* s - 状態\n"
-"* S - スナップショットの数\n"
-"* t - タイプ (persistent or ephemeral)\n"
-"\n"
-"デフォルトのカラムレイアウト: ns46tS\n"
-"Fast モードのカラムレイアウト: nsacPt"
 
-#: lxc/info.go:228
+#: lxc/info.go:230
 msgid "Log:"
 msgstr "ログ:"
 
-#: lxc/image.go:163
+#: lxc/image.go:131
 msgid "Make image public"
 msgstr "イメージを public にする"
 
-#: lxc/publish.go:32
+#: lxc/publish.go:33
 msgid "Make the image public"
 msgstr "イメージを public にする"
 
-#: lxc/profile.go:47
+#: lxc/info.go:152
+msgid "Memory (current)"
+msgstr "メモリ (現在値)"
+
+#: lxc/info.go:156
+msgid "Memory (peak)"
+msgstr "メモリ (ピーク)"
+
+#: lxc/info.go:168
 #, fuzzy
-msgid ""
-"Manage configuration profiles.\n"
-"\n"
-"lxc profile list [filters]                     List available profiles.\n"
-"lxc profile show <profile>                     Show details of a profile.\n"
-"lxc profile create <profile>                   Create a profile.\n"
-"lxc profile copy <profile> <remote>            Copy the profile to the "
-"specified remote.\n"
-"lxc profile get <profile> <key>                Get profile configuration.\n"
-"lxc profile set <profile> <key> <value>        Set profile configuration.\n"
-"lxc profile unset <profile> <key>              Unset profile configuration.\n"
-"lxc profile delete <profile>                   Delete a profile.\n"
-"lxc profile edit <profile>\n"
-"    Edit profile, either by launching external editor or reading STDIN.\n"
-"    Example: lxc profile edit <profile> # launch editor\n"
-"             cat profile.yaml | lxc profile edit <profile> # read from "
-"profile.yaml\n"
-"lxc profile apply <container> <profiles>\n"
-"    Apply a comma-separated list of profiles to a container, in order.\n"
-"    All profiles passed in this call (and only those) will be applied\n"
-"    to the specified container.\n"
-"    Example: lxc profile apply foo default,bar # Apply default and bar\n"
-"             lxc profile apply foo default # Only default is active\n"
-"             lxc profile apply '' # no profiles are applied anymore\n"
-"             lxc profile apply bar,default # Apply default second now\n"
-"\n"
-"Devices:\n"
-"lxc profile device list <profile>                                   List "
-"devices in the given profile.\n"
-"lxc profile device show <profile>                                   Show "
-"full device details in the given profile.\n"
-"lxc profile device remove <profile> <name>                          Remove a "
-"device from a profile.\n"
-"lxc profile device get <[remote:]profile> <name> <key>              Get a "
-"device property.\n"
-"lxc profile device set <[remote:]profile> <name> <key> <value>      Set a "
-"device property.\n"
-"lxc profile device unset <[remote:]profile> <name> <key>            Unset a "
-"device property.\n"
-"lxc profile device add <profile name> <device name> <device type> "
-"[key=value]...\n"
-"    Add a profile device, such as a disk or a nic, to the containers\n"
-"    using the specified profile."
-msgstr ""
-"設定プロファイルを管理します。\n"
-"\n"
-"lxc profile list [filters]\n"
-"    利用可能なプロファイルを一覧します。\n"
-"lxc profile show <profile>\n"
-"    プロファイルの詳細を表示します。\n"
-"lxc profile create <profile>\n"
-"    プロファイルを作成します。\n"
-"lxc profile copy <profile> <remote>\n"
-"    プロファイルを remote にコピーします。\n"
-"lxc profile get <profile> <key>\n"
-"    プロファイルの設定を取得します。\n"
-"lxc profile set <profile> <key> <value>\n"
-"    プロファイルの設定を設定します。\n"
-"lxc profile delete <profile>\n"
-"    プロファイルを削除します。\n"
-"lxc profile edit <profile>\n"
-"    プロファイルを編集します。外部エディタもしくはSTDINから読み込みます。\n"
-"    例: lxc profile edit <profile> # エディタの起動\n"
-"        cat profile.yml | lxc profile edit <profile> # profile.yml から読み込"
-"み\n"
-"\n"
-"lxc profile apply <container> <profiles>\n"
-"    プロファイルのコンマ区切りのリストをコンテナに順番に適用します。\n"
-"    このコマンドで指定したプロファイルだけが対象のコンテナに適用されます。\n"
-"    例: lxc profile apply foo default,bar # defaultとbarを適用\n"
-"        lxc profile apply foo default # defaultだけを有効化\n"
-"        lxc profile apply '' # 一切のプロファイルを適用しない\n"
-"        lxc profile apply bar,default # defaultを2番目に適用\n"
-"lxc profile apply-add <container> <profile>\n"
-"lxc profile apply-remove <container> <profile>\n"
-"\n"
-"デバイス:\n"
-"lxc profile device list <profile>\n"
-"    指定したプロファイル内のデバイスを一覧表示します\n"
-"lxc profile device show <profile>\n"
-"    指定したプロファイル内の全デバイスの詳細を表示します\n"
-"lxc profile device remove <profile> <name>\n"
-"    プロファイルからデバイスを削除します\n"
-"lxc profile device get <[remote:]profile> <name> <key>\n"
-"    デバイスプロパティを取得します\n"
-"lxc profile device set <[remote:]profile> <name> <key> <value>\n"
-"    デバイスプロパティを設定します\n"
-"lxc profile device unset <[remote:]profile> <name> <key>\n"
-"    デバイスプロパティを削除します\n"
-"lxc profile device add <profile name> <device name> <device type> "
-"[key=value]...\n"
-"    ディスクやNICのようなプロファイルデバイスを指定したプロファイルを使って\n"
-"    コンテナに追加します。"
-
-#: lxc/config.go:57
-#, fuzzy
-msgid ""
-"Manage configuration.\n"
-"\n"
-"lxc config device add <[remote:]container> <name> <type> [key=value]...     "
-"Add a device to a container.\n"
-"lxc config device get <[remote:]container> <name> <key>                     "
-"Get a device property.\n"
-"lxc config device set <[remote:]container> <name> <key> <value>             "
-"Set a device property.\n"
-"lxc config device unset <[remote:]container> <name> <key>                   "
-"Unset a device property.\n"
-"lxc config device list <[remote:]container>                                 "
-"List devices for container.\n"
-"lxc config device show <[remote:]container>                                 "
-"Show full device details for container.\n"
-"lxc config device remove <[remote:]container> <name>                        "
-"Remove device from container.\n"
-"\n"
-"lxc config get [remote:][container] <key>                                   "
-"Get container or server configuration key.\n"
-"lxc config set [remote:][container] <key> <value>                           "
-"Set container or server configuration key.\n"
-"lxc config unset [remote:][container] <key>                                 "
-"Unset container or server configuration key.\n"
-"lxc config show [remote:][container] [--expanded]                           "
-"Show container or server configuration.\n"
-"lxc config edit [remote:][container]                                        "
-"Edit container or server configuration in external editor.\n"
-"    Edit configuration, either by launching external editor or reading "
-"STDIN.\n"
-"    Example: lxc config edit <container> # launch editor\n"
-"             cat config.yaml | lxc config edit <config> # read from config."
-"yaml\n"
-"\n"
-"lxc config trust list [remote]                                              "
-"List all trusted certs.\n"
-"lxc config trust add [remote] <certfile.crt>                                "
-"Add certfile.crt to trusted hosts.\n"
-"lxc config trust remove [remote] [hostname|fingerprint]                     "
-"Remove the cert from trusted hosts.\n"
-"\n"
-"Examples:\n"
-"To mount host's /share/c1 onto /opt in the container:\n"
-"    lxc config device add [remote:]container1 <device-name> disk source=/"
-"share/c1 path=opt\n"
-"\n"
-"To set an lxc config value:\n"
-"    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete = "
-"1'\n"
-"\n"
-"To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the "
-"default):\n"
-"    lxc config set core.https_address [::]:8443\n"
-"\n"
-"To set the server trust password:\n"
-"    lxc config set core.trust_password blah"
-msgstr ""
-"設定を管理します。\n"
-"\n"
-"lxc config device add <[remote:]container> <name> <type> [key=value]...\n"
-"    コンテナにデバイスを追加します。\n"
-"lxc config device get <[remote:]container> <name> <key>\n"
-"    デバイスのプロパティを取得します。\n"
-"lxc config device set <[remote:]container> <name> <key> <value>\n"
-"    デバイスのプロパティを設定します。\n"
-"lxc config device unset <[remote:]container> <name> <key>\n"
-"    デバイスのプロパティを削除します。\n"
-"lxc config device list <[remote:]container>\n"
-"    コンテナのデバイスを一覧表示します。\n"
-"lxc config device show <[remote:]container>\n"
-"    全てのデバイスの詳細を表示します。\n"
-"lxc config device remove <[remote:]container> <name>\n"
-"    コンテナからデバイスを削除します。\n"
-"\n"
-"lxc config get [remote:][container] <key>\n"
-"    コンテナもしくはサーバの設定項目の値を取得します。\n"
-"lxc config set [remote:][container] <key> <value>\n"
-"    コンテナもしくはサーバの設定項目に値を設定します。\n"
-"lxc config unset [remote:][container] <key>\n"
-"    コンテナもしくはサーバの設定項目を削除します。\n"
-"lxc config show [remote:][container] [--expanded]\n"
-"    コンテナもしくはサーバの設定を表示します。\n"
-"lxc config edit [remote:][container]\n"
-"    コンテナもしくはサーバの設定を外部エディタで編集します。\n"
-"    設定の編集は外部エディタを起動するか、標準入力からの読み込みで行いま"
-"す。\n"
-"    例: lxc config edit <container> # エディタの起動\n"
-"        cat config.yml | lxc config edit <config> # config.ymlから読み込み\n"
-"\n"
-"lxc config trust list [remote]\n"
-"    信頼する証明書を全て表示します。\n"
-"lxc config trust add [remote] <certfile.crt>\n"
-"    certfile.crt を信頼するホストに追加します。\n"
-"lxc config trust remove [remote] [hostname|fingerprint]\n"
-"    信頼するホストから証明書を消去します。\n"
-"\n"
-"例:\n"
-"ホストの /share/c1 をコンテナ内の /opt にマウントするには:\n"
-"    lxc config device add [remote:]container1 <device-name> disk source=/"
-"share/c1 path=opt\n"
-"\n"
-"lxc 設定項目に値を設定するには:\n"
-"    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete = "
-"1'\n"
-"\n"
-"IPv4 と IPv6 のポート 8443 で Listen するには\n"
-"(8443 はデフォルトなので省略できます):\n"
-"    lxc config set core.https_address [::]:8443\n"
-"\n"
-"サーバのパスワードを設定するには:\n"
-"    lxc config set core.trust_password blah"
-
-#: lxc/file.go:32
-msgid ""
-"Manage files on a container.\n"
-"\n"
-"lxc file pull <source> [<source>...] <target>\n"
-"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] "
-"<target>\n"
-"lxc file edit <file>\n"
-"\n"
-"<source> in the case of pull, <target> in the case of push and <file> in the "
-"case of edit are <container name>/<path>"
-msgstr ""
-"コンテナ上のファイルを管理します。\n"
-"\n"
-"lxc file pull <source> [<source>...] <target>\n"
-"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> [<source>...] "
-"<target>\n"
-"lxc file edit <file>\n"
-"\n"
-"pull の場合の <source>、push の場合の <target>、edit の場合の <file> は、い\n"
-"ずれも <container name>/<path> の形式です。"
-
-#: lxc/remote.go:38
-msgid ""
-"Manage remote LXD servers.\n"
-"\n"
-"lxc remote add <name> <url> [--accept-certificate] [--password=PASSWORD]\n"
-"                            [--public] [--protocol=PROTOCOL]                "
-"Add the remote <name> at <url>.\n"
-"lxc remote remove <name>                                                    "
-"Remove the remote <name>.\n"
-"lxc remote list                                                             "
-"List all remotes.\n"
-"lxc remote rename <old> <new>                                               "
-"Rename remote <old> to <new>.\n"
-"lxc remote set-url <name> <url>                                             "
-"Update <name>'s url to <url>.\n"
-"lxc remote set-default <name>                                               "
-"Set the default remote.\n"
-"lxc remote get-default                                                      "
-"Print the default remote."
-msgstr ""
-"リモートの LXD サーバを管理します。\n"
-"\n"
-"lxc remote add <name> <url>\n"
-"    [--accept-certificate] [--password=PASSWORD]\n"
-"    [--public] [--protocol=PROTOCOL]\n"
-"    <url> をリモートホスト <name> として追加します。\n"
-"lxc remote remove <name>\n"
-"    リモートホスト <name> を削除します。\n"
-"lxc remote list\n"
-"    登録済みのリモートホストを全て一覧表示します。\n"
-"lxc remote rename <old> <new>\n"
-"    リモートホストの名前を <old> から <new> に変更します。\n"
-"lxc remote set-url <name> <url>\n"
-"    <name> の url を <url> に更新します。\n"
-"lxc remote set-default <name>\n"
-"    <name> をデフォルトのリモートホストに設定します。\n"
-"lxc remote get-default\n"
-"    デフォルトに設定されているリモートホストを表示します。"
-
-#: lxc/image.go:93
-#, fuzzy
-msgid ""
-"Manipulate container images.\n"
-"\n"
-"In LXD containers are created from images. Those images were themselves\n"
-"either generated from an existing container or downloaded from an image\n"
-"server.\n"
-"\n"
-"When using remote images, LXD will automatically cache images for you\n"
-"and remove them upon expiration.\n"
-"\n"
-"The image unique identifier is the hash (sha-256) of its representation\n"
-"as a compressed tarball (or for split images, the concatenation of the\n"
-"metadata and rootfs tarballs).\n"
-"\n"
-"Images can be referenced by their full hash, shortest unique partial\n"
-"hash or alias name (if one is set).\n"
-"\n"
-"\n"
-"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
-"alias=ALIAS].. [prop=value]\n"
-"    Import an image tarball (or tarballs) into the LXD image store.\n"
-"\n"
-"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
-"[--public] [--auto-update]\n"
-"    Copy an image from one LXD daemon to another over the network.\n"
-"\n"
-"    The auto-update flag instructs the server to keep this image up to\n"
-"    date. It requires the source to be an alias and for it to be public.\n"
-"\n"
-"lxc image delete [remote:]<image>\n"
-"    Delete an image from the LXD image store.\n"
-"\n"
-"lxc image export [remote:]<image> [target]\n"
-"    Export an image from the LXD image store into a distributable tarball.\n"
-"\n"
-"    The output target is optional and defaults to the working directory.\n"
-"    The target may be an existing directory, file name, or \"-\" to specify\n"
-"    stdout.  The target MUST be a directory when exporting a split image.\n"
-"    If the target is a directory, the image's name (each part's name for\n"
-"    split images) as found in the database will be used for the exported\n"
-"    image.  If the target is a file (not a directory and not stdout), then\n"
-"    the appropriate extension will be appended to the provided file name\n"
-"    based on the algorithm used to compress the image. \n"
-"\n"
-"lxc image info [remote:]<image>\n"
-"    Print everything LXD knows about a given image.\n"
-"\n"
-"lxc image list [remote:] [filter]\n"
-"    List images in the LXD image store. Filters may be of the\n"
-"    <key>=<value> form for property based filtering, or part of the image\n"
-"    hash or part of the image alias name.\n"
-"\n"
-"lxc image show [remote:]<image>\n"
-"    Yaml output of the user modifiable properties of an image.\n"
-"\n"
-"lxc image edit [remote:]<image>\n"
-"    Edit image, either by launching external editor or reading STDIN.\n"
-"    Example: lxc image edit <image> # launch editor\n"
-"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
-"\n"
-"lxc image alias create [remote:]<alias> <fingerprint>\n"
-"    Create a new alias for an existing image.\n"
-"\n"
-"lxc image alias delete [remote:]<alias>\n"
-"    Delete an alias.\n"
-"\n"
-"lxc image alias list [remote:] [filter]\n"
-"    List the aliases. Filters may be part of the image hash or part of the "
-"image alias name.\n"
-msgstr ""
-"コンテナイメージを操作します。\n"
-"\n"
-"LXD では、コンテナはイメージから作られます。このイメージは、既存のコンテナ\n"
-"やイメージサーバからダウンロードしたイメージから作られます。\n"
-"\n"
-"リモートのイメージを使う場合、LXD は自動的にイメージをキャッシュします。そ\n"
-"して、イメージの期限が切れるとキャッシュを削除します。\n"
-"\n"
-"イメージ固有の識別子は圧縮された tarball (分割イメージの場合は、メタデータ\n"
-"と rootfs tarball を結合したもの) のハッシュ (sha-256) です。\n"
-"\n"
-"イメージは全ハッシュ文字列、一意に定まるハッシュの短縮表現、(設定されてい\n"
-"る場合は) エイリアスで参照できます。\n"
-"\n"
-"\n"
-"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
-"alias=ALIAS].. [prop=value]\n"
-"    イメージの tarball (複数も可能) を LXD のイメージストアにインポートしま\n"
-"    す。\n"
-"\n"
-"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
-"[--public] [--auto-update]\n"
-"    ネットワーク経由である LXD デーモンから他の LXD デーモンへイメージを\n"
-"    コピーします。\n"
-"\n"
-"    auto-update フラグは、サーバがイメージを最新に保つように指示します。イ\n"
-"    メージのソースがエイリアスであり、public である必要があります。\n"
-"\n"
-"lxc image delete [remote:]<image>\n"
-"    LXD のイメージストアからイメージを削除します。\n"
-"\n"
-"lxc image export [remote:]<image>\n"
-"    LXD のイメージストアから配布可能な tarball としてイメージをエクスポート\n"
-"    します。\n"
-"\n"
-"lxc image info [remote:]<image>\n"
-"    指定したイメージについてのすべての情報を表示します。\n"
-"\n"
-"lxc image list [remote:] [filter]\n"
-"    LXD のイメージストア内のイメージを一覧表示します。プロパティでフィルタ\n"
-"    を行う場合は、フィルタは <key>=<value> の形になります。フィルタはイメー\n"
-"    ジハッシュの一部やイメージエイリアス名の一部も指定できます。\n"
-"\n"
-"lxc image show [remote:]<image>\n"
-"    ユーザが変更できるプロパティの YAML 形式の出力を行います。\n"
-"\n"
-"lxc image edit [remote:]<image>\n"
-"    外部エディタまたは標準入力からの読み込みにより、イメージを編集します。\n"
-"    例: lxc image edit <image> # エディタの起動\n"
-"        cat image.yml | lxc image edit <image> # image.yml から読み込み\n"
-"\n"
-"lxc image alias create [remote:]<alias> <fingerprint>\n"
-"    既存のイメージに新たにエイリアスを作成します。\n"
-"\n"
-"lxc image alias delete [remote:]<alias>\n"
-"    エイリアスを削除します。\n"
-"\n"
-"lxc image alias list [remote:] [filter]\n"
-"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリア"
-"ス\n"
-"    名の一部をフィルタとして指定できます。\n"
-
-#: lxc/info.go:150
-msgid "Memory (current)"
-msgstr "メモリ (現在値)"
-
-#: lxc/info.go:154
-msgid "Memory (peak)"
-msgstr "メモリ (ピーク)"
-
-#: lxc/help.go:87
-msgid "Missing summary."
-msgstr "サマリーはありません。"
-
-#: lxc/monitor.go:41
-msgid ""
-"Monitor activity on the LXD server.\n"
-"\n"
-"lxc monitor [remote:] [--type=TYPE...]\n"
-"\n"
-"Connects to the monitoring interface of the specified LXD server.\n"
-"\n"
-"By default will listen to all message types.\n"
-"Specific types to listen to can be specified with --type.\n"
-"\n"
-"Example:\n"
-"lxc monitor --type=logging"
-msgstr ""
-"LXD サーバの動作をモニタリングします。\n"
-"\n"
-"lxc monitor [remote:] [--type=TYPE...]\n"
-"\n"
-"指定した LXD サーバのモニタリングインターフェースに接続します。\n"
-"\n"
-"デフォルトでは全てのタイプをモニタリングします。\n"
-"--type により、モニタリングするタイプを指定できます。\n"
-"\n"
-"例:\n"
-"lxc monitor --type=logging"
-
-#: lxc/file.go:183
-msgid "More than one file to download, but target is not a directory"
+msgid "Memory usage:"
+msgstr "  メモリ消費量:"
+
+#: lxc/utils.go:173
+msgid "Missing summary."
+msgstr "サマリーはありません。"
+
+#: lxc/file.go:195
+msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "ダウンロード対象のファイルが複数ありますが、コピー先がディレクトリではありま"
 "せん。"
 
-#: lxc/move.go:16
-msgid ""
-"Move containers within or in between lxd instances.\n"
-"\n"
-"lxc move [remote:]<source container> [remote:]<destination container>\n"
-"    Move a container between two hosts, renaming it if destination name "
-"differs.\n"
-"\n"
-"lxc move <old name> <new name>\n"
-"    Rename a local container.\n"
-msgstr ""
-"LXD ホスト内、もしくは LXD ホスト間でコンテナを移動します。\n"
-"\n"
-"lxc move [remote:]<source container> [remote:]<destination container>\n"
-"    2 つのホスト間でコンテナを移動します。コピー先の名前が元と違う場合は\n"
-"    同時にリネームされます。\n"
-"\n"
-"lxc move <old name> <new name>\n"
-"    ローカルのコンテナをリネームします。\n"
-
-#: lxc/action.go:69
+#: lxc/action.go:68
 msgid "Must supply container name for: "
 msgstr "コンテナ名を指定する必要があります: "
 
-#: lxc/list.go:396 lxc/remote.go:352
+#: lxc/list.go:420 lxc/remote.go:365
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:326 lxc/remote.go:331
+#: lxc/remote.go:339 lxc/remote.go:344
 msgid "NO"
 msgstr ""
 
-#: lxc/info.go:89
+#: lxc/info.go:91
 #, c-format
 msgid "Name: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/image.go:166 lxc/publish.go:33
+#: lxc/info.go:185
+#, fuzzy
+msgid "Network usage:"
+msgstr "  ネットワーク使用状況:"
+
+#: lxc/image.go:134 lxc/publish.go:34
 msgid "New alias to define at target"
 msgstr "新しいエイリアスを定義する"
 
-#: lxc/config.go:284
+#: lxc/config.go:321
 msgid "No certificate provided to add"
 msgstr "追加すべき証明書が提供されていません"
 
-#: lxc/config.go:307
+#: lxc/config.go:344
 msgid "No fingerprint specified."
 msgstr "フィンガープリントが指定されていません。"
 
-#: lxc/remote.go:106
+#: lxc/remote.go:119
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
-#: lxc/image.go:425
+#: lxc/image.go:403
 msgid "Only https:// is supported for remote image import."
 msgstr "リモートイメージのインポートは https:// のみをサポートします。"
 
-#: lxc/help.go:63 lxc/main.go:108
+#: lxc/help.go:71 lxc/main.go:112 lxc/main.go:164
 msgid "Options:"
 msgstr "オプション:"
 
-#: lxc/image.go:520
+#: lxc/image.go:505
 #, c-format
 msgid "Output is in %s"
 msgstr "%s に出力されます"
 
-#: lxc/exec.go:55
+#: lxc/exec.go:56
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)"
 
-#: lxc/list.go:480
+#: lxc/list.go:506
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:397
+#: lxc/list.go:421
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:398
+#: lxc/list.go:422
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:354
+#: lxc/remote.go:367
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:620 lxc/remote.go:355
+#: lxc/image.go:605 lxc/remote.go:368
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:177
+#: lxc/info.go:179
 msgid "Packets received"
 msgstr "受信パケット"
 
-#: lxc/info.go:178
+#: lxc/info.go:180
 msgid "Packets sent"
 msgstr "送信パケット"
 
-#: lxc/help.go:70
-msgid "Path to an alternate client configuration directory."
+#: lxc/help.go:78
+#, fuzzy
+msgid "Path to an alternate client configuration directory"
 msgstr "別のクライアント用設定ディレクトリ"
 
-#: lxc/help.go:71
-msgid "Path to an alternate server directory."
+#: lxc/help.go:79
+#, fuzzy
+msgid "Path to an alternate server directory"
 msgstr "別のサーバ用設定ディレクトリ"
 
-#: lxc/main.go:31
+#: lxc/main.go:201
+#, fuzzy
+msgid "Pause containers."
+msgstr "コンテナの不正なURL %s"
+
+#: lxc/main.go:34
 #, fuzzy
 msgid "Permission denied, are you in the lxd group?"
 msgstr "アクセスが拒否されました。lxd グループに所属していますか?"
 
-#: lxc/info.go:106
+#: lxc/info.go:108
 #, c-format
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/help.go:25
-msgid ""
-"Presents details on how to use LXD.\n"
-"\n"
-"lxd help [--all]"
-msgstr ""
-"LXDの使い方の詳細を表示します。\n"
-"\n"
-"lxd help [--all]"
-
-#: lxc/profile.go:191
+#: lxc/profile.go:229
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:531 lxc/config.go:596 lxc/image.go:702
+#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:687
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
-#: lxc/help.go:65
-msgid "Print debug information."
+#: lxc/help.go:73
+#, fuzzy
+msgid "Print debug information"
 msgstr "デバッグ情報を表示します。"
 
-#: lxc/help.go:64
-msgid "Print less common commands."
+#: lxc/help.go:72
+#, fuzzy
+msgid "Print less common commands"
 msgstr "全てのコマンドを表示します (主なコマンドだけではなく)。"
 
-#: lxc/help.go:66
-msgid "Print verbose information."
+#: lxc/help.go:74
+#, fuzzy
+msgid "Print verbose information"
 msgstr "詳細情報を表示します。"
 
-#: lxc/version.go:18
-msgid ""
-"Prints the version number of this client tool.\n"
-"\n"
-"lxc version"
-msgstr ""
-"お使いのクライアントのバージョン番号を表示します。\n"
-"\n"
-"lxc version"
-
-#: lxc/info.go:130
+#: lxc/info.go:132
 #, c-format
 msgid "Processes: %d"
 msgstr "プロセス数: %d"
 
-#: lxc/profile.go:228
+#: lxc/profile.go:266
 #, fuzzy, c-format
 msgid "Profile %s applied to %s"
 msgstr "プロファイル %s が %s に追加されました"
 
-#: lxc/profile.go:142
+#: lxc/profile.go:180
 #, c-format
 msgid "Profile %s created"
 msgstr "プロファイル %s を作成しました"
 
-#: lxc/profile.go:212
+#: lxc/profile.go:250
 #, c-format
 msgid "Profile %s deleted"
 msgstr "プロファイル %s を削除しました"
 
-#: lxc/init.go:136 lxc/init.go:137 lxc/launch.go:42 lxc/launch.go:43
+#: lxc/init.go:135 lxc/init.go:136
 msgid "Profile to apply to the new container"
 msgstr "新しいコンテナに適用するプロファイル"
 
-#: lxc/info.go:104
+#: lxc/info.go:106
 #, c-format
 msgid "Profiles: %s"
 msgstr "プロファイル: %s"
 
-#: lxc/image.go:352
+#: lxc/image.go:337
 msgid "Properties:"
 msgstr "プロパティ:"
 
-#: lxc/remote.go:55
+#: lxc/remote.go:68
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
-#: lxc/image.go:340
+#: lxc/image.go:320
 #, c-format
 msgid "Public: %s"
 msgstr ""
 
-#: lxc/publish.go:25
-msgid ""
-"Publish containers as images.\n"
-"\n"
-"lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
-"value]..."
-msgstr ""
-"イメージとしてコンテナを publish します。\n"
-"\n"
-"lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
-"value]..."
-
-#: lxc/remote.go:53
+#: lxc/remote.go:66
 msgid "Remote admin password"
 msgstr "リモートの管理者パスワード"
 
-#: lxc/info.go:91
+#: lxc/info.go:93
 #, fuzzy, c-format
 msgid "Remote: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/delete.go:42
+#: lxc/delete.go:41
 #, c-format
 msgid "Remove %s (yes/no): "
 msgstr "%s を消去しますか (yes/no): "
 
-#: lxc/delete.go:36 lxc/delete.go:37
-msgid "Require user confirmation."
+#: lxc/delete.go:35 lxc/delete.go:36
+#, fuzzy
+msgid "Require user confirmation"
 msgstr "ユーザの確認を要求する。"
 
-#: lxc/info.go:127
+#: lxc/info.go:129
 msgid "Resources:"
 msgstr "リソース:"
 
-#: lxc/init.go:247
+#: lxc/main.go:209
+#, fuzzy
+msgid "Restart containers."
+msgstr "コンテナを作成中"
+
+#: lxc/init.go:218
 #, c-format
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:623
+#: lxc/image.go:608
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:399
+#: lxc/list.go:423
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:400
-msgid "STATE"
+#: lxc/list.go:424
+msgid "STATE"
+msgstr ""
+
+#: lxc/remote.go:369
+msgid "STATIC"
+msgstr ""
+
+#: lxc/remote.go:216
+msgid "Server certificate NACKed by user"
+msgstr "ユーザによりサーバ証明書が拒否されました"
+
+#: lxc/remote.go:278
+msgid "Server doesn't trust us after adding our cert"
+msgstr "サーバが我々の証明書を追加した後我々を信頼していません"
+
+#: lxc/remote.go:67
+msgid "Server protocol (lxd or simplestreams)"
+msgstr "サーバのプロトコル (lxd or simplestreams)"
+
+#: lxc/file.go:56
+msgid "Set the file's gid on push"
+msgstr "プッシュ時にファイルのgidを設定します"
+
+#: lxc/file.go:57
+msgid "Set the file's perms on push"
+msgstr "プッシュ時にファイルのパーミションを設定します"
+
+#: lxc/file.go:55
+msgid "Set the file's uid on push"
+msgstr "プッシュ時にファイルのuidを設定します"
+
+#: lxc/help.go:29
+msgid "Show all commands (not just interesting ones)"
+msgstr "全てコマンドを表示します (主なコマンドだけではなく)"
+
+#: lxc/help.go:75
+msgid "Show client version"
+msgstr ""
+
+#: lxc/info.go:38
+msgid "Show the container's last 100 log lines?"
+msgstr "コンテナログの最後の 100 行を表示しますか?"
+
+#: lxc/config.go:33
+#, fuzzy
+msgid "Show the expanded configuration"
+msgstr "拡張した設定を表示するかどうか"
+
+#: lxc/image.go:318
+#, c-format
+msgid "Size: %.2fMB"
+msgstr "サイズ: %.2fMB"
+
+#: lxc/info.go:199
+msgid "Snapshots:"
+msgstr "スナップショット:"
+
+#: lxc/action.go:134
+#, fuzzy, c-format
+msgid "Some containers failed to %s"
+msgstr "コンテナの停止に失敗しました!"
+
+#: lxc/image.go:347
+msgid "Source:"
+msgstr "取得元:"
+
+#: lxc/main.go:219
+#, fuzzy
+msgid "Start containers."
+msgstr "コンテナの不正なURL %s"
+
+#: lxc/launch.go:118
+#, c-format
+msgid "Starting %s"
+msgstr "%s を起動中"
+
+#: lxc/info.go:100
+#, c-format
+msgid "Status: %s"
+msgstr "状態: %s"
+
+#: lxc/main.go:225
+#, fuzzy
+msgid "Stop containers."
+msgstr "コンテナの停止に失敗しました!"
+
+#: lxc/publish.go:35 lxc/publish.go:36
+msgid "Stop the container if currently running"
+msgstr "実行中の場合、コンテナを停止します"
+
+#: lxc/delete.go:105 lxc/publish.go:112
+msgid "Stopping container failed!"
+msgstr "コンテナの停止に失敗しました!"
+
+#: lxc/action.go:49
+#, fuzzy
+msgid "Store the container state (only for stop)"
+msgstr "コンテナの状態を保存します (stopのみ)。"
+
+#: lxc/info.go:160
+msgid "Swap (current)"
+msgstr "Swap (現在値)"
+
+#: lxc/info.go:164
+msgid "Swap (peak)"
+msgstr "Swap (ピーク)"
+
+#: lxc/list.go:425
+msgid "TYPE"
+msgstr ""
+
+#: lxc/delete.go:91
+msgid "The container is currently running, stop it first or pass --force."
+msgstr "コンテナは実行中です。先に停止させるか、--force を指定してください。"
+
+#: lxc/publish.go:90
+msgid ""
+"The container is currently running. Use --force to have it stopped and "
+"restarted."
+msgstr ""
+"コンテナは現在実行中です。停止して、再起動するために --force を使用してくだ\n"
+"さい。"
+
+#: lxc/config.go:716 lxc/config.go:728 lxc/config.go:761 lxc/config.go:779
+#: lxc/config.go:817 lxc/config.go:835
+msgid "The device doesn't exist"
+msgstr "デバイスが存在しません"
+
+#: lxc/init.go:275
+#, c-format
+msgid "The local image '%s' couldn't be found, trying '%s:' instead."
+msgstr ""
+"ローカルイメージ '%s' が見つかりません。代わりに '%s:' を試してみてください。"
+
+#: lxc/action.go:34
+msgid "The opposite of \"lxc pause\" is \"lxc start\"."
+msgstr ""
+
+#: lxc/publish.go:63
+msgid "There is no \"image name\".  Did you want an alias?"
+msgstr ""
+"publish 先にはイメージ名は指定できません。\"--alias\" オプションを使ってく"
+"だ\n"
+"さい。"
+
+#: lxc/help.go:47
+msgid ""
+"This is the LXD command line client.\n"
+"\n"
+"All of LXD's features can be driven through the various commands below.\n"
+"For help with any of those, simply call them with --help."
+msgstr ""
+
+#: lxc/action.go:45
+#, fuzzy
+msgid "Time to wait for the container before killing it"
+msgstr "コンテナを強制停止するまでの時間"
+
+#: lxc/image.go:321
+msgid "Timestamps:"
+msgstr "タイムスタンプ:"
+
+#: lxc/main.go:140
+msgid "To start your first container, try: lxc launch ubuntu:16.04"
+msgstr ""
+"初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてく"
+"だ\n"
+"さい。"
+
+#: lxc/image.go:405
+#, fuzzy, c-format
+msgid "Transferring image: %s"
+msgstr "イメージを転送中: %d%%"
+
+#: lxc/action.go:98 lxc/launch.go:131
+#, c-format
+msgid "Try `lxc info --show-log %s` for more info"
+msgstr "更に情報を得るために `lxc info --show-log` を実行してみてください"
+
+#: lxc/info.go:102
+msgid "Type: ephemeral"
+msgstr "タイプ: ephemeral"
+
+#: lxc/info.go:104
+msgid "Type: persistent"
+msgstr "タイプ: persistent"
+
+#: lxc/image.go:609
+msgid "UPLOAD DATE"
+msgstr ""
+
+#: lxc/remote.go:366
+msgid "URL"
+msgstr ""
+
+#: lxc/manpage.go:36
+msgid "Unable to find help2man."
+msgstr ""
+
+#: lxc/remote.go:94
+msgid "Unable to read remote TLS certificate"
+msgstr "リモートの TLS 証明書を読めません"
+
+#: lxc/image.go:326
+#, c-format
+msgid "Uploaded: %s"
+msgstr "アップロード日時: %s"
+
+#: lxc/action.go:37
+#, c-format
+msgid ""
+"Usage: lxc %s [<remote>:]<container> [[<remote>:]<container>...]\n"
+"\n"
+"%s%s"
+msgstr ""
+
+#: lxc/help.go:45
+#, fuzzy
+msgid "Usage: lxc <command> [options]"
+msgstr "使い方: lxc [サブコマンド] [オプション]"
+
+#: lxc/config.go:58
+msgid ""
+"Usage: lxc config <subcommand> [options]\n"
+"\n"
+"Change container or server configuration options.\n"
+"\n"
+"*Container configuration*\n"
+"\n"
+"lxc config get [<remote>:][container] <key>\n"
+"    Get container or server configuration key.\n"
+"\n"
+"lxc config set [<remote>:][container] <key> <value>\n"
+"    Set container or server configuration key.\n"
+"\n"
+"lxc config unset [<remote>:][container] <key>\n"
+"    Unset container or server configuration key.\n"
+"\n"
+"lxc config show [<remote>:][container] [--expanded]\n"
+"    Show container or server configuration.\n"
+"\n"
+"lxc config edit [<remote>:][container]\n"
+"    Edit configuration, either by launching external editor or reading "
+"STDIN.\n"
+"\n"
+"*Device management*\n"
+"\n"
+"lxc config device add [<remote>:]<container> <device> <type> [key=value...]\n"
+"    Add a device to a container.\n"
+"\n"
+"lxc config device get [<remote>:]<container> <device> <key>\n"
+"    Get a device property.\n"
+"\n"
+"lxc config device set [<remote>:]<container> <device> <key> <value>\n"
+"    Set a device property.\n"
+"\n"
+"lxc config device unset [<remote>:]<container> <device> <key>\n"
+"    Unset a device property.\n"
+"\n"
+"lxc config device list [<remote>:]<container>\n"
+"    List devices for container.\n"
+"\n"
+"lxc config device show [<remote>:]<container>\n"
+"    Show full device details for container.\n"
+"\n"
+"lxc config device remove [<remote>:]<container> <name>\n"
+"    Remove device from container.\n"
+"\n"
+"*Client trust store management*\n"
+"\n"
+"lxc config trust list [<remote>:]\n"
+"    List all trusted certs.\n"
+"\n"
+"lxc config trust add [<remote>:] <certfile.crt>\n"
+"    Add certfile.crt to trusted hosts.\n"
+"\n"
+"lxc config trust remove [<remote>:] [hostname|fingerprint]\n"
+"    Remove the cert from trusted hosts.\n"
+"\n"
+"*Examples*\n"
+"\n"
+"cat config.yaml | lxc config edit <container>\n"
+"    Update the container configuration from config.yaml.\n"
+"\n"
+"lxc config device add [<remote>:]container1 <device-name> disk source=/share/"
+"c1 path=opt\n"
+"    Will mount the host's /share/c1 onto /opt in the container.\n"
+"\n"
+"lxc config set [<remote>:]<container> limits.cpu 2\n"
+"    Will set a CPU limit of \"2\" for the container.\n"
+"\n"
+"lxc config set core.https_address [::]:8443\n"
+"    Will have LXD listen on IPv4 and IPv6 port 8443.\n"
+"\n"
+"lxc config set core.trust_password blah\n"
+"    Will set the server's trust password to blah."
+msgstr ""
+
+#: lxc/copy.go:23
+msgid ""
+"Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
+"[--ephemeral|e]\n"
+"\n"
+"Copy containers within or in between LXD instances."
+msgstr ""
+
+#: lxc/delete.go:26
+#, fuzzy
+msgid ""
+"Usage: lxc delete [<remote>:]<container>[/<snapshot>] "
+"[[<remote>:]<container>[/<snapshot>]...]\n"
+"\n"
+"Delete containers and snapshots."
+msgstr ""
+"コンテナもしくはコンテナのスナップショットを消去します。\n"
+"\n"
+"lxc delete [remote:]<container>[/<snapshot>] [remote:]"
+"[<container>[<snapshot>]...]\n"
+"\n"
+"付属するデータ (設定、スナップショット、...) と一緒にコンテナもしくはコンテ\n"
+"ナのスナップショットを消去します。"
+
+#: lxc/exec.go:46
+#, fuzzy
+msgid ""
+"Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-"
+"interactive] [--env KEY=VALUE...] [--] <command line>\n"
+"\n"
+"Execute commands in containers.\n"
+"\n"
+"Mode defaults to non-interactive, interactive mode is selected if both stdin "
+"AND stdout are terminals (stderr is ignored)."
+msgstr ""
+"指定したコマンドをコンテナ内で実行します。\n"
+"\n"
+"lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
+"EDITOR=/usr/bin/vim]... <command>\n"
+"\n"
+"デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナ\n"
+"ルの場合は interactive モードが選択されます (標準エラー出力は無視されます)。"
+
+#: lxc/file.go:32
+msgid ""
+"Usage: lxc file <subcommand> [options]\n"
+"\n"
+"Manage files in containers.\n"
+"\n"
+"lxc file pull [<remote>:]<container>/<path> [[<remote>:]<container>/"
+"<path>...] <target path>\n"
+"    Pull files from containers.\n"
+"\n"
+"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source "
+"path>...] [<remote>:]<container>/<path>\n"
+"    Push files into containers.\n"
+"\n"
+"lxc file edit [<remote>:]<container>/<path>\n"
+"    Edit files in containers using the default text editor.\n"
+"\n"
+"*Examples*\n"
+"lxc file push /etc/hosts foo/etc/hosts\n"
+"   To push /etc/hosts into the container \"foo\".\n"
+"\n"
+"lxc file pull foo/etc/hosts .\n"
+"   To pull /etc/hosts from the container and write it to the current "
+"directory."
+msgstr ""
+
+#: lxc/finger.go:15
+msgid ""
+"Usage: lxc finger [<remote>:]\n"
+"\n"
+"Check if the LXD server is alive."
+msgstr ""
+
+#: lxc/help.go:22
+msgid ""
+"Usage: lxc help [--all]\n"
+"\n"
+"Help page for the LXD client."
+msgstr ""
+
+#: lxc/image.go:60
+#, fuzzy
+msgid ""
+"Usage: lxc image <subcommand> [options]\n"
+"\n"
+"Manipulate container images.\n"
+"\n"
+"In LXD containers are created from images. Those images were themselves\n"
+"either generated from an existing container or downloaded from an image\n"
+"server.\n"
+"\n"
+"When using remote images, LXD will automatically cache images for you\n"
+"and remove them upon expiration.\n"
+"\n"
+"The image unique identifier is the hash (sha-256) of its representation\n"
+"as a compressed tarball (or for split images, the concatenation of the\n"
+"metadata and rootfs tarballs).\n"
+"\n"
+"Images can be referenced by their full hash, shortest unique partial\n"
+"hash or alias name (if one is set).\n"
+"\n"
+"\n"
+"lxc image import <tarball> [<rootfs tarball>|<URL>] [<remote>:] [--public] "
+"[--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] "
+"[--alias=ALIAS...] [prop=value]\n"
+"    Import an image tarball (or tarballs) into the LXD image store.\n"
+"\n"
+"lxc image copy [<remote>:]<image> <remote>: [--alias=ALIAS...] [--copy-"
+"aliases] [--public] [--auto-update]\n"
+"    Copy an image from one LXD daemon to another over the network.\n"
+"\n"
+"    The auto-update flag instructs the server to keep this image up to\n"
+"    date. It requires the source to be an alias and for it to be public.\n"
+"\n"
+"lxc image delete [<remote>:]<image> [[<remote>:]<image>...]\n"
+"    Delete one or more images from the LXD image store.\n"
+"\n"
+"lxc image export [<remote>:]<image> [target]\n"
+"    Export an image from the LXD image store into a distributable tarball.\n"
+"\n"
+"    The output target is optional and defaults to the working directory.\n"
+"    The target may be an existing directory, file name, or \"-\" to specify\n"
+"    stdout.  The target MUST be a directory when exporting a split image.\n"
+"    If the target is a directory, the image's name (each part's name for\n"
+"    split images) as found in the database will be used for the exported\n"
+"    image.  If the target is a file (not a directory and not stdout), then\n"
+"    the appropriate extension will be appended to the provided file name\n"
+"    based on the algorithm used to compress the image. \n"
+"\n"
+"lxc image info [<remote>:]<image>\n"
+"    Print everything LXD knows about a given image.\n"
+"\n"
+"lxc image list [<remote>:] [filter]\n"
+"    List images in the LXD image store. Filters may be of the\n"
+"    <key>=<value> form for property based filtering, or part of the image\n"
+"    hash or part of the image alias name.\n"
+"\n"
+"lxc image show [<remote>:]<image>\n"
+"    Yaml output of the user modifiable properties of an image.\n"
+"\n"
+"lxc image edit [<remote>:]<image>\n"
+"    Edit image, either by launching external editor or reading STDIN.\n"
+"    Example: lxc image edit <image> # launch editor\n"
+"             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
+"\n"
+"lxc image alias create [<remote>:]<alias> <fingerprint>\n"
+"    Create a new alias for an existing image.\n"
+"\n"
+"lxc image alias delete [<remote>:]<alias>\n"
+"    Delete an alias.\n"
+"\n"
+"lxc image alias list [<remote>:] [filter]\n"
+"    List the aliases. Filters may be part of the image hash or part of the "
+"image alias name."
+msgstr ""
+"コンテナイメージを操作します。\n"
+"\n"
+"LXD では、コンテナはイメージから作られます。このイメージは、既存のコンテナ\n"
+"やイメージサーバからダウンロードしたイメージから作られます。\n"
+"\n"
+"リモートのイメージを使う場合、LXD は自動的にイメージをキャッシュします。そ\n"
+"して、イメージの期限が切れるとキャッシュを削除します。\n"
+"\n"
+"イメージ固有の識別子は圧縮された tarball (分割イメージの場合は、メタデータ\n"
+"と rootfs tarball を結合したもの) のハッシュ (sha-256) です。\n"
+"\n"
+"イメージは全ハッシュ文字列、一意に定まるハッシュの短縮表現、(設定されてい\n"
+"る場合は) エイリアスで参照できます。\n"
+"\n"
+"\n"
+"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
+"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
+"alias=ALIAS].. [prop=value]\n"
+"    イメージの tarball (複数も可能) を LXD のイメージストアにインポートしま\n"
+"    す。\n"
+"\n"
+"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
+"[--public] [--auto-update]\n"
+"    ネットワーク経由である LXD デーモンから他の LXD デーモンへイメージを\n"
+"    コピーします。\n"
+"\n"
+"    auto-update フラグは、サーバがイメージを最新に保つように指示します。イ\n"
+"    メージのソースがエイリアスであり、public である必要があります。\n"
+"\n"
+"lxc image delete [remote:]<image>\n"
+"    LXD のイメージストアからイメージを削除します。\n"
+"\n"
+"lxc image export [remote:]<image>\n"
+"    LXD のイメージストアから配布可能な tarball としてイメージをエクスポート\n"
+"    します。\n"
+"\n"
+"lxc image info [remote:]<image>\n"
+"    指定したイメージについてのすべての情報を表示します。\n"
+"\n"
+"lxc image list [remote:] [filter]\n"
+"    LXD のイメージストア内のイメージを一覧表示します。プロパティでフィルタ\n"
+"    を行う場合は、フィルタは <key>=<value> の形になります。フィルタはイメー\n"
+"    ジハッシュの一部やイメージエイリアス名の一部も指定できます。\n"
+"\n"
+"lxc image show [remote:]<image>\n"
+"    ユーザが変更できるプロパティの YAML 形式の出力を行います。\n"
+"\n"
+"lxc image edit [remote:]<image>\n"
+"    外部エディタまたは標準入力からの読み込みにより、イメージを編集します。\n"
+"    例: lxc image edit <image> # エディタの起動\n"
+"        cat image.yml | lxc image edit <image> # image.yml から読み込み\n"
+"\n"
+"lxc image alias create [remote:]<alias> <fingerprint>\n"
+"    既存のイメージに新たにエイリアスを作成します。\n"
+"\n"
+"lxc image alias delete [remote:]<alias>\n"
+"    エイリアスを削除します。\n"
+"\n"
+"lxc image alias list [remote:] [filter]\n"
+"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリア"
+"ス\n"
+"    名の一部をフィルタとして指定できます。\n"
+
+#: lxc/info.go:25
+msgid ""
+"Usage: lxc info [<remote>:][<container>] [--show-log]\n"
+"\n"
+"Show container or server information.\n"
+"\n"
+"lxc info [<remote>:]<container> [--show-log]\n"
+"    For container information.\n"
+"\n"
+"lxc info [<remote>:]\n"
+"    For LXD server information."
+msgstr ""
+
+#: lxc/init.go:73
+#, fuzzy
+msgid ""
+"Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
+"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"\n"
+"Create containers from images.\n"
+"\n"
+"Not specifying -p will result in the default profile.\n"
+"Specifying \"-p\" with no argument will result in no profile.\n"
+"\n"
+"Examples:\n"
+"    lxc init ubuntu:16.04 u1"
+msgstr ""
+"指定したイメージからコンテナを初期化します。\n"
+"\n"
+"lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
+"<profile>...] [--config|-c <key=value>...]\n"
+"\n"
+"指定したイメージとコンテナ名を使ってコンテナを初期化します。\n"
+"\n"
+"-p を指定しない場合はデフォルトのプロファイルを使います。\n"
+"\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n"
+"\n"
+"例:\n"
+"lxc init ubuntu u1"
+
+#: lxc/launch.go:23
+#, fuzzy
+msgid ""
+"Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
+"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"\n"
+"Create and start containers from images.\n"
+"\n"
+"Not specifying -p will result in the default profile.\n"
+"Specifying \"-p\" with no argument will result in no profile.\n"
+"\n"
+"Examples:\n"
+"    lxc launch ubuntu:16.04 u1"
+msgstr ""
+"指定したイメージからコンテナを起動します。\n"
+"\n"
+"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
+"<profile>...] [--config|-c <key=value>...]\n"
+"\n"
+"指定したイメージと名前を使ってコンテナを起動します。\n"
+"\n"
+"-p を指定しない場合はデフォルトのプロファイルを使います。\n"
+"\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n"
+"\n"
+"例:\n"
+"lxc launch ubuntu:16.04 u1"
+
+#: lxc/list.go:46
+#, fuzzy
+msgid ""
+"Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
+"[--fast]\n"
+"\n"
+"List the existing containers.\n"
+"\n"
+"Default column layout: ns46tS\n"
+"Fast column layout: nsacPt\n"
+"\n"
+"*Filters*\n"
+"A single keyword like \"web\" which will list any container with a name "
+"starting by \"web\".\n"
+"\n"
+"A regular expression on the container name. (e.g. .*web.*01$).\n"
+"\n"
+"A key/value pair referring to a configuration item. For those, the namespace "
+"can be abbreviated to the smallest unambiguous identifier.\n"
+"    - \"user.blah=abc\" will list all containers with the \"blah\" user "
+"property set to \"abc\".\n"
+"\n"
+"    - \"u.blah=abc\" will do the same\n"
+"\n"
+"    - \"security.privileged=1\" will list all privileged containers\n"
+"\n"
+"    - \"s.privileged=1\" will do the same\n"
+"\n"
+"A regular expression matching a configuration item or its value. (e.g. "
+"volatile.eth0.hwaddr=00:16:3e:.*).\n"
+"\n"
+"*Columns*\n"
+"The -c option takes a comma separated list of arguments that control\n"
+"which container attributes to output when displaying in table format.\n"
+"\n"
+"Column arguments are either pre-defined shorthand chars (see below),\n"
+"or (extended) config keys.\n"
+"\n"
+"Commas between consecutive shorthand chars are optional.\n"
+"\n"
+"Pre-defined column shorthand chars:\n"
+"\n"
+"    4 - IPv4 address\n"
+"\n"
+"    6 - IPv6 address\n"
+"\n"
+"    a - Architecture\n"
+"\n"
+"    c - Creation date\n"
+"\n"
+"    l - Last used date\n"
+"\n"
+"    n - Name\n"
+"\n"
+"    p - PID of the container's init process\n"
+"\n"
+"    P - Profiles\n"
+"\n"
+"    s - State\n"
+"\n"
+"    S - Number of snapshots\n"
+"\n"
+"    t - Type (persistent or ephemeral)\n"
+"\n"
+"Custom columns are defined with \"key[:name][:maxWidth]\":\n"
+"\n"
+"    KEY: The (extended) config key to display\n"
+"\n"
+"    NAME: Name to display in the column header.\n"
+"    Defaults to the key if not specified or empty.\n"
+"\n"
+"    MAXWIDTH: Max width of the column (longer results are truncated).\n"
+"    Defaults to -1 (unlimited). Use 0 to limit to the column header size.\n"
+"\n"
+"*Examples*\n"
+"lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:"
+"MAC\n"
+"    Shows a list of containers using the \"NAME\", \"BASE IMAGE\", \"STATE"
+"\", \"IPV4\",\n"
+"    \"IPV6\" and \"MAC\" columns.\n"
+"\n"
+"    \"BASE IMAGE\" and \"MAC\" are custom columns generated from container "
+"configuration keys."
+msgstr ""
+"利用可能なリソースを一覧表示します。\n"
+"\n"
+"lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
+"\n"
+"フィルタの指定:\n"
+"* 単一の \"web\" のようなキーワードを指定すると、名前が \"web\" ではじまるコ"
+"ンテ\n"
+"  ナが一覧表示されます。\n"
+"* コンテナ名の正規表現 (例: .*web.*01$)\n"
+"* 設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することが"
+"で\n"
+"  きます:\n"
+" * \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定され"
+"ている\n"
+"  コンテナをすべて一覧表示します。\n"
+" * \"u.blah=abc\" は上記と同じ意味になります。\n"
+" * \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
+" * \"s.privilaged=1\" は上記と同じ意味になります。\n"
+" * 設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:."
+"*)\n"
+"\n"
+"表のカラムの指定:\n"
+"* 4 - IPv4 アドレス\n"
+"* 6 - IPv6 アドレス\n"
+"* a - アーキテクチャ\n"
+"* c - 作成日\n"
+"* n - 名前\n"
+"* p - コンテナの init プロセスの pid\n"
+"* P - プロファイル\n"
+"* s - 状態\n"
+"* S - スナップショットの数\n"
+"* t - タイプ (persistent or ephemeral)\n"
+"\n"
+"デフォルトのカラムレイアウト: ns46tS\n"
+"Fast モードのカラムレイアウト: nsacPt"
+
+#: lxc/manpage.go:20
+msgid ""
+"Usage: lxc manpage <directory>\n"
+"\n"
+"Generate all the LXD manpages."
+msgstr ""
+
+#: lxc/monitor.go:41
+#, fuzzy
+msgid ""
+"Usage: lxc monitor [<remote>:] [--type=TYPE...]\n"
+"\n"
+"Monitor a local or remote LXD server.\n"
+"\n"
+"By default the monitor will listen to all message types.\n"
+"\n"
+"Message types to listen for can be specified with --type.\n"
+"\n"
+"*Examples*\n"
+"lxc monitor --type=logging\n"
+"    Only show log message."
+msgstr ""
+"LXD サーバの動作をモニタリングします。\n"
+"\n"
+"lxc monitor [remote:] [--type=TYPE...]\n"
+"\n"
+"指定した LXD サーバのモニタリングインターフェースに接続します。\n"
+"\n"
+"デフォルトでは全てのタイプをモニタリングします。\n"
+"--type により、モニタリングするタイプを指定できます。\n"
+"\n"
+"例:\n"
+"lxc monitor --type=logging"
+
+#: lxc/move.go:16
+#, fuzzy
+msgid ""
+"Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
+"<snapshot>]]\n"
+"\n"
+"Move containers within or in between LXD instances.\n"
+"\n"
+"lxc move [<remote>:]<source container> [<remote>:][<destination container>]\n"
+"    Move a container between two hosts, renaming it if destination name "
+"differs.\n"
+"\n"
+"lxc move <old name> <new name>\n"
+"    Rename a local container.\n"
+"\n"
+"lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
+"    Rename a snapshot."
+msgstr ""
+"LXD ホスト内、もしくは LXD ホスト間でコンテナを移動します。\n"
+"\n"
+"lxc move [remote:]<source container> [remote:]<destination container>\n"
+"    2 つのホスト間でコンテナを移動します。コピー先の名前が元と違う場合は\n"
+"    同時にリネームされます。\n"
+"\n"
+"lxc move <old name> <new name>\n"
+"    ローカルのコンテナをリネームします。\n"
+
+#: lxc/profile.go:48
+#, fuzzy
+msgid ""
+"Usage: lxc profile <subcommand> [options]\n"
+"\n"
+"Manage container configuration profiles.\n"
+"\n"
+"*Profile configuration*\n"
+"lxc profile list [<remote>:]\n"
+"    List available profiles.\n"
+"\n"
+"lxc profile show [<remote>:]<profile>\n"
+"    Show details of a profile.\n"
+"\n"
+"lxc profile create [<remote>:]<profile>\n"
+"    Create a profile.\n"
+"\n"
+"lxc profile copy [<remote>:]<profile> [<remote>:]<profile>\n"
+"    Copy the profile.\n"
+"\n"
+"lxc profile get [<remote>:]<profile> <key>\n"
+"    Get profile configuration.\n"
+"\n"
+"lxc profile set [<remote>:]<profile> <key> <value>\n"
+"    Set profile configuration.\n"
+"\n"
+"lxc profile unset [<remote>:]<profile> <key>\n"
+"    Unset profile configuration.\n"
+"\n"
+"lxc profile delete [<remote>:]<profile>\n"
+"    Delete a profile.\n"
+"\n"
+"lxc profile edit [<remote>:]<profile>\n"
+"    Edit profile, either by launching external editor or reading STDIN.\n"
+"\n"
+"*Profile assignment*\n"
+"lxc profile apply [<remote>:]<container> <profiles>\n"
+"    Replace the current set of profiles for the container by the one "
+"provided.\n"
+"\n"
+"*Device management*\n"
+"lxc profile device list [<remote>:]<profile>\n"
+"    List devices in the given profile.\n"
+"\n"
+"lxc profile device show [<remote>:]<profile>\n"
+"    Show full device details in the given profile.\n"
+"\n"
+"lxc profile device remove [<remote>:]<profile> <name>\n"
+"    Remove a device from a profile.\n"
+"\n"
+"lxc profile device get [<remote>:]<profile> <name> <key>\n"
+"    Get a device property.\n"
+"\n"
+"lxc profile device set [<remote>:]<profile> <name> <key> <value>\n"
+"    Set a device property.\n"
+"\n"
+"lxc profile device unset [<remote>:]<profile> <name> <key>\n"
+"    Unset a device property.\n"
+"\n"
+"lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]\n"
+"    Add a profile device, such as a disk or a nic, to the containers using "
+"the specified profile.\n"
+"\n"
+"*Examples*\n"
+"cat profile.yaml | lxc profile edit <profile>\n"
+"    Update a profile using the content of profile.yaml\n"
+"\n"
+"lxc profile apply foo default,bar\n"
+"    Set the profiles for \"foo\" to \"default\" and \"bar\".\n"
+"\n"
+"lxc profile apply foo default\n"
+"    Reset \"foo\" to only using the \"default\" profile.\n"
+"\n"
+"lxc profile apply foo ''\n"
+"    Remove all profile from \"foo\""
+msgstr ""
+"設定プロファイルを管理します。\n"
+"\n"
+"lxc profile list [filters]\n"
+"    利用可能なプロファイルを一覧します。\n"
+"lxc profile show <profile>\n"
+"    プロファイルの詳細を表示します。\n"
+"lxc profile create <profile>\n"
+"    プロファイルを作成します。\n"
+"lxc profile copy <profile> <remote>\n"
+"    プロファイルを remote にコピーします。\n"
+"lxc profile get <profile> <key>\n"
+"    プロファイルの設定を取得します。\n"
+"lxc profile set <profile> <key> <value>\n"
+"    プロファイルの設定を設定します。\n"
+"lxc profile delete <profile>\n"
+"    プロファイルを削除します。\n"
+"lxc profile edit <profile>\n"
+"    プロファイルを編集します。外部エディタもしくはSTDINから読み込みます。\n"
+"    例: lxc profile edit <profile> # エディタの起動\n"
+"        cat profile.yml | lxc profile edit <profile> # profile.yml から読み込"
+"み\n"
+"\n"
+"lxc profile apply <container> <profiles>\n"
+"    プロファイルのコンマ区切りのリストをコンテナに順番に適用します。\n"
+"    このコマンドで指定したプロファイルだけが対象のコンテナに適用されます。\n"
+"    例: lxc profile apply foo default,bar # defaultとbarを適用\n"
+"        lxc profile apply foo default # defaultだけを有効化\n"
+"        lxc profile apply '' # 一切のプロファイルを適用しない\n"
+"        lxc profile apply bar,default # defaultを2番目に適用\n"
+"lxc profile apply-add <container> <profile>\n"
+"lxc profile apply-remove <container> <profile>\n"
+"\n"
+"デバイス:\n"
+"lxc profile device list <profile>\n"
+"    指定したプロファイル内のデバイスを一覧表示します\n"
+"lxc profile device show <profile>\n"
+"    指定したプロファイル内の全デバイスの詳細を表示します\n"
+"lxc profile device remove <profile> <name>\n"
+"    プロファイルからデバイスを削除します\n"
+"lxc profile device get <[remote:]profile> <name> <key>\n"
+"    デバイスプロパティを取得します\n"
+"lxc profile device set <[remote:]profile> <name> <key> <value>\n"
+"    デバイスプロパティを設定します\n"
+"lxc profile device unset <[remote:]profile> <name> <key>\n"
+"    デバイスプロパティを削除します\n"
+"lxc profile device add <profile name> <device name> <device type> "
+"[key=value]...\n"
+"    ディスクやNICのようなプロファイルデバイスを指定したプロファイルを使って\n"
+"    コンテナに追加します。"
+
+#: lxc/publish.go:26
+#, fuzzy
+msgid ""
+"Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--"
+"alias=ALIAS...] [prop-key=prop-value...]\n"
+"\n"
+"Publish containers as images."
 msgstr ""
+"イメージとしてコンテナを publish します。\n"
+"\n"
+"lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
+"value]..."
 
-#: lxc/remote.go:356
-msgid "STATIC"
+#: lxc/remote.go:37
+#, fuzzy
+msgid ""
+"Usage: lxc remote <subcommand> [options]\n"
+"\n"
+"Manage the list of remote LXD servers.\n"
+"\n"
+"lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--"
+"password=PASSWORD] [--public] [--protocol=PROTOCOL]\n"
+"    Add the remote <remote> at <url>.\n"
+"\n"
+"lxc remote remove <remote>\n"
+"    Remove the remote <remote>.\n"
+"\n"
+"lxc remote list\n"
+"    List all remotes.\n"
+"\n"
+"lxc remote rename <old name> <new name>\n"
+"    Rename remote <old name> to <new name>.\n"
+"\n"
+"lxc remote set-url <remote> <url>\n"
+"    Update <remote>'s url to <url>.\n"
+"\n"
+"lxc remote set-default <remote>\n"
+"    Set the default remote.\n"
+"\n"
+"lxc remote get-default\n"
+"    Print the default remote."
 msgstr ""
-
-#: lxc/remote.go:203
-msgid "Server certificate NACKed by user"
-msgstr "ユーザによりサーバ証明書が拒否されました"
-
-#: lxc/remote.go:265
-msgid "Server doesn't trust us after adding our cert"
-msgstr "サーバが我々の証明書を追加した後我々を信頼していません"
-
-#: lxc/remote.go:54
-msgid "Server protocol (lxd or simplestreams)"
-msgstr "サーバのプロトコル (lxd or simplestreams)"
+"リモートの LXD サーバを管理します。\n"
+"\n"
+"lxc remote add <name> <url>\n"
+"    [--accept-certificate] [--password=PASSWORD]\n"
+"    [--public] [--protocol=PROTOCOL]\n"
+"    <url> をリモートホスト <name> として追加します。\n"
+"lxc remote remove <name>\n"
+"    リモートホスト <name> を削除します。\n"
+"lxc remote list\n"
+"    登録済みのリモートホストを全て一覧表示します。\n"
+"lxc remote rename <old> <new>\n"
+"    リモートホストの名前を <old> から <new> に変更します。\n"
+"lxc remote set-url <name> <url>\n"
+"    <name> の url を <url> に更新します。\n"
+"lxc remote set-default <name>\n"
+"    <name> をデフォルトのリモートホストに設定します。\n"
+"lxc remote get-default\n"
+"    デフォルトに設定されているリモートホストを表示します。"
 
 #: lxc/restore.go:21
+#, fuzzy
 msgid ""
-"Set the current state of a resource back to a snapshot.\n"
+"Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
 "\n"
-"lxc restore [remote:]<container> <snapshot name> [--stateful]\n"
+"Restore containers from snapshots.\n"
+"\n"
+"If --stateful is passed, then the running state will be restored too.\n"
 "\n"
-"Restores a container from a snapshot (optionally with running state, see\n"
-"snapshot help for details).\n"
+"*Examples*\n"
+"lxc snapshot u1 snap0\n"
+"    Create the snapshot.\n"
 "\n"
-"For example:\n"
-"lxc snapshot u1 snap0 # create the snapshot\n"
-"lxc restore u1 snap0 # restore the snapshot"
+"lxc restore u1 snap0\n"
+"    Restore the snapshot."
 msgstr ""
 "リソースの現在の状態をスナップショット時点の状態に設定します。\n"
 "\n"
@@ -1393,312 +1644,388 @@ msgstr ""
 "lxc snapshot u1 snap0 # スナップショットの作成\n"
 "lxc restore u1 snap0 # スナップショットからリストア"
 
-#: lxc/file.go:44
-msgid "Set the file's gid on push"
-msgstr "プッシュ時にファイルのgidを設定します"
-
-#: lxc/file.go:45
-msgid "Set the file's perms on push"
-msgstr "プッシュ時にファイルのパーミションを設定します"
-
-#: lxc/file.go:43
-msgid "Set the file's uid on push"
-msgstr "プッシュ時にファイルのuidを設定します"
-
-#: lxc/help.go:32
-msgid "Show all commands (not just interesting ones)"
-msgstr "全てコマンドを表示します (主なコマンドだけではなく)"
-
-#: lxc/help.go:67
-msgid "Show client version."
-msgstr ""
-
-#: lxc/info.go:36
-msgid "Show the container's last 100 log lines?"
-msgstr "コンテナログの最後の 100 行を表示しますか?"
-
-#: lxc/image.go:338
-#, c-format
-msgid "Size: %.2fMB"
-msgstr "サイズ: %.2fMB"
-
-#: lxc/info.go:197
-msgid "Snapshots:"
-msgstr "スナップショット:"
-
-#: lxc/image.go:362
-msgid "Source:"
-msgstr "取得元:"
-
-#: lxc/launch.go:124
-#, c-format
-msgid "Starting %s"
-msgstr "%s を起動中"
-
-#: lxc/info.go:98
-#, c-format
-msgid "Status: %s"
-msgstr "状態: %s"
-
-#: lxc/publish.go:34 lxc/publish.go:35
-msgid "Stop the container if currently running"
-msgstr "実行中の場合、コンテナを停止します"
-
-#: lxc/delete.go:106 lxc/publish.go:111
-msgid "Stopping container failed!"
-msgstr "コンテナの停止に失敗しました!"
-
-#: lxc/action.go:45
-msgid "Store the container state (only for stop)."
-msgstr "コンテナの状態を保存します (stopのみ)。"
-
-#: lxc/info.go:158
-msgid "Swap (current)"
-msgstr "Swap (現在値)"
-
-#: lxc/info.go:162
-msgid "Swap (peak)"
-msgstr "Swap (ピーク)"
-
-#: lxc/list.go:401
-msgid "TYPE"
-msgstr ""
-
-#: lxc/delete.go:92
-msgid "The container is currently running, stop it first or pass --force."
-msgstr "コンテナは実行中です。先に停止させるか、--force を指定してください。"
-
-#: lxc/publish.go:89
+#: lxc/snapshot.go:21
+#, fuzzy
 msgid ""
-"The container is currently running. Use --force to have it stopped and "
-"restarted."
-msgstr ""
-"コンテナは現在実行中です。停止して、再起動するために --force を使用してくだ\n"
-"さい。"
-
-#: lxc/config.go:675 lxc/config.go:687 lxc/config.go:720 lxc/config.go:738
-#: lxc/config.go:776 lxc/config.go:794
-msgid "The device doesn't exist"
-msgstr "デバイスが存在しません"
-
-#: lxc/init.go:277
-#, c-format
-msgid "The local image '%s' couldn't be found, trying '%s:' instead."
-msgstr ""
-"ローカルイメージ '%s' が見つかりません。代わりに '%s:' を試してみてください。"
-
-#: lxc/main.go:176
-msgid "The opposite of `lxc pause` is `lxc start`."
-msgstr ""
-
-#: lxc/publish.go:62
-msgid "There is no \"image name\".  Did you want an alias?"
-msgstr ""
-"publish 先にはイメージ名は指定できません。\"--alias\" オプションを使ってく"
-"だ\n"
-"さい。"
-
-#: lxc/action.go:41
-msgid "Time to wait for the container before killing it."
-msgstr "コンテナを強制停止するまでの時間"
-
-#: lxc/image.go:341
-msgid "Timestamps:"
-msgstr "タイムスタンプ:"
-
-#: lxc/main.go:133
-msgid "To start your first container, try: lxc launch ubuntu:16.04"
-msgstr ""
-"初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてく"
-"だ\n"
-"さい。"
-
-#: lxc/image.go:412
-#, c-format
-msgid "Transferring image: %d%%"
-msgstr "イメージを転送中: %d%%"
-
-#: lxc/action.go:99 lxc/launch.go:137
-#, c-format
-msgid "Try `lxc info --show-log %s` for more info"
-msgstr "更に情報を得るために `lxc info --show-log` を実行してみてください"
-
-#: lxc/info.go:100
-msgid "Type: ephemeral"
-msgstr "タイプ: ephemeral"
-
-#: lxc/info.go:102
-msgid "Type: persistent"
-msgstr "タイプ: persistent"
-
-#: lxc/image.go:624
-msgid "UPLOAD DATE"
+"Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
+"\n"
+"Create container snapshots.\n"
+"\n"
+"When --stateful is used, LXD attempts to checkpoint the container's\n"
+"running state, including process memory state, TCP connections, ...\n"
+"\n"
+"*Examples*\n"
+"lxc snapshot u1 snap0\n"
+"    Create a snapshot of \"u1\" called \"snap0\"."
 msgstr ""
+"コンテナの読み取り専用のスナップショットを作成します。\n"
+"\n"
+"lxc snapshot [remote:]<source> <snapshot name> [--stateful]\n"
+"\n"
+"コンテナのスナップショットを作成します (オプションでコンテナのメモリ状態を\n"
+"含めて)。--statefulが指定された場合、LXDはコンテナプロセスのメモリ状態、TCP\n"
+"接続などの実行状態を、あとでlxc restoreを使ってリストアできるように、コンテ\n"
+"ナのチェックポイントを取得しようとします (しかし、タイムアウトウィンドウが\n"
+"expireしたあとのTCP接続のように正常にリストアできないものもあります)\n"
+"\n"
+"例:\n"
+"lxc snapshot u1 snap0"
 
-#: lxc/remote.go:353
-msgid "URL"
+#: lxc/version.go:18
+#, fuzzy
+msgid ""
+"Usage: lxc version\n"
+"\n"
+"Print the version number of this client tool."
 msgstr ""
+"お使いのクライアントのバージョン番号を表示します。\n"
+"\n"
+"lxc version"
 
-#: lxc/remote.go:81
-msgid "Unable to read remote TLS certificate"
-msgstr "リモートの TLS 証明書を読めません"
-
-#: lxc/image.go:346
-#, c-format
-msgid "Uploaded: %s"
-msgstr "アップロード日時: %s"
-
-#: lxc/main.go:108
-#, c-format
-msgid "Usage: %s"
-msgstr "使い方: %s"
-
-#: lxc/help.go:48
-msgid "Usage: lxc [subcommand] [options]"
-msgstr "使い方: lxc [サブコマンド] [オプション]"
-
-#: lxc/delete.go:46
+#: lxc/delete.go:45
 msgid "User aborted delete operation."
 msgstr "ユーザが削除操作を中断しました。"
 
-#: lxc/restore.go:35
+#: lxc/restore.go:37
 msgid ""
 "Whether or not to restore the container's running state from snapshot (if "
 "available)"
 msgstr ""
 "スナップショットからコンテナの稼動状態をリストアするかどうか (取得可能な場合)"
 
-#: lxc/snapshot.go:38
+#: lxc/snapshot.go:35
 msgid "Whether or not to snapshot the container's running state"
 msgstr "コンテナの稼動状態のスナップショットを取得するかどうか"
 
-#: lxc/config.go:32
-msgid "Whether to show the expanded configuration"
-msgstr "拡張した設定を表示するかどうか"
-
-#: lxc/remote.go:328 lxc/remote.go:333
+#: lxc/remote.go:341 lxc/remote.go:346
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:52
+#: lxc/main.go:55
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
-#: lxc/launch.go:111
+#: lxc/launch.go:104
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "イメージ、コンテナ、スナップショットのいずれかからスキャンされた数が不正"
 
-#: lxc/action.go:95
+#: lxc/action.go:94
 msgid "bad result type from action"
 msgstr "アクションからの結果タイプが不正です"
 
-#: lxc/copy.go:99
+#: lxc/copy.go:100
 msgid "can't copy to the same container name"
 msgstr "同じコンテナ名へはコピーできません"
 
-#: lxc/remote.go:316
+#: lxc/remote.go:329
 msgid "can't remove the default remote"
 msgstr "デフォルトのリモートは削除できません"
 
-#: lxc/remote.go:342
+#: lxc/remote.go:355
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:200 lxc/init.go:205 lxc/launch.go:95 lxc/launch.go:100
+#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:88 lxc/launch.go:93
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
 "した"
 
-#: lxc/image.go:332
+#: lxc/image.go:312
 msgid "disabled"
 msgstr "無効"
 
-#: lxc/image.go:334
+#: lxc/image.go:314
 msgid "enabled"
 msgstr "有効"
 
-#: lxc/main.go:22 lxc/main.go:145
+#: lxc/action.go:126 lxc/main.go:25 lxc/main.go:160
 #, c-format
 msgid "error: %v"
 msgstr "エラー: %v"
 
-#: lxc/help.go:40 lxc/main.go:103
+#: lxc/help.go:37 lxc/main.go:106
 #, c-format
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/launch.go:115
+#: lxc/launch.go:108
 msgid "got bad version"
 msgstr "不正なバージョンを得ました"
 
-#: lxc/image.go:327 lxc/image.go:600
+#: lxc/image.go:307 lxc/image.go:585
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:122
+#: lxc/copy.go:123
 msgid "not all the profiles from the source exist on the target"
 msgstr "コピー元の全てのプロファイルがターゲットに存在しません"
 
-#: lxc/remote.go:196
+#: lxc/remote.go:209
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:265 lxc/main.go:269
+#: lxc/main.go:295 lxc/main.go:299
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr "エイリアスの処理が失敗しました %s\n"
 
-#: lxc/remote.go:378
+#: lxc/remote.go:391
 #, c-format
 msgid "remote %s already exists"
 msgstr "リモート %s は既に存在します"
 
-#: lxc/remote.go:308 lxc/remote.go:370 lxc/remote.go:405 lxc/remote.go:421
+#: lxc/remote.go:321 lxc/remote.go:383 lxc/remote.go:418 lxc/remote.go:434
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "リモート %s は存在しません"
 
-#: lxc/remote.go:291
+#: lxc/remote.go:304
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "リモート %s は <%s> として存在します"
 
-#: lxc/remote.go:312 lxc/remote.go:374 lxc/remote.go:409
+#: lxc/remote.go:325 lxc/remote.go:387 lxc/remote.go:422
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr "リモート %s は static ですので変更できません"
 
-#: lxc/info.go:208
+#: lxc/info.go:210
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:210
+#: lxc/info.go:212
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:204
+#: lxc/info.go:206
 #, c-format
 msgid "taken at %s"
 msgstr "%s に取得しました"
 
-#: lxc/exec.go:163
+#: lxc/exec.go:167
 msgid "unreachable return reached"
 msgstr "到達しないはずのreturnに到達しました"
 
-#: lxc/main.go:205
+#: lxc/main.go:234
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:45 lxc/image.go:329 lxc/image.go:604
+#: lxc/delete.go:44 lxc/image.go:309 lxc/image.go:589
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:38
+#: lxc/copy.go:39
 msgid "you must specify a source container name"
 msgstr "コピー元のコンテナ名を指定してください"
 
+#~ msgid "Available commands:"
+#~ msgstr "使用可能なコマンド:"
+
+#, fuzzy
+#~ msgid ""
+#~ "Changes state of one or more containers to %s.\n"
+#~ "\n"
+#~ "lxc %s <name> [<name>...]%s"
+#~ msgstr ""
+#~ "1つまたは複数のコンテナの状態を %s に変更します。\n"
+#~ "\n"
+#~ "lxc %s <name> [<name>...]"
+
+#~ msgid ""
+#~ "Copy containers within or in between lxd instances.\n"
+#~ "\n"
+#~ "lxc copy [remote:]<source container> [remote:]<destination container> [--"
+#~ "ephemeral|e]"
+#~ msgstr ""
+#~ "LXDインスタンス内もしくはLXDインスタンス間でコンテナをコピーします。\n"
+#~ "\n"
+#~ "lxc copy [remote:]<source container> [remote:]<destination container> [--"
+#~ "ephemeral|e]"
+
+#~ msgid ""
+#~ "Fingers the LXD instance to check if it is up and working.\n"
+#~ "\n"
+#~ "lxc finger <remote>"
+#~ msgstr ""
+#~ "LXDインスタンスが稼働中かを確認します。\n"
+#~ "\n"
+#~ "lxc finger <remote>"
+
+#~ msgid ""
+#~ "List information on LXD servers and containers.\n"
+#~ "\n"
+#~ "For a container:\n"
+#~ " lxc info [<remote>:]container [--show-log]\n"
+#~ "\n"
+#~ "For a server:\n"
+#~ " lxc info [<remote>:]"
+#~ msgstr ""
+#~ "LXD サーバとコンテナの情報を一覧表示します。\n"
+#~ "\n"
+#~ "コンテナ情報:\n"
+#~ " lxc info [<remote>:]container [--show-log]\n"
+#~ "\n"
+#~ "サーバ情報:\n"
+#~ " lxc info [<remote>:]"
+
+#, fuzzy
+#~ msgid ""
+#~ "Manage configuration.\n"
+#~ "\n"
+#~ "lxc config device add <[remote:]container> <name> <type> "
+#~ "[key=value]...     Add a device to a container.\n"
+#~ "lxc config device get <[remote:]container> <name> "
+#~ "<key>                     Get a device property.\n"
+#~ "lxc config device set <[remote:]container> <name> <key> "
+#~ "<value>             Set a device property.\n"
+#~ "lxc config device unset <[remote:]container> <name> "
+#~ "<key>                   Unset a device property.\n"
+#~ "lxc config device list "
+#~ "<[remote:]container>                                 List devices for "
+#~ "container.\n"
+#~ "lxc config device show "
+#~ "<[remote:]container>                                 Show full device "
+#~ "details for container.\n"
+#~ "lxc config device remove <[remote:]container> "
+#~ "<name>                        Remove device from container.\n"
+#~ "\n"
+#~ "lxc config get [remote:][container] "
+#~ "<key>                                   Get container or server "
+#~ "configuration key.\n"
+#~ "lxc config set [remote:][container] <key> "
+#~ "<value>                           Set container or server configuration "
+#~ "key.\n"
+#~ "lxc config unset [remote:][container] "
+#~ "<key>                                 Unset container or server "
+#~ "configuration key.\n"
+#~ "lxc config show [remote:][container] [--"
+#~ "expanded]                           Show container or server "
+#~ "configuration.\n"
+#~ "lxc config edit [remote:]"
+#~ "[container]                                        Edit container or "
+#~ "server configuration in external editor.\n"
+#~ "    Edit configuration, either by launching external editor or reading "
+#~ "STDIN.\n"
+#~ "    Example: lxc config edit <container> # launch editor\n"
+#~ "             cat config.yaml | lxc config edit <config> # read from "
+#~ "config.yaml\n"
+#~ "\n"
+#~ "lxc config trust list "
+#~ "[remote]                                              List all trusted "
+#~ "certs.\n"
+#~ "lxc config trust add [remote] <certfile."
+#~ "crt>                                Add certfile.crt to trusted hosts.\n"
+#~ "lxc config trust remove [remote] [hostname|"
+#~ "fingerprint]                     Remove the cert from trusted hosts.\n"
+#~ "\n"
+#~ "Examples:\n"
+#~ "To mount host's /share/c1 onto /opt in the container:\n"
+#~ "    lxc config device add [remote:]container1 <device-name> disk source=/"
+#~ "share/c1 path=opt\n"
+#~ "\n"
+#~ "To set an lxc config value:\n"
+#~ "    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete "
+#~ "= 1'\n"
+#~ "\n"
+#~ "To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the "
+#~ "default):\n"
+#~ "    lxc config set core.https_address [::]:8443\n"
+#~ "\n"
+#~ "To set the server trust password:\n"
+#~ "    lxc config set core.trust_password blah"
+#~ msgstr ""
+#~ "設定を管理します。\n"
+#~ "\n"
+#~ "lxc config device add <[remote:]container> <name> <type> [key=value]...\n"
+#~ "    コンテナにデバイスを追加します。\n"
+#~ "lxc config device get <[remote:]container> <name> <key>\n"
+#~ "    デバイスのプロパティを取得します。\n"
+#~ "lxc config device set <[remote:]container> <name> <key> <value>\n"
+#~ "    デバイスのプロパティを設定します。\n"
+#~ "lxc config device unset <[remote:]container> <name> <key>\n"
+#~ "    デバイスのプロパティを削除します。\n"
+#~ "lxc config device list <[remote:]container>\n"
+#~ "    コンテナのデバイスを一覧表示します。\n"
+#~ "lxc config device show <[remote:]container>\n"
+#~ "    全てのデバイスの詳細を表示します。\n"
+#~ "lxc config device remove <[remote:]container> <name>\n"
+#~ "    コンテナからデバイスを削除します。\n"
+#~ "\n"
+#~ "lxc config get [remote:][container] <key>\n"
+#~ "    コンテナもしくはサーバの設定項目の値を取得します。\n"
+#~ "lxc config set [remote:][container] <key> <value>\n"
+#~ "    コンテナもしくはサーバの設定項目に値を設定します。\n"
+#~ "lxc config unset [remote:][container] <key>\n"
+#~ "    コンテナもしくはサーバの設定項目を削除します。\n"
+#~ "lxc config show [remote:][container] [--expanded]\n"
+#~ "    コンテナもしくはサーバの設定を表示します。\n"
+#~ "lxc config edit [remote:][container]\n"
+#~ "    コンテナもしくはサーバの設定を外部エディタで編集します。\n"
+#~ "    設定の編集は外部エディタを起動するか、標準入力からの読み込みで行いま"
+#~ "す。\n"
+#~ "    例: lxc config edit <container> # エディタの起動\n"
+#~ "        cat config.yml | lxc config edit <config> # config.ymlから読み込"
+#~ "み\n"
+#~ "\n"
+#~ "lxc config trust list [remote]\n"
+#~ "    信頼する証明書を全て表示します。\n"
+#~ "lxc config trust add [remote] <certfile.crt>\n"
+#~ "    certfile.crt を信頼するホストに追加します。\n"
+#~ "lxc config trust remove [remote] [hostname|fingerprint]\n"
+#~ "    信頼するホストから証明書を消去します。\n"
+#~ "\n"
+#~ "例:\n"
+#~ "ホストの /share/c1 をコンテナ内の /opt にマウントするには:\n"
+#~ "    lxc config device add [remote:]container1 <device-name> disk source=/"
+#~ "share/c1 path=opt\n"
+#~ "\n"
+#~ "lxc 設定項目に値を設定するには:\n"
+#~ "    lxc config set [remote:]<container> raw.lxc 'lxc.aa_allow_incomplete "
+#~ "= 1'\n"
+#~ "\n"
+#~ "IPv4 と IPv6 のポート 8443 で Listen するには\n"
+#~ "(8443 はデフォルトなので省略できます):\n"
+#~ "    lxc config set core.https_address [::]:8443\n"
+#~ "\n"
+#~ "サーバのパスワードを設定するには:\n"
+#~ "    lxc config set core.trust_password blah"
+
+#~ msgid ""
+#~ "Manage files on a container.\n"
+#~ "\n"
+#~ "lxc file pull <source> [<source>...] <target>\n"
+#~ "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> "
+#~ "[<source>...] <target>\n"
+#~ "lxc file edit <file>\n"
+#~ "\n"
+#~ "<source> in the case of pull, <target> in the case of push and <file> in "
+#~ "the case of edit are <container name>/<path>"
+#~ msgstr ""
+#~ "コンテナ上のファイルを管理します。\n"
+#~ "\n"
+#~ "lxc file pull <source> [<source>...] <target>\n"
+#~ "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source> "
+#~ "[<source>...] <target>\n"
+#~ "lxc file edit <file>\n"
+#~ "\n"
+#~ "pull の場合の <source>、push の場合の <target>、edit の場合の <file> は、"
+#~ "い\n"
+#~ "ずれも <container name>/<path> の形式です。"
+
+#~ msgid ""
+#~ "Presents details on how to use LXD.\n"
+#~ "\n"
+#~ "lxd help [--all]"
+#~ msgstr ""
+#~ "LXDの使い方の詳細を表示します。\n"
+#~ "\n"
+#~ "lxd help [--all]"
+
+#~ msgid "Usage: %s"
+#~ msgstr "使い方: %s"
+
 #~ msgid "Profile %s added to %s"
 #~ msgstr "プロファイル %s が %s に追加されました"
 
@@ -1823,9 +2150,6 @@ msgstr "コピー元のコンテナ名を指定してください"
 #~ msgid "(Bad alias entry: %s\n"
 #~ msgstr "(不正なエイリアス: %s\n"
 
-#~ msgid "bad container url %s"
-#~ msgstr "コンテナの不正なURL %s"
-
 #~ msgid "bad version in container url"
 #~ msgstr "コンテナURL内のバージョンが不正"
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 54b411226..983c25a0b 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-04-17 16:35-0400\n"
+        "POT-Creation-Date: 2017-04-17 17:27-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -65,124 +65,111 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:580
+#: lxc/image.go:582
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
 
-#: lxc/snapshot.go:61
+#: lxc/snapshot.go:58
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/profile.go:226
+#: lxc/profile.go:264
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:601 lxc/image.go:630
+#: lxc/image.go:603 lxc/image.go:632
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:605
+#: lxc/image.go:607
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:374
+#: lxc/list.go:418
 msgid   "ARCHITECTURE"
 msgstr  ""
 
-#: lxc/remote.go:51
+#: lxc/remote.go:65
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:244
+#: lxc/remote.go:258
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:339
+#: lxc/image.go:341
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:317 lxc/info.go:93
+#: lxc/image.go:319 lxc/info.go:95
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:343
+#: lxc/image.go:345
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
 
-#: lxc/help.go:49
-msgid   "Available commands:"
-msgstr  ""
-
-#: lxc/info.go:175
+#: lxc/info.go:177
 msgid   "Bytes received"
 msgstr  ""
 
-#: lxc/info.go:176
+#: lxc/info.go:178
 msgid   "Bytes sent"
 msgstr  ""
 
-#: lxc/config.go:274
+#: lxc/config.go:310
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:375
+#: lxc/list.go:419
 msgid   "CREATED AT"
 msgstr  ""
 
-#: lxc/config.go:114
+#: lxc/config.go:150
 #, c-format
 msgid   "Can't read from stdin: %s"
 msgstr  ""
 
-#: lxc/config.go:127 lxc/config.go:160 lxc/config.go:182
+#: lxc/config.go:163 lxc/config.go:196 lxc/config.go:218
 #, c-format
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:356
+#: lxc/profile.go:394
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:194
+#: lxc/remote.go:208
 #, c-format
 msgid   "Certificate fingerprint: %x"
 msgstr  ""
 
-#: lxc/action.go:36
-#, c-format
-msgid   "Change state of one or more containers to %s.\n"
-        "\n"
-        "lxc %s [<remote>:]<container> [[<remote>:]<container>...]%s"
-msgstr  ""
-
-#: lxc/finger.go:15
-msgid   "Check if the LXD instance is up.\n"
-        "\n"
-        "lxc finger [<remote>:]"
-msgstr  ""
-
-#: lxc/remote.go:267
+#: lxc/remote.go:281
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
-#: lxc/list.go:78 lxc/list.go:79
+#: lxc/list.go:122 lxc/list.go:123
 msgid   "Columns"
 msgstr  ""
 
-#: lxc/init.go:135 lxc/init.go:136
+#: lxc/help.go:53
+msgid   "Commands:"
+msgstr  ""
+
+#: lxc/init.go:133 lxc/init.go:134
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:535 lxc/config.go:600 lxc/image.go:684 lxc/profile.go:190
+#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:686 lxc/profile.go:228
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
 
-#: lxc/main.go:29
+#: lxc/main.go:32
 msgid   "Connection refused; is LXD running?"
 msgstr  ""
 
@@ -190,7 +177,7 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/init.go:213
+#: lxc/init.go:211
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
@@ -200,94 +187,64 @@ msgstr  ""
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:130
+#: lxc/image.go:132
 msgid   "Copy aliases from source"
 msgstr  ""
 
-#: lxc/copy.go:23
-msgid   "Copy containers within or in between LXD instances.\n"
-        "\n"
-        "lxc copy [<remote>:]<source>[/<snapshot>] [<remote>:]<destination> [--ephemeral|e]"
-msgstr  ""
-
-#: lxc/image.go:242
+#: lxc/image.go:244
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:209
+#: lxc/remote.go:223
 msgid   "Could not create server cert dir"
 msgstr  ""
 
-#: lxc/snapshot.go:21
-msgid   "Create a read-only snapshot of a container.\n"
-        "\n"
-        "lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
-        "\n"
-        "Creates a snapshot of the container (optionally with the container's memory\n"
-        "state). When --stateful is used, LXD attempts to checkpoint the container's\n"
-        "running state, including process memory state, TCP connections, etc. so that it\n"
-        "can be restored (via lxc restore) at a later time (although some things, e.g.\n"
-        "TCP connections after the TCP timeout window has expired, may not be restored\n"
-        "successfully).\n"
-        "\n"
-        "Example:\n"
-        "    lxc snapshot u1 snap0"
-msgstr  ""
-
-#: lxc/image.go:322 lxc/info.go:95
+#: lxc/image.go:324 lxc/info.go:97
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:178 lxc/launch.go:113
+#: lxc/init.go:176 lxc/launch.go:111
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
 
-#: lxc/init.go:176
+#: lxc/init.go:174
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:604 lxc/image.go:632
+#: lxc/image.go:606 lxc/image.go:634
 msgid   "DESCRIPTION"
 msgstr  ""
 
-#: lxc/delete.go:26
-msgid   "Delete containers or snapshots.\n"
-        "\n"
-        "lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]\n"
-        "\n"
-        "Destroy containers or snapshots with any attached data (configuration, snapshots, ...)."
-msgstr  ""
-
-#: lxc/config.go:652
+#: lxc/config.go:688
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:839
+#: lxc/config.go:875
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
 
-#: lxc/info.go:143
+#: lxc/info.go:145
 msgid   "Disk usage:"
 msgstr  ""
 
-#: lxc/list.go:460
+#: lxc/list.go:504
 msgid   "EPHEMERAL"
 msgstr  ""
 
-#: lxc/config.go:276
+#: lxc/config.go:312
 msgid   "EXPIRY DATE"
 msgstr  ""
 
-#: lxc/main.go:41
+#: lxc/main.go:44
 msgid   "Enable debug mode"
 msgstr  ""
 
-#: lxc/main.go:40
+#: lxc/main.go:43
 msgid   "Enable verbose mode"
 msgstr  ""
 
@@ -295,11 +252,11 @@ msgstr  ""
 msgid   "Environment variable to set (e.g. HOME=/home/foo)"
 msgstr  ""
 
-#: lxc/help.go:69
+#: lxc/help.go:77
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:139 lxc/init.go:140
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:137 lxc/init.go:138
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -307,120 +264,102 @@ msgstr  ""
 msgid   "Event type to listen for"
 msgstr  ""
 
-#: lxc/exec.go:46
-msgid   "Execute the specified command in a container.\n"
-        "\n"
-        "lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>\n"
-        "\n"
-        "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
-msgstr  ""
-
-#: lxc/image.go:326
+#: lxc/image.go:328
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:328
+#: lxc/image.go:330
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:273 lxc/image.go:602 lxc/image.go:631
+#: lxc/config.go:309 lxc/image.go:604 lxc/image.go:633
 msgid   "FINGERPRINT"
 msgstr  ""
 
-#: lxc/list.go:81
-msgid   "Fast mode (same as --columns=nsacPt"
+#: lxc/manpage.go:62
+#, c-format
+msgid   "Failed to generate 'lxc.%s.1': %v"
+msgstr  ""
+
+#: lxc/manpage.go:55
+#, c-format
+msgid   "Failed to generate 'lxc.1': %v"
+msgstr  ""
+
+#: lxc/list.go:125
+msgid   "Fast mode (same as --columns=nsacPt)"
 msgstr  ""
 
-#: lxc/image.go:315
+#: lxc/image.go:317
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
 
-#: lxc/action.go:45 lxc/action.go:46
+#: lxc/action.go:46 lxc/action.go:47
 msgid   "Force the container to shutdown"
 msgstr  ""
 
-#: lxc/delete.go:35 lxc/delete.go:36
+#: lxc/delete.go:33 lxc/delete.go:34
 msgid   "Force the removal of stopped containers"
 msgstr  ""
 
-#: lxc/main.go:42
+#: lxc/main.go:45
 msgid   "Force using the local unix socket"
 msgstr  ""
 
-#: lxc/list.go:80
+#: lxc/list.go:124
 msgid   "Format"
 msgstr  ""
 
-#: lxc/main.go:128
+#: lxc/main.go:131
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/help.go:25
-msgid   "Help page for the LXD client.\n"
-        "\n"
-        "lxc help [--all]"
-msgstr  ""
-
-#: lxc/list.go:372
+#: lxc/list.go:416
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:373
+#: lxc/list.go:417
 msgid   "IPV6"
 msgstr  ""
 
-#: lxc/config.go:275
+#: lxc/config.go:311
 msgid   "ISSUE DATE"
 msgstr  ""
 
-#: lxc/main.go:136
+#: lxc/main.go:139
 msgid   "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr  ""
 
-#: lxc/main.go:43
+#: lxc/main.go:46
 msgid   "Ignore aliases when determining what command to run"
 msgstr  ""
 
-#: lxc/action.go:49
+#: lxc/action.go:50
 msgid   "Ignore the container state (only for start)"
 msgstr  ""
 
-#: lxc/image.go:245
+#: lxc/image.go:247
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:398 lxc/image.go:410
+#: lxc/image.go:400 lxc/image.go:412
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:395
+#: lxc/image.go:397
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
 
-#: lxc/init.go:73
-msgid   "Initialize a container from a particular image.\n"
-        "\n"
-        "lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
-        "\n"
-        "Initializes a container using the specified image and name.\n"
-        "\n"
-        "Not specifying -p will result in the default profile.\n"
-        "Specifying \"-p\" with no argument will result in no profile.\n"
-        "\n"
-        "Example:\n"
-        "    lxc init ubuntu:16.04 u1"
-msgstr  ""
-
-#: lxc/remote.go:120
+#: lxc/remote.go:134
 #, c-format
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
 
-#: lxc/config.go:254
+#: lxc/config.go:290
 msgid   "Invalid certificate"
 msgstr  ""
 
@@ -428,97 +367,42 @@ msgstr  ""
 msgid   "Invalid configuration key"
 msgstr  ""
 
-#: lxc/file.go:202
+#: lxc/file.go:207
 #, c-format
 msgid   "Invalid source %s"
 msgstr  ""
 
-#: lxc/file.go:64
+#: lxc/file.go:69
 #, c-format
 msgid   "Invalid target %s"
 msgstr  ""
 
-#: lxc/info.go:124
+#: lxc/info.go:126
 msgid   "Ips:"
 msgstr  ""
 
-#: lxc/image.go:131
+#: lxc/image.go:133
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
-#: lxc/main.go:27
+#: lxc/main.go:30
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/image.go:331
+#: lxc/image.go:333
 #, c-format
 msgid   "Last used: %s"
 msgstr  ""
 
-#: lxc/image.go:333
+#: lxc/image.go:335
 msgid   "Last used: never"
 msgstr  ""
 
-#: lxc/launch.go:23
-msgid   "Launch a container from a particular image.\n"
-        "\n"
-        "lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
-        "\n"
-        "Launches a container using the specified image and name.\n"
-        "\n"
-        "Not specifying -p will result in the default profile.\n"
-        "Specifying \"-p\" with no argument will result in no profile.\n"
-        "\n"
-        "Example:\n"
-        "    lxc launch ubuntu:16.04 u1"
-msgstr  ""
-
-#: lxc/info.go:25
-msgid   "List information on LXD servers and containers.\n"
-        "\n"
-        "For a container:\n"
-        "    lxc info [<remote:>]<container> [--show-log]\n"
-        "\n"
-        "For a server:\n"
-        "    lxc info [<remote:>]"
-msgstr  ""
-
-#: lxc/list.go:46
-msgid   "Lists the containers.\n"
-        "\n"
-        "lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]\n"
-        "\n"
-        "The filters are:\n"
-        "* A single keyword like \"web\" which will list any container with a name starting by \"web\".\n"
-        "* A regular expression on the container name. (e.g. .*web.*01$)\n"
-        "* A key/value pair referring to a configuration item. For those, the namespace can be abbreviated to the smallest unambiguous identifier:\n"
-        " * \"user.blah=abc\" will list all containers with the \"blah\" user property set to \"abc\".\n"
-        " * \"u.blah=abc\" will do the same\n"
-        " * \"security.privileged=1\" will list all privileged containers\n"
-        " * \"s.privileged=1\" will do the same\n"
-        "* A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*)\n"
-        "\n"
-        "Columns for table format are:\n"
-        "* 4 - IPv4 address\n"
-        "* 6 - IPv6 address\n"
-        "* a - architecture\n"
-        "* c - creation date\n"
-        "* n - name\n"
-        "* p - pid of container init process\n"
-        "* P - profiles\n"
-        "* s - state\n"
-        "* S - number of snapshots\n"
-        "* t - type (persistent or ephemeral)\n"
-        "\n"
-        "Default column layout: ns46tS\n"
-        "Fast column layout: nsacPt"
-msgstr  ""
-
-#: lxc/info.go:228
+#: lxc/info.go:230
 msgid   "Log:"
 msgstr  ""
 
-#: lxc/image.go:129
+#: lxc/image.go:131
 msgid   "Make image public"
 msgstr  ""
 
@@ -526,270 +410,72 @@ msgstr  ""
 msgid   "Make the image public"
 msgstr  ""
 
-#: lxc/profile.go:48
-msgid   "Manage configuration profiles.\n"
-        "\n"
-        "lxc profile list [<remote>:]                                    List available profiles.\n"
-        "lxc profile show [<remote>:]<profile>                           Show details of a profile.\n"
-        "lxc profile create [<remote>:]<profile>                         Create a profile.\n"
-        "lxc profile copy [<remote>:]<profile> [<remote>:]<profile>      Copy the profile.\n"
-        "lxc profile get [<remote>:]<profile> <key>                      Get profile configuration.\n"
-        "lxc profile set [<remote>:]<profile> <key> <value>              Set profile configuration.\n"
-        "lxc profile unset [<remote>:]<profile> <key>                    Unset profile configuration.\n"
-        "lxc profile delete [<remote>:]<profile>                         Delete a profile.\n"
-        "lxc profile edit [<remote>:]<profile>\n"
-        "    Edit profile, either by launching external editor or reading STDIN.\n"
-        "    Example: lxc profile edit <profile> # launch editor\n"
-        "             cat profile.yaml | lxc profile edit <profile> # read from profile.yaml\n"
-        "lxc profile apply [<remote>:]<container> <profiles>\n"
-        "    Apply a comma-separated list of profiles to a container, in order.\n"
-        "    All profiles passed in this call (and only those) will be applied\n"
-        "    to the specified container.\n"
-        "    Example: lxc profile apply foo default,bar # Apply default and bar\n"
-        "             lxc profile apply foo default # Only default is active\n"
-        "             lxc profile apply '' # no profiles are applied anymore\n"
-        "             lxc profile apply bar,default # Apply default second now\n"
-        "\n"
-        "Devices:\n"
-        "lxc profile device list [<remote>:]<profile>                                List devices in the given profile.\n"
-        "lxc profile device show [<remote>:]<profile>                                Show full device details in the given profile.\n"
-        "lxc profile device remove [<remote>:]<profile> <name>                       Remove a device from a profile.\n"
-        "lxc profile device get [<remote>:]<profile> <name> <key>                    Get a device property.\n"
-        "lxc profile device set [<remote>:]<profile> <name> <key> <value>            Set a device property.\n"
-        "lxc profile device unset [<remote>:]<profile> <name> <key>                  Unset a device property.\n"
-        "lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]\n"
-        "    Add a profile device, such as a disk or a nic, to the containers using the specified profile."
-msgstr  ""
-
-#: lxc/config.go:58
-msgid   "Manage configuration.\n"
-        "\n"
-        "lxc config device add [<remote>:]<container> <device> <type> [key=value...]   Add a device to a container.\n"
-        "lxc config device get [<remote>:]<container> <device> <key>                   Get a device property.\n"
-        "lxc config device set [<remote>:]<container> <device> <key> <value>           Set a device property.\n"
-        "lxc config device unset [<remote>:]<container> <device> <key>                 Unset a device property.\n"
-        "lxc config device list [<remote>:]<container>                                 List devices for container.\n"
-        "lxc config device show [<remote>:]<container>                                 Show full device details for container.\n"
-        "lxc config device remove [<remote>:]<container> <name>                        Remove device from container.\n"
-        "\n"
-        "lxc config get [<remote>:][container] <key>                                   Get container or server configuration key.\n"
-        "lxc config set [<remote>:][container] <key> <value>                           Set container or server configuration key.\n"
-        "lxc config unset [<remote>:][container] <key>                                 Unset container or server configuration key.\n"
-        "lxc config show [<remote>:][container] [--expanded]                           Show container or server configuration.\n"
-        "lxc config edit [<remote>:][container]                                        Edit container or server configuration in external editor.\n"
-        "    Edit configuration, either by launching external editor or reading STDIN.\n"
-        "    Example: lxc config edit <container> # launch editor\n"
-        "             cat config.yaml | lxc config edit <container> # read from config.yaml\n"
-        "\n"
-        "lxc config trust list [<remote>:]                                             List all trusted certs.\n"
-        "lxc config trust add [<remote>:] <certfile.crt>                               Add certfile.crt to trusted hosts.\n"
-        "lxc config trust remove [<remote>:] [hostname|fingerprint]                    Remove the cert from trusted hosts.\n"
-        "\n"
-        "Examples:\n"
-        "To mount host's /share/c1 onto /opt in the container:\n"
-        "    lxc config device add [<remote>:]container1 <device-name> disk source=/share/c1 path=opt\n"
-        "\n"
-        "To set an lxc config value:\n"
-        "    lxc config set [<remote>:]<container> raw.lxc 'lxc.aa_allow_incomplete = 1'\n"
-        "\n"
-        "To listen on IPv4 and IPv6 port 8443 (you can omit the 8443 its the default):\n"
-        "    lxc config set core.https_address [::]:8443\n"
-        "\n"
-        "To set the server trust password:\n"
-        "    lxc config set core.trust_password blah"
-msgstr  ""
-
-#: lxc/file.go:32
-msgid   "Manage files in a container.\n"
-        "\n"
-        "lxc file pull [<remote>:]<container> [[<remote>:]<container>...] <target path>\n"
-        "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source path>...] [<remote>:]<container>\n"
-        "lxc file edit [<remote>:]<container>/<path>\n"
-        "\n"
-        "<source> in the case of pull, <target> in the case of push and <file> in the case of edit are <container name>/<path>\n"
-        "\n"
-        "Examples:\n"
-        "To push /etc/hosts into the container foo:\n"
-        "    lxc file push /etc/hosts foo/etc/hosts\n"
-        "\n"
-        "To pull /etc/hosts from the container:\n"
-        "    lxc file pull foo/etc/hosts ."
-msgstr  ""
-
-#: lxc/remote.go:37
-msgid   "Manage remote LXD servers.\n"
-        "\n"
-        "lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD]\n"
-        "                                      [--public] [--protocol=PROTOCOL]      Add the remote <remote> at <url>.\n"
-        "lxc remote remove <remote>                                                  Remove the remote <remote>.\n"
-        "lxc remote list                                                             List all remotes.\n"
-        "lxc remote rename <old name> <new name>                                     Rename remote <old name> to <new name>.\n"
-        "lxc remote set-url <remote> <url>                                           Update <remote>'s url to <url>.\n"
-        "lxc remote set-default <remote>                                             Set the default remote.\n"
-        "lxc remote get-default                                                      Print the default remote."
-msgstr  ""
-
-#: lxc/image.go:60
-msgid   "Manipulate container images.\n"
-        "\n"
-        "In LXD containers are created from images. Those images were themselves\n"
-        "either generated from an existing container or downloaded from an image\n"
-        "server.\n"
-        "\n"
-        "When using remote images, LXD will automatically cache images for you\n"
-        "and remove them upon expiration.\n"
-        "\n"
-        "The image unique identifier is the hash (sha-256) of its representation\n"
-        "as a compressed tarball (or for split images, the concatenation of the\n"
-        "metadata and rootfs tarballs).\n"
-        "\n"
-        "Images can be referenced by their full hash, shortest unique partial\n"
-        "hash or alias name (if one is set).\n"
-        "\n"
-        "\n"
-        "lxc image import <tarball> [<rootfs tarball>|<URL>] [<remote>:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS...] [prop=value]\n"
-        "    Import an image tarball (or tarballs) into the LXD image store.\n"
-        "\n"
-        "lxc image copy [<remote>:]<image> <remote>: [--alias=ALIAS...] [--copy-aliases] [--public] [--auto-update]\n"
-        "    Copy an image from one LXD daemon to another over the network.\n"
-        "\n"
-        "    The auto-update flag instructs the server to keep this image up to\n"
-        "    date. It requires the source to be an alias and for it to be public.\n"
-        "\n"
-        "lxc image delete [<remote>:]<image> [[<remote>:]<image>...]\n"
-        "    Delete one or more images from the LXD image store.\n"
-        "\n"
-        "lxc image export [<remote>:]<image> [target]\n"
-        "    Export an image from the LXD image store into a distributable tarball.\n"
-        "\n"
-        "    The output target is optional and defaults to the working directory.\n"
-        "    The target may be an existing directory, file name, or \"-\" to specify\n"
-        "    stdout.  The target MUST be a directory when exporting a split image.\n"
-        "    If the target is a directory, the image's name (each part's name for\n"
-        "    split images) as found in the database will be used for the exported\n"
-        "    image.  If the target is a file (not a directory and not stdout), then\n"
-        "    the appropriate extension will be appended to the provided file name\n"
-        "    based on the algorithm used to compress the image. \n"
-        "\n"
-        "lxc image info [<remote>:]<image>\n"
-        "    Print everything LXD knows about a given image.\n"
-        "\n"
-        "lxc image list [<remote>:] [filter]\n"
-        "    List images in the LXD image store. Filters may be of the\n"
-        "    <key>=<value> form for property based filtering, or part of the image\n"
-        "    hash or part of the image alias name.\n"
-        "\n"
-        "lxc image show [<remote>:]<image>\n"
-        "    Yaml output of the user modifiable properties of an image.\n"
-        "\n"
-        "lxc image edit [<remote>:]<image>\n"
-        "    Edit image, either by launching external editor or reading STDIN.\n"
-        "    Example: lxc image edit <image> # launch editor\n"
-        "             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
-        "\n"
-        "lxc image alias create [<remote>:]<alias> <fingerprint>\n"
-        "    Create a new alias for an existing image.\n"
-        "\n"
-        "lxc image alias delete [<remote>:]<alias>\n"
-        "    Delete an alias.\n"
-        "\n"
-        "lxc image alias list [<remote>:] [filter]\n"
-        "    List the aliases. Filters may be part of the image hash or part of the image alias name."
-msgstr  ""
-
-#: lxc/info.go:150
+#: lxc/info.go:152
 msgid   "Memory (current)"
 msgstr  ""
 
-#: lxc/info.go:154
+#: lxc/info.go:156
 msgid   "Memory (peak)"
 msgstr  ""
 
-#: lxc/info.go:166
+#: lxc/info.go:168
 msgid   "Memory usage:"
 msgstr  ""
 
-#: lxc/help.go:87
+#: lxc/utils.go:173
 msgid   "Missing summary."
 msgstr  ""
 
-#: lxc/monitor.go:41
-msgid   "Monitor activity on the LXD server.\n"
-        "\n"
-        "lxc monitor [<remote>:] [--type=TYPE...]\n"
-        "\n"
-        "Connects to the monitoring interface of the specified LXD server.\n"
-        "\n"
-        "By default will listen to all message types.\n"
-        "Specific types to listen to can be specified with --type.\n"
-        "\n"
-        "Example:\n"
-        "    lxc monitor --type=logging"
-msgstr  ""
-
-#: lxc/file.go:190
+#: lxc/file.go:195
 msgid   "More than one file to download, but target is not a directory"
 msgstr  ""
 
-#: lxc/move.go:16
-msgid   "Move containers within or in between lxd instances.\n"
-        "\n"
-        "lxc move [<remote>:]<source container> [<remote>:][<destination container>]\n"
-        "    Move a container between two hosts, renaming it if destination name differs.\n"
-        "\n"
-        "lxc move <old name> <new name>\n"
-        "    Rename a local container.\n"
-        "\n"
-        "lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
-        "    Rename a snapshot."
-msgstr  ""
-
-#: lxc/action.go:67
+#: lxc/action.go:68
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:376 lxc/remote.go:351
+#: lxc/list.go:420 lxc/remote.go:365
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/remote.go:325 lxc/remote.go:330
+#: lxc/remote.go:339 lxc/remote.go:344
 msgid   "NO"
 msgstr  ""
 
-#: lxc/info.go:89
+#: lxc/info.go:91
 #, c-format
 msgid   "Name: %s"
 msgstr  ""
 
-#: lxc/info.go:183
+#: lxc/info.go:185
 msgid   "Network usage:"
 msgstr  ""
 
-#: lxc/image.go:132 lxc/publish.go:34
+#: lxc/image.go:134 lxc/publish.go:34
 msgid   "New alias to define at target"
 msgstr  ""
 
-#: lxc/config.go:285
+#: lxc/config.go:321
 msgid   "No certificate provided to add"
 msgstr  ""
 
-#: lxc/config.go:308
+#: lxc/config.go:344
 msgid   "No fingerprint specified."
 msgstr  ""
 
-#: lxc/remote.go:105
+#: lxc/remote.go:119
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:401
+#: lxc/image.go:403
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
-#: lxc/help.go:63 lxc/main.go:109 lxc/main.go:153
+#: lxc/help.go:71 lxc/main.go:112 lxc/main.go:164
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:503
+#: lxc/image.go:505
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -798,221 +484,197 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:462
+#: lxc/list.go:506
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:377
+#: lxc/list.go:421
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:378
+#: lxc/list.go:422
 msgid   "PROFILES"
 msgstr  ""
 
-#: lxc/remote.go:353
+#: lxc/remote.go:367
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:603 lxc/remote.go:354
+#: lxc/image.go:605 lxc/remote.go:368
 msgid   "PUBLIC"
 msgstr  ""
 
-#: lxc/info.go:177
+#: lxc/info.go:179
 msgid   "Packets received"
 msgstr  ""
 
-#: lxc/info.go:178
+#: lxc/info.go:180
 msgid   "Packets sent"
 msgstr  ""
 
-#: lxc/help.go:70
+#: lxc/help.go:78
 msgid   "Path to an alternate client configuration directory"
 msgstr  ""
 
-#: lxc/help.go:71
+#: lxc/help.go:79
 msgid   "Path to an alternate server directory"
 msgstr  ""
 
-#: lxc/main.go:31
+#: lxc/main.go:201
+msgid   "Pause containers."
+msgstr  ""
+
+#: lxc/main.go:34
 msgid   "Permission denied, are you in the lxd group?"
 msgstr  ""
 
-#: lxc/info.go:106
+#: lxc/info.go:108
 #, c-format
 msgid   "Pid: %d"
 msgstr  ""
 
-#: lxc/profile.go:191
+#: lxc/profile.go:229
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:536 lxc/config.go:601 lxc/image.go:685
+#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:687
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
-#: lxc/help.go:65
+#: lxc/help.go:73
 msgid   "Print debug information"
 msgstr  ""
 
-#: lxc/help.go:64
+#: lxc/help.go:72
 msgid   "Print less common commands"
 msgstr  ""
 
-#: lxc/help.go:66
+#: lxc/help.go:74
 msgid   "Print verbose information"
 msgstr  ""
 
-#: lxc/manpage.go:18
-msgid   "Prints all the subcommands help."
-msgstr  ""
-
-#: lxc/version.go:18
-msgid   "Prints the version number of this client tool.\n"
-        "\n"
-        "lxc version"
-msgstr  ""
-
-#: lxc/info.go:130
+#: lxc/info.go:132
 #, c-format
 msgid   "Processes: %d"
 msgstr  ""
 
-#: lxc/profile.go:228
+#: lxc/profile.go:266
 #, c-format
 msgid   "Profile %s applied to %s"
 msgstr  ""
 
-#: lxc/profile.go:142
+#: lxc/profile.go:180
 #, c-format
 msgid   "Profile %s created"
 msgstr  ""
 
-#: lxc/profile.go:212
+#: lxc/profile.go:250
 #, c-format
 msgid   "Profile %s deleted"
 msgstr  ""
 
-#: lxc/init.go:137 lxc/init.go:138
+#: lxc/init.go:135 lxc/init.go:136
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
-#: lxc/info.go:104
+#: lxc/info.go:106
 #, c-format
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:335
+#: lxc/image.go:337
 msgid   "Properties:"
 msgstr  ""
 
-#: lxc/remote.go:54
+#: lxc/remote.go:68
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:318
+#: lxc/image.go:320
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
 
-#: lxc/publish.go:26
-msgid   "Publish containers as images.\n"
-        "\n"
-        "lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]"
-msgstr  ""
-
-#: lxc/remote.go:52
+#: lxc/remote.go:66
 msgid   "Remote admin password"
 msgstr  ""
 
-#: lxc/info.go:91
+#: lxc/info.go:93
 #, c-format
 msgid   "Remote: %s"
 msgstr  ""
 
-#: lxc/delete.go:43
+#: lxc/delete.go:41
 #, c-format
 msgid   "Remove %s (yes/no): "
 msgstr  ""
 
-#: lxc/delete.go:37 lxc/delete.go:38
+#: lxc/delete.go:35 lxc/delete.go:36
 msgid   "Require user confirmation"
 msgstr  ""
 
-#: lxc/info.go:127
+#: lxc/info.go:129
 msgid   "Resources:"
 msgstr  ""
 
-#: lxc/restore.go:21
-msgid   "Restore a container's state to a previous snapshot.\n"
-        "\n"
-        "lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
-        "\n"
-        "Restores a container from a snapshot (optionally with running state, see\n"
-        "snapshot help for details).\n"
-        "\n"
-        "Examples:\n"
-        "Create the snapshot:\n"
-        "    lxc snapshot u1 snap0\n"
-        "\n"
-        "Restore the snapshot:\n"
-        "    lxc restore u1 snap0"
+#: lxc/main.go:209
+msgid   "Restart containers."
 msgstr  ""
 
-#: lxc/init.go:220
+#: lxc/init.go:218
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:606
+#: lxc/image.go:608
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:379
+#: lxc/list.go:423
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:380
+#: lxc/list.go:424
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:355
+#: lxc/remote.go:369
 msgid   "STATIC"
 msgstr  ""
 
-#: lxc/remote.go:202
+#: lxc/remote.go:216
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:264
+#: lxc/remote.go:278
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
-#: lxc/remote.go:53
+#: lxc/remote.go:67
 msgid   "Server protocol (lxd or simplestreams)"
 msgstr  ""
 
-#: lxc/file.go:51
+#: lxc/file.go:56
 msgid   "Set the file's gid on push"
 msgstr  ""
 
-#: lxc/file.go:52
+#: lxc/file.go:57
 msgid   "Set the file's perms on push"
 msgstr  ""
 
-#: lxc/file.go:50
+#: lxc/file.go:55
 msgid   "Set the file's uid on push"
 msgstr  ""
 
-#: lxc/help.go:32
+#: lxc/help.go:29
 msgid   "Show all commands (not just interesting ones)"
 msgstr  ""
 
-#: lxc/help.go:67
+#: lxc/help.go:75
 msgid   "Show client version"
 msgstr  ""
 
-#: lxc/info.go:36
+#: lxc/info.go:38
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
@@ -1020,59 +682,67 @@ msgstr  ""
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:316
+#: lxc/image.go:318
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
 
-#: lxc/info.go:197
+#: lxc/info.go:199
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/action.go:133
+#: lxc/action.go:134
 #, c-format
 msgid   "Some containers failed to %s"
 msgstr  ""
 
-#: lxc/image.go:345
+#: lxc/image.go:347
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/launch.go:120
+#: lxc/main.go:219
+msgid   "Start containers."
+msgstr  ""
+
+#: lxc/launch.go:118
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
 
-#: lxc/info.go:98
+#: lxc/info.go:100
 #, c-format
 msgid   "Status: %s"
 msgstr  ""
 
+#: lxc/main.go:225
+msgid   "Stop containers."
+msgstr  ""
+
 #: lxc/publish.go:35 lxc/publish.go:36
 msgid   "Stop the container if currently running"
 msgstr  ""
 
-#: lxc/delete.go:107 lxc/publish.go:112
+#: lxc/delete.go:105 lxc/publish.go:112
 msgid   "Stopping container failed!"
 msgstr  ""
 
-#: lxc/action.go:48
+#: lxc/action.go:49
 msgid   "Store the container state (only for stop)"
 msgstr  ""
 
-#: lxc/info.go:158
+#: lxc/info.go:160
 msgid   "Swap (current)"
 msgstr  ""
 
-#: lxc/info.go:162
+#: lxc/info.go:164
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:381
+#: lxc/list.go:425
 msgid   "TYPE"
 msgstr  ""
 
-#: lxc/delete.go:93
+#: lxc/delete.go:91
 msgid   "The container is currently running, stop it first or pass --force."
 msgstr  ""
 
@@ -1080,99 +750,601 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/config.go:680 lxc/config.go:692 lxc/config.go:725 lxc/config.go:743 lxc/config.go:781 lxc/config.go:799
+#: lxc/config.go:716 lxc/config.go:728 lxc/config.go:761 lxc/config.go:779 lxc/config.go:817 lxc/config.go:835
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:277
+#: lxc/init.go:275
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
 
-#: lxc/main.go:188
-msgid   "The opposite of `lxc pause` is `lxc start`."
+#: lxc/action.go:34
+msgid   "The opposite of \"lxc pause\" is \"lxc start\"."
 msgstr  ""
 
 #: lxc/publish.go:63
 msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
 
-#: lxc/action.go:44
+#: lxc/help.go:47
+msgid   "This is the LXD command line client.\n"
+        "\n"
+        "All of LXD's features can be driven through the various commands below.\n"
+        "For help with any of those, simply call them with --help."
+msgstr  ""
+
+#: lxc/action.go:45
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:319
+#: lxc/image.go:321
 msgid   "Timestamps:"
 msgstr  ""
 
-#: lxc/main.go:137
+#: lxc/main.go:140
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:403
+#: lxc/image.go:405
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:97 lxc/launch.go:133
+#: lxc/action.go:98 lxc/launch.go:131
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
 
-#: lxc/info.go:100
+#: lxc/info.go:102
 msgid   "Type: ephemeral"
 msgstr  ""
 
-#: lxc/info.go:102
+#: lxc/info.go:104
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:607
+#: lxc/image.go:609
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:352
+#: lxc/remote.go:366
 msgid   "URL"
 msgstr  ""
 
-#: lxc/remote.go:80
+#: lxc/manpage.go:36
+msgid   "Unable to find help2man."
+msgstr  ""
+
+#: lxc/remote.go:94
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:324
+#: lxc/image.go:326
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
 
-#: lxc/help.go:48
+#: lxc/action.go:37
+#, c-format
+msgid   "Usage: lxc %s [<remote>:]<container> [[<remote>:]<container>...]\n"
+        "\n"
+        "%s%s"
+msgstr  ""
+
+#: lxc/help.go:45
 msgid   "Usage: lxc <command> [options]"
 msgstr  ""
 
-#: lxc/delete.go:47
+#: lxc/config.go:58
+msgid   "Usage: lxc config <subcommand> [options]\n"
+        "\n"
+        "Change container or server configuration options.\n"
+        "\n"
+        "*Container configuration*\n"
+        "\n"
+        "lxc config get [<remote>:][container] <key>\n"
+        "    Get container or server configuration key.\n"
+        "\n"
+        "lxc config set [<remote>:][container] <key> <value>\n"
+        "    Set container or server configuration key.\n"
+        "\n"
+        "lxc config unset [<remote>:][container] <key>\n"
+        "    Unset container or server configuration key.\n"
+        "\n"
+        "lxc config show [<remote>:][container] [--expanded]\n"
+        "    Show container or server configuration.\n"
+        "\n"
+        "lxc config edit [<remote>:][container]\n"
+        "    Edit configuration, either by launching external editor or reading STDIN.\n"
+        "\n"
+        "*Device management*\n"
+        "\n"
+        "lxc config device add [<remote>:]<container> <device> <type> [key=value...]\n"
+        "    Add a device to a container.\n"
+        "\n"
+        "lxc config device get [<remote>:]<container> <device> <key>\n"
+        "    Get a device property.\n"
+        "\n"
+        "lxc config device set [<remote>:]<container> <device> <key> <value>\n"
+        "    Set a device property.\n"
+        "\n"
+        "lxc config device unset [<remote>:]<container> <device> <key>\n"
+        "    Unset a device property.\n"
+        "\n"
+        "lxc config device list [<remote>:]<container>\n"
+        "    List devices for container.\n"
+        "\n"
+        "lxc config device show [<remote>:]<container>\n"
+        "    Show full device details for container.\n"
+        "\n"
+        "lxc config device remove [<remote>:]<container> <name>\n"
+        "    Remove device from container.\n"
+        "\n"
+        "*Client trust store management*\n"
+        "\n"
+        "lxc config trust list [<remote>:]\n"
+        "    List all trusted certs.\n"
+        "\n"
+        "lxc config trust add [<remote>:] <certfile.crt>\n"
+        "    Add certfile.crt to trusted hosts.\n"
+        "\n"
+        "lxc config trust remove [<remote>:] [hostname|fingerprint]\n"
+        "    Remove the cert from trusted hosts.\n"
+        "\n"
+        "*Examples*\n"
+        "\n"
+        "cat config.yaml | lxc config edit <container>\n"
+        "    Update the container configuration from config.yaml.\n"
+        "\n"
+        "lxc config device add [<remote>:]container1 <device-name> disk source=/share/c1 path=opt\n"
+        "    Will mount the host's /share/c1 onto /opt in the container.\n"
+        "\n"
+        "lxc config set [<remote>:]<container> limits.cpu 2\n"
+        "    Will set a CPU limit of \"2\" for the container.\n"
+        "\n"
+        "lxc config set core.https_address [::]:8443\n"
+        "    Will have LXD listen on IPv4 and IPv6 port 8443.\n"
+        "\n"
+        "lxc config set core.trust_password blah\n"
+        "    Will set the server's trust password to blah."
+msgstr  ""
+
+#: lxc/copy.go:23
+msgid   "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e]\n"
+        "\n"
+        "Copy containers within or in between LXD instances."
+msgstr  ""
+
+#: lxc/delete.go:26
+msgid   "Usage: lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]\n"
+        "\n"
+        "Delete containers and snapshots."
+msgstr  ""
+
+#: lxc/exec.go:46
+msgid   "Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>\n"
+        "\n"
+        "Execute commands in containers.\n"
+        "\n"
+        "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
+msgstr  ""
+
+#: lxc/file.go:32
+msgid   "Usage: lxc file <subcommand> [options]\n"
+        "\n"
+        "Manage files in containers.\n"
+        "\n"
+        "lxc file pull [<remote>:]<container>/<path> [[<remote>:]<container>/<path>...] <target path>\n"
+        "    Pull files from containers.\n"
+        "\n"
+        "lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source path>...] [<remote>:]<container>/<path>\n"
+        "    Push files into containers.\n"
+        "\n"
+        "lxc file edit [<remote>:]<container>/<path>\n"
+        "    Edit files in containers using the default text editor.\n"
+        "\n"
+        "*Examples*\n"
+        "lxc file push /etc/hosts foo/etc/hosts\n"
+        "   To push /etc/hosts into the container \"foo\".\n"
+        "\n"
+        "lxc file pull foo/etc/hosts .\n"
+        "   To pull /etc/hosts from the container and write it to the current directory."
+msgstr  ""
+
+#: lxc/finger.go:15
+msgid   "Usage: lxc finger [<remote>:]\n"
+        "\n"
+        "Check if the LXD server is alive."
+msgstr  ""
+
+#: lxc/help.go:22
+msgid   "Usage: lxc help [--all]\n"
+        "\n"
+        "Help page for the LXD client."
+msgstr  ""
+
+#: lxc/image.go:60
+msgid   "Usage: lxc image <subcommand> [options]\n"
+        "\n"
+        "Manipulate container images.\n"
+        "\n"
+        "In LXD containers are created from images. Those images were themselves\n"
+        "either generated from an existing container or downloaded from an image\n"
+        "server.\n"
+        "\n"
+        "When using remote images, LXD will automatically cache images for you\n"
+        "and remove them upon expiration.\n"
+        "\n"
+        "The image unique identifier is the hash (sha-256) of its representation\n"
+        "as a compressed tarball (or for split images, the concatenation of the\n"
+        "metadata and rootfs tarballs).\n"
+        "\n"
+        "Images can be referenced by their full hash, shortest unique partial\n"
+        "hash or alias name (if one is set).\n"
+        "\n"
+        "\n"
+        "lxc image import <tarball> [<rootfs tarball>|<URL>] [<remote>:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS...] [prop=value]\n"
+        "    Import an image tarball (or tarballs) into the LXD image store.\n"
+        "\n"
+        "lxc image copy [<remote>:]<image> <remote>: [--alias=ALIAS...] [--copy-aliases] [--public] [--auto-update]\n"
+        "    Copy an image from one LXD daemon to another over the network.\n"
+        "\n"
+        "    The auto-update flag instructs the server to keep this image up to\n"
+        "    date. It requires the source to be an alias and for it to be public.\n"
+        "\n"
+        "lxc image delete [<remote>:]<image> [[<remote>:]<image>...]\n"
+        "    Delete one or more images from the LXD image store.\n"
+        "\n"
+        "lxc image export [<remote>:]<image> [target]\n"
+        "    Export an image from the LXD image store into a distributable tarball.\n"
+        "\n"
+        "    The output target is optional and defaults to the working directory.\n"
+        "    The target may be an existing directory, file name, or \"-\" to specify\n"
+        "    stdout.  The target MUST be a directory when exporting a split image.\n"
+        "    If the target is a directory, the image's name (each part's name for\n"
+        "    split images) as found in the database will be used for the exported\n"
+        "    image.  If the target is a file (not a directory and not stdout), then\n"
+        "    the appropriate extension will be appended to the provided file name\n"
+        "    based on the algorithm used to compress the image. \n"
+        "\n"
+        "lxc image info [<remote>:]<image>\n"
+        "    Print everything LXD knows about a given image.\n"
+        "\n"
+        "lxc image list [<remote>:] [filter]\n"
+        "    List images in the LXD image store. Filters may be of the\n"
+        "    <key>=<value> form for property based filtering, or part of the image\n"
+        "    hash or part of the image alias name.\n"
+        "\n"
+        "lxc image show [<remote>:]<image>\n"
+        "    Yaml output of the user modifiable properties of an image.\n"
+        "\n"
+        "lxc image edit [<remote>:]<image>\n"
+        "    Edit image, either by launching external editor or reading STDIN.\n"
+        "    Example: lxc image edit <image> # launch editor\n"
+        "             cat image.yaml | lxc image edit <image> # read from image.yaml\n"
+        "\n"
+        "lxc image alias create [<remote>:]<alias> <fingerprint>\n"
+        "    Create a new alias for an existing image.\n"
+        "\n"
+        "lxc image alias delete [<remote>:]<alias>\n"
+        "    Delete an alias.\n"
+        "\n"
+        "lxc image alias list [<remote>:] [filter]\n"
+        "    List the aliases. Filters may be part of the image hash or part of the image alias name."
+msgstr  ""
+
+#: lxc/info.go:25
+msgid   "Usage: lxc info [<remote>:][<container>] [--show-log]\n"
+        "\n"
+        "Show container or server information.\n"
+        "\n"
+        "lxc info [<remote>:]<container> [--show-log]\n"
+        "    For container information.\n"
+        "\n"
+        "lxc info [<remote>:]\n"
+        "    For LXD server information."
+msgstr  ""
+
+#: lxc/init.go:73
+msgid   "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+        "\n"
+        "Create containers from images.\n"
+        "\n"
+        "Not specifying -p will result in the default profile.\n"
+        "Specifying \"-p\" with no argument will result in no profile.\n"
+        "\n"
+        "Examples:\n"
+        "    lxc init ubuntu:16.04 u1"
+msgstr  ""
+
+#: lxc/launch.go:23
+msgid   "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+        "\n"
+        "Create and start containers from images.\n"
+        "\n"
+        "Not specifying -p will result in the default profile.\n"
+        "Specifying \"-p\" with no argument will result in no profile.\n"
+        "\n"
+        "Examples:\n"
+        "    lxc launch ubuntu:16.04 u1"
+msgstr  ""
+
+#: lxc/list.go:46
+msgid   "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]\n"
+        "\n"
+        "List the existing containers.\n"
+        "\n"
+        "Default column layout: ns46tS\n"
+        "Fast column layout: nsacPt\n"
+        "\n"
+        "*Filters*\n"
+        "A single keyword like \"web\" which will list any container with a name starting by \"web\".\n"
+        "\n"
+        "A regular expression on the container name. (e.g. .*web.*01$).\n"
+        "\n"
+        "A key/value pair referring to a configuration item. For those, the namespace can be abbreviated to the smallest unambiguous identifier.\n"
+        "    - \"user.blah=abc\" will list all containers with the \"blah\" user property set to \"abc\".\n"
+        "\n"
+        "    - \"u.blah=abc\" will do the same\n"
+        "\n"
+        "    - \"security.privileged=1\" will list all privileged containers\n"
+        "\n"
+        "    - \"s.privileged=1\" will do the same\n"
+        "\n"
+        "A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*).\n"
+        "\n"
+        "*Columns*\n"
+        "The -c option takes a comma separated list of arguments that control\n"
+        "which container attributes to output when displaying in table format.\n"
+        "\n"
+        "Column arguments are either pre-defined shorthand chars (see below),\n"
+        "or (extended) config keys.\n"
+        "\n"
+        "Commas between consecutive shorthand chars are optional.\n"
+        "\n"
+        "Pre-defined column shorthand chars:\n"
+        "\n"
+        "    4 - IPv4 address\n"
+        "\n"
+        "    6 - IPv6 address\n"
+        "\n"
+        "    a - Architecture\n"
+        "\n"
+        "    c - Creation date\n"
+        "\n"
+        "    l - Last used date\n"
+        "\n"
+        "    n - Name\n"
+        "\n"
+        "    p - PID of the container's init process\n"
+        "\n"
+        "    P - Profiles\n"
+        "\n"
+        "    s - State\n"
+        "\n"
+        "    S - Number of snapshots\n"
+        "\n"
+        "    t - Type (persistent or ephemeral)\n"
+        "\n"
+        "Custom columns are defined with \"key[:name][:maxWidth]\":\n"
+        "\n"
+        "    KEY: The (extended) config key to display\n"
+        "\n"
+        "    NAME: Name to display in the column header.\n"
+        "    Defaults to the key if not specified or empty.\n"
+        "\n"
+        "    MAXWIDTH: Max width of the column (longer results are truncated).\n"
+        "    Defaults to -1 (unlimited). Use 0 to limit to the column header size.\n"
+        "\n"
+        "*Examples*\n"
+        "lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:MAC\n"
+        "    Shows a list of containers using the \"NAME\", \"BASE IMAGE\", \"STATE\", \"IPV4\",\n"
+        "    \"IPV6\" and \"MAC\" columns.\n"
+        "\n"
+        "    \"BASE IMAGE\" and \"MAC\" are custom columns generated from container configuration keys."
+msgstr  ""
+
+#: lxc/manpage.go:20
+msgid   "Usage: lxc manpage <directory>\n"
+        "\n"
+        "Generate all the LXD manpages."
+msgstr  ""
+
+#: lxc/monitor.go:41
+msgid   "Usage: lxc monitor [<remote>:] [--type=TYPE...]\n"
+        "\n"
+        "Monitor a local or remote LXD server.\n"
+        "\n"
+        "By default the monitor will listen to all message types.\n"
+        "\n"
+        "Message types to listen for can be specified with --type.\n"
+        "\n"
+        "*Examples*\n"
+        "lxc monitor --type=logging\n"
+        "    Only show log message."
+msgstr  ""
+
+#: lxc/move.go:16
+msgid   "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]]\n"
+        "\n"
+        "Move containers within or in between LXD instances.\n"
+        "\n"
+        "lxc move [<remote>:]<source container> [<remote>:][<destination container>]\n"
+        "    Move a container between two hosts, renaming it if destination name differs.\n"
+        "\n"
+        "lxc move <old name> <new name>\n"
+        "    Rename a local container.\n"
+        "\n"
+        "lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
+        "    Rename a snapshot."
+msgstr  ""
+
+#: lxc/profile.go:48
+msgid   "Usage: lxc profile <subcommand> [options]\n"
+        "\n"
+        "Manage container configuration profiles.\n"
+        "\n"
+        "*Profile configuration*\n"
+        "lxc profile list [<remote>:]\n"
+        "    List available profiles.\n"
+        "\n"
+        "lxc profile show [<remote>:]<profile>\n"
+        "    Show details of a profile.\n"
+        "\n"
+        "lxc profile create [<remote>:]<profile>\n"
+        "    Create a profile.\n"
+        "\n"
+        "lxc profile copy [<remote>:]<profile> [<remote>:]<profile>\n"
+        "    Copy the profile.\n"
+        "\n"
+        "lxc profile get [<remote>:]<profile> <key>\n"
+        "    Get profile configuration.\n"
+        "\n"
+        "lxc profile set [<remote>:]<profile> <key> <value>\n"
+        "    Set profile configuration.\n"
+        "\n"
+        "lxc profile unset [<remote>:]<profile> <key>\n"
+        "    Unset profile configuration.\n"
+        "\n"
+        "lxc profile delete [<remote>:]<profile>\n"
+        "    Delete a profile.\n"
+        "\n"
+        "lxc profile edit [<remote>:]<profile>\n"
+        "    Edit profile, either by launching external editor or reading STDIN.\n"
+        "\n"
+        "*Profile assignment*\n"
+        "lxc profile apply [<remote>:]<container> <profiles>\n"
+        "    Replace the current set of profiles for the container by the one provided.\n"
+        "\n"
+        "*Device management*\n"
+        "lxc profile device list [<remote>:]<profile>\n"
+        "    List devices in the given profile.\n"
+        "\n"
+        "lxc profile device show [<remote>:]<profile>\n"
+        "    Show full device details in the given profile.\n"
+        "\n"
+        "lxc profile device remove [<remote>:]<profile> <name>\n"
+        "    Remove a device from a profile.\n"
+        "\n"
+        "lxc profile device get [<remote>:]<profile> <name> <key>\n"
+        "    Get a device property.\n"
+        "\n"
+        "lxc profile device set [<remote>:]<profile> <name> <key> <value>\n"
+        "    Set a device property.\n"
+        "\n"
+        "lxc profile device unset [<remote>:]<profile> <name> <key>\n"
+        "    Unset a device property.\n"
+        "\n"
+        "lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]\n"
+        "    Add a profile device, such as a disk or a nic, to the containers using the specified profile.\n"
+        "\n"
+        "*Examples*\n"
+        "cat profile.yaml | lxc profile edit <profile>\n"
+        "    Update a profile using the content of profile.yaml\n"
+        "\n"
+        "lxc profile apply foo default,bar\n"
+        "    Set the profiles for \"foo\" to \"default\" and \"bar\".\n"
+        "\n"
+        "lxc profile apply foo default\n"
+        "    Reset \"foo\" to only using the \"default\" profile.\n"
+        "\n"
+        "lxc profile apply foo ''\n"
+        "    Remove all profile from \"foo\""
+msgstr  ""
+
+#: lxc/publish.go:26
+msgid   "Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]\n"
+        "\n"
+        "Publish containers as images."
+msgstr  ""
+
+#: lxc/remote.go:37
+msgid   "Usage: lxc remote <subcommand> [options]\n"
+        "\n"
+        "Manage the list of remote LXD servers.\n"
+        "\n"
+        "lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD] [--public] [--protocol=PROTOCOL]\n"
+        "    Add the remote <remote> at <url>.\n"
+        "\n"
+        "lxc remote remove <remote>\n"
+        "    Remove the remote <remote>.\n"
+        "\n"
+        "lxc remote list\n"
+        "    List all remotes.\n"
+        "\n"
+        "lxc remote rename <old name> <new name>\n"
+        "    Rename remote <old name> to <new name>.\n"
+        "\n"
+        "lxc remote set-url <remote> <url>\n"
+        "    Update <remote>'s url to <url>.\n"
+        "\n"
+        "lxc remote set-default <remote>\n"
+        "    Set the default remote.\n"
+        "\n"
+        "lxc remote get-default\n"
+        "    Print the default remote."
+msgstr  ""
+
+#: lxc/restore.go:21
+msgid   "Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
+        "\n"
+        "Restore containers from snapshots.\n"
+        "\n"
+        "If --stateful is passed, then the running state will be restored too.\n"
+        "\n"
+        "*Examples*\n"
+        "lxc snapshot u1 snap0\n"
+        "    Create the snapshot.\n"
+        "\n"
+        "lxc restore u1 snap0\n"
+        "    Restore the snapshot."
+msgstr  ""
+
+#: lxc/snapshot.go:21
+msgid   "Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
+        "\n"
+        "Create container snapshots.\n"
+        "\n"
+        "When --stateful is used, LXD attempts to checkpoint the container's\n"
+        "running state, including process memory state, TCP connections, ...\n"
+        "\n"
+        "*Examples*\n"
+        "lxc snapshot u1 snap0\n"
+        "    Create a snapshot of \"u1\" called \"snap0\"."
+msgstr  ""
+
+#: lxc/version.go:18
+msgid   "Usage: lxc version\n"
+        "\n"
+        "Print the version number of this client tool."
+msgstr  ""
+
+#: lxc/delete.go:45
 msgid   "User aborted delete operation."
 msgstr  ""
 
-#: lxc/restore.go:38
+#: lxc/restore.go:37
 msgid   "Whether or not to restore the container's running state from snapshot (if available)"
 msgstr  ""
 
-#: lxc/snapshot.go:38
+#: lxc/snapshot.go:35
 msgid   "Whether or not to snapshot the container's running state"
 msgstr  ""
 
-#: lxc/remote.go:327 lxc/remote.go:332
+#: lxc/remote.go:341 lxc/remote.go:346
 msgid   "YES"
 msgstr  ""
 
-#: lxc/main.go:52
+#: lxc/main.go:55
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:106
+#: lxc/launch.go:104
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
-#: lxc/action.go:93
+#: lxc/action.go:94
 msgid   "bad result type from action"
 msgstr  ""
 
@@ -1180,41 +1352,41 @@ msgstr  ""
 msgid   "can't copy to the same container name"
 msgstr  ""
 
-#: lxc/remote.go:315
+#: lxc/remote.go:329
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:341
+#: lxc/remote.go:355
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:203 lxc/init.go:208 lxc/launch.go:90 lxc/launch.go:95
+#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:88 lxc/launch.go:93
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:310
+#: lxc/image.go:312
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:312
+#: lxc/image.go:314
 msgid   "enabled"
 msgstr  ""
 
-#: lxc/action.go:125 lxc/main.go:22 lxc/main.go:150
+#: lxc/action.go:126 lxc/main.go:25 lxc/main.go:160
 #, c-format
 msgid   "error: %v"
 msgstr  ""
 
-#: lxc/help.go:40 lxc/main.go:103
+#: lxc/help.go:37 lxc/main.go:106
 #, c-format
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:110
+#: lxc/launch.go:108
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:305 lxc/image.go:583
+#: lxc/image.go:307 lxc/image.go:585
 msgid   "no"
 msgstr  ""
 
@@ -1222,44 +1394,44 @@ msgstr  ""
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
-#: lxc/remote.go:195
+#: lxc/remote.go:209
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:277 lxc/main.go:281
+#: lxc/main.go:295 lxc/main.go:299
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:377
+#: lxc/remote.go:391
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:307 lxc/remote.go:369 lxc/remote.go:404 lxc/remote.go:420
+#: lxc/remote.go:321 lxc/remote.go:383 lxc/remote.go:418 lxc/remote.go:434
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:290
+#: lxc/remote.go:304
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
-#: lxc/remote.go:311 lxc/remote.go:373 lxc/remote.go:408
+#: lxc/remote.go:325 lxc/remote.go:387 lxc/remote.go:422
 #, c-format
 msgid   "remote %s is static and cannot be modified"
 msgstr  ""
 
-#: lxc/info.go:208
+#: lxc/info.go:210
 msgid   "stateful"
 msgstr  ""
 
-#: lxc/info.go:210
+#: lxc/info.go:212
 msgid   "stateless"
 msgstr  ""
 
-#: lxc/info.go:204
+#: lxc/info.go:206
 #, c-format
 msgid   "taken at %s"
 msgstr  ""
@@ -1268,11 +1440,11 @@ msgstr  ""
 msgid   "unreachable return reached"
 msgstr  ""
 
-#: lxc/main.go:217
+#: lxc/main.go:234
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:46 lxc/image.go:307 lxc/image.go:587
+#: lxc/delete.go:44 lxc/image.go:309 lxc/image.go:589
 msgid   "yes"
 msgstr  ""
 

From 855f42dedc29ffc73aef8cbffe2ba5ff175e48b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 6 Mar 2017 18:31:23 -0500
Subject: [PATCH 0783/1193] shared/idmap: Fix various issues
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 - Restore our old default map for when no shadow is found
 - Fix the logic that determines whether a map is valid or not to work
   with hybrid maps (entries that are listed with Isuid and Isgid both true).

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/idmapset_linux.go | 55 +++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 43 insertions(+), 12 deletions(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 85e991f2a..7a8648aba 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -7,6 +7,7 @@ import (
 	"os/exec"
 	"path"
 	"path/filepath"
+	"reflect"
 	"sort"
 	"strconv"
 	"strings"
@@ -106,24 +107,42 @@ func (e *IdmapEntry) Usable() error {
 		return err
 	}
 
-	valid := false
-	for _, kernelRange := range kernelRanges {
-		if kernelRange.Isuid != e.Isuid {
-			continue
-		}
+	// Validate the uid map
+	if e.Isuid {
+		valid := false
+		for _, kernelRange := range kernelRanges {
+			if !kernelRange.Isuid {
+				continue
+			}
 
-		if kernelRange.Isgid != e.Isgid {
-			continue
+			if kernelRange.Contains(e.Hostid) && kernelRange.Contains(e.Hostid+e.Maprange-1) {
+				valid = true
+				break
+			}
 		}
 
-		if kernelRange.Contains(e.Hostid) && kernelRange.Contains(e.Hostid+e.Maprange-1) {
-			valid = true
-			break
+		if !valid {
+			return fmt.Errorf("The '%s' map can't work in the current user namespace.", e.ToLxcString())
 		}
 	}
 
-	if !valid {
-		return fmt.Errorf("The '%s' map can't work in the current user namespace.", e.ToLxcString())
+	// Validate the gid map
+	if e.Isgid {
+		valid := false
+		for _, kernelRange := range kernelRanges {
+			if !kernelRange.Isgid {
+				continue
+			}
+
+			if kernelRange.Contains(e.Hostid) && kernelRange.Contains(e.Hostid+e.Maprange-1) {
+				valid = true
+				break
+			}
+		}
+
+		if !valid {
+			return fmt.Errorf("The '%s' map can't work in the current user namespace.", e.ToLxcString())
+		}
 	}
 
 	return nil
@@ -683,6 +702,18 @@ func DefaultIdmapSet() (*IdmapSet, error) {
 		return nil, err
 	}
 
+	// Special case for when we have the full kernel range
+	fullKernelRanges := []*IdRange{
+		{true, false, int64(0), int64(4294967294)},
+		{false, true, int64(0), int64(4294967294)}}
+
+	if reflect.DeepEqual(kernelRanges, fullKernelRanges) {
+		// Hardcoded fallback map
+		e := IdmapEntry{Isuid: true, Isgid: true, Nsid: 0, Hostid: 1000000, Maprange: 1000000000}
+		idmapset.Idmap = Extend(idmapset.Idmap, e)
+		return idmapset, nil
+	}
+
 	// Find a suitable uid range
 	for _, entry := range kernelRanges {
 		// We only care about uids right now

From cc264e1e59b169b0bfc493c844089a5a3110334f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 6 Mar 2017 21:46:48 -0500
Subject: [PATCH 0784/1193] global: Use RunCommand everywhere
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go      | 10 +++-----
 lxd/container_lxc.go | 64 +++++++++++++++++++++++------------------------
 lxd/db_update.go     |  5 ++--
 lxd/devices.go       | 12 ++++-----
 lxd/images.go        | 10 ++++----
 lxd/main_init.go     |  6 ++---
 lxd/networks.go      |  3 +--
 lxd/storage.go       | 25 +++----------------
 lxd/storage_btrfs.go | 36 +++++++++++++--------------
 lxd/storage_dir.go   |  3 +--
 lxd/storage_lvm.go   | 39 +++++++++++++++--------------
 lxd/storage_zfs.go   | 70 ++++++++++++++++++++++++++--------------------------
 lxd/util.go          |  3 ++-
 shared/util.go       | 28 ++++++++++++++++-----
 shared/util_linux.go |  2 +-
 15 files changed, 156 insertions(+), 160 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index cd4ac6c49..89881a89d 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path"
 	"strings"
 
@@ -395,16 +394,15 @@ func runApparmor(command string, c container) error {
 		return nil
 	}
 
-	cmd := exec.Command("apparmor_parser", []string{
+	output, err := shared.RunCommand("apparmor_parser", []string{
 		fmt.Sprintf("-%sWL", command),
 		path.Join(aaPath, "cache"),
 		path.Join(aaPath, "profiles", AAProfileShort(c)),
 	}...)
 
-	output, err := cmd.CombinedOutput()
 	if err != nil {
 		shared.LogError("Running apparmor",
-			log.Ctx{"action": command, "output": string(output), "err": err})
+			log.Ctx{"action": command, "output": output, "err": err})
 	}
 
 	return err
@@ -519,7 +517,7 @@ func aaProfile() string {
 }
 
 func aaParserSupports(feature string) bool {
-	out, err := exec.Command("apparmor_parser", "--version").CombinedOutput()
+	out, err := shared.RunCommand("apparmor_parser", "--version")
 	if err != nil {
 		return false
 	}
@@ -528,7 +526,7 @@ func aaParserSupports(feature string) bool {
 	minor := 0
 	micro := 0
 
-	_, err = fmt.Sscanf(strings.Split(string(out), "\n")[0], "AppArmor parser version %d.%d.%d", &major, &minor, &micro)
+	_, err = fmt.Sscanf(strings.Split(out, "\n")[0], "AppArmor parser version %d.%d.%d", &major, &minor, &micro)
 	if err != nil {
 		return false
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 3076cce35..8ccc0fe74 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1713,12 +1713,12 @@ func (c *containerLXC) Start(stateful bool) error {
 	}
 
 	// Start the LXC container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkstart",
 		c.name,
 		c.daemon.lxcpath,
-		configPath).CombinedOutput()
+		configPath)
 
 	// Capture debug output
 	if string(out) != "" {
@@ -3548,15 +3548,15 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 
 		configPath := filepath.Join(c.LogPath(), "lxc.conf")
 
-		var out []byte
-		out, migrateErr = exec.Command(
+		var out string
+		out, migrateErr = shared.RunCommand(
 			execPath,
 			"forkmigrate",
 			c.name,
 			c.daemon.lxcpath,
 			configPath,
 			stateDir,
-			fmt.Sprintf("%v", preservesInodes)).CombinedOutput()
+			fmt.Sprintf("%v", preservesInodes))
 
 		if string(out) != "" {
 			for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
@@ -3791,13 +3791,13 @@ func (c *containerLXC) FileExists(path string) error {
 	}
 
 	// Check if the file exists in the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkcheckfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
 		path,
-	).CombinedOutput()
+	)
 
 	// Tear down container storage if needed
 	if !c.IsRunning() {
@@ -3840,14 +3840,14 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int64, int64, o
 	}
 
 	// Get the file from the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkgetfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
 		dstpath,
 		srcpath,
-	).CombinedOutput()
+	)
 
 	// Tear down container storage if needed
 	if !c.IsRunning() {
@@ -3968,7 +3968,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int64, gid i
 	}
 
 	// Push the file to the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkputfile",
 		c.RootfsPath(),
@@ -3981,7 +3981,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int64, gid i
 		fmt.Sprintf("%d", rootUid),
 		fmt.Sprintf("%d", rootGid),
 		fmt.Sprintf("%d", int(os.FileMode(0640)&os.ModePerm)),
-	).CombinedOutput()
+	)
 
 	// Tear down container storage if needed
 	if !c.IsRunning() {
@@ -4044,13 +4044,13 @@ func (c *containerLXC) FileRemove(path string) error {
 	}
 
 	// Remove the file from the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkremovefile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
 		path,
-	).CombinedOutput()
+	)
 
 	// Tear down container storage if needed
 	if !c.IsRunning() {
@@ -4243,10 +4243,10 @@ func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
 	}
 
 	// Get the network state from the container
-	out, err := exec.Command(
+	out, err := shared.RunCommand(
 		execPath,
 		"forkgetnet",
-		fmt.Sprintf("%d", pid)).CombinedOutput()
+		fmt.Sprintf("%d", pid))
 
 	// Process forkgetnet response
 	if err != nil {
@@ -4256,7 +4256,7 @@ func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
 
 	networks := map[string]api.ContainerStateNetwork{}
 
-	err = json.Unmarshal(out, &networks)
+	err = json.Unmarshal([]byte(out), &networks)
 	if err != nil {
 		shared.LogError("Failure to read forkgetnet json", log.Ctx{"container": c.name, "err": err})
 		return result
@@ -4460,7 +4460,7 @@ func (c *containerLXC) insertMount(source, target, fstype string, flags int) err
 	mntsrc := filepath.Join("/dev/.lxd-mounts", filepath.Base(tmpMount))
 	pidStr := fmt.Sprintf("%d", pid)
 
-	out, err := exec.Command(execPath, "forkmount", pidStr, mntsrc, target).CombinedOutput()
+	out, err := shared.RunCommand(execPath, "forkmount", pidStr, mntsrc, target)
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
@@ -4490,7 +4490,7 @@ func (c *containerLXC) removeMount(mount string) error {
 
 	// Remove the mount from the container
 	pidStr := fmt.Sprintf("%d", pid)
-	out, err := exec.Command(execPath, "forkumount", pidStr, mount).CombinedOutput()
+	out, err := shared.RunCommand(execPath, "forkumount", pidStr, mount)
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
@@ -4832,25 +4832,25 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 	if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p"}) {
 		n2 := deviceNextVeth()
 
-		err := exec.Command("ip", "link", "add", n1, "type", "veth", "peer", "name", n2).Run()
+		_, err := shared.RunCommand("ip", "link", "add", n1, "type", "veth", "peer", "name", n2)
 		if err != nil {
 			return "", fmt.Errorf("Failed to create the veth interface: %s", err)
 		}
 
-		err = exec.Command("ip", "link", "set", n1, "up").Run()
+		_, err = shared.RunCommand("ip", "link", "set", n1, "up")
 		if err != nil {
 			return "", fmt.Errorf("Failed to bring up the veth interface %s: %s", n1, err)
 		}
 
 		if m["nictype"] == "bridged" {
 			if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", m["parent"])) {
-				err = exec.Command("ip", "link", "set", n1, "master", m["parent"]).Run()
+				_, err = shared.RunCommand("ip", "link", "set", n1, "master", m["parent"])
 				if err != nil {
 					deviceRemoveInterface(n2)
 					return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
 				}
 			} else {
-				err = exec.Command("ovs-vsctl", "add-port", m["parent"], n1).Run()
+				_, err = shared.RunCommand("ovs-vsctl", "add-port", m["parent"], n1)
 				if err != nil {
 					deviceRemoveInterface(n2)
 					return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
@@ -4874,7 +4874,7 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 	// Handle macvlan
 	if m["nictype"] == "macvlan" {
 
-		err := exec.Command("ip", "link", "add", n1, "link", m["parent"], "type", "macvlan", "mode", "bridge").Run()
+		_, err := shared.RunCommand("ip", "link", "add", n1, "link", m["parent"], "type", "macvlan", "mode", "bridge")
 		if err != nil {
 			return "", fmt.Errorf("Failed to create the new macvlan interface: %s", err)
 		}
@@ -4884,7 +4884,7 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 
 	// Set the MAC address
 	if m["hwaddr"] != "" {
-		err := exec.Command("ip", "link", "set", "dev", dev, "address", m["hwaddr"]).Run()
+		_, err := shared.RunCommand("ip", "link", "set", "dev", dev, "address", m["hwaddr"])
 		if err != nil {
 			deviceRemoveInterface(dev)
 			return "", fmt.Errorf("Failed to set the MAC address: %s", err)
@@ -4892,7 +4892,7 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 	}
 
 	// Bring the interface up
-	err := exec.Command("ip", "link", "set", "dev", dev, "up").Run()
+	_, err := shared.RunCommand("ip", "link", "set", "dev", dev, "up")
 	if err != nil {
 		deviceRemoveInterface(dev)
 		return "", fmt.Errorf("Failed to bring up the interface: %s", err)
@@ -5609,34 +5609,34 @@ func (c *containerLXC) setNetworkLimits(name string, m types.Device) error {
 	}
 
 	// Clean any existing entry
-	_ = exec.Command("tc", "qdisc", "del", "dev", veth, "root").Run()
-	_ = exec.Command("tc", "qdisc", "del", "dev", veth, "ingress").Run()
+	shared.RunCommand("tc", "qdisc", "del", "dev", veth, "root")
+	shared.RunCommand("tc", "qdisc", "del", "dev", veth, "ingress")
 
 	// Apply new limits
 	if m["limits.ingress"] != "" {
-		out, err := exec.Command("tc", "qdisc", "add", "dev", veth, "root", "handle", "1:0", "htb", "default", "10").CombinedOutput()
+		out, err := shared.RunCommand("tc", "qdisc", "add", "dev", veth, "root", "handle", "1:0", "htb", "default", "10")
 		if err != nil {
 			return fmt.Errorf("Failed to create root tc qdisc: %s", out)
 		}
 
-		out, err = exec.Command("tc", "class", "add", "dev", veth, "parent", "1:0", "classid", "1:10", "htb", "rate", fmt.Sprintf("%dbit", ingressInt)).CombinedOutput()
+		out, err = shared.RunCommand("tc", "class", "add", "dev", veth, "parent", "1:0", "classid", "1:10", "htb", "rate", fmt.Sprintf("%dbit", ingressInt))
 		if err != nil {
 			return fmt.Errorf("Failed to create limit tc class: %s", out)
 		}
 
-		out, err = exec.Command("tc", "filter", "add", "dev", veth, "parent", "1:0", "protocol", "all", "u32", "match", "u32", "0", "0", "flowid", "1:1").CombinedOutput()
+		out, err = shared.RunCommand("tc", "filter", "add", "dev", veth, "parent", "1:0", "protocol", "all", "u32", "match", "u32", "0", "0", "flowid", "1:1")
 		if err != nil {
 			return fmt.Errorf("Failed to create tc filter: %s", out)
 		}
 	}
 
 	if m["limits.egress"] != "" {
-		out, err := exec.Command("tc", "qdisc", "add", "dev", veth, "handle", "ffff:0", "ingress").CombinedOutput()
+		out, err := shared.RunCommand("tc", "qdisc", "add", "dev", veth, "handle", "ffff:0", "ingress")
 		if err != nil {
 			return fmt.Errorf("Failed to create ingress tc qdisc: %s", out)
 		}
 
-		out, err = exec.Command("tc", "filter", "add", "dev", veth, "parent", "ffff:0", "protocol", "all", "u32", "match", "u32", "0", "0", "police", "rate", fmt.Sprintf("%dbit", egressInt), "burst", "1024k", "mtu", "64kb", "drop", "flowid", ":1").CombinedOutput()
+		out, err = shared.RunCommand("tc", "filter", "add", "dev", veth, "parent", "ffff:0", "protocol", "all", "u32", "match", "u32", "0", "0", "police", "rate", fmt.Sprintf("%dbit", egressInt), "burst", "1024k", "mtu", "64kb", "drop", "flowid", ":1")
 		if err != nil {
 			return fmt.Errorf("Failed to create ingress tc qdisc: %s", out)
 		}
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 22b3832e8..0f4177cc4 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -418,9 +417,9 @@ func dbUpdateFromV15(currentVersion int, version int, d *Daemon) error {
 
 		shared.LogDebug("About to rename cName in lv upgrade", log.Ctx{"lvLinkPath": lvLinkPath, "cName": cName, "newLVName": newLVName})
 
-		output, err := exec.Command("lvrename", vgName, cName, newLVName).CombinedOutput()
+		output, err := shared.RunCommand("lvrename", vgName, cName, newLVName)
 		if err != nil {
-			return fmt.Errorf("Could not rename LV '%s' to '%s': %v\noutput:%s", cName, newLVName, err, string(output))
+			return fmt.Errorf("Could not rename LV '%s' to '%s': %v\noutput:%s", cName, newLVName, err, output)
 		}
 
 		if err := os.Remove(lvLinkPath); err != nil {
diff --git a/lxd/devices.go b/lxd/devices.go
index 455f7d122..5895bb968 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -8,7 +8,6 @@ import (
 	"fmt"
 	"math/big"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"sort"
@@ -498,7 +497,8 @@ func deviceNextVeth() string {
 }
 
 func deviceRemoveInterface(nic string) error {
-	return exec.Command("ip", "link", "del", nic).Run()
+	_, err := shared.RunCommand("ip", "link", "del", nic)
+	return err
 }
 
 func deviceMountDisk(srcPath string, dstPath string, readonly bool, recursive bool) error {
@@ -692,13 +692,13 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 		// Accessible zfs filesystems
 		poolName := strings.Split(device[1], "/")[0]
 
-		output, err := exec.Command("zpool", "status", "-P", "-L", poolName).CombinedOutput()
+		output, err := shared.RunCommand("zpool", "status", "-P", "-L", poolName)
 		if err != nil {
 			return nil, fmt.Errorf("Failed to query zfs filesystem information for %s: %s", device[1], output)
 		}
 
 		header := true
-		for _, line := range strings.Split(string(output), "\n") {
+		for _, line := range strings.Split(output, "\n") {
 			fields := strings.Fields(line)
 			if len(fields) < 5 {
 				continue
@@ -746,12 +746,12 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 		}
 	} else if fs == "btrfs" && shared.PathExists(device[1]) {
 		// Accessible btrfs filesystems
-		output, err := exec.Command("btrfs", "filesystem", "show", device[1]).CombinedOutput()
+		output, err := shared.RunCommand("btrfs", "filesystem", "show", device[1])
 		if err != nil {
 			return nil, fmt.Errorf("Failed to query btrfs filesystem information for %s: %s", device[1], output)
 		}
 
-		for _, line := range strings.Split(string(output), "\n") {
+		for _, line := range strings.Split(output, "\n") {
 			fields := strings.Fields(line)
 			if len(fields) == 0 || fields[0] != "devid" {
 				continue
diff --git a/lxd/images.go b/lxd/images.go
index 6527ae723..3e936a136 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -117,7 +117,7 @@ func unpack(d *Daemon, file string, path string) error {
 		return fmt.Errorf("Unsupported image format: %s", extension)
 	}
 
-	output, err := exec.Command(command, args...).CombinedOutput()
+	output, err := shared.RunCommand(command, args...)
 	if err != nil {
 		// Check if we ran out of space
 		fs := syscall.Statfs_t{}
@@ -136,7 +136,7 @@ func unpack(d *Daemon, file string, path string) error {
 			}
 		}
 
-		co := string(output)
+		co := output
 		shared.LogDebugf("Unpacking failed")
 		shared.LogDebugf(co)
 
@@ -777,15 +777,15 @@ func getImageMetadata(fname string) (*imageMetadata, error) {
 	args = append(args, fname, metadataName)
 
 	// read the metadata.yaml
-	output, err := exec.Command("tar", args...).CombinedOutput()
+	output, err := shared.RunCommand("tar", args...)
 
 	if err != nil {
-		outputLines := strings.Split(string(output), "\n")
+		outputLines := strings.Split(output, "\n")
 		return nil, fmt.Errorf("Could not extract image %s from tar: %v (%s)", metadataName, err, outputLines[0])
 	}
 
 	metadata := imageMetadata{}
-	err = yaml.Unmarshal(output, &metadata)
+	err = yaml.Unmarshal([]byte(output), &metadata)
 
 	if err != nil {
 		return nil, fmt.Errorf("Could not parse %s: %v", metadataName, err)
diff --git a/lxd/main_init.go b/lxd/main_init.go
index 6a2b50c59..26a0157b5 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -44,7 +44,7 @@ func cmdInit() error {
 	if err == nil && len(out) != 0 && !runningInUserns {
 		_ = loadModule("zfs")
 
-		err := shared.RunCommand("zpool", "list")
+		_, err := shared.RunCommand("zpool", "list")
 		if err == nil {
 			backendsAvailable = append(backendsAvailable, "zfs")
 		}
@@ -368,10 +368,10 @@ they otherwise would.
 		}
 
 		if shared.StringInSlice(storageMode, []string{"loop", "device"}) {
-			output, err := exec.Command(
+			output, err := shared.RunCommand(
 				"zpool",
 				"create", storagePool, storageDevice,
-				"-f", "-m", "none", "-O", "compression=on").CombinedOutput()
+				"-f", "-m", "none", "-O", "compression=on")
 			if err != nil {
 				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
 			}
diff --git a/lxd/networks.go b/lxd/networks.go
index ac9534743..afc05a367 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"net"
 	"net/http"
-	"os/exec"
 	"strconv"
 
 	"github.com/gorilla/mux"
@@ -125,7 +124,7 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 	} else if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bonding", n.Name)) {
 		n.Type = "bond"
 	} else {
-		_, err := exec.Command("ovs-vsctl", "br-exists", n.Name).CombinedOutput()
+		_, err := shared.RunCommand("ovs-vsctl", "br-exists", n.Name)
 		if err == nil {
 			n.Type = "bridge"
 		} else {
diff --git a/lxd/storage.go b/lxd/storage.go
index 0f9f0ce3c..f8736a069 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"reflect"
 	"syscall"
@@ -71,7 +70,7 @@ func storageRsyncCopy(source string, dest string) (string, error) {
 		rsyncVerbosity = "-vi"
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"rsync",
 		"-a",
 		"-HAX",
@@ -81,7 +80,7 @@ func storageRsyncCopy(source string, dest string) (string, error) {
 		"--numeric-ids",
 		rsyncVerbosity,
 		shared.AddSlash(source),
-		dest).CombinedOutput()
+		dest)
 
 	return string(output), err
 }
@@ -363,9 +362,9 @@ func (ss *storageShared) setUnprivUserAcl(c container, destPath string) error {
 
 	// Attempt to set a POSIX ACL first. Fallback to chmod if the fs doesn't support it.
 	acl := fmt.Sprintf("%d:rx", uid)
-	_, err = exec.Command("setfacl", "-m", acl, destPath).CombinedOutput()
+	_, err = shared.RunCommand("setfacl", "-m", acl, destPath)
 	if err != nil {
-		_, err := exec.Command("chmod", "+x", destPath).CombinedOutput()
+		_, err := shared.RunCommand("chmod", "+x", destPath)
 		if err != nil {
 			return fmt.Errorf("Failed to chmod the container path.")
 		}
@@ -761,22 +760,6 @@ func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, c
 }
 
 // Useful functions for unreliable backends
-func tryExec(name string, arg ...string) ([]byte, error) {
-	var err error
-	var output []byte
-
-	for i := 0; i < 20; i++ {
-		output, err = exec.Command(name, arg...).CombinedOutput()
-		if err == nil {
-			break
-		}
-
-		time.Sleep(500 * time.Millisecond)
-	}
-
-	return output, err
-}
-
 func tryMount(src string, dst string, fs string, flags uintptr, options string) error {
 	var err error
 
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index a28683030..eddb28452 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -38,7 +38,7 @@ func (s *storageBtrfs) Init(config map[string]interface{}) (storage, error) {
 		return s, fmt.Errorf("The 'btrfs' tool isn't available")
 	}
 
-	output, err := exec.Command("btrfs", "version").CombinedOutput()
+	output, err := shared.RunCommand("btrfs", "version")
 	if err != nil {
 		return s, fmt.Errorf("The 'btrfs' tool isn't working properly")
 	}
@@ -266,12 +266,12 @@ func (s *storageBtrfs) ContainerSetQuota(container container, size int64) error
 		return err
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"qgroup",
 		"limit",
 		"-e", fmt.Sprintf("%d", size),
-		subvol).CombinedOutput()
+		subvol)
 
 	if err != nil {
 		return fmt.Errorf("Failed to set btrfs quota: %s", output)
@@ -443,11 +443,11 @@ func (s *storageBtrfs) subvolCreate(subvol string) error {
 		}
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"subvolume",
 		"create",
-		subvol).CombinedOutput()
+		subvol)
 	if err != nil {
 		s.log.Debug(
 			"subvolume create failed",
@@ -464,13 +464,13 @@ func (s *storageBtrfs) subvolCreate(subvol string) error {
 }
 
 func (s *storageBtrfs) subvolQGroup(subvol string) (string, error) {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"qgroup",
 		"show",
 		subvol,
 		"-e",
-		"-f").CombinedOutput()
+		"-f")
 
 	if err != nil {
 		return "", fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
@@ -498,13 +498,13 @@ func (s *storageBtrfs) subvolQGroup(subvol string) (string, error) {
 }
 
 func (s *storageBtrfs) subvolQGroupUsage(subvol string) (int64, error) {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"qgroup",
 		"show",
 		subvol,
 		"-e",
-		"-f").CombinedOutput()
+		"-f")
 
 	if err != nil {
 		return -1, fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
@@ -535,12 +535,12 @@ func (s *storageBtrfs) subvolDelete(subvol string) error {
 	// Attempt (but don't fail on) to delete any qgroup on the subvolume
 	qgroup, err := s.subvolQGroup(subvol)
 	if err == nil {
-		output, err := exec.Command(
+		output, err := shared.RunCommand(
 			"btrfs",
 			"qgroup",
 			"destroy",
 			qgroup,
-			subvol).CombinedOutput()
+			subvol)
 
 		if err != nil {
 			s.log.Warn(
@@ -551,12 +551,12 @@ func (s *storageBtrfs) subvolDelete(subvol string) error {
 	}
 
 	// Delete the subvolume itself
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"btrfs",
 		"subvolume",
 		"delete",
 		subvol,
-	).CombinedOutput()
+	)
 
 	if err != nil {
 		s.log.Warn(
@@ -618,23 +618,23 @@ func (s *storageBtrfs) subvolSnapshot(
 		}
 	}
 
-	var output []byte
+	var output string
 	var err error
 	if readonly {
-		output, err = exec.Command(
+		output, err = shared.RunCommand(
 			"btrfs",
 			"subvolume",
 			"snapshot",
 			"-r",
 			source,
-			dest).CombinedOutput()
+			dest)
 	} else {
-		output, err = exec.Command(
+		output, err = shared.RunCommand(
 			"btrfs",
 			"subvolume",
 			"snapshot",
 			source,
-			dest).CombinedOutput()
+			dest)
 	}
 	if err != nil {
 		s.log.Error(
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index e938a0531..63003c6ac 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -3,7 +3,6 @@ package main
 import (
 	"fmt"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 
@@ -99,7 +98,7 @@ func (s *storageDir) ContainerDelete(container container) error {
 	err := os.RemoveAll(cPath)
 	if err != nil {
 		// RemovaAll fails on very long paths, so attempt an rm -Rf
-		output, err := exec.Command("rm", "-Rf", cPath).CombinedOutput()
+		output, err := shared.RunCommand("rm", "-Rf", cPath)
 		if err != nil {
 			s.log.Error("ContainerDelete: failed", log.Ctx{"cPath": cPath, "output": output})
 			return fmt.Errorf("Error cleaning up %s: %s", cPath, string(output))
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index ac4f4b2f2..e04e111ec 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -18,7 +18,7 @@ import (
 )
 
 func storageLVMCheckVolumeGroup(vgName string) error {
-	output, err := exec.Command("vgdisplay", "-s", vgName).CombinedOutput()
+	output, err := shared.RunCommand("vgdisplay", "-s", vgName)
 	if err != nil {
 		shared.LogDebug("vgdisplay failed to find vg", log.Ctx{"output": string(output)})
 		return fmt.Errorf("LVM volume group '%s' not found", vgName)
@@ -138,10 +138,10 @@ func storageLVMValidateVolumeGroupName(d *Daemon, key string, value string) erro
 }
 
 func xfsGenerateNewUUID(lvpath string) error {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"xfs_admin",
 		"-U", "generate",
-		lvpath).CombinedOutput()
+		lvpath)
 	if err != nil {
 		return fmt.Errorf("Error generating new UUID: %v\noutput:'%s'", err, string(output))
 	}
@@ -168,7 +168,7 @@ func (s *storageLvm) Init(config map[string]interface{}) (storage, error) {
 		return s, err
 	}
 
-	output, err := exec.Command("lvm", "version").CombinedOutput()
+	output, err := shared.RunCommand("lvm", "version")
 	if err != nil {
 		return nil, fmt.Errorf("Error getting LVM version: %v\noutput:'%s'", err, string(output))
 	}
@@ -784,16 +784,16 @@ func (s *storageLvm) createDefaultThinPool() (string, error) {
 	}
 
 	// Create the thin pool
-	var output []byte
+	var output string
 	if isRecent {
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvcreate",
 			"--poolmetadatasize", "1G",
 			"-l", "100%FREE",
 			"--thinpool",
 			fmt.Sprintf("%s/%s", s.vgName, thinPoolName))
 	} else {
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvcreate",
 			"--poolmetadatasize", "1G",
 			"-L", "1G",
@@ -815,7 +815,7 @@ func (s *storageLvm) createDefaultThinPool() (string, error) {
 
 	if !isRecent {
 		// Grow it to the maximum VG size (two step process required by old LVM)
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvextend",
 			"--alloc", "anywhere",
 			"-l", "100%FREE",
@@ -862,7 +862,7 @@ func (s *storageLvm) createThinLV(lvname string) (string, error) {
 
 	lvSize := daemonConfig["storage.lvm_volume_size"].Get()
 
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"lvcreate",
 		"--thin",
 		"-n", lvname,
@@ -878,12 +878,12 @@ func (s *storageLvm) createThinLV(lvname string) (string, error) {
 	fstype := daemonConfig["storage.lvm_fstype"].Get()
 	switch fstype {
 	case "xfs":
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"mkfs.xfs",
 			lvpath)
 	default:
 		// default = ext4
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"mkfs.ext4",
 			"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0",
 			lvpath)
@@ -899,9 +899,9 @@ func (s *storageLvm) createThinLV(lvname string) (string, error) {
 
 func (s *storageLvm) removeLV(lvname string) error {
 	var err error
-	var output []byte
+	var output string
 
-	output, err = tryExec(
+	output, err = shared.TryRunCommand(
 		"lvremove", "-f", fmt.Sprintf("%s/%s", s.vgName, lvname))
 
 	if err != nil {
@@ -918,15 +918,16 @@ func (s *storageLvm) createSnapshotLV(lvname string, origlvname string, readonly
 	if err != nil {
 		return "", fmt.Errorf("Error checking LVM version: %v", err)
 	}
-	var output []byte
+
+	var output string
 	if isRecent {
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvcreate",
 			"-kn",
 			"-n", lvname,
 			"-s", fmt.Sprintf("/dev/%s/%s", s.vgName, origlvname))
 	} else {
-		output, err = tryExec(
+		output, err = shared.TryRunCommand(
 			"lvcreate",
 			"-n", lvname,
 			"-s", fmt.Sprintf("/dev/%s/%s", s.vgName, origlvname))
@@ -939,9 +940,9 @@ func (s *storageLvm) createSnapshotLV(lvname string, origlvname string, readonly
 	snapshotFullName := fmt.Sprintf("/dev/%s/%s", s.vgName, lvname)
 
 	if readonly {
-		output, err = tryExec("lvchange", "-ay", "-pr", snapshotFullName)
+		output, err = shared.TryRunCommand("lvchange", "-ay", "-pr", snapshotFullName)
 	} else {
-		output, err = tryExec("lvchange", "-ay", snapshotFullName)
+		output, err = shared.TryRunCommand("lvchange", "-ay", snapshotFullName)
 	}
 
 	if err != nil {
@@ -956,7 +957,7 @@ func (s *storageLvm) isLVMContainer(container container) bool {
 }
 
 func (s *storageLvm) renameLV(oldName string, newName string) (string, error) {
-	output, err := tryExec("lvrename", s.vgName, oldName, newName)
+	output, err := shared.TryRunCommand("lvrename", s.vgName, oldName, newName)
 	return string(output), err
 }
 
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index bff396fd7..21977fe85 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -55,8 +55,8 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 		if shared.PathExists(shared.VarPath("zfs.img")) {
 			_ = loadModule("zfs")
 
-			output, err := exec.Command("zpool", "import",
-				"-d", shared.VarPath(), s.zfsPool).CombinedOutput()
+			output, err := shared.RunCommand("zpool", "import",
+				"-d", shared.VarPath(), s.zfsPool)
 			if err != nil {
 				return s, fmt.Errorf("Unable to import the ZFS pool: %s", output)
 			}
@@ -65,7 +65,7 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 		}
 	}
 
-	output, err := exec.Command("zfs", "get", "version", "-H", "-o", "value", s.zfsPool).CombinedOutput()
+	output, err := shared.RunCommand("zfs", "get", "version", "-H", "-o", "value", s.zfsPool)
 	if err != nil {
 		return s, fmt.Errorf("The 'zfs' tool isn't working properly")
 	}
@@ -75,13 +75,13 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 		return s, fmt.Errorf("The 'zfs' tool isn't working properly")
 	}
 
-	output, err = exec.Command("zfs", "get", "mountpoint", "-H", "-o", "source", s.zfsPool).CombinedOutput()
+	output, err = shared.RunCommand("zfs", "get", "mountpoint", "-H", "-o", "source", s.zfsPool)
 	if err != nil {
 		return s, fmt.Errorf("Unable to query ZFS mountpoint")
 	}
 
 	if strings.TrimSpace(string(output)) != "local" {
-		err = shared.RunCommand("zfs", "set", "mountpoint=none", s.zfsPool)
+		_, err = shared.RunCommand("zfs", "set", "mountpoint=none", s.zfsPool)
 		if err != nil {
 			return s, err
 		}
@@ -685,8 +685,8 @@ func (s *storageZfs) ImageDelete(fingerprint string) error {
 
 // Helper functions
 func (s *storageZfs) zfsCheckPool(pool string) error {
-	output, err := exec.Command(
-		"zfs", "get", "type", "-H", "-o", "value", pool).CombinedOutput()
+	output, err := shared.RunCommand(
+		"zfs", "get", "type", "-H", "-o", "value", pool)
 	if err != nil {
 		return fmt.Errorf(strings.Split(string(output), "\n")[0])
 	}
@@ -707,13 +707,13 @@ func (s *storageZfs) zfsClone(source string, name string, dest string, dotZfs bo
 		mountpoint += ".zfs"
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"clone",
 		"-p",
 		"-o", fmt.Sprintf("mountpoint=%s", mountpoint),
 		fmt.Sprintf("%s/%s@%s", s.zfsPool, source, name),
-		fmt.Sprintf("%s/%s", s.zfsPool, dest)).CombinedOutput()
+		fmt.Sprintf("%s/%s", s.zfsPool, dest))
 	if err != nil {
 		s.log.Error("zfs clone failed", log.Ctx{"output": string(output)})
 		return fmt.Errorf("Failed to clone the filesystem: %s", output)
@@ -740,13 +740,13 @@ func (s *storageZfs) zfsClone(source string, name string, dest string, dotZfs bo
 			mountpoint += ".zfs"
 		}
 
-		output, err := exec.Command(
+		output, err := shared.RunCommand(
 			"zfs",
 			"clone",
 			"-p",
 			"-o", fmt.Sprintf("mountpoint=%s", mountpoint),
 			fmt.Sprintf("%s/%s@%s", s.zfsPool, sub, name),
-			fmt.Sprintf("%s/%s", s.zfsPool, destSubvol)).CombinedOutput()
+			fmt.Sprintf("%s/%s", s.zfsPool, destSubvol))
 		if err != nil {
 			s.log.Error("zfs clone failed", log.Ctx{"output": string(output)})
 			return fmt.Errorf("Failed to clone the sub-volume: %s", output)
@@ -757,12 +757,12 @@ func (s *storageZfs) zfsClone(source string, name string, dest string, dotZfs bo
 }
 
 func (s *storageZfs) zfsCreate(path string) error {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"create",
 		"-p",
 		"-o", fmt.Sprintf("mountpoint=%s.zfs", shared.VarPath(path)),
-		fmt.Sprintf("%s/%s", s.zfsPool, path)).CombinedOutput()
+		fmt.Sprintf("%s/%s", s.zfsPool, path))
 	if err != nil {
 		s.log.Error("zfs create failed", log.Ctx{"output": string(output)})
 		return fmt.Errorf("Failed to create ZFS filesystem: %s", output)
@@ -786,7 +786,7 @@ func (s *storageZfs) zfsDestroy(path string) error {
 	}
 
 	// Due to open fds or kernel refs, this may fail for a bit, give it 10s
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"zfs",
 		"destroy",
 		"-r",
@@ -876,14 +876,14 @@ func (s *storageZfs) zfsExists(path string) bool {
 }
 
 func (s *storageZfs) zfsGet(path string, key string) (string, error) {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"get",
 		"-H",
 		"-p",
 		"-o", "value",
 		key,
-		fmt.Sprintf("%s/%s", s.zfsPool, path)).CombinedOutput()
+		fmt.Sprintf("%s/%s", s.zfsPool, path))
 	if err != nil {
 		return string(output), fmt.Errorf("Failed to get ZFS config: %s", output)
 	}
@@ -893,15 +893,15 @@ func (s *storageZfs) zfsGet(path string, key string) (string, error) {
 
 func (s *storageZfs) zfsRename(source string, dest string) error {
 	var err error
-	var output []byte
+	var output string
 
 	for i := 0; i < 20; i++ {
-		output, err = exec.Command(
+		output, err = shared.RunCommand(
 			"zfs",
 			"rename",
 			"-p",
 			fmt.Sprintf("%s/%s", s.zfsPool, source),
-			fmt.Sprintf("%s/%s", s.zfsPool, dest)).CombinedOutput()
+			fmt.Sprintf("%s/%s", s.zfsPool, dest))
 
 		// Success
 		if err == nil {
@@ -922,11 +922,11 @@ func (s *storageZfs) zfsRename(source string, dest string) error {
 }
 
 func (s *storageZfs) zfsSet(path string, key string, value string) error {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"set",
 		fmt.Sprintf("%s=%s", key, value),
-		fmt.Sprintf("%s/%s", s.zfsPool, path)).CombinedOutput()
+		fmt.Sprintf("%s/%s", s.zfsPool, path))
 	if err != nil {
 		s.log.Error("zfs set failed", log.Ctx{"output": string(output)})
 		return fmt.Errorf("Failed to set ZFS config: %s", output)
@@ -936,11 +936,11 @@ func (s *storageZfs) zfsSet(path string, key string, value string) error {
 }
 
 func (s *storageZfs) zfsSnapshotCreate(path string, name string) error {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"snapshot",
 		"-r",
-		fmt.Sprintf("%s/%s@%s", s.zfsPool, path, name)).CombinedOutput()
+		fmt.Sprintf("%s/%s@%s", s.zfsPool, path, name))
 	if err != nil {
 		s.log.Error("zfs snapshot failed", log.Ctx{"output": string(output)})
 		return fmt.Errorf("Failed to create ZFS snapshot: %s", output)
@@ -950,11 +950,11 @@ func (s *storageZfs) zfsSnapshotCreate(path string, name string) error {
 }
 
 func (s *storageZfs) zfsSnapshotDestroy(path string, name string) error {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"destroy",
 		"-r",
-		fmt.Sprintf("%s/%s@%s", s.zfsPool, path, name)).CombinedOutput()
+		fmt.Sprintf("%s/%s@%s", s.zfsPool, path, name))
 	if err != nil {
 		s.log.Error("zfs destroy failed", log.Ctx{"output": string(output)})
 		return fmt.Errorf("Failed to destroy ZFS snapshot: %s", output)
@@ -964,7 +964,7 @@ func (s *storageZfs) zfsSnapshotDestroy(path string, name string) error {
 }
 
 func (s *storageZfs) zfsSnapshotRestore(path string, name string) error {
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"zfs",
 		"rollback",
 		fmt.Sprintf("%s/%s@%s", s.zfsPool, path, name))
@@ -988,7 +988,7 @@ func (s *storageZfs) zfsSnapshotRestore(path string, name string) error {
 			continue
 		}
 
-		output, err := tryExec(
+		output, err := shared.TryRunCommand(
 			"zfs",
 			"rollback",
 			fmt.Sprintf("%s/%s@%s", s.zfsPool, sub, name))
@@ -1002,12 +1002,12 @@ func (s *storageZfs) zfsSnapshotRestore(path string, name string) error {
 }
 
 func (s *storageZfs) zfsSnapshotRename(path string, oldName string, newName string) error {
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"rename",
 		"-r",
 		fmt.Sprintf("%s/%s@%s", s.zfsPool, path, oldName),
-		fmt.Sprintf("%s/%s@%s", s.zfsPool, path, newName)).CombinedOutput()
+		fmt.Sprintf("%s/%s@%s", s.zfsPool, path, newName))
 	if err != nil {
 		s.log.Error("zfs snapshot rename failed", log.Ctx{"output": string(output)})
 		return fmt.Errorf("Failed to rename ZFS snapshot: %s", output)
@@ -1017,7 +1017,7 @@ func (s *storageZfs) zfsSnapshotRename(path string, oldName string, newName stri
 }
 
 func (s *storageZfs) zfsMount(path string) error {
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"zfs",
 		"mount",
 		fmt.Sprintf("%s/%s", s.zfsPool, path))
@@ -1030,7 +1030,7 @@ func (s *storageZfs) zfsMount(path string) error {
 }
 
 func (s *storageZfs) zfsUnmount(path string) error {
-	output, err := tryExec(
+	output, err := shared.TryRunCommand(
 		"zfs",
 		"unmount",
 		fmt.Sprintf("%s/%s", s.zfsPool, path))
@@ -1049,13 +1049,13 @@ func (s *storageZfs) zfsListSubvolumes(path string) ([]string, error) {
 		fullPath = fmt.Sprintf("%s/%s", s.zfsPool, path)
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"list",
 		"-t", "filesystem",
 		"-o", "name",
 		"-H",
-		"-r", fullPath).CombinedOutput()
+		"-r", fullPath)
 	if err != nil {
 		s.log.Error("zfs list failed", log.Ctx{"output": string(output)})
 		return []string{}, fmt.Errorf("Failed to list ZFS filesystems: %s", output)
@@ -1084,7 +1084,7 @@ func (s *storageZfs) zfsListSnapshots(path string) ([]string, error) {
 		fullPath = fmt.Sprintf("%s/%s", s.zfsPool, path)
 	}
 
-	output, err := exec.Command(
+	output, err := shared.RunCommand(
 		"zfs",
 		"list",
 		"-t", "snapshot",
@@ -1092,7 +1092,7 @@ func (s *storageZfs) zfsListSnapshots(path string) ([]string, error) {
 		"-H",
 		"-d", "1",
 		"-s", "creation",
-		"-r", fullPath).CombinedOutput()
+		"-r", fullPath)
 	if err != nil {
 		s.log.Error("zfs list failed", log.Ctx{"output": string(output)})
 		return []string{}, fmt.Errorf("Failed to list ZFS snapshots: %s", output)
diff --git a/lxd/util.go b/lxd/util.go
index 736cb6393..1eccf0d44 100644
--- a/lxd/util.go
+++ b/lxd/util.go
@@ -34,5 +34,6 @@ func loadModule(module string) error {
 		return nil
 	}
 
-	return shared.RunCommand("modprobe", module)
+	_, err := shared.RunCommand("modprobe", module)
+	return err
 }
diff --git a/shared/util.go b/shared/util.go
index 524429c45..5ac2ace25 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -438,12 +438,12 @@ func IsBlockdevPath(pathName string) bool {
 }
 
 func BlockFsDetect(dev string) (string, error) {
-	out, err := exec.Command("blkid", "-s", "TYPE", "-o", "value", dev).Output()
+	out, err := RunCommand("blkid", "-s", "TYPE", "-o", "value", dev)
 	if err != nil {
-		return "", fmt.Errorf("Failed to run blkid on: %s", dev)
+		return "", err
 	}
 
-	return strings.TrimSpace(string(out)), nil
+	return strings.TrimSpace(out), nil
 }
 
 // DeepCopy copies src to dest by using encoding/gob so its not that fast.
@@ -728,13 +728,29 @@ func GetByteSizeString(input int64, precision uint) string {
 	return fmt.Sprintf("%.*fEB", precision, value)
 }
 
-func RunCommand(name string, arg ...string) error {
+func RunCommand(name string, arg ...string) (string, error) {
 	output, err := exec.Command(name, arg...).CombinedOutput()
 	if err != nil {
-		return fmt.Errorf("Failed to run: %s %s: %s", name, strings.Join(arg, " "), strings.TrimSpace(string(output)))
+		return string(output), fmt.Errorf("Failed to run: %s %s: %s", name, strings.Join(arg, " "), strings.TrimSpace(string(output)))
 	}
 
-	return nil
+	return string(output), nil
+}
+
+func TryRunCommand(name string, arg ...string) (string, error) {
+	var err error
+	var output string
+
+	for i := 0; i < 20; i++ {
+		output, err = RunCommand(name, arg...)
+		if err == nil {
+			break
+		}
+
+		time.Sleep(500 * time.Millisecond)
+	}
+
+	return output, err
 }
 
 func TimeIsSet(ts time.Time) bool {
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 95e365df8..ebdfa0ac4 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -368,7 +368,7 @@ func GetFileStat(p string) (uid int, gid int, major int, minor int,
 func IsMountPoint(name string) bool {
 	_, err := exec.LookPath("mountpoint")
 	if err == nil {
-		err = exec.Command("mountpoint", "-q", name).Run()
+		_, err = RunCommand("mountpoint", "-q", name)
 		if err != nil {
 			return false
 		}

From f11e3dfbb83855b675f79575bf0298540f7ee607 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 7 Mar 2017 20:47:47 -0500
Subject: [PATCH 0785/1193] Better handle rsync errors (subprocesses)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/rsync.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/rsync.go b/lxd/rsync.go
index fe5770c04..b02ecc926 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -78,6 +78,8 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 
 	conn, err := l.Accept()
 	if err != nil {
+		cmd.Process.Kill()
+		cmd.Wait()
 		return nil, nil, nil, err
 	}
 	l.Close()
@@ -101,6 +103,8 @@ func RsyncSend(path string, conn *websocket.Conn) error {
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
+		cmd.Process.Kill()
+		cmd.Wait()
 		return err
 	}
 
@@ -150,6 +154,8 @@ func RsyncRecv(path string, conn *websocket.Conn) error {
 	readDone, writeDone := shared.WebsocketMirror(conn, stdin, stdout, nil, nil)
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
+		cmd.Process.Kill()
+		cmd.Wait()
 		return err
 	}
 

From 07031f78244da86975026311bc33f8e26534d3c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 01:49:07 -0500
Subject: [PATCH 0786/1193] images: Properly return the alias description
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db_images.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/db_images.go b/lxd/db_images.go
index 081f1a338..6f2466e11 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -227,7 +227,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 	aliases := []api.ImageAlias{}
 	for _, r := range results {
 		name = r[0].(string)
-		desc = r[0].(string)
+		desc = r[1].(string)
 		a := api.ImageAlias{Name: name, Description: desc}
 		aliases = append(aliases, a)
 	}

From b4b19e108c78a8230b5435d36aaca0ba5af43233 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 01:49:31 -0500
Subject: [PATCH 0787/1193] image: Show the alias description
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/lxc/image.go b/lxc/image.go
index 71ab3d15f..75102c53b 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -340,7 +340,11 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		}
 		fmt.Println(i18n.G("Aliases:"))
 		for _, alias := range info.Aliases {
-			fmt.Printf("    - %s\n", alias.Name)
+			if alias.Description != "" {
+				fmt.Printf("    - %s (%s)\n", alias.Name, alias.Description)
+			} else {
+				fmt.Printf("    - %s\n", alias.Name)
+			}
 		}
 		fmt.Printf(i18n.G("Auto update: %s")+"\n", autoUpdate)
 		if info.UpdateSource != nil {

From f8dafef2d89033d3118d110dd8b09cac3712f71d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 17:20:05 -0500
Subject: [PATCH 0788/1193] lxd-benchmark: Fix --help and --version handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/lxd-benchmark/main.go | 31 ++++++++++++++++++++++++-------
 1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/test/lxd-benchmark/main.go b/test/lxd-benchmark/main.go
index 45c90fe6f..09fe3b044 100644
--- a/test/lxd-benchmark/main.go
+++ b/test/lxd-benchmark/main.go
@@ -12,6 +12,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
+	"github.com/lxc/lxd/shared/version"
 )
 
 var argCount = gnuflag.Int("count", 100, "Number of containers to create")
@@ -32,16 +33,32 @@ func main() {
 
 func run(args []string) error {
 	// Parse command line
-	gnuflag.Parse(true)
-
 	if len(os.Args) == 1 || !shared.StringInSlice(os.Args[1], []string{"spawn", "delete"}) {
-		fmt.Printf("Usage: %s spawn [--count=COUNT] [--image=IMAGE] [--privileged=BOOL] [--parallel=COUNT]\n", os.Args[0])
-		fmt.Printf("       %s delete [--parallel=COUNT]\n\n", os.Args[0])
-		gnuflag.Usage()
-		fmt.Printf("\n")
-		return fmt.Errorf("An action (spawn or delete) must be passed.")
+		if len(os.Args) > 1 && os.Args[1] == "--version" {
+			fmt.Println(version.Version)
+			return nil
+		}
+
+		out := os.Stderr
+		if len(os.Args) > 1 && os.Args[1] == "--help" {
+			out = os.Stdout
+		}
+		gnuflag.SetOut(out)
+
+		fmt.Fprintf(out, "Usage: %s spawn [--count=COUNT] [--image=IMAGE] [--privileged=BOOL] [--parallel=COUNT]\n", os.Args[0])
+		fmt.Fprintf(out, "       %s delete [--parallel=COUNT]\n\n", os.Args[0])
+		gnuflag.PrintDefaults()
+		fmt.Fprintf(out, "\n")
+
+		if len(os.Args) > 1 && os.Args[1] == "--help" {
+			return nil
+		}
+
+		return fmt.Errorf("An valid action (spawn or delete) must be passed.")
 	}
 
+	gnuflag.Parse(true)
+
 	// Connect to LXD
 	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
 	if err != nil {

From 594014e7ff7ec9a8fd7af08955f7da0e09178ecd Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 9 Mar 2017 15:32:16 +0100
Subject: [PATCH 0789/1193] snapshots: find max value currently used

The MAX() statement consistently returns snap9 when it should actualy return
snap10 or greater.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_snapshot.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index a8576d60d..ca5a930c0 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -64,7 +64,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 func nextSnapshot(d *Daemon, name string) int {
 	base := name + shared.SnapshotDelimiter + "snap"
 	length := len(base)
-	q := fmt.Sprintf("SELECT MAX(name) FROM containers WHERE type=? AND SUBSTR(name,1,?)=?")
+	q := fmt.Sprintf("SELECT name FROM containers WHERE type=? AND SUBSTR(name,1,?)=?")
 	var numstr string
 	inargs := []interface{}{cTypeSnapshot, length, base}
 	outfmt := []interface{}{numstr}

From 5da9532e18dcbf11779e65706aea41f17fee11bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 9 Mar 2017 18:06:10 -0500
Subject: [PATCH 0790/1193] images: Record the server certificate in the cache
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 287a3e802..173e4dfa0 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -26,6 +26,7 @@ import (
 // Simplestream cache
 type imageStreamCacheEntry struct {
 	Aliases      []api.ImageAliasesEntry `yaml:"aliases"`
+	Certificate  string                  `yaml:"certificate"`
 	Fingerprints []string                `yaml:"fingerprints"`
 	expiry       time.Time
 	ss           *simplestreams.SimpleStreams
@@ -71,7 +72,7 @@ func imageLoadStreamCache(d *Daemon) error {
 
 	for url, entry := range imageStreamCache {
 		if entry.ss == nil {
-			myhttp, err := d.httpClient("")
+			myhttp, err := d.httpClient(entry.Certificate)
 			if err != nil {
 				return err
 			}
@@ -129,7 +130,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 				}
 
 				// Generate cache entry
-				entry = &imageStreamCacheEntry{ss: ss, Aliases: aliases, Fingerprints: fingerprints, expiry: time.Now().Add(time.Hour)}
+				entry = &imageStreamCacheEntry{ss: ss, Aliases: aliases, Certificate: certificate, Fingerprints: fingerprints, expiry: time.Now().Add(time.Hour)}
 				imageStreamCache[server] = entry
 				imageSaveStreamCache()
 

From 28de7fe27c23f06221bbefccc323c7133fb26a9f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 9 Mar 2017 20:36:50 -0500
Subject: [PATCH 0791/1193] lxc: Properly clear transfer stats on error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go  | 1 +
 lxc/launch.go | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/lxc/image.go b/lxc/image.go
index 75102c53b..659842b7a 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -246,6 +246,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		if err == nil {
 			progress.Done(i18n.G("Image copied successfully!"))
 		}
+		progress.Done("")
 
 		return err
 
diff --git a/lxc/launch.go b/lxc/launch.go
index f6e18dcce..e3720fcc2 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -85,11 +85,13 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 	if name == "" {
 		op, err := resp.MetadataAsOperation()
 		if err != nil {
+			progress.Done("")
 			return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
 		}
 
 		containers, ok := op.Resources["containers"]
 		if !ok || len(containers) == 0 {
+			progress.Done("")
 			return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
 		}
 
@@ -97,20 +99,24 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 		toScan := strings.Replace(containers[0], "/", " ", -1)
 		count, err := fmt.Sscanf(toScan, " %s containers %s", &restVersion, &name)
 		if err != nil {
+			progress.Done("")
 			return err
 		}
 
 		if count != 2 {
+			progress.Done("")
 			return fmt.Errorf(i18n.G("bad number of things scanned from image, container or snapshot"))
 		}
 
 		if restVersion != version.APIVersion {
+			progress.Done("")
 			return fmt.Errorf(i18n.G("got bad version"))
 		}
 	}
 	fmt.Printf(i18n.G("Creating %s")+"\n", name)
 
 	if err = d.WaitForSuccess(resp.Operation); err != nil {
+		progress.Done("")
 		return err
 	}
 	progress.Done("")

From a730eab03ee5622f9bb413a4854901973b96b778 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 9 Mar 2017 14:36:46 -0500
Subject: [PATCH 0792/1193] shared/simplestreams: Export image file list
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams/simplestreams.go | 66 +++++++++++++++++++++++++----------
 1 file changed, 47 insertions(+), 19 deletions(-)

diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index e4251e80a..0615fe011 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -10,6 +10,7 @@ import (
 	"os"
 	"path/filepath"
 	"sort"
+	"strconv"
 	"strings"
 	"time"
 
@@ -137,8 +138,10 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 
 			metaPath := meta.Path
 			metaHash := meta.HashSha256
+			metaSize := meta.Size
 			rootfsPath := ""
 			rootfsHash := ""
+			rootfsSize := int64(0)
 			fields := strings.Split(meta.Path, "/")
 			filename := fields[len(fields)-1]
 			size := meta.Size
@@ -153,6 +156,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 				size += rootSquash.Size
 				rootfsPath = rootSquash.Path
 				rootfsHash = rootSquash.HashSha256
+				rootfsSize = rootSquash.Size
 			} else {
 				if meta.LXDHashSha256RootXz != "" {
 					fingerprint = meta.LXDHashSha256RootXz
@@ -162,6 +166,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 				size += rootTar.Size
 				rootfsPath = rootTar.Path
 				rootfsHash = rootTar.HashSha256
+				rootfsSize = rootTar.Size
 			}
 
 			if size == 0 || filename == "" || fingerprint == "" {
@@ -218,7 +223,9 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 				}
 			}
 
-			downloads[fingerprint] = [][]string{{metaPath, metaHash, "meta"}, {rootfsPath, rootfsHash, "root"}}
+			downloads[fingerprint] = [][]string{
+				{metaPath, metaHash, "meta", fmt.Sprintf("%d", metaSize)},
+				{rootfsPath, rootfsHash, "root", fmt.Sprintf("%d", rootfsSize)}}
 			images = append(images, image)
 		}
 	}
@@ -269,6 +276,12 @@ type SimpleStreamsIndexStream struct {
 	Products []string `json:"products"`
 }
 
+type SimpleStreamsFile struct {
+	Path   string
+	Sha256 string
+	Size   int64
+}
+
 func NewClient(url string, httpClient http.Client, useragent string) *SimpleStreams {
 	return &SimpleStreams{
 		http:           &httpClient,
@@ -484,7 +497,7 @@ func (s *SimpleStreams) getImages() ([]api.Image, map[string]*api.ImageAliasesEn
 	return images, aliases, nil
 }
 
-func (s *SimpleStreams) getPaths(fingerprint string) ([][]string, error) {
+func (s *SimpleStreams) GetFiles(fingerprint string) (map[string]SimpleStreamsFile, error) {
 	// Load the main index
 	ssIndex, err := s.parseIndex()
 	if err != nil {
@@ -512,11 +525,21 @@ func (s *SimpleStreams) getPaths(fingerprint string) ([][]string, error) {
 
 		for _, image := range manifestImages {
 			if strings.HasPrefix(image.Fingerprint, fingerprint) {
-				urls := [][]string{}
+				files := map[string]SimpleStreamsFile{}
+
 				for _, path := range downloads[image.Fingerprint] {
-					urls = append(urls, []string{path[0], path[1], path[2]})
+					size, err := strconv.ParseInt(path[3], 10, 64)
+					if err != nil {
+						return nil, err
+					}
+
+					files[path[2]] = SimpleStreamsFile{
+						Path:   path[0],
+						Sha256: path[1],
+						Size:   size}
 				}
-				return urls, nil
+
+				return files, nil
 			}
 		}
 	}
@@ -630,13 +653,21 @@ func (s *SimpleStreams) GetImage(fingerprint string) (*api.Image, error) {
 		return nil, err
 	}
 
+	matches := []api.Image{}
+
 	for _, image := range images {
 		if strings.HasPrefix(image.Fingerprint, fingerprint) {
-			return &image, nil
+			matches = append(matches, image)
 		}
 	}
 
-	return nil, fmt.Errorf("The requested image couldn't be found.")
+	if len(matches) == 0 {
+		return nil, fmt.Errorf("The requested image couldn't be found.")
+	} else if len(matches) > 1 {
+		return nil, fmt.Errorf("More than one match for the provided partial fingerprint.")
+	}
+
+	return &matches[0], nil
 }
 
 func (s *SimpleStreams) ExportImage(image string, target string) (string, error) {
@@ -644,16 +675,16 @@ func (s *SimpleStreams) ExportImage(image string, target string) (string, error)
 		return "", fmt.Errorf("Split images can only be written to a directory.")
 	}
 
-	paths, err := s.getPaths(image)
+	files, err := s.GetFiles(image)
 	if err != nil {
 		return "", err
 	}
 
-	for _, path := range paths {
-		fields := strings.Split(path[0], "/")
+	for _, file := range files {
+		fields := strings.Split(file.Path, "/")
 		targetFile := filepath.Join(target, fields[len(fields)-1])
 
-		err := s.downloadFile(path[0], path[1], targetFile, nil)
+		err := s.downloadFile(file.Path, file.Sha256, targetFile, nil)
 		if err != nil {
 			return "", err
 		}
@@ -662,18 +693,15 @@ func (s *SimpleStreams) ExportImage(image string, target string) (string, error)
 	return target, nil
 }
 
-func (s *SimpleStreams) Download(image string, file string, target string, progress func(int64, int64)) error {
-	paths, err := s.getPaths(image)
+func (s *SimpleStreams) Download(image string, fileType string, target string, progress func(int64, int64)) error {
+	files, err := s.GetFiles(image)
 	if err != nil {
 		return err
 	}
 
-	for _, path := range paths {
-		if file != path[2] {
-			continue
-		}
-
-		return s.downloadFile(path[0], path[1], target, progress)
+	file, ok := files[fileType]
+	if ok {
+		return s.downloadFile(file.Path, file.Sha256, target, progress)
 	}
 
 	return fmt.Errorf("The file couldn't be found.")

From 70dd485eb68a71e1898495a119db5078a34351e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Apr 2017 19:22:18 -0400
Subject: [PATCH 0793/1193] list: Update help for stable-2.0
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Some options don't exist there.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go |  17 +-------
 po/lxd.pot  | 129 +++++++++++++++++++++++++++---------------------------------
 2 files changed, 60 insertions(+), 86 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index 177e760d1..80a7d340c 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -100,22 +100,9 @@ Pre-defined column shorthand chars:
 
     t - Type (persistent or ephemeral)
 
-Custom columns are defined with "key[:name][:maxWidth]":
-
-    KEY: The (extended) config key to display
-
-    NAME: Name to display in the column header.
-    Defaults to the key if not specified or empty.
-
-    MAXWIDTH: Max width of the column (longer results are truncated).
-    Defaults to -1 (unlimited). Use 0 to limit to the column header size.
-
 *Examples*
-lxc list -c n,volatile.base_image:"BASE IMAGE":0,s46,volatile.eth0.hwaddr:MAC
-    Shows a list of containers using the "NAME", "BASE IMAGE", "STATE", "IPV4",
-    "IPV6" and "MAC" columns.
-
-    "BASE IMAGE" and "MAC" are custom columns generated from container configuration keys.`)
+lxc list -c ns46
+    Shows a list of containers using the "NAME", "STATE", "IPV4", "IPV6" columns.`)
 }
 
 func (c *listCmd) flags() {
diff --git a/po/lxd.pot b/po/lxd.pot
index 983c25a0b..3bcc559b0 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-04-17 17:27-0400\n"
+        "POT-Creation-Date: 2017-04-17 19:23-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -65,7 +65,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:582
+#: lxc/image.go:587
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -78,15 +78,15 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:603 lxc/image.go:632
+#: lxc/image.go:608 lxc/image.go:637
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:607
+#: lxc/image.go:612
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:418
+#: lxc/list.go:405
 msgid   "ARCHITECTURE"
 msgstr  ""
 
@@ -99,16 +99,16 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:341
+#: lxc/image.go:342
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:319 lxc/info.go:95
+#: lxc/image.go:320 lxc/info.go:95
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:345
+#: lxc/image.go:350
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
@@ -125,7 +125,7 @@ msgstr  ""
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:419
+#: lxc/list.go:406
 msgid   "CREATED AT"
 msgstr  ""
 
@@ -152,7 +152,7 @@ msgstr  ""
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
-#: lxc/list.go:122 lxc/list.go:123
+#: lxc/list.go:109 lxc/list.go:110
 msgid   "Columns"
 msgstr  ""
 
@@ -164,7 +164,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:686 lxc/profile.go:228
+#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:691 lxc/profile.go:228
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -200,12 +200,12 @@ msgstr  ""
 msgid   "Could not create server cert dir"
 msgstr  ""
 
-#: lxc/image.go:324 lxc/info.go:97
+#: lxc/image.go:325 lxc/info.go:97
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:176 lxc/launch.go:111
+#: lxc/init.go:176 lxc/launch.go:116
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
@@ -214,7 +214,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:606 lxc/image.go:634
+#: lxc/image.go:611 lxc/image.go:639
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -232,7 +232,7 @@ msgstr  ""
 msgid   "Disk usage:"
 msgstr  ""
 
-#: lxc/list.go:504
+#: lxc/list.go:491
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -264,16 +264,16 @@ msgstr  ""
 msgid   "Event type to listen for"
 msgstr  ""
 
-#: lxc/image.go:328
+#: lxc/image.go:329
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:330
+#: lxc/image.go:331
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:309 lxc/image.go:604 lxc/image.go:633
+#: lxc/config.go:309 lxc/image.go:609 lxc/image.go:638
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -287,11 +287,11 @@ msgstr  ""
 msgid   "Failed to generate 'lxc.1': %v"
 msgstr  ""
 
-#: lxc/list.go:125
+#: lxc/list.go:112
 msgid   "Fast mode (same as --columns=nsacPt)"
 msgstr  ""
 
-#: lxc/image.go:317
+#: lxc/image.go:318
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -308,7 +308,7 @@ msgstr  ""
 msgid   "Force using the local unix socket"
 msgstr  ""
 
-#: lxc/list.go:124
+#: lxc/list.go:111
 msgid   "Format"
 msgstr  ""
 
@@ -316,11 +316,11 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:416
+#: lxc/list.go:403
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:417
+#: lxc/list.go:404
 msgid   "IPV6"
 msgstr  ""
 
@@ -344,12 +344,12 @@ msgstr  ""
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:400 lxc/image.go:412
+#: lxc/image.go:405 lxc/image.go:417
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:397
+#: lxc/image.go:402
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
@@ -389,12 +389,12 @@ msgstr  ""
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/image.go:333
+#: lxc/image.go:334
 #, c-format
 msgid   "Last used: %s"
 msgstr  ""
 
-#: lxc/image.go:335
+#: lxc/image.go:336
 msgid   "Last used: never"
 msgstr  ""
 
@@ -434,7 +434,7 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:420 lxc/remote.go:365
+#: lxc/list.go:407 lxc/remote.go:365
 msgid   "NAME"
 msgstr  ""
 
@@ -467,7 +467,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:403
+#: lxc/image.go:408
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -475,7 +475,7 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:505
+#: lxc/image.go:510
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
@@ -484,15 +484,15 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:506
+#: lxc/list.go:493
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:421
+#: lxc/list.go:408
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:422
+#: lxc/list.go:409
 msgid   "PROFILES"
 msgstr  ""
 
@@ -500,7 +500,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:605 lxc/remote.go:368
+#: lxc/image.go:610 lxc/remote.go:368
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -537,7 +537,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:687
+#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:692
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -582,7 +582,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:337
+#: lxc/image.go:338
 msgid   "Properties:"
 msgstr  ""
 
@@ -590,7 +590,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:320
+#: lxc/image.go:321
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -626,15 +626,15 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:608
+#: lxc/image.go:613
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:423
+#: lxc/list.go:410
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:424
+#: lxc/list.go:411
 msgid   "STATE"
 msgstr  ""
 
@@ -682,7 +682,7 @@ msgstr  ""
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:318
+#: lxc/image.go:319
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -696,7 +696,7 @@ msgstr  ""
 msgid   "Some containers failed to %s"
 msgstr  ""
 
-#: lxc/image.go:347
+#: lxc/image.go:352
 msgid   "Source:"
 msgstr  ""
 
@@ -704,7 +704,7 @@ msgstr  ""
 msgid   "Start containers."
 msgstr  ""
 
-#: lxc/launch.go:118
+#: lxc/launch.go:124
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -738,7 +738,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:425
+#: lxc/list.go:412
 msgid   "TYPE"
 msgstr  ""
 
@@ -778,7 +778,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:321
+#: lxc/image.go:322
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -786,12 +786,12 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:405
+#: lxc/image.go:410
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:98 lxc/launch.go:131
+#: lxc/action.go:98 lxc/launch.go:137
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -804,7 +804,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:609
+#: lxc/image.go:614
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -820,7 +820,7 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:326
+#: lxc/image.go:327
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1127,22 +1127,9 @@ msgid   "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <column
         "\n"
         "    t - Type (persistent or ephemeral)\n"
         "\n"
-        "Custom columns are defined with \"key[:name][:maxWidth]\":\n"
-        "\n"
-        "    KEY: The (extended) config key to display\n"
-        "\n"
-        "    NAME: Name to display in the column header.\n"
-        "    Defaults to the key if not specified or empty.\n"
-        "\n"
-        "    MAXWIDTH: Max width of the column (longer results are truncated).\n"
-        "    Defaults to -1 (unlimited). Use 0 to limit to the column header size.\n"
-        "\n"
         "*Examples*\n"
-        "lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:MAC\n"
-        "    Shows a list of containers using the \"NAME\", \"BASE IMAGE\", \"STATE\", \"IPV4\",\n"
-        "    \"IPV6\" and \"MAC\" columns.\n"
-        "\n"
-        "    \"BASE IMAGE\" and \"MAC\" are custom columns generated from container configuration keys."
+        "lxc list -c ns46\n"
+        "    Shows a list of containers using the \"NAME\", \"STATE\", \"IPV4\", \"IPV6\" columns."
 msgstr  ""
 
 #: lxc/manpage.go:20
@@ -1340,7 +1327,7 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:104
+#: lxc/launch.go:108
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
@@ -1360,15 +1347,15 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:88 lxc/launch.go:93
+#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:89 lxc/launch.go:95
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:312
+#: lxc/image.go:313
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:314
+#: lxc/image.go:315
 msgid   "enabled"
 msgstr  ""
 
@@ -1382,11 +1369,11 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:108
+#: lxc/launch.go:113
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:307 lxc/image.go:585
+#: lxc/image.go:308 lxc/image.go:590
 msgid   "no"
 msgstr  ""
 
@@ -1444,7 +1431,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:44 lxc/image.go:309 lxc/image.go:589
+#: lxc/delete.go:44 lxc/image.go:310 lxc/image.go:594
 msgid   "yes"
 msgstr  ""
 

From 63d8c137a85ce54eefc6eda221da7ba26d7d3d09 Mon Sep 17 00:00:00 2001
From: Luke Faraone <luke at faraone.cc>
Date: Sun, 12 Mar 2017 19:38:39 +0000
Subject: [PATCH 0794/1193] lxc-to-lxd: Typo in description of --move-rootfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The description of the `--move-rootfs` argument swaps the effect of the
flag — it actually moves the container, rather than copying it.

Signed-off-by: Luke W Faraone <luke at faraone.cc>
---
 scripts/lxc-to-lxd | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index a5421321e..7224cc160 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -576,7 +576,7 @@ parser.add_argument("--all", action="store_true", default=False,
 parser.add_argument("--delete", action="store_true", default=False,
                     help="Delete the source container")
 parser.add_argument("--move-rootfs", action="store_true", default=False,
-                    help="Copy the container rootfs rather than moving it")
+                    help="Move the container rootfs rather than copying it")
 parser.add_argument("--lxcpath", type=str, default=False,
                     help="Alternate LXC path")
 parser.add_argument("--lxdpath", type=str, default="/var/lib/lxd",

From bff371f22f979014cd78e5547aa0e1cb3c129018 Mon Sep 17 00:00:00 2001
From: Frode Nordahl <frode.nordahl at canonical.com>
Date: Mon, 13 Mar 2017 09:58:35 +0100
Subject: [PATCH 0795/1193] Add note about restricting access to kernel ring
 buffer

By default the Linux kernel grants access to the kernel ring buffer
(dmesg) to all users. Consequently containers do have access to this
data. The kernel ring buffer might contain sensitive information.

Fixes #1397
---
 doc/production-setup.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/production-setup.md b/doc/production-setup.md
index eb71a2ae6..0e860d7fd 100644
--- a/doc/production-setup.md
+++ b/doc/production-setup.md
@@ -38,6 +38,7 @@ fs.inotify.max\_queued\_events  | 1048576   | 16384   | This specifies an upper
 fs.inotify.max\_user\_instances | 1048576   | 128     | This specifies an upper limit on the number of inotify instances that can be created per real user ID. [1]
 fs.inotify.max\_user\_watches   | 1048576   | 8192    | This specifies an upper limit on the number of watches that can be created per real user ID. [1]
 vm.max\_map\_count              | 262144    | 65530   | This file contains the maximum number of memory map areas a process may have. Memory map areas are used as a side-effect of calling malloc, directly by mmap and mprotect, and also when loading shared libraries.
+kernel.dmesg\_restrict          | 1         | 0       | This denies container access to the messages in the kernel ring buffer. Please note that this also will deny access to non-root users on the host system.
 
 
 Then, reboot the server.

From cb7836c1f406cb6e649aff00559e71e3c57c02de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 20 Mar 2017 18:45:29 -0400
Subject: [PATCH 0796/1193] lxc/copy: Return the source error too
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This will get us rid off the annoying "bad handshake" error.

Closes #3086

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go   | 10 +++++++++-
 lxc/copy.go |  9 ++++++++-
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/client.go b/client.go
index d7d4d6bc8..2a723bda8 100644
--- a/client.go
+++ b/client.go
@@ -1867,7 +1867,6 @@ func (c *Client) WaitFor(waitURL string) (*api.Operation, error) {
 	 * "/<version>/operations/" in it; we chop off the leading / and pass
 	 * it to url directly.
 	 */
-	shared.LogDebugf(path.Join(waitURL[1:], "wait"))
 	resp, err := c.baseGet(c.url(waitURL, "wait"))
 	if err != nil {
 		return nil, err
@@ -1876,6 +1875,15 @@ func (c *Client) WaitFor(waitURL string) (*api.Operation, error) {
 	return resp.MetadataAsOperation()
 }
 
+func (c *Client) GetOperation(url string) (*api.Operation, error) {
+	resp, err := c.baseGet(c.url(url))
+	if err != nil {
+		return nil, err
+	}
+
+	return resp.MetadataAsOperation()
+}
+
 func (c *Client) WaitForSuccess(waitURL string) error {
 	op, err := c.WaitFor(waitURL)
 	if err != nil {
diff --git a/lxc/copy.go b/lxc/copy.go
index b25c3fb24..5f5c8bdf9 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -185,7 +185,14 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 		return nil
 	}
 
-	return err
+	// Check for an error at the source
+	sourceOp, sourceErr := source.GetOperation(sourceWSResponse.Operation)
+	if sourceErr == nil && sourceOp.Err != "" {
+		return fmt.Errorf(i18n.G("Migration failed on source host: %s"), sourceOp.Err)
+	}
+
+	// Return the error from destination
+	return fmt.Errorf(i18n.G("Migration failed on target host: %s"), err)
 }
 
 func (c *copyCmd) run(config *lxd.Config, args []string) error {

From 4bad4f9c2c8d238a6b07ece7868bad70556bbfc2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 20 Mar 2017 19:10:23 -0400
Subject: [PATCH 0797/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 143 +++++++++++++++++++++++++++++--------------------------------
 po/fr.po   | 143 +++++++++++++++++++++++++++++--------------------------------
 po/ja.po   | 143 +++++++++++++++++++++++++++++--------------------------------
 po/lxd.pot |  12 +++++-
 4 files changed, 218 insertions(+), 223 deletions(-)

diff --git a/po/de.po b/po/de.po
index 4767e3ebe..254d6a663 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-17 17:27-0400\n"
+"POT-Creation-Date: 2017-04-17 19:28-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -111,7 +111,7 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:582
+#: lxc/image.go:587
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -125,15 +125,15 @@ msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:603 lxc/image.go:632
+#: lxc/image.go:608 lxc/image.go:637
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:607
+#: lxc/image.go:612
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:418
+#: lxc/list.go:405
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -146,17 +146,17 @@ msgstr "Akzeptiere Zertifikat"
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
 
-#: lxc/image.go:341
+#: lxc/image.go:342
 #, fuzzy
 msgid "Aliases:"
 msgstr "Aliasse:\n"
 
-#: lxc/image.go:319 lxc/info.go:95
+#: lxc/image.go:320 lxc/info.go:95
 #, fuzzy, c-format
 msgid "Architecture: %s"
 msgstr "Architektur: %s\n"
 
-#: lxc/image.go:345
+#: lxc/image.go:350
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
@@ -173,7 +173,7 @@ msgstr ""
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:419
+#: lxc/list.go:406
 msgid "CREATED AT"
 msgstr ""
 
@@ -200,7 +200,7 @@ msgstr "Fingerabdruck des Zertifikats: % x\n"
 msgid "Client certificate stored at server: "
 msgstr "Gespeichertes Nutzerzertifikat auf dem Server: "
 
-#: lxc/list.go:122 lxc/list.go:123
+#: lxc/list.go:109 lxc/list.go:110
 msgid "Columns"
 msgstr ""
 
@@ -213,7 +213,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:686 lxc/profile.go:228
+#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:691 lxc/profile.go:228
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
@@ -249,12 +249,12 @@ msgstr ""
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
-#: lxc/image.go:324 lxc/info.go:97
+#: lxc/image.go:325 lxc/info.go:97
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:176 lxc/launch.go:111
+#: lxc/init.go:176 lxc/launch.go:116
 #, c-format
 msgid "Creating %s"
 msgstr ""
@@ -264,7 +264,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:606 lxc/image.go:634
+#: lxc/image.go:611 lxc/image.go:639
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -282,7 +282,7 @@ msgstr "Gerät %s wurde von %s entfernt\n"
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:504
+#: lxc/list.go:491
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -316,16 +316,16 @@ msgstr "Flüchtiger Container"
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:328
+#: lxc/image.go:329
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:330
+#: lxc/image.go:331
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:309 lxc/image.go:604 lxc/image.go:633
+#: lxc/config.go:309 lxc/image.go:609 lxc/image.go:638
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -339,11 +339,11 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:125
+#: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:317
+#: lxc/image.go:318
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Fingerabdruck: %s\n"
@@ -361,7 +361,7 @@ msgstr ""
 msgid "Force using the local unix socket"
 msgstr ""
 
-#: lxc/list.go:124
+#: lxc/list.go:111
 msgid "Format"
 msgstr ""
 
@@ -370,11 +370,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Generiere Nutzerzertifikat. Dies kann wenige Minuten dauern...\n"
 
-#: lxc/list.go:416
+#: lxc/list.go:403
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:417
+#: lxc/list.go:404
 msgid "IPV6"
 msgstr ""
 
@@ -400,12 +400,12 @@ msgstr "Herunterfahren des Containers erzwingen."
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:400 lxc/image.go:412
+#: lxc/image.go:405 lxc/image.go:417
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/image.go:397
+#: lxc/image.go:402
 #, c-format
 msgid "Importing the image: %s"
 msgstr ""
@@ -446,12 +446,12 @@ msgstr ""
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:333
+#: lxc/image.go:334
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:335
+#: lxc/image.go:336
 msgid "Last used: never"
 msgstr ""
 
@@ -480,6 +480,16 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
+#: lxc/copy.go:191
+#, c-format
+msgid "Migration failed on source host: %s"
+msgstr ""
+
+#: lxc/copy.go:195
+#, c-format
+msgid "Migration failed on target host: %s"
+msgstr ""
+
 #: lxc/utils.go:173
 msgid "Missing summary."
 msgstr "Fehlende Zusammenfassung."
@@ -494,7 +504,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
-#: lxc/list.go:420 lxc/remote.go:365
+#: lxc/list.go:407 lxc/remote.go:365
 msgid "NAME"
 msgstr ""
 
@@ -528,7 +538,7 @@ msgstr "Kein Fingerabdruck angegeben."
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:403
+#: lxc/image.go:408
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
@@ -536,7 +546,7 @@ msgstr ""
 msgid "Options:"
 msgstr ""
 
-#: lxc/image.go:505
+#: lxc/image.go:510
 #, c-format
 msgid "Output is in %s"
 msgstr ""
@@ -545,15 +555,15 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:506
+#: lxc/list.go:493
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:421
+#: lxc/list.go:408
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:422
+#: lxc/list.go:409
 msgid "PROFILES"
 msgstr ""
 
@@ -561,7 +571,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:605 lxc/remote.go:368
+#: lxc/image.go:610 lxc/remote.go:368
 msgid "PUBLIC"
 msgstr ""
 
@@ -601,7 +611,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:687
+#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:692
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -647,7 +657,7 @@ msgstr "kann nicht zum selben Container Namen kopieren"
 msgid "Profiles: %s"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/image.go:337
+#: lxc/image.go:338
 #, fuzzy
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
@@ -656,7 +666,7 @@ msgstr "Eigenschaften:\n"
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:320
+#: lxc/image.go:321
 #, fuzzy, c-format
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
@@ -693,15 +703,15 @@ msgstr "kann nicht zum selben Container Namen kopieren"
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:608
+#: lxc/image.go:613
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:423
+#: lxc/list.go:410
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:424
+#: lxc/list.go:411
 msgid "STATE"
 msgstr ""
 
@@ -750,7 +760,7 @@ msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:318
+#: lxc/image.go:319
 #, fuzzy, c-format
 msgid "Size: %.2fMB"
 msgstr "Größe: %.2vMB\n"
@@ -764,7 +774,7 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/image.go:347
+#: lxc/image.go:352
 msgid "Source:"
 msgstr ""
 
@@ -773,7 +783,7 @@ msgstr ""
 msgid "Start containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/launch.go:118
+#: lxc/launch.go:124
 #, c-format
 msgid "Starting %s"
 msgstr ""
@@ -809,7 +819,7 @@ msgstr ""
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:425
+#: lxc/list.go:412
 msgid "TYPE"
 msgstr ""
 
@@ -855,7 +865,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Wartezeit bevor der Container gestoppt wird."
 
-#: lxc/image.go:321
+#: lxc/image.go:322
 #, fuzzy
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
@@ -864,12 +874,12 @@ msgstr "Zeitstempel:\n"
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:405
+#: lxc/image.go:410
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
 
-#: lxc/action.go:98 lxc/launch.go:131
+#: lxc/action.go:98 lxc/launch.go:137
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
@@ -882,7 +892,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:609
+#: lxc/image.go:614
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -898,7 +908,7 @@ msgstr ""
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:326
+#: lxc/image.go:327
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1273,25 +1283,10 @@ msgid ""
 "\n"
 "    t - Type (persistent or ephemeral)\n"
 "\n"
-"Custom columns are defined with \"key[:name][:maxWidth]\":\n"
-"\n"
-"    KEY: The (extended) config key to display\n"
-"\n"
-"    NAME: Name to display in the column header.\n"
-"    Defaults to the key if not specified or empty.\n"
-"\n"
-"    MAXWIDTH: Max width of the column (longer results are truncated).\n"
-"    Defaults to -1 (unlimited). Use 0 to limit to the column header size.\n"
-"\n"
 "*Examples*\n"
-"lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:"
-"MAC\n"
-"    Shows a list of containers using the \"NAME\", \"BASE IMAGE\", \"STATE"
-"\", \"IPV4\",\n"
-"    \"IPV6\" and \"MAC\" columns.\n"
-"\n"
-"    \"BASE IMAGE\" and \"MAC\" are custom columns generated from container "
-"configuration keys."
+"lxc list -c ns46\n"
+"    Shows a list of containers using the \"NAME\", \"STATE\", \"IPV4\", "
+"\"IPV6\" columns."
 msgstr ""
 "Listet vorhandene Ressourcen.\n"
 "\n"
@@ -1581,7 +1576,7 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:104
+#: lxc/launch.go:108
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "Falsche Anzahl an Objekten im Abbild, Container oder Sicherungspunkt gelesen."
@@ -1602,15 +1597,15 @@ msgstr ""
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:88 lxc/launch.go:93
+#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:89 lxc/launch.go:95
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
-#: lxc/image.go:312
+#: lxc/image.go:313
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:314
+#: lxc/image.go:315
 msgid "enabled"
 msgstr ""
 
@@ -1624,11 +1619,11 @@ msgstr "Fehler: %v\n"
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/launch.go:108
+#: lxc/launch.go:113
 msgid "got bad version"
 msgstr "Versionskonflikt"
 
-#: lxc/image.go:307 lxc/image.go:585
+#: lxc/image.go:308 lxc/image.go:590
 msgid "no"
 msgstr ""
 
@@ -1687,7 +1682,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:44 lxc/image.go:309 lxc/image.go:589
+#: lxc/delete.go:44 lxc/image.go:310 lxc/image.go:594
 msgid "yes"
 msgstr ""
 
diff --git a/po/fr.po b/po/fr.po
index 775ae18fd..d674fc1a3 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-17 17:27-0400\n"
+"POT-Creation-Date: 2017-04-17 19:28-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -68,7 +68,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:582
+#: lxc/image.go:587
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -82,15 +82,15 @@ msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:603 lxc/image.go:632
+#: lxc/image.go:608 lxc/image.go:637
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:607
+#: lxc/image.go:612
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:418
+#: lxc/list.go:405
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -103,16 +103,16 @@ msgstr ""
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
 
-#: lxc/image.go:341
+#: lxc/image.go:342
 msgid "Aliases:"
 msgstr ""
 
-#: lxc/image.go:319 lxc/info.go:95
+#: lxc/image.go:320 lxc/info.go:95
 #, c-format
 msgid "Architecture: %s"
 msgstr ""
 
-#: lxc/image.go:345
+#: lxc/image.go:350
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
@@ -129,7 +129,7 @@ msgstr ""
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:419
+#: lxc/list.go:406
 msgid "CREATED AT"
 msgstr ""
 
@@ -156,7 +156,7 @@ msgstr "Empreinte du certificat: % x\n"
 msgid "Client certificate stored at server: "
 msgstr "Certificat client enregistré avec le serveur: "
 
-#: lxc/list.go:122 lxc/list.go:123
+#: lxc/list.go:109 lxc/list.go:110
 msgid "Columns"
 msgstr ""
 
@@ -168,7 +168,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:686 lxc/profile.go:228
+#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:691 lxc/profile.go:228
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
@@ -204,12 +204,12 @@ msgstr ""
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
-#: lxc/image.go:324 lxc/info.go:97
+#: lxc/image.go:325 lxc/info.go:97
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:176 lxc/launch.go:111
+#: lxc/init.go:176 lxc/launch.go:116
 #, c-format
 msgid "Creating %s"
 msgstr ""
@@ -218,7 +218,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:606 lxc/image.go:634
+#: lxc/image.go:611 lxc/image.go:639
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -236,7 +236,7 @@ msgstr ""
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:504
+#: lxc/list.go:491
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -270,16 +270,16 @@ msgstr ""
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:328
+#: lxc/image.go:329
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:330
+#: lxc/image.go:331
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:309 lxc/image.go:604 lxc/image.go:633
+#: lxc/config.go:309 lxc/image.go:609 lxc/image.go:638
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -293,11 +293,11 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:125
+#: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:317
+#: lxc/image.go:318
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -315,7 +315,7 @@ msgstr ""
 msgid "Force using the local unix socket"
 msgstr ""
 
-#: lxc/list.go:124
+#: lxc/list.go:111
 msgid "Format"
 msgstr ""
 
@@ -324,11 +324,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Géneration d'un certificat client. Ceci peut prendre une minute...\n"
 
-#: lxc/list.go:416
+#: lxc/list.go:403
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:417
+#: lxc/list.go:404
 msgid "IPV6"
 msgstr ""
 
@@ -354,12 +354,12 @@ msgstr "Force l'arrêt du conteneur."
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:400 lxc/image.go:412
+#: lxc/image.go:405 lxc/image.go:417
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/image.go:397
+#: lxc/image.go:402
 #, c-format
 msgid "Importing the image: %s"
 msgstr ""
@@ -401,12 +401,12 @@ msgstr ""
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:333
+#: lxc/image.go:334
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:335
+#: lxc/image.go:336
 msgid "Last used: never"
 msgstr ""
 
@@ -434,6 +434,16 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
+#: lxc/copy.go:191
+#, c-format
+msgid "Migration failed on source host: %s"
+msgstr ""
+
+#: lxc/copy.go:195
+#, c-format
+msgid "Migration failed on target host: %s"
+msgstr ""
+
 #: lxc/utils.go:173
 msgid "Missing summary."
 msgstr "Sommaire manquant."
@@ -447,7 +457,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr ""
 
-#: lxc/list.go:420 lxc/remote.go:365
+#: lxc/list.go:407 lxc/remote.go:365
 msgid "NAME"
 msgstr ""
 
@@ -481,7 +491,7 @@ msgstr "Aucune empreinte n'a été spécifié."
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:403
+#: lxc/image.go:408
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
@@ -490,7 +500,7 @@ msgstr ""
 msgid "Options:"
 msgstr "Opération %s"
 
-#: lxc/image.go:505
+#: lxc/image.go:510
 #, c-format
 msgid "Output is in %s"
 msgstr ""
@@ -499,15 +509,15 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:506
+#: lxc/list.go:493
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:421
+#: lxc/list.go:408
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:422
+#: lxc/list.go:409
 msgid "PROFILES"
 msgstr ""
 
@@ -515,7 +525,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:605 lxc/remote.go:368
+#: lxc/image.go:610 lxc/remote.go:368
 msgid "PUBLIC"
 msgstr ""
 
@@ -555,7 +565,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:687
+#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:692
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -600,7 +610,7 @@ msgstr ""
 msgid "Profiles: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:337
+#: lxc/image.go:338
 msgid "Properties:"
 msgstr ""
 
@@ -608,7 +618,7 @@ msgstr ""
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:320
+#: lxc/image.go:321
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -645,15 +655,15 @@ msgstr "Liste de l'information sur les conteneurs.\n"
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:608
+#: lxc/image.go:613
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:423
+#: lxc/list.go:410
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:424
+#: lxc/list.go:411
 msgid "STATE"
 msgstr ""
 
@@ -701,7 +711,7 @@ msgstr ""
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:318
+#: lxc/image.go:319
 #, c-format
 msgid "Size: %.2fMB"
 msgstr ""
@@ -715,7 +725,7 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/image.go:347
+#: lxc/image.go:352
 msgid "Source:"
 msgstr ""
 
@@ -724,7 +734,7 @@ msgstr ""
 msgid "Start containers."
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/launch.go:118
+#: lxc/launch.go:124
 #, c-format
 msgid "Starting %s"
 msgstr ""
@@ -760,7 +770,7 @@ msgstr ""
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:425
+#: lxc/list.go:412
 msgid "TYPE"
 msgstr ""
 
@@ -806,7 +816,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Temps d'attente avant de tuer le conteneur."
 
-#: lxc/image.go:321
+#: lxc/image.go:322
 msgid "Timestamps:"
 msgstr ""
 
@@ -814,12 +824,12 @@ msgstr ""
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:405
+#: lxc/image.go:410
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
 
-#: lxc/action.go:98 lxc/launch.go:131
+#: lxc/action.go:98 lxc/launch.go:137
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
@@ -832,7 +842,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:609
+#: lxc/image.go:614
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -848,7 +858,7 @@ msgstr ""
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:326
+#: lxc/image.go:327
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1192,25 +1202,10 @@ msgid ""
 "\n"
 "    t - Type (persistent or ephemeral)\n"
 "\n"
-"Custom columns are defined with \"key[:name][:maxWidth]\":\n"
-"\n"
-"    KEY: The (extended) config key to display\n"
-"\n"
-"    NAME: Name to display in the column header.\n"
-"    Defaults to the key if not specified or empty.\n"
-"\n"
-"    MAXWIDTH: Max width of the column (longer results are truncated).\n"
-"    Defaults to -1 (unlimited). Use 0 to limit to the column header size.\n"
-"\n"
 "*Examples*\n"
-"lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:"
-"MAC\n"
-"    Shows a list of containers using the \"NAME\", \"BASE IMAGE\", \"STATE"
-"\", \"IPV4\",\n"
-"    \"IPV6\" and \"MAC\" columns.\n"
-"\n"
-"    \"BASE IMAGE\" and \"MAC\" are custom columns generated from container "
-"configuration keys."
+"lxc list -c ns46\n"
+"    Shows a list of containers using the \"NAME\", \"STATE\", \"IPV4\", "
+"\"IPV6\" columns."
 msgstr ""
 
 #: lxc/manpage.go:20
@@ -1431,7 +1426,7 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:104
+#: lxc/launch.go:108
 #, fuzzy
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr "nombre de propriété invalide pour la ressource"
@@ -1452,16 +1447,16 @@ msgstr ""
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:88 lxc/launch.go:93
+#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:89 lxc/launch.go:95
 #, fuzzy
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
 
-#: lxc/image.go:312
+#: lxc/image.go:313
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:314
+#: lxc/image.go:315
 msgid "enabled"
 msgstr ""
 
@@ -1475,11 +1470,11 @@ msgstr "erreur: %v\n"
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/launch.go:108
+#: lxc/launch.go:113
 msgid "got bad version"
 msgstr "reçu une version invalide"
 
-#: lxc/image.go:307 lxc/image.go:585
+#: lxc/image.go:308 lxc/image.go:590
 msgid "no"
 msgstr ""
 
@@ -1538,7 +1533,7 @@ msgstr "Un retour inacessible à été atteint"
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:44 lxc/image.go:309 lxc/image.go:589
+#: lxc/delete.go:44 lxc/image.go:310 lxc/image.go:594
 msgid "yes"
 msgstr ""
 
diff --git a/po/ja.po b/po/ja.po
index e372886ba..b8c1b5a4a 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-17 17:27-0400\n"
+"POT-Creation-Date: 2017-04-17 19:28-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -68,7 +68,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:582
+#: lxc/image.go:587
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -81,15 +81,15 @@ msgstr "'/' はスナップショットの名前には使用できません。"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:603 lxc/image.go:632
+#: lxc/image.go:608 lxc/image.go:637
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:607
+#: lxc/image.go:612
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:418
+#: lxc/list.go:405
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -102,16 +102,16 @@ msgstr "証明書のフィンガープリントの確認なしで証明書を受
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
 
-#: lxc/image.go:341
+#: lxc/image.go:342
 msgid "Aliases:"
 msgstr "エイリアス:"
 
-#: lxc/image.go:319 lxc/info.go:95
+#: lxc/image.go:320 lxc/info.go:95
 #, c-format
 msgid "Architecture: %s"
 msgstr "アーキテクチャ: %s"
 
-#: lxc/image.go:345
+#: lxc/image.go:350
 #, c-format
 msgid "Auto update: %s"
 msgstr "自動更新: %s"
@@ -128,7 +128,7 @@ msgstr "送信バイト数"
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:419
+#: lxc/list.go:406
 msgid "CREATED AT"
 msgstr ""
 
@@ -155,7 +155,7 @@ msgstr "証明書のフィンガープリント: %x"
 msgid "Client certificate stored at server: "
 msgstr "クライアント証明書がサーバに格納されました: "
 
-#: lxc/list.go:122 lxc/list.go:123
+#: lxc/list.go:109 lxc/list.go:110
 msgid "Columns"
 msgstr "カラムレイアウト"
 
@@ -167,7 +167,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:686 lxc/profile.go:228
+#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:691 lxc/profile.go:228
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
@@ -203,12 +203,12 @@ msgstr "イメージのコピー中: %s"
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
-#: lxc/image.go:324 lxc/info.go:97
+#: lxc/image.go:325 lxc/info.go:97
 #, c-format
 msgid "Created: %s"
 msgstr "作成日時: %s"
 
-#: lxc/init.go:176 lxc/launch.go:111
+#: lxc/init.go:176 lxc/launch.go:116
 #, c-format
 msgid "Creating %s"
 msgstr "%s を作成中"
@@ -217,7 +217,7 @@ msgstr "%s を作成中"
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:606 lxc/image.go:634
+#: lxc/image.go:611 lxc/image.go:639
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -236,7 +236,7 @@ msgstr "デバイス %s が %s から削除されました"
 msgid "Disk usage:"
 msgstr "  ディスク使用量:"
 
-#: lxc/list.go:504
+#: lxc/list.go:491
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -271,16 +271,16 @@ msgstr "Ephemeral コンテナ"
 msgid "Event type to listen for"
 msgstr "Listenするイベントタイプ"
 
-#: lxc/image.go:328
+#: lxc/image.go:329
 #, c-format
 msgid "Expires: %s"
 msgstr "失効日時: %s"
 
-#: lxc/image.go:330
+#: lxc/image.go:331
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/config.go:309 lxc/image.go:604 lxc/image.go:633
+#: lxc/config.go:309 lxc/image.go:609 lxc/image.go:638
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -294,12 +294,12 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:125
+#: lxc/list.go:112
 #, fuzzy
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
-#: lxc/image.go:317
+#: lxc/image.go:318
 #, c-format
 msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
@@ -319,7 +319,7 @@ msgstr "停止したコンテナを強制的に削除します。"
 msgid "Force using the local unix socket"
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
-#: lxc/list.go:124
+#: lxc/list.go:111
 msgid "Format"
 msgstr ""
 
@@ -327,11 +327,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "クライアント証明書を生成します。1分ぐらいかかります..."
 
-#: lxc/list.go:416
+#: lxc/list.go:403
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:417
+#: lxc/list.go:404
 msgid "IPV6"
 msgstr ""
 
@@ -358,12 +358,12 @@ msgstr "コンテナの状態を無視します (startのみ)。"
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:400 lxc/image.go:412
+#: lxc/image.go:405 lxc/image.go:417
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
 
-#: lxc/image.go:397
+#: lxc/image.go:402
 #, fuzzy, c-format
 msgid "Importing the image: %s"
 msgstr "イメージのコピー中: %s"
@@ -404,12 +404,12 @@ msgstr "最初にコピーした後も常にイメージを最新の状態に保
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
-#: lxc/image.go:333
+#: lxc/image.go:334
 #, fuzzy, c-format
 msgid "Last used: %s"
 msgstr "状態: %s"
 
-#: lxc/image.go:335
+#: lxc/image.go:336
 msgid "Last used: never"
 msgstr ""
 
@@ -438,6 +438,16 @@ msgstr "メモリ (ピーク)"
 msgid "Memory usage:"
 msgstr "  メモリ消費量:"
 
+#: lxc/copy.go:191
+#, c-format
+msgid "Migration failed on source host: %s"
+msgstr ""
+
+#: lxc/copy.go:195
+#, c-format
+msgid "Migration failed on target host: %s"
+msgstr ""
+
 #: lxc/utils.go:173
 msgid "Missing summary."
 msgstr "サマリーはありません。"
@@ -452,7 +462,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "コンテナ名を指定する必要があります: "
 
-#: lxc/list.go:420 lxc/remote.go:365
+#: lxc/list.go:407 lxc/remote.go:365
 msgid "NAME"
 msgstr ""
 
@@ -486,7 +496,7 @@ msgstr "フィンガープリントが指定されていません。"
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
-#: lxc/image.go:403
+#: lxc/image.go:408
 msgid "Only https:// is supported for remote image import."
 msgstr "リモートイメージのインポートは https:// のみをサポートします。"
 
@@ -494,7 +504,7 @@ msgstr "リモートイメージのインポートは https:// のみをサポ
 msgid "Options:"
 msgstr "オプション:"
 
-#: lxc/image.go:505
+#: lxc/image.go:510
 #, c-format
 msgid "Output is in %s"
 msgstr "%s に出力されます"
@@ -503,15 +513,15 @@ msgstr "%s に出力されます"
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)"
 
-#: lxc/list.go:506
+#: lxc/list.go:493
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:421
+#: lxc/list.go:408
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:422
+#: lxc/list.go:409
 msgid "PROFILES"
 msgstr ""
 
@@ -519,7 +529,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:605 lxc/remote.go:368
+#: lxc/image.go:610 lxc/remote.go:368
 msgid "PUBLIC"
 msgstr ""
 
@@ -560,7 +570,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:687
+#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:692
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -608,7 +618,7 @@ msgstr "新しいコンテナに適用するプロファイル"
 msgid "Profiles: %s"
 msgstr "プロファイル: %s"
 
-#: lxc/image.go:337
+#: lxc/image.go:338
 msgid "Properties:"
 msgstr "プロパティ:"
 
@@ -616,7 +626,7 @@ msgstr "プロパティ:"
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
-#: lxc/image.go:320
+#: lxc/image.go:321
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -654,15 +664,15 @@ msgstr "コンテナを作成中"
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:608
+#: lxc/image.go:613
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:423
+#: lxc/list.go:410
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:424
+#: lxc/list.go:411
 msgid "STATE"
 msgstr ""
 
@@ -711,7 +721,7 @@ msgstr "コンテナログの最後の 100 行を表示しますか?"
 msgid "Show the expanded configuration"
 msgstr "拡張した設定を表示するかどうか"
 
-#: lxc/image.go:318
+#: lxc/image.go:319
 #, c-format
 msgid "Size: %.2fMB"
 msgstr "サイズ: %.2fMB"
@@ -725,7 +735,7 @@ msgstr "スナップショット:"
 msgid "Some containers failed to %s"
 msgstr "コンテナの停止に失敗しました!"
 
-#: lxc/image.go:347
+#: lxc/image.go:352
 msgid "Source:"
 msgstr "取得元:"
 
@@ -734,7 +744,7 @@ msgstr "取得元:"
 msgid "Start containers."
 msgstr "コンテナの不正なURL %s"
 
-#: lxc/launch.go:118
+#: lxc/launch.go:124
 #, c-format
 msgid "Starting %s"
 msgstr "%s を起動中"
@@ -770,7 +780,7 @@ msgstr "Swap (現在値)"
 msgid "Swap (peak)"
 msgstr "Swap (ピーク)"
 
-#: lxc/list.go:425
+#: lxc/list.go:412
 msgid "TYPE"
 msgstr ""
 
@@ -821,7 +831,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "コンテナを強制停止するまでの時間"
 
-#: lxc/image.go:321
+#: lxc/image.go:322
 msgid "Timestamps:"
 msgstr "タイムスタンプ:"
 
@@ -832,12 +842,12 @@ msgstr ""
 "だ\n"
 "さい。"
 
-#: lxc/image.go:405
+#: lxc/image.go:410
 #, fuzzy, c-format
 msgid "Transferring image: %s"
 msgstr "イメージを転送中: %d%%"
 
-#: lxc/action.go:98 lxc/launch.go:131
+#: lxc/action.go:98 lxc/launch.go:137
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr "更に情報を得るために `lxc info --show-log` を実行してみてください"
@@ -850,7 +860,7 @@ msgstr "タイプ: ephemeral"
 msgid "Type: persistent"
 msgstr "タイプ: persistent"
 
-#: lxc/image.go:609
+#: lxc/image.go:614
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -866,7 +876,7 @@ msgstr ""
 msgid "Unable to read remote TLS certificate"
 msgstr "リモートの TLS 証明書を読めません"
 
-#: lxc/image.go:326
+#: lxc/image.go:327
 #, c-format
 msgid "Uploaded: %s"
 msgstr "アップロード日時: %s"
@@ -1313,25 +1323,10 @@ msgid ""
 "\n"
 "    t - Type (persistent or ephemeral)\n"
 "\n"
-"Custom columns are defined with \"key[:name][:maxWidth]\":\n"
-"\n"
-"    KEY: The (extended) config key to display\n"
-"\n"
-"    NAME: Name to display in the column header.\n"
-"    Defaults to the key if not specified or empty.\n"
-"\n"
-"    MAXWIDTH: Max width of the column (longer results are truncated).\n"
-"    Defaults to -1 (unlimited). Use 0 to limit to the column header size.\n"
-"\n"
 "*Examples*\n"
-"lxc list -c n,volatile.base_image:\"BASE IMAGE\":0,s46,volatile.eth0.hwaddr:"
-"MAC\n"
-"    Shows a list of containers using the \"NAME\", \"BASE IMAGE\", \"STATE"
-"\", \"IPV4\",\n"
-"    \"IPV6\" and \"MAC\" columns.\n"
-"\n"
-"    \"BASE IMAGE\" and \"MAC\" are custom columns generated from container "
-"configuration keys."
+"lxc list -c ns46\n"
+"    Shows a list of containers using the \"NAME\", \"STATE\", \"IPV4\", "
+"\"IPV6\" columns."
 msgstr ""
 "利用可能なリソースを一覧表示します。\n"
 "\n"
@@ -1705,7 +1700,7 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
-#: lxc/launch.go:104
+#: lxc/launch.go:108
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "イメージ、コンテナ、スナップショットのいずれかからスキャンされた数が不正"
@@ -1726,17 +1721,17 @@ msgstr "デフォルトのリモートは削除できません"
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:88 lxc/launch.go:93
+#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:89 lxc/launch.go:95
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
 "した"
 
-#: lxc/image.go:312
+#: lxc/image.go:313
 msgid "disabled"
 msgstr "無効"
 
-#: lxc/image.go:314
+#: lxc/image.go:315
 msgid "enabled"
 msgstr "有効"
 
@@ -1750,11 +1745,11 @@ msgstr "エラー: %v"
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/launch.go:108
+#: lxc/launch.go:113
 msgid "got bad version"
 msgstr "不正なバージョンを得ました"
 
-#: lxc/image.go:307 lxc/image.go:585
+#: lxc/image.go:308 lxc/image.go:590
 msgid "no"
 msgstr ""
 
@@ -1812,7 +1807,7 @@ msgstr "到達しないはずのreturnに到達しました"
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:44 lxc/image.go:309 lxc/image.go:589
+#: lxc/delete.go:44 lxc/image.go:310 lxc/image.go:594
 msgid "yes"
 msgstr ""
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 3bcc559b0..55f573cd9 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-04-17 19:23-0400\n"
+        "POT-Creation-Date: 2017-04-17 19:28-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -422,6 +422,16 @@ msgstr  ""
 msgid   "Memory usage:"
 msgstr  ""
 
+#: lxc/copy.go:191
+#, c-format
+msgid   "Migration failed on source host: %s"
+msgstr  ""
+
+#: lxc/copy.go:195
+#, c-format
+msgid   "Migration failed on target host: %s"
+msgstr  ""
+
 #: lxc/utils.go:173
 msgid   "Missing summary."
 msgstr  ""

From 03ddfa1b9e093ea7b7cbbe18c4862d55f3f06046 Mon Sep 17 00:00:00 2001
From: kurochan <kuro at kurochan.org>
Date: Wed, 22 Mar 2017 20:54:11 +0900
Subject: [PATCH 0798/1193] doc: fix broken table

Signed-off-by: Yuta Kurosaki <kuro at kurochan.org>
---
 doc/production-setup.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/doc/production-setup.md b/doc/production-setup.md
index 0e860d7fd..d961d12c1 100644
--- a/doc/production-setup.md
+++ b/doc/production-setup.md
@@ -22,12 +22,12 @@ using containers that require tens of thousands of file operations.
 
 Domain  | Type  | Item    | Value     | Default   | Description
 :-----  | :---  | :----   | :-------- | :-------- | :----------
-*       | soft  | nofile  | 1048576   | unset     | maximum number of open files
-*       | hard  | nofile  | 1048576   | unset     | maximum number of open files
+\*      | soft  | nofile  | 1048576   | unset     | maximum number of open files
+\*      | hard  | nofile  | 1048576   | unset     | maximum number of open files
 root    | soft  | nofile  | 1048576   | unset     | maximum number of open files
 root    | hard  | nofile  | 1048576   | unset     | maximum number of open files
-*       | soft  | memlock | unlimited | unset     | maximum locked-in-memory address space (KB)
-*       | hard  | memlock | unlimited | unset     | maximum locked-in-memory address space (KB)
+\*      | soft  | memlock | unlimited | unset     | maximum locked-in-memory address space (KB)
+\*      | hard  | memlock | unlimited | unset     | maximum locked-in-memory address space (KB)
 
 
 ## /etc/sysctl.conf

From d72ad4a5c928659aead1359281d33db8c933ad70 Mon Sep 17 00:00:00 2001
From: Lukasz Matczak <lukasz.matczak at cba.pl>
Date: Thu, 23 Mar 2017 11:48:51 +0100
Subject: [PATCH 0799/1193] Added soft memory limit even when hard is selected

Signed-off-by: Lukasz Matczak <lukasz.matczak at cba.pl>
---
 lxd/container_lxc.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 8ccc0fe74..d22dd80c7 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3001,6 +3001,12 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 							return err
 						}
 					}
+					// Set soft limit to value 10% less than hard limit
+					err = c.CGroupSet("memory.soft_limit_in_bytes", memory*0.9)
+					if err != nil {
+						revertMemory()
+						return err
+					}
 				}
 
 				// Configure the swappiness

From 8da570d77934af88b95629ce0cd7530b786e6a24 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 24 Mar 2017 01:46:59 +0100
Subject: [PATCH 0800/1193] build: add debug logging

This allows to build LXD with debug logging enabled which prints the file name,
line number, and function name in all log output. This is mostly meant for
development purposes.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 Makefile            |   6 +++
 shared/log.go       |   2 +
 shared/log_debug.go | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 132 insertions(+)
 create mode 100644 shared/log_debug.go

diff --git a/Makefile b/Makefile
index 25d894ef0..05e5f0ba1 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,12 @@ update:
 	go get -t -v -d -u ./...
 	@echo "Dependencies updated"
 
+.PHONY: debug
+debug:
+	go get -t -v -d ./...
+	go install -v $(TAGS) -tags logdebug $(DEBUG) ./...
+	@echo "LXD built successfully"
+
 # This only needs to be done when migrate.proto is actually changed; since we
 # commit the .pb.go in the tree and it's not expected to change very often,
 # it's not a default build step.
diff --git a/shared/log.go b/shared/log.go
index f696febff..ec464e06b 100644
--- a/shared/log.go
+++ b/shared/log.go
@@ -1,3 +1,5 @@
+// +build !logdebug
+
 package shared
 
 import (
diff --git a/shared/log_debug.go b/shared/log_debug.go
new file mode 100644
index 000000000..fdded57f0
--- /dev/null
+++ b/shared/log_debug.go
@@ -0,0 +1,124 @@
+// +build logdebug
+
+package shared
+
+import (
+	"fmt"
+	"runtime"
+)
+
+type Logger interface {
+	Debug(msg string, ctx ...interface{})
+	Info(msg string, ctx ...interface{})
+	Warn(msg string, ctx ...interface{})
+	Error(msg string, ctx ...interface{})
+	Crit(msg string, ctx ...interface{})
+}
+
+var Log Logger
+
+type nullLogger struct{}
+
+func (nl nullLogger) Debug(msg string, ctx ...interface{}) {}
+func (nl nullLogger) Info(msg string, ctx ...interface{})  {}
+func (nl nullLogger) Warn(msg string, ctx ...interface{})  {}
+func (nl nullLogger) Error(msg string, ctx ...interface{}) {}
+func (nl nullLogger) Crit(msg string, ctx ...interface{})  {}
+
+func init() {
+	Log = nullLogger{}
+}
+
+// General wrappers around Logger interface functions.
+func LogDebug(msg string, ctx interface{}) {
+	if Log != nil {
+		pc, fn, line, _ := runtime.Caller(1)
+		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Debug(msg, ctx)
+	}
+}
+
+func LogInfo(msg string, ctx interface{}) {
+	if Log != nil {
+		pc, fn, line, _ := runtime.Caller(1)
+		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Info(msg, ctx)
+	}
+}
+
+func LogWarn(msg string, ctx interface{}) {
+	if Log != nil {
+		pc, fn, line, _ := runtime.Caller(1)
+		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Warn(msg, ctx)
+	}
+}
+
+func LogError(msg string, ctx interface{}) {
+	if Log != nil {
+		pc, fn, line, _ := runtime.Caller(1)
+		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Error(msg, ctx)
+	}
+}
+
+func LogCrit(msg string, ctx interface{}) {
+	if Log != nil {
+		pc, fn, line, _ := runtime.Caller(1)
+		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Crit(msg, ctx)
+	}
+}
+
+// Wrappers around Logger interface functions that send a string to the Logger
+// by running it through fmt.Sprintf().
+func LogInfof(format string, args ...interface{}) {
+	if Log != nil {
+		msg := fmt.Sprintf(format, args...)
+		pc, fn, line, _ := runtime.Caller(1)
+		msg = fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Info(msg)
+	}
+}
+
+func LogDebugf(format string, args ...interface{}) {
+	if Log != nil {
+		msg := fmt.Sprintf(format, args...)
+		pc, fn, line, _ := runtime.Caller(1)
+		msg = fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Debug(msg)
+	}
+}
+
+func LogWarnf(format string, args ...interface{}) {
+	if Log != nil {
+		msg := fmt.Sprintf(format, args...)
+		pc, fn, line, _ := runtime.Caller(1)
+		msg = fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Warn(msg)
+	}
+}
+
+func LogErrorf(format string, args ...interface{}) {
+	if Log != nil {
+		msg := fmt.Sprintf(format, args...)
+		pc, fn, line, _ := runtime.Caller(1)
+		msg = fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Error(msg)
+	}
+}
+
+func LogCritf(format string, args ...interface{}) {
+	if Log != nil {
+		msg := fmt.Sprintf(format, args...)
+		pc, fn, line, _ := runtime.Caller(1)
+		msg = fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
+		Log.Crit(msg)
+	}
+}
+
+func PrintStack() {
+	buf := make([]byte, 1<<16)
+	runtime.Stack(buf, true)
+	LogErrorf("%s", buf)
+}

From a153fb50cf8df89e0cdb0b51fa59ffe7095eb090 Mon Sep 17 00:00:00 2001
From: Lukasz Matczak <lukasz.matczak at cba.pl>
Date: Fri, 24 Mar 2017 08:41:18 +0100
Subject: [PATCH 0801/1193] Added soft limit in initLXD()

Signed-off-by: Lukasz Matczak <lukasz.matczak at cba.pl>
---
 lxd/container_lxc.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d22dd80c7..bb8b62b1f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1017,6 +1017,11 @@ func (c *containerLXC) initLXC() error {
 						return err
 					}
 				}
+				// Set soft limit to value 10% less than hard limit
+				err = lxcSetConfigItem(cc, "lxc.cgroup.memory.soft_limit_in_bytes", fmt.Sprintf("%d", valueInt*0.9))
+				if err != nil {
+					return err
+				}
 			}
 		}
 

From 734d6a82536feba503425e26040111c378fa0fa7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 7 Mar 2017 19:59:47 -0500
Subject: [PATCH 0802/1193] Don't use FindProcess, just pass exec.Cmd
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3037

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go      |  2 +-
 lxd/container_exec.go | 24 +++++++++---------------
 lxd/container_lxc.go  | 22 ++++++++++++----------
 3 files changed, 22 insertions(+), 26 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index fa5250d36..6e2e54f3c 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -512,7 +512,7 @@ type container interface {
 	         *      (the PID returned in the first return argument). It can however
 	         *      be used to e.g. forward signals.)
 	*/
-	Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, wait bool) (int, int, error)
+	Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, wait bool) (*exec.Cmd, int, int, error)
 
 	// Status
 	Render() (interface{}, error)
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index ed0d9fbe6..d4f2635e7 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -6,6 +6,7 @@ import (
 	"io/ioutil"
 	"net/http"
 	"os"
+	"os/exec"
 	"strconv"
 	"strings"
 	"sync"
@@ -284,7 +285,7 @@ func (s *execWs) Do(op *operation) error {
 		return cmdErr
 	}
 
-	pid, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false)
+	cmd, _, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false)
 	if err != nil {
 		return err
 	}
@@ -293,23 +294,15 @@ func (s *execWs) Do(op *operation) error {
 		attachedChildIsBorn <- attachedPid
 	}
 
-	proc, err := os.FindProcess(pid)
-	if err != nil {
-		return finisher(-1, fmt.Errorf("Failed finding process: %q", err))
-	}
-
-	procState, err := proc.Wait()
-	if err != nil {
-		return finisher(-1, fmt.Errorf("Failed waiting on process %d: %q", pid, err))
-	}
-
-	if procState.Success() {
+	err = cmd.Wait()
+	if err == nil {
 		return finisher(0, nil)
 	}
 
-	status, ok := procState.Sys().(syscall.WaitStatus)
+	exitErr, ok := err.(*exec.ExitError)
 	if ok {
-		if status.Exited() {
+		status, ok := exitErr.Sys().(syscall.WaitStatus)
+		if ok {
 			return finisher(status.ExitStatus(), nil)
 		}
 
@@ -437,8 +430,9 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	}
 
 	run := func(op *operation) error {
-		cmdResult, _, cmdErr := c.Exec(post.Command, env, nil, nil, nil, true)
+		_, cmdResult, _, cmdErr := c.Exec(post.Command, env, nil, nil, nil, true)
 		metadata := shared.Jmap{"return": cmdResult}
+
 		err = op.UpdateMetadata(metadata)
 		if err != nil {
 			shared.LogError("error updating metadata for cmd", log.Ctx{"err": err, "cmd": post.Command})
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index bb8b62b1f..1256958e3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4105,7 +4105,7 @@ func (c *containerLXC) FileRemove(path string) error {
 	return nil
 }
 
-func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, wait bool) (int, int, error) {
+func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.File, stdout *os.File, stderr *os.File, wait bool) (*exec.Cmd, int, int, error) {
 	envSlice := []string{}
 
 	for k, v := range env {
@@ -4133,25 +4133,26 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	defer r.Close()
 	if err != nil {
 		shared.LogErrorf("s", err)
-		return -1, -1, err
+		return nil, -1, -1, err
 	}
 
 	cmd.ExtraFiles = []*os.File{w}
 	err = cmd.Start()
 	if err != nil {
 		w.Close()
-		return -1, -1, err
+		return nil, -1, -1, err
 	}
 	w.Close()
+
 	attachedPid := -1
 	if err := json.NewDecoder(r).Decode(&attachedPid); err != nil {
 		shared.LogErrorf("Failed to retrieve PID of executing child process: %s", err)
-		return -1, -1, err
+		return nil, -1, -1, err
 	}
 
 	// It's the callers responsibility to wait or not wait.
 	if !wait {
-		return cmd.Process.Pid, attachedPid, nil
+		return &cmd, -1, attachedPid, nil
 	}
 
 	err = cmd.Wait()
@@ -4160,18 +4161,19 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 		if ok {
 			status, ok := exitErr.Sys().(syscall.WaitStatus)
 			if ok {
-				return status.ExitStatus(), attachedPid, nil
+				return nil, status.ExitStatus(), attachedPid, nil
 			}
 
 			if status.Signaled() {
-				// COMMENT(brauner): 128 + n == Fatal error signal "n"
-				return 128 + int(status.Signal()), attachedPid, nil
+				// 128 + n == Fatal error signal "n"
+				return nil, 128 + int(status.Signal()), attachedPid, nil
 			}
 		}
-		return -1, -1, err
+
+		return nil, -1, -1, err
 	}
 
-	return 0, attachedPid, nil
+	return nil, 0, attachedPid, nil
 }
 
 func (c *containerLXC) diskState() map[string]api.ContainerStateDisk {

From 2a4dcb5ead5720e36c71326f51817218eea8663c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 24 Mar 2017 15:54:30 -0400
Subject: [PATCH 0803/1193] Fix soft limit logic to use float64
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 1256958e3..de6fa2b27 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1018,7 +1018,7 @@ func (c *containerLXC) initLXC() error {
 					}
 				}
 				// Set soft limit to value 10% less than hard limit
-				err = lxcSetConfigItem(cc, "lxc.cgroup.memory.soft_limit_in_bytes", fmt.Sprintf("%d", valueInt*0.9))
+				err = lxcSetConfigItem(cc, "lxc.cgroup.memory.soft_limit_in_bytes", fmt.Sprintf("%.0f", float64(valueInt)*0.9))
 				if err != nil {
 					return err
 				}
@@ -3006,8 +3006,15 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 							return err
 						}
 					}
+
 					// Set soft limit to value 10% less than hard limit
-					err = c.CGroupSet("memory.soft_limit_in_bytes", memory*0.9)
+					valueInt, err := strconv.ParseInt(memory, 10, 64)
+					if err != nil {
+						revertMemory()
+						return err
+					}
+
+					err = c.CGroupSet("memory.soft_limit_in_bytes", fmt.Sprintf("%.0f", float64(valueInt)*0.9))
 					if err != nil {
 						revertMemory()
 						return err

From a89fb0e5e59fee47e39ec9939bc3ae75f067997f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 24 Mar 2017 15:41:29 -0400
Subject: [PATCH 0804/1193] tests: Record how long the tests take
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/test/main.sh b/test/main.sh
index ec202394d..ba4391656 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -442,8 +442,11 @@ run_test() {
   TEST_CURRENT_DESCRIPTION=${2:-${1}}
 
   echo "==> TEST BEGIN: ${TEST_CURRENT_DESCRIPTION}"
+  START_TIME=$(date +%s)
   ${TEST_CURRENT}
-  echo "==> TEST DONE: ${TEST_CURRENT_DESCRIPTION}"
+  END_TIME=$(date +%s)
+
+  echo "==> TEST DONE: ${TEST_CURRENT_DESCRIPTION} ($((END_TIME-START_TIME))s)"
 }
 
 # allow for running a specific set of tests

From fdb70e3cf2fde81138ee0465317cdb7cc7a2ae20 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 28 Mar 2017 17:04:44 -0400
Subject: [PATCH 0805/1193] operations: Remove useless for loops
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/operations.go | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/lxd/operations.go b/lxd/operations.go
index f285d051a..517652b1c 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -283,23 +283,19 @@ func (op *operation) WaitFinal(timeout int) (bool, error) {
 
 	// Wait indefinitely
 	if timeout == -1 {
-		for {
-			<-op.chanDone
-			return true, nil
-		}
+		<-op.chanDone
+		return true, nil
 	}
 
 	// Wait until timeout
 	if timeout > 0 {
 		timer := time.NewTimer(time.Duration(timeout) * time.Second)
-		for {
-			select {
-			case <-op.chanDone:
-				return false, nil
+		select {
+		case <-op.chanDone:
+			return false, nil
 
-			case <-timer.C:
-				return false, nil
-			}
+		case <-timer.C:
+			return false, nil
 		}
 	}
 

From 5cb6854c14fe3590f70de17b65b8a359ccd343bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 22:50:19 -0400
Subject: [PATCH 0806/1193] api: Add the Stateful field to ContainerPut
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go    | 2 +-
 shared/api/container.go | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index de6fa2b27..6225928c8 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2228,7 +2228,6 @@ func (c *containerLXC) Render() (interface{}, error) {
 			Name:            c.name,
 			Status:          statusCode.String(),
 			StatusCode:      statusCode,
-			Stateful:        c.stateful,
 		}
 
 		ct.Architecture = architectureName
@@ -2237,6 +2236,7 @@ func (c *containerLXC) Render() (interface{}, error) {
 		ct.Devices = c.localDevices
 		ct.Ephemeral = c.ephemeral
 		ct.Profiles = c.profiles
+		ct.Stateful = c.stateful
 
 		return &ct, nil
 	}
diff --git a/shared/api/container.go b/shared/api/container.go
index 64812b4e0..5fd43dd7f 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -25,7 +25,10 @@ type ContainerPut struct {
 	Devices      map[string]map[string]string `json:"devices" yaml:"devices"`
 	Ephemeral    bool                         `json:"ephemeral" yaml:"ephemeral"`
 	Profiles     []string                     `json:"profiles" yaml:"profiles"`
-	Restore      string                       `json:"restore,omitempty" yaml:"restore,omitempty"`
+
+	// For snapshot restore
+	Restore  string `json:"restore,omitempty" yaml:"restore,omitempty"`
+	Stateful bool   `json:"stateful" yaml:"stateful"`
 }
 
 // Container represents a LXD container
@@ -36,7 +39,6 @@ type Container struct {
 	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"`
-	Stateful        bool                         `json:"stateful" yaml:"stateful"`
 	Status          string                       `json:"status" yaml:"status"`
 	StatusCode      StatusCode                   `json:"status_code" yaml:"status_code"`
 }

From 5e9d6f17a002288a75278c00b87900c5a2792dd8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 5 Apr 2017 12:24:33 -0400
Subject: [PATCH 0807/1193] Enable stacking for privileged containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go      | 10 ++--------
 lxd/container_lxc.go |  4 ++--
 2 files changed, 4 insertions(+), 10 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index 89881a89d..b11be5b0d 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -320,12 +320,7 @@ func getAAProfileContent(c container) string {
 
 	if aaStacking {
 		profile += "\n  ### Feature: apparmor stacking\n"
-
-		if c.IsPrivileged() {
-			profile += "\n  ### Configuration: apparmor loading disabled in privileged containers\n"
-			profile += "  deny /sys/k*{,/**} rwklx,\n"
-		} else {
-			profile += `  ### Configuration: apparmor loading in unprivileged containers
+		profile += `  ### Configuration: apparmor profile loading (in namespace)
   deny /sys/k[^e]*{,/**} wklx,
   deny /sys/ke[^r]*{,/**} wklx,
   deny /sys/ker[^n]*{,/**} wklx,
@@ -351,8 +346,7 @@ func getAAProfileContent(c container) string {
   deny /sys/kernel/security?*{,/**} wklx,
   deny /sys/kernel?*{,/**} wklx,
 `
-			profile += fmt.Sprintf("  change_profile -> \":%s://*\",\n", AANamespace(c))
-		}
+		profile += fmt.Sprintf("  change_profile -> \":%s://*\",\n", AANamespace(c))
 	} else {
 		profile += "\n  ### Feature: apparmor stacking (not present)\n"
 		profile += "  deny /sys/k*{,/**} rwklx,\n"
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 6225928c8..00bcc31f0 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -705,7 +705,7 @@ func (c *containerLXC) initLXC() error {
 
 	// Base config
 	toDrop := "sys_time sys_module sys_rawio"
-	if !aaStacking || c.IsPrivileged() {
+	if !aaStacking {
 		toDrop = toDrop + " mac_admin mac_override"
 	}
 
@@ -924,7 +924,7 @@ func (c *containerLXC) initLXC() error {
 			 * the old way of nesting, i.e. using the parent's
 			 * profile.
 			 */
-			if aaStacking && (!c.IsNesting() || !c.IsPrivileged()) {
+			if aaStacking {
 				profile = fmt.Sprintf("%s//&:%s:", profile, AANamespace(c))
 			}
 

From 72202338c15833ebcafcd7aa5b4961a8479dee4c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Apr 2017 22:00:21 -0400
Subject: [PATCH 0808/1193] shared/i18n: Simplify and make golint clean
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/i18n/i18n.go            | 5 +----
 shared/i18n/i18n_linux.go      | 9 ++-------
 test/suites/static_analysis.sh | 1 +
 3 files changed, 4 insertions(+), 11 deletions(-)

diff --git a/shared/i18n/i18n.go b/shared/i18n/i18n.go
index 54efaa244..9a43f14b7 100644
--- a/shared/i18n/i18n.go
+++ b/shared/i18n/i18n.go
@@ -2,10 +2,7 @@
 
 package i18n
 
+// G returns the translated string
 func G(msgid string) string {
 	return msgid
 }
-
-func NG(msgid string, msgidPlural string, n uint64) string {
-	return msgid
-}
diff --git a/shared/i18n/i18n_linux.go b/shared/i18n/i18n_linux.go
index 80fc0b5ff..11399daee 100644
--- a/shared/i18n/i18n_linux.go
+++ b/shared/i18n/i18n_linux.go
@@ -6,14 +6,9 @@ import (
 	"github.com/gosexy/gettext"
 )
 
-var TEXTDOMAIN = "lxd"
-
+// G returns the translated string
 func G(msgid string) string {
-	return gettext.DGettext(TEXTDOMAIN, msgid)
-}
-
-func NG(msgid string, msgidPlural string, n uint64) string {
-	return gettext.DNGettext(TEXTDOMAIN, msgid, msgidPlural, n)
+	return gettext.DGettext("lxd", msgid)
 }
 
 func init() {
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index d4b5bdf5a..18e68f383 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -37,6 +37,7 @@ test_static_analysis() {
     ## golint
     if which golint >/dev/null 2>&1; then
       golint -set_exit_status shared/api/
+      golint -set_exit_status shared/i18n/
     fi
 
     ## deadcode

From ccfed1e969bf45df9daba93c8b32fb9acc47f7d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Apr 2017 22:04:17 -0400
Subject: [PATCH 0809/1193] shared/gnuflag: Fix golint
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/gnuflag/flag.go         | 47 +++++++++++++++++++++++-------------------
 test/suites/static_analysis.sh |  1 +
 2 files changed, 27 insertions(+), 21 deletions(-)

diff --git a/shared/gnuflag/flag.go b/shared/gnuflag/flag.go
index 76e04b6df..94b7b84ad 100644
--- a/shared/gnuflag/flag.go
+++ b/shared/gnuflag/flag.go
@@ -3,27 +3,27 @@
 // license that can be found in the LICENSE file.
 
 /*
-	Package flag implements command-line flag parsing in the GNU style.
-	It is almost exactly the same as the standard flag package,
-	the only difference being the extra argument to Parse.
-
-	Command line flag syntax:
-		-f		// single letter flag
-		-fg		// two single letter flags together
-		--flag	// multiple letter flag
-		--flag x  // non-boolean flags only
-		-f x		// non-boolean flags only
-		-fx		// if f is a non-boolean flag, x is its argument.
-
-	The last three forms are not permitted for boolean flags because the
-	meaning of the command
-		cmd -f *
-	will change if there is a file called 0, false, etc.  There is currently
-	no way to turn off a boolean flag.
-
-	Flag parsing stops after the terminator "--", or just before the first
-	non-flag argument ("-" is a non-flag argument) if the interspersed
-	argument to Parse is false.
+Package gnuflag implements command-line flag parsing in the GNU style.
+It is almost exactly the same as the standard flag package,
+the only difference being the extra argument to Parse.
+
+Command line flag syntax:
+	-f		// single letter flag
+	-fg		// two single letter flags together
+	--flag	// multiple letter flag
+	--flag x  // non-boolean flags only
+	-f x		// non-boolean flags only
+	-fx		// if f is a non-boolean flag, x is its argument.
+
+The last three forms are not permitted for boolean flags because the
+meaning of the command
+	cmd -f *
+will change if there is a file called 0, false, etc.  There is currently
+no way to turn off a boolean flag.
+
+Flag parsing stops after the terminator "--", or just before the first
+non-flag argument ("-" is a non-flag argument) if the interspersed
+argument to Parse is false.
 */
 package gnuflag
 
@@ -181,8 +181,13 @@ type Value interface {
 type ErrorHandling int
 
 const (
+	// ContinueOnError will cause gnuflag to just return the error
 	ContinueOnError ErrorHandling = iota
+
+	// ExitOnError will cause gnuflag to exit with return code 2
 	ExitOnError
+
+	// PanicOnError will cause gnuflag to to call panic()
 	PanicOnError
 )
 
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 18e68f383..43a720eac 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -37,6 +37,7 @@ test_static_analysis() {
     ## golint
     if which golint >/dev/null 2>&1; then
       golint -set_exit_status shared/api/
+      golint -set_exit_status shared/gnuflag/
       golint -set_exit_status shared/i18n/
     fi
 

From 37b45e9678ec1963331a712f62b0d7f5ade90671 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Apr 2017 22:18:25 -0400
Subject: [PATCH 0810/1193] shared/ioprogress: Simplify and make golint clean
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/ioprogress/reader.go    | 4 +++-
 shared/ioprogress/tracker.go   | 3 ++-
 shared/ioprogress/writer.go    | 4 +++-
 test/suites/static_analysis.sh | 1 +
 4 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/shared/ioprogress/reader.go b/shared/ioprogress/reader.go
index 262aa40a0..299cb6b29 100644
--- a/shared/ioprogress/reader.go
+++ b/shared/ioprogress/reader.go
@@ -4,11 +4,13 @@ import (
 	"io"
 )
 
+// ProgressReader is a wrapper around ReadCloser which allows for progress tracking
 type ProgressReader struct {
 	io.ReadCloser
 	Tracker *ProgressTracker
 }
 
+// Read in ProgressReader is the same as io.Read
 func (pt *ProgressReader) Read(p []byte) (int, error) {
 	// Do normal reader tasks
 	n, err := pt.ReadCloser.Read(p)
@@ -16,7 +18,7 @@ func (pt *ProgressReader) Read(p []byte) (int, error) {
 	// Do the actual progress tracking
 	if pt.Tracker != nil {
 		pt.Tracker.total += int64(n)
-		pt.Tracker.Update(n)
+		pt.Tracker.update(n)
 	}
 
 	return n, err
diff --git a/shared/ioprogress/tracker.go b/shared/ioprogress/tracker.go
index 78c730b2d..ad790890f 100644
--- a/shared/ioprogress/tracker.go
+++ b/shared/ioprogress/tracker.go
@@ -4,6 +4,7 @@ import (
 	"time"
 )
 
+// ProgressTracker provides the stream information needed for tracking
 type ProgressTracker struct {
 	Length  int64
 	Handler func(int64, int64)
@@ -14,7 +15,7 @@ type ProgressTracker struct {
 	last       *time.Time
 }
 
-func (pt *ProgressTracker) Update(n int) {
+func (pt *ProgressTracker) update(n int) {
 	// Skip the rest if no handler attached
 	if pt.Handler == nil {
 		return
diff --git a/shared/ioprogress/writer.go b/shared/ioprogress/writer.go
index 708911b28..f45b45e8b 100644
--- a/shared/ioprogress/writer.go
+++ b/shared/ioprogress/writer.go
@@ -4,11 +4,13 @@ import (
 	"io"
 )
 
+// ProgressWriter is a wrapper around WriteCloser which allows for progress tracking
 type ProgressWriter struct {
 	io.WriteCloser
 	Tracker *ProgressTracker
 }
 
+// Write in ProgressWriter is the same as io.Write
 func (pt *ProgressWriter) Write(p []byte) (int, error) {
 	// Do normal writer tasks
 	n, err := pt.WriteCloser.Write(p)
@@ -16,7 +18,7 @@ func (pt *ProgressWriter) Write(p []byte) (int, error) {
 	// Do the actual progress tracking
 	if pt.Tracker != nil {
 		pt.Tracker.total += int64(n)
-		pt.Tracker.Update(n)
+		pt.Tracker.update(n)
 	}
 
 	return n, err
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 43a720eac..bd61d8430 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -39,6 +39,7 @@ test_static_analysis() {
       golint -set_exit_status shared/api/
       golint -set_exit_status shared/gnuflag/
       golint -set_exit_status shared/i18n/
+      golint -set_exit_status shared/ioprogress/
     fi
 
     ## deadcode

From b70d9d3ed8104e46b96afd6aa6388126d8057026 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Apr 2017 22:29:06 -0400
Subject: [PATCH 0811/1193] shared/version: Make golint clean
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/version/flex.go         | 12 ++++--------
 test/suites/static_analysis.sh |  1 +
 2 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/shared/version/flex.go b/shared/version/flex.go
index 03c8fa76c..cf650cc63 100644
--- a/shared/version/flex.go
+++ b/shared/version/flex.go
@@ -1,14 +1,10 @@
-/* This is a FLEXible file which can be used by both client and daemon.
- * Teehee.
- */
 package version
 
+// Version contains the LXD version number
 var Version = "2.0.9"
+
+// UserAgent contains a string suitable as a user-agent
 var UserAgent = "LXD " + Version
 
-/*
- * Please increment the api compat number every time you change the API.
- *
- * Version 1.0: ping
- */
+// APIVersion contains the API base version. Only bumped for backward incompatible changes.
 var APIVersion = "1.0"
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index bd61d8430..a26492dac 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -40,6 +40,7 @@ test_static_analysis() {
       golint -set_exit_status shared/gnuflag/
       golint -set_exit_status shared/i18n/
       golint -set_exit_status shared/ioprogress/
+      golint -set_exit_status shared/version/
     fi
 
     ## deadcode

From 05f584b00d7e2fb2533bc565d86fa67ddbbeb860 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 9 Apr 2017 22:55:30 -0400
Subject: [PATCH 0812/1193] db: Deal with the case where no updates exist
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This can't ever happen for LXD because we're past this point now but
still worth fixing for people copy/pasting from our code :)

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/db.go b/lxd/db.go
index 944926b27..c65e51ba7 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -221,6 +221,10 @@ func dbGetSchema(db *sql.DB) (v int) {
 }
 
 func dbGetLatestSchema() int {
+	if len(dbUpdates) == 0 {
+		return 0
+	}
+
 	return dbUpdates[len(dbUpdates)-1].version
 }
 

From d3afc82b3cb37c32bb63d3cd9b7a87a25ca92461 Mon Sep 17 00:00:00 2001
From: Ken Tossell <ken at tossell.net>
Date: Wed, 12 Apr 2017 16:07:07 +0000
Subject: [PATCH 0813/1193] Fix handling of devices with minor>255

Device minor numbers are 12-bit, and the kernel expects
combined major-minor device IDs to be passed as a 32-bit
integer with the following components, from MSB to LSB:

 * 12 zero bits
 * the 4 upper bits of the minor number
 * all 8 bits of the major number
 * the 8 lower bits of the minor number

Currently LXD writes (major | minor >> 8) in the bits where
the kernel expects to find the major number.

Signed-off-by: Ken Tossell <ken at tossell.net>
---
 lxd/container_lxc.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 00bcc31f0..ee3d7e0e9 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4626,7 +4626,8 @@ func (c *containerLXC) createUnixDevice(m types.Device) (string, error) {
 
 	// Create the new entry
 	if !runningInUserns {
-		if err := syscall.Mknod(devPath, uint32(mode), minor|(major<<8)); err != nil {
+		encoded_device_number := (minor & 0xff) | (major << 8) | ((minor & ^0xff) << 12)
+		if err := syscall.Mknod(devPath, uint32(mode), encoded_device_number); err != nil {
 			return "", fmt.Errorf("Failed to create device %s for %s: %s", devPath, m["path"], err)
 		}
 

From 5fa8c0ac211f32427ab296f0ca49093c0555ca78 Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Sat, 15 Apr 2017 11:45:52 +0300
Subject: [PATCH 0814/1193] Document list format options

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 lxc/list.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/list.go b/lxc/list.go
index 80a7d340c..68e925957 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -108,7 +108,7 @@ lxc list -c ns46
 func (c *listCmd) flags() {
 	gnuflag.StringVar(&c.chosenColumnRunes, "c", "ns46tS", i18n.G("Columns"))
 	gnuflag.StringVar(&c.chosenColumnRunes, "columns", "ns46tS", i18n.G("Columns"))
-	gnuflag.StringVar(&c.format, "format", "table", i18n.G("Format"))
+	gnuflag.StringVar(&c.format, "format", "table", i18n.G("Format (table|json)"))
 	gnuflag.BoolVar(&c.fast, "fast", false, i18n.G("Fast mode (same as --columns=nsacPt)"))
 }
 

From 3ba2e059feb69fe281ec8bf247c7ea944769b3e2 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sat, 15 Apr 2017 11:45:58 +0200
Subject: [PATCH 0815/1193] make i18n

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 po/de.po   | 4 ++--
 po/fr.po   | 4 ++--
 po/ja.po   | 4 ++--
 po/lxd.pot | 4 ++--
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/po/de.po b/po/de.po
index 254d6a663..254810567 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-17 19:28-0400\n"
+"POT-Creation-Date: 2017-04-18 01:16-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -362,7 +362,7 @@ msgid "Force using the local unix socket"
 msgstr ""
 
 #: lxc/list.go:111
-msgid "Format"
+msgid "Format (table|json)"
 msgstr ""
 
 #: lxc/main.go:131
diff --git a/po/fr.po b/po/fr.po
index d674fc1a3..bd4574336 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-17 19:28-0400\n"
+"POT-Creation-Date: 2017-04-18 01:16-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -316,7 +316,7 @@ msgid "Force using the local unix socket"
 msgstr ""
 
 #: lxc/list.go:111
-msgid "Format"
+msgid "Format (table|json)"
 msgstr ""
 
 #: lxc/main.go:131
diff --git a/po/ja.po b/po/ja.po
index b8c1b5a4a..4565b3c61 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-17 19:28-0400\n"
+"POT-Creation-Date: 2017-04-18 01:16-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -320,7 +320,7 @@ msgid "Force using the local unix socket"
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
 #: lxc/list.go:111
-msgid "Format"
+msgid "Format (table|json)"
 msgstr ""
 
 #: lxc/main.go:131
diff --git a/po/lxd.pot b/po/lxd.pot
index 55f573cd9..34d597054 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-04-17 19:28-0400\n"
+        "POT-Creation-Date: 2017-04-18 01:16-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -309,7 +309,7 @@ msgid   "Force using the local unix socket"
 msgstr  ""
 
 #: lxc/list.go:111
-msgid   "Format"
+msgid   "Format (table|json)"
 msgstr  ""
 
 #: lxc/main.go:131

From aee9cc095daf48017913238eece043608192c56e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 18 Apr 2017 02:04:32 -0400
Subject: [PATCH 0816/1193] Properly invalidate the idmap cache
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Without this we'd run the risk of changing idmap values and then doing
an action like a publish with an incorect map.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ee3d7e0e9..e6eba3a09 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -303,6 +303,9 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		return nil, err
 	}
 
+	// Invalid idmap cache
+	c.idmapset = nil
+
 	// Set last_state to the map we have on disk
 	if c.localConfig["volatile.last_state.idmap"] == "" {
 		err = c.ConfigKeySet("volatile.last_state.idmap", jsonIdmap)
@@ -2795,6 +2798,9 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 		c.localConfig["volatile.idmap.next"] = jsonIdmap
 		c.localConfig["volatile.idmap.base"] = fmt.Sprintf("%v", base)
+
+		// Invalid idmap cache
+		c.idmapset = nil
 	}
 
 	// Apply disk quota changes

From a5089d6239e32f3eed469fd4c5a0357c4216fd06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 01:34:21 -0500
Subject: [PATCH 0817/1193] images: Refactor code a bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go |   6 +-
 lxd/images.go        | 214 ++++++++++++++++++++++-----------------------------
 2 files changed, 94 insertions(+), 126 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 173e4dfa0..0899139bf 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -327,7 +327,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		info.Public = false
 		info.AutoUpdate = autoUpdate
 
-		_, err = imageBuildFromInfo(d, *info)
+		// Create the database entry
+		err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
 			return "", err
 		}
@@ -495,7 +496,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		info.AutoUpdate = autoUpdate
 	}
 
-	_, err = imageBuildFromInfo(d, info)
+	// Create the database entry
+	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
 		shared.LogError(
 			"Failed to create image",
diff --git a/lxd/images.go b/lxd/images.go
index 3e936a136..5c2b4c37b 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -221,27 +221,26 @@ type imageMetadata struct {
  * This function takes a container or snapshot from the local image server and
  * exports it as an image.
  */
-func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
-	builddir string) (info api.Image, err error) {
-
+func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, builddir string) (*api.Image, error) {
+	info := api.Image{}
 	info.Properties = map[string]string{}
 	name := req.Source["name"]
 	ctype := req.Source["type"]
 	if ctype == "" || name == "" {
-		return info, fmt.Errorf("No source provided")
+		return nil, fmt.Errorf("No source provided")
 	}
 
 	switch ctype {
 	case "snapshot":
 		if !shared.IsSnapshot(name) {
-			return info, fmt.Errorf("Not a snapshot")
+			return nil, fmt.Errorf("Not a snapshot")
 		}
 	case "container":
 		if shared.IsSnapshot(name) {
-			return info, fmt.Errorf("This is a snapshot")
+			return nil, fmt.Errorf("This is a snapshot")
 		}
 	default:
-		return info, fmt.Errorf("Bad type")
+		return nil, fmt.Errorf("Bad type")
 	}
 
 	info.Filename = req.Filename
@@ -254,19 +253,19 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
 
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return info, err
+		return nil, err
 	}
 
 	// Build the actual image file
 	tarfile, err := ioutil.TempFile(builddir, "lxd_build_tar_")
 	if err != nil {
-		return info, err
+		return nil, err
 	}
 	defer os.Remove(tarfile.Name())
 
 	if err := c.Export(tarfile, req.Properties); err != nil {
 		tarfile.Close()
-		return info, err
+		return nil, err
 	}
 	tarfile.Close()
 
@@ -275,7 +274,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
 	if compress != "none" {
 		compressedPath, err = compressFile(tarfile.Name(), compress)
 		if err != nil {
-			return info, err
+			return nil, err
 		}
 	} else {
 		compressedPath = tarfile.Name()
@@ -285,34 +284,42 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost,
 	sha256 := sha256.New()
 	tarf, err := os.Open(compressedPath)
 	if err != nil {
-		return info, err
+		return nil, err
 	}
+
 	info.Size, err = io.Copy(sha256, tarf)
 	tarf.Close()
 	if err != nil {
-		return info, err
+		return nil, err
 	}
+
 	info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil))
 
 	_, _, err = dbImageGet(d.db, info.Fingerprint, false, true)
 	if err == nil {
-		return info, fmt.Errorf("The image already exists: %s", info.Fingerprint)
+		return nil, fmt.Errorf("The image already exists: %s", info.Fingerprint)
 	}
 
 	/* rename the the file to the expected name so our caller can use it */
 	finalName := shared.VarPath("images", info.Fingerprint)
 	err = shared.FileMove(compressedPath, finalName)
 	if err != nil {
-		return info, err
+		return nil, err
 	}
 
 	info.Architecture, _ = osarch.ArchitectureName(c.Architecture())
 	info.Properties = req.Properties
 
-	return info, nil
+	// Create the database entry
+	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+	if err != nil {
+		return nil, err
+	}
+
+	return &info, nil
 }
 
-func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) error {
+func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, error) {
 	var err error
 	var hash string
 
@@ -321,17 +328,17 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 	} else if req.Source["alias"] != "" {
 		hash = req.Source["alias"]
 	} else {
-		return fmt.Errorf("must specify one of alias or fingerprint for init from image")
+		return nil, fmt.Errorf("must specify one of alias or fingerprint for init from image")
 	}
 
 	hash, err = d.ImageDownload(op, req.Source["server"], req.Source["protocol"], req.Source["certificate"], req.Source["secret"], hash, false, req.AutoUpdate)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	id, info, err := dbImageGet(d.db, hash, false, false)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// Allow overriding or adding properties
@@ -343,34 +350,29 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
 		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
-	metadata := make(map[string]string)
-	metadata["fingerprint"] = info.Fingerprint
-	metadata["size"] = strconv.FormatInt(info.Size, 10)
-	op.UpdateMetadata(metadata)
-
-	return nil
+	return info, nil
 }
 
-func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) error {
+func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, error) {
 	var err error
 
 	if req.Source["url"] == "" {
-		return fmt.Errorf("Missing URL")
+		return nil, fmt.Errorf("Missing URL")
 	}
 
 	myhttp, err := d.httpClient("")
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// Resolve the image URL
 	head, err := http.NewRequest("HEAD", req.Source["url"], nil)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	architecturesStr := []string{}
@@ -384,28 +386,28 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 
 	raw, err := myhttp.Do(head)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	hash := raw.Header.Get("LXD-Image-Hash")
 	if hash == "" {
-		return fmt.Errorf("Missing LXD-Image-Hash header")
+		return nil, fmt.Errorf("Missing LXD-Image-Hash header")
 	}
 
 	url := raw.Header.Get("LXD-Image-URL")
 	if url == "" {
-		return fmt.Errorf("Missing LXD-Image-URL header")
+		return nil, fmt.Errorf("Missing LXD-Image-URL header")
 	}
 
 	// Import the image
 	hash, err = d.ImageDownload(op, url, "direct", "", "", hash, false, req.AutoUpdate)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	id, info, err := dbImageGet(d.db, hash, false, false)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	// Allow overriding or adding properties
@@ -416,21 +418,15 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) error {
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
 		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
-			return err
+			return nil, err
 		}
 	}
 
-	metadata := make(map[string]string)
-	metadata["fingerprint"] = info.Fingerprint
-	metadata["size"] = strconv.FormatInt(info.Size, 10)
-	op.UpdateMetadata(metadata)
-
-	return nil
+	return info, nil
 }
 
-func getImgPostInfo(d *Daemon, r *http.Request,
-	builddir string, post *os.File) (info api.Image, err error) {
-
+func getImgPostInfo(d *Daemon, r *http.Request, builddir string, post *os.File) (*api.Image, error) {
+	info := api.Image{}
 	var imageMeta *imageMetadata
 	logger := logging.AddContext(shared.Log, log.Ctx{"function": "getImgPostInfo"})
 
@@ -449,7 +445,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		// Create a temporary file for the image tarball
 		imageTarf, err := ioutil.TempFile(builddir, "lxd_tar_")
 		if err != nil {
-			return info, err
+			return nil, err
 		}
 		defer os.Remove(imageTarf.Name())
 
@@ -460,11 +456,11 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		// Get the metadata tarball
 		part, err := mr.NextPart()
 		if err != nil {
-			return info, err
+			return nil, err
 		}
 
 		if part.FormName() != "metadata" {
-			return info, fmt.Errorf("Invalid multipart image")
+			return nil, fmt.Errorf("Invalid multipart image")
 		}
 
 		size, err = io.Copy(io.MultiWriter(imageTarf, sha256), part)
@@ -475,7 +471,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to copy the image tarfile",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		// Get the rootfs tarball
@@ -484,20 +480,20 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to get the next part",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		if part.FormName() != "rootfs" {
 			logger.Error(
 				"Invalid multipart image")
 
-			return info, fmt.Errorf("Invalid multipart image")
+			return nil, fmt.Errorf("Invalid multipart image")
 		}
 
 		// Create a temporary file for the rootfs tarball
 		rootfsTarf, err := ioutil.TempFile(builddir, "lxd_tar_")
 		if err != nil {
-			return info, err
+			return nil, err
 		}
 		defer os.Remove(rootfsTarf.Name())
 
@@ -509,7 +505,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to copy the rootfs tarfile",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		info.Filename = part.FileName()
@@ -518,7 +514,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		expectedFingerprint := r.Header.Get("X-LXD-fingerprint")
 		if expectedFingerprint != "" && info.Fingerprint != expectedFingerprint {
 			err = fmt.Errorf("fingerprints don't match, got %s expected %s", info.Fingerprint, expectedFingerprint)
-			return info, err
+			return nil, err
 		}
 
 		imageMeta, err = getImageMetadata(imageTarf.Name())
@@ -526,7 +522,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to get image metadata",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		imgfname := shared.VarPath("images", info.Fingerprint)
@@ -538,7 +534,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 					"err":    err,
 					"source": imageTarf.Name(),
 					"dest":   imgfname})
-			return info, err
+			return nil, err
 		}
 
 		rootfsfname := shared.VarPath("images", info.Fingerprint+".rootfs")
@@ -550,7 +546,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 					"err":    err,
 					"source": rootfsTarf.Name(),
 					"dest":   imgfname})
-			return info, err
+			return nil, err
 		}
 	} else {
 		post.Seek(0, 0)
@@ -561,7 +557,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to copy the tarfile",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		info.Filename = r.Header.Get("X-LXD-filename")
@@ -578,7 +574,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 				"fingerprints don't match, got %s expected %s",
 				info.Fingerprint,
 				expectedFingerprint)
-			return info, err
+			return nil, err
 		}
 
 		imageMeta, err = getImageMetadata(post.Name())
@@ -586,7 +582,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 			logger.Error(
 				"Failed to get image metadata",
 				log.Ctx{"err": err})
-			return info, err
+			return nil, err
 		}
 
 		imgfname := shared.VarPath("images", info.Fingerprint)
@@ -598,7 +594,7 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 					"err":    err,
 					"source": post.Name(),
 					"dest":   imgfname})
-			return info, err
+			return nil, err
 		}
 	}
 
@@ -616,38 +612,13 @@ func getImgPostInfo(d *Daemon, r *http.Request,
 		}
 	}
 
-	return info, nil
-}
-
-func imageBuildFromInfo(d *Daemon, info api.Image) (metadata map[string]string, err error) {
-	err = d.Storage.ImageCreate(info.Fingerprint)
+	// Create the database entry
+	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
-		os.Remove(shared.VarPath("images", info.Fingerprint))
-		os.Remove(shared.VarPath("images", info.Fingerprint) + ".rootfs")
-
-		return metadata, err
-	}
-
-	err = dbImageInsert(
-		d.db,
-		info.Fingerprint,
-		info.Filename,
-		info.Size,
-		info.Public,
-		info.AutoUpdate,
-		info.Architecture,
-		info.CreatedAt,
-		info.ExpiresAt,
-		info.Properties)
-	if err != nil {
-		return metadata, err
+		return nil, err
 	}
 
-	metadata = make(map[string]string)
-	metadata["fingerprint"] = info.Fingerprint
-	metadata["size"] = strconv.FormatInt(info.Size, 10)
-
-	return metadata, nil
+	return &info, nil
 }
 
 func imagesPost(d *Daemon, r *http.Request) Response {
@@ -703,51 +674,46 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 
 	// Begin background operation
 	run := func(op *operation) error {
-		var info api.Image
+		var info *api.Image
 
 		// Setup the cleanup function
 		defer cleanup(builddir, post)
 
-		/* Processing image copy from remote */
-		if !imageUpload && req.Source["type"] == "image" {
-			err := imgPostRemoteInfo(d, req, op)
-			if err != nil {
-				return err
-			}
-			return nil
-		}
-
-		/* Processing image copy from URL */
-		if !imageUpload && req.Source["type"] == "url" {
-			err := imgPostURLInfo(d, req, op)
-			if err != nil {
-				return err
+		if !imageUpload {
+			if req.Source["type"] == "image" {
+				/* Processing image copy from remote */
+				info, err = imgPostRemoteInfo(d, req, op)
+				if err != nil {
+					return err
+				}
+			} else if req.Source["type"] == "url" {
+				/* Processing image copy from URL */
+				info, err = imgPostURLInfo(d, req, op)
+				if err != nil {
+					return err
+				}
+			} else {
+				/* Processing image creation from container */
+				imagePublishLock.Lock()
+				info, err = imgPostContInfo(d, r, req, builddir)
+				if err != nil {
+					imagePublishLock.Unlock()
+					return err
+				}
+				imagePublishLock.Unlock()
 			}
-			return nil
-		}
-
-		if imageUpload {
+		} else {
 			/* Processing image upload */
 			info, err = getImgPostInfo(d, r, builddir, post)
 			if err != nil {
 				return err
 			}
-		} else {
-			/* Processing image creation from container */
-			imagePublishLock.Lock()
-			info, err = imgPostContInfo(d, r, req, builddir)
-			if err != nil {
-				imagePublishLock.Unlock()
-				return err
-			}
-			imagePublishLock.Unlock()
-		}
-
-		metadata, err := imageBuildFromInfo(d, info)
-		if err != nil {
-			return err
 		}
 
+		// Set the metadata
+		metadata := make(map[string]string)
+		metadata["fingerprint"] = info.Fingerprint
+		metadata["size"] = strconv.FormatInt(info.Size, 10)
 		op.UpdateMetadata(metadata)
 		return nil
 	}

From afe55b4418ee8a6a77fce974a6e56b1c63517256 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 24 Mar 2017 11:39:51 +0100
Subject: [PATCH 0818/1193] non-functional changes

Closes #3006.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_post.go |  3 ++-
 lxd/migrate.go        | 47 ++++++++++++++++++++++++++++++++---------------
 lxd/storage_btrfs.go  | 21 ++++++++++++++-------
 3 files changed, 48 insertions(+), 23 deletions(-)

diff --git a/lxd/container_post.go b/lxd/container_post.go
index 066a2a3b0..90e9e4d3e 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -23,7 +23,8 @@ func containerPost(d *Daemon, r *http.Request) Response {
 	}
 
 	body := api.ContainerPost{}
-	if err := json.Unmarshal(buf, &body); err != nil {
+	err = json.Unmarshal(buf, &body)
+	if err != nil {
 		return BadRequest(err)
 	}
 
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 90ca3fdc3..2dcfd06fd 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -211,8 +211,9 @@ func (s *migrationSourceWs) Connect(op *operation, r *http.Request, w http.Respo
 	case s.fsSecret:
 		conn = &s.fsConn
 	default:
-		/* If we didn't find the right secret, the user provided a bad one,
-		 * which 403, not 404, since this operation actually exists */
+		// If we didn't find the right secret, the user provided a bad
+		// one, which 403, not 404, since this operation actually
+		// exists.
 		return os.ErrPermission
 	}
 
@@ -243,7 +244,8 @@ fi
 	}
 	defer f.Close()
 
-	if err := f.Chmod(0500); err != nil {
+	err = f.Chmod(0500)
+	if err != nil {
 		return err
 	}
 
@@ -275,6 +277,7 @@ func snapshotToProtobuf(c container) *Snapshot {
 	isEphemeral := c.IsEphemeral()
 	arch := int32(c.Architecture())
 	stateful := c.IsStateful()
+
 	return &Snapshot{
 		Name:         &parts[len(parts)-1],
 		LocalConfig:  config,
@@ -336,6 +339,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		}
 	}
 
+	// The protocol says we have to send a header no matter what, so let's
+	// do that, but then immediately send an error.
 	myType := s.container.Storage().MigrationType()
 	header := MigrationHeader{
 		Fs:            &myType,
@@ -345,7 +350,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		Snapshots:     snapshots,
 	}
 
-	if err := s.send(&header); err != nil {
+	err = s.send(&header)
+	if err != nil {
 		s.sendControl(err)
 		return err
 	}
@@ -355,7 +361,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		return fsErr
 	}
 
-	if err := s.recv(&header); err != nil {
+	err = s.recv(&header)
+	if err != nil {
 		s.sendControl(err)
 		return err
 	}
@@ -380,7 +387,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		return err
 	}
 
-	if err := driver.SendWhileRunning(s.fsConn); err != nil {
+	err = driver.SendWhileRunning(s.fsConn)
+	if err != nil {
 		return abort(err)
 	}
 
@@ -460,7 +468,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				return abort(err)
 			}
 
-			if err := writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret); err != nil {
+			err = writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret)
+			if err != nil {
 				os.RemoveAll(checkpointDir)
 				return abort(err)
 			}
@@ -486,7 +495,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 			}
 		} else {
 			defer os.RemoveAll(checkpointDir)
-			if err := s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, false); err != nil {
+			err = s.container.Migrate(lxc.MIGRATE_DUMP, checkpointDir, "migration", true, false)
+			if err != nil {
 				return abort(err)
 			}
 		}
@@ -498,11 +508,13 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		 * no reason to do these in parallel. In the future when we're using
 		 * p.haul's protocol, it will make sense to do these in parallel.
 		 */
-		if err := RsyncSend(shared.AddSlash(checkpointDir), s.criuConn); err != nil {
+		err = RsyncSend(shared.AddSlash(checkpointDir), s.criuConn)
+		if err != nil {
 			return abort(err)
 		}
 
-		if err := driver.SendAfterCheckpoint(s.fsConn); err != nil {
+		err = driver.SendAfterCheckpoint(s.fsConn)
+		if err != nil {
 			return abort(err)
 		}
 	}
@@ -510,7 +522,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 	driver.Cleanup()
 
 	msg := MigrationControl{}
-	if err := s.recv(&msg); err != nil {
+	err = s.recv(&msg)
+	if err != nil {
 		s.disconnect()
 		return err
 	}
@@ -636,7 +649,8 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 		resp.Fs = &myType
 	}
 
-	if err := c.src.send(&resp); err != nil {
+	err = c.src.send(&resp)
+	if err != nil {
 		c.src.container.Delete()
 		c.src.sendControl(err)
 		return err
@@ -681,12 +695,14 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 				snapshots = header.Snapshots
 			}
 
-			if err := mySink(c.src.live, c.src.container, snapshots, c.src.fsConn, srcIdmap); err != nil {
+			err := mySink(c.src.live, c.src.container, snapshots, c.src.fsConn, srcIdmap)
+			if err != nil {
 				fsTransfer <- err
 				return
 			}
 
-			if err := ShiftIfNecessary(c.src.container, srcIdmap); err != nil {
+			err = ShiftIfNecessary(c.src.container, srcIdmap)
+			if err != nil {
 				fsTransfer <- err
 				return
 			}
@@ -704,7 +720,8 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 
 			defer os.RemoveAll(imagesDir)
 
-			if err := RsyncRecv(shared.AddSlash(imagesDir), c.src.criuConn); err != nil {
+			err = RsyncRecv(shared.AddSlash(imagesDir), c.src.criuConn)
+			if err != nil {
 				restore <- err
 				return
 			}
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index eddb28452..9b3c21147 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -673,7 +673,8 @@ func (s *storageBtrfs) subvolsSnapshot(
 	}
 
 	// First snapshot the root
-	if err := s.subvolSnapshot(source, dest, readonly); err != nil {
+	err = s.subvolSnapshot(source, dest, readonly)
+	if err != nil {
 		return err
 	}
 
@@ -682,7 +683,8 @@ func (s *storageBtrfs) subvolsSnapshot(
 		// Clear the target for the subvol to use
 		os.Remove(path.Join(dest, subsubvol))
 
-		if err := s.subvolSnapshot(path.Join(source, subsubvol), path.Join(dest, subsubvol), readonly); err != nil {
+		err := s.subvolSnapshot(path.Join(source, subsubvol), path.Join(dest, subsubvol), readonly)
+		if err != nil {
 			return err
 		}
 	}
@@ -776,7 +778,8 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
 		return err
 	}
 
-	if err := cmd.Start(); err != nil {
+	err = cmd.Start()
+	if err != nil {
 		return err
 	}
 
@@ -791,6 +794,7 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
 	if err != nil {
 		shared.LogError("problem with btrfs send", log.Ctx{"output": string(output)})
 	}
+
 	return err
 }
 
@@ -853,7 +857,8 @@ func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) e
 	}
 
 	s.stoppedSnapName = fmt.Sprintf("%s/.root", tmpPath)
-	if err := s.btrfs.subvolsSnapshot(s.container.Path(), s.stoppedSnapName, true); err != nil {
+	err = s.btrfs.subvolsSnapshot(s.container.Path(), s.stoppedSnapName, true)
+	if err != nil {
 		return err
 	}
 
@@ -873,9 +878,9 @@ func (s *btrfsMigrationSourceDriver) Cleanup() {
 func (s *storageBtrfs) MigrationType() MigrationFSType {
 	if runningInUserns {
 		return MigrationFSType_RSYNC
-	} else {
-		return MigrationFSType_BTRFS
 	}
+
+	return MigrationFSType_BTRFS
 }
 
 func (s *storageBtrfs) PreservesInodes() bool {
@@ -937,6 +942,7 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 		// Remove the existing pre-created subvolume
 		err := s.subvolsDelete(targetPath)
 		if err != nil {
+			shared.LogErrorf("Failed to delete pre-created BTRFS subvolume: %s.", btrfsPath)
 			return err
 		}
 
@@ -950,7 +956,8 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 			return err
 		}
 
-		if err := cmd.Start(); err != nil {
+		err = cmd.Start()
+		if err != nil {
 			return err
 		}
 

From b79647c9ccab1cee53c78748681514f8ab2e56dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 24 Mar 2017 02:53:05 -0400
Subject: [PATCH 0819/1193] api: Properly define the image creation source
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go       | 24 ++++++++++++------------
 shared/api/image.go | 20 +++++++++++++++++++-
 2 files changed, 31 insertions(+), 13 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 5c2b4c37b..08c94ff3e 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -224,8 +224,8 @@ type imageMetadata struct {
 func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, builddir string) (*api.Image, error) {
 	info := api.Image{}
 	info.Properties = map[string]string{}
-	name := req.Source["name"]
-	ctype := req.Source["type"]
+	name := req.Source.Name
+	ctype := req.Source.Type
 	if ctype == "" || name == "" {
 		return nil, fmt.Errorf("No source provided")
 	}
@@ -323,15 +323,15 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image
 	var err error
 	var hash string
 
-	if req.Source["fingerprint"] != "" {
-		hash = req.Source["fingerprint"]
-	} else if req.Source["alias"] != "" {
-		hash = req.Source["alias"]
+	if req.Source.Fingerprint != "" {
+		hash = req.Source.Fingerprint
+	} else if req.Source.Alias != "" {
+		hash = req.Source.Alias
 	} else {
 		return nil, fmt.Errorf("must specify one of alias or fingerprint for init from image")
 	}
 
-	hash, err = d.ImageDownload(op, req.Source["server"], req.Source["protocol"], req.Source["certificate"], req.Source["secret"], hash, false, req.AutoUpdate)
+	hash, err = d.ImageDownload(op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret, hash, false, req.AutoUpdate)
 	if err != nil {
 		return nil, err
 	}
@@ -360,7 +360,7 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image
 func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, error) {
 	var err error
 
-	if req.Source["url"] == "" {
+	if req.Source.URL == "" {
 		return nil, fmt.Errorf("Missing URL")
 	}
 
@@ -370,7 +370,7 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, e
 	}
 
 	// Resolve the image URL
-	head, err := http.NewRequest("HEAD", req.Source["url"], nil)
+	head, err := http.NewRequest("HEAD", req.Source.URL, nil)
 	if err != nil {
 		return nil, err
 	}
@@ -667,7 +667,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		imageUpload = true
 	}
 
-	if !imageUpload && !shared.StringInSlice(req.Source["type"], []string{"container", "snapshot", "image", "url"}) {
+	if !imageUpload && !shared.StringInSlice(req.Source.Type, []string{"container", "snapshot", "image", "url"}) {
 		cleanup(builddir, post)
 		return InternalError(fmt.Errorf("Invalid images JSON"))
 	}
@@ -680,13 +680,13 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		defer cleanup(builddir, post)
 
 		if !imageUpload {
-			if req.Source["type"] == "image" {
+			if req.Source.Type == "image" {
 				/* Processing image copy from remote */
 				info, err = imgPostRemoteInfo(d, req, op)
 				if err != nil {
 					return err
 				}
-			} else if req.Source["type"] == "url" {
+			} else if req.Source.Type == "url" {
 				/* Processing image copy from URL */
 				info, err = imgPostURLInfo(d, req, op)
 				if err != nil {
diff --git a/shared/api/image.go b/shared/api/image.go
index ea8093fe9..f88751a37 100644
--- a/shared/api/image.go
+++ b/shared/api/image.go
@@ -9,7 +9,25 @@ type ImagesPost struct {
 	ImagePut `yaml:",inline"`
 
 	Filename string            `json:"filename" yaml:"filename"`
-	Source   map[string]string `json:"source" yaml:"source"`
+	Source   *ImagesPostSource `json:"source" yaml:"source"`
+}
+
+// ImagesPostSource represents the source of a new LXD image
+type ImagesPostSource struct {
+	ImageSource `yaml:",inline"`
+
+	Mode string `json:"mode" yaml:"mode"`
+	Type string `json:"type" yaml:"type"`
+
+	// For protocol "direct"
+	URL string `json:"url" yaml:"url"`
+
+	// For type "container"
+	Name string `json:"name" yaml:"name"`
+
+	// For type "image"
+	Fingerprint string `json:"fingerprint" yaml:"fingerprint"`
+	Secret      string `json:"secret" yaml:"secret"`
 }
 
 // ImagePut represents the modifiable fields of a LXD image

From 12dca768ea8f12e7eae49a3c4386c017b6e5b694 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Dec 2016 16:46:14 -0500
Subject: [PATCH 0820/1193] client: New client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #1926

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go           | 169 ++++++++++++++
 client/doc.go                  | 154 +++++++++++++
 client/events.go               | 100 +++++++++
 client/interfaces.go           | 208 +++++++++++++++++
 client/lxd.go                  | 201 +++++++++++++++++
 client/lxd_certificates.go     |  78 +++++++
 client/lxd_containers.go       | 496 +++++++++++++++++++++++++++++++++++++++++
 client/lxd_events.go           | 105 +++++++++
 client/lxd_images.go           | 375 +++++++++++++++++++++++++++++++
 client/lxd_networks.go         |  54 +++++
 client/lxd_operations.go       |  43 ++++
 client/lxd_profiles.go         | 100 +++++++++
 client/lxd_server.go           |  50 +++++
 client/operations.go           | 221 ++++++++++++++++++
 client/simplestreams.go        |  17 ++
 client/simplestreams_images.go | 169 ++++++++++++++
 client/util.go                 | 145 ++++++++++++
 17 files changed, 2685 insertions(+)
 create mode 100644 client/connection.go
 create mode 100644 client/doc.go
 create mode 100644 client/events.go
 create mode 100644 client/interfaces.go
 create mode 100644 client/lxd.go
 create mode 100644 client/lxd_certificates.go
 create mode 100644 client/lxd_containers.go
 create mode 100644 client/lxd_events.go
 create mode 100644 client/lxd_images.go
 create mode 100644 client/lxd_networks.go
 create mode 100644 client/lxd_operations.go
 create mode 100644 client/lxd_profiles.go
 create mode 100644 client/lxd_server.go
 create mode 100644 client/operations.go
 create mode 100644 client/simplestreams.go
 create mode 100644 client/simplestreams_images.go
 create mode 100644 client/util.go

diff --git a/client/connection.go b/client/connection.go
new file mode 100644
index 000000000..6ed4e14b0
--- /dev/null
+++ b/client/connection.go
@@ -0,0 +1,169 @@
+package lxd
+
+import (
+	"net/http"
+	"net/url"
+	"os"
+	"path/filepath"
+
+	"github.com/lxc/lxd/shared/simplestreams"
+)
+
+// ConnectionArgs represents a set of common connection properties
+type ConnectionArgs struct {
+	// TLS certificate of the remote server. If not specified, the system CA is used.
+	TLSServerCert string
+
+	// TLS certificate to use for client authentication.
+	TLSClientCert string
+
+	// TLS key to use for client authentication.
+	TLSClientKey string
+
+	// User agent string
+	UserAgent string
+
+	// Custom proxy
+	Proxy func(*http.Request) (*url.URL, error)
+}
+
+// ConnectLXD lets you connect to a remote LXD daemon over HTTPs.
+//
+// A client certificate (TLSClientCert) and key (TLSClientKey) must be provided.
+//
+// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
+func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
+	// Use empty args if not specified
+	if args == nil {
+		args = &ConnectionArgs{}
+	}
+
+	// Initialize the client struct
+	server := ProtocolLXD{
+		httpHost:        url,
+		httpUserAgent:   args.UserAgent,
+		httpCertificate: args.TLSClientCert,
+	}
+
+	// Setup the HTTP client
+	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSServerCert, args.Proxy)
+	if err != nil {
+		return nil, err
+	}
+	server.http = httpClient
+
+	// Test the connection and seed the server information
+	_, _, err = server.GetServer()
+	if err != nil {
+		return nil, err
+	}
+
+	return &server, nil
+}
+
+// ConnectLXDUnix lets you connect to a remote LXD daemon over a local unix socket.
+//
+// If the path argument is empty, then $LXD_DIR/unix.socket will be used.
+// If that one isn't set either, then the path will default to /var/lib/lxd/unix.socket.
+func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error) {
+	// Use empty args if not specified
+	if args == nil {
+		args = &ConnectionArgs{}
+	}
+
+	// Initialize the client struct
+	server := ProtocolLXD{
+		httpHost:      "http://unix.socket",
+		httpUserAgent: args.UserAgent,
+	}
+
+	// Determine the socket path
+	if path == "" {
+		lxdDir := os.Getenv("LXD_DIR")
+		if lxdDir == "" {
+			lxdDir = "/var/lib/lxd"
+		}
+
+		path = filepath.Join(lxdDir, "unix.socket")
+	}
+
+	// Setup the HTTP client
+	httpClient, err := unixHTTPClient(path)
+	if err != nil {
+		return nil, err
+	}
+	server.http = httpClient
+
+	// Test the connection and seed the server information
+	serverStatus, _, err := server.GetServer()
+	if err != nil {
+		return nil, err
+	}
+
+	// Record the server certificate
+	server.httpCertificate = serverStatus.Environment.Certificate
+
+	return &server, nil
+}
+
+// ConnectPublicLXD lets you connect to a remote public LXD daemon over HTTPs.
+//
+// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
+func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
+	// Use empty args if not specified
+	if args == nil {
+		args = &ConnectionArgs{}
+	}
+
+	// Initialize the client struct
+	server := ProtocolLXD{
+		httpHost:        url,
+		httpUserAgent:   args.UserAgent,
+		httpCertificate: args.TLSClientCert,
+	}
+
+	// Setup the HTTP client
+	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSServerCert, args.Proxy)
+	if err != nil {
+		return nil, err
+	}
+	server.http = httpClient
+
+	// Test the connection and seed the server information
+	_, _, err = server.GetServer()
+	if err != nil {
+		return nil, err
+	}
+
+	return &server, nil
+}
+
+// ConnectSimpleStreams lets you connect to a remote SimpleStreams image server over HTTPs.
+//
+// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
+func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error) {
+	// Use empty args if not specified
+	if args == nil {
+		args = &ConnectionArgs{}
+	}
+
+	// Initialize the client struct
+	server := ProtocolSimpleStreams{
+		httpHost:        url,
+		httpUserAgent:   args.UserAgent,
+		httpCertificate: args.TLSClientCert,
+	}
+
+	// Setup the HTTP client
+	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSServerCert, args.Proxy)
+	if err != nil {
+		return nil, err
+	}
+	server.http = httpClient
+
+	// Get simplestreams client
+	ssClient := simplestreams.NewClient(url, *httpClient, args.UserAgent)
+	server.ssClient = ssClient
+
+	return &server, nil
+}
diff --git a/client/doc.go b/client/doc.go
new file mode 100644
index 000000000..f54f5b9db
--- /dev/null
+++ b/client/doc.go
@@ -0,0 +1,154 @@
+// Package lxd implements a client for the LXD API
+//
+// Warning
+//
+// This API isn't considered STABLE yet!
+//
+// This client library is planned to become the new supported Go library
+// for LXD which will come with guaranteed API stability. New functions and
+// struct arguments may be added over time but no existing signature or
+// type will be changed and structs will only gain new members.
+//
+// Overview
+//
+// This package lets you connect to LXD daemons or SimpleStream image
+// servers over a Unix socket or HTTPs. You can then interact with those
+// remote servers, creating containers, images, moving them around, ...
+//
+// Example - container creation
+//
+// This creates a container on a local LXD daemon and then starts it.
+//
+//  // Connect to LXD over the Unix socket
+//  c, err := lxd.ConnectLXDUnix("", nil)
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Container creation request
+//  req := api.ContainersPost{
+//    Name: "my-container",
+//    Source: api.ContainerSource{
+//      Type:  "image",
+//      Alias: "my-image",
+//    },
+//  }
+//
+//  // Get LXD to create the container (background operation)
+//  op, err := c.CreateContainer(req)
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Wait for the operation to complete
+//  err = op.Wait()
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Get LXD to start the container (background operation)
+//  reqState := api.ContainerStatePut{
+//    Action: "start",
+//    Timeout: -1,
+//  }
+//
+//  op, err = c.UpdateContainerState(name, reqState, "")
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Wait for the operation to complete
+//  err = op.Wait()
+//  if err != nil {
+//    return err
+//  }
+//
+// Example - command execution
+//
+// This executes an interactive bash terminal
+//
+//  // Connect to LXD over the Unix socket
+//  c, err := lxd.ConnectLXDUnix("", nil)
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Setup the exec request
+//  req := api.ContainerExecPost{
+//    Command: []string{"bash"},
+//    WaitForWS: true,
+//    Interactive: true,
+//    Width: 80,
+//    Height: 15,
+//  }
+//
+//  // Setup the exec arguments (fds)
+//  args := lxd.ContainerExecArgs{
+//    Stdin: os.Stdin,
+//    Stdout: os.Stdout,
+//    Stderr: os.Stderr,
+//  }
+//
+//  // Setup the terminal (set to raw mode)
+//  if req.Interactive {
+//    cfd := int(syscall.Stdin)
+//    oldttystate, err := termios.MakeRaw(cfd)
+//    if err != nil {
+//      return err
+//    }
+//
+//    defer termios.Restore(cfd, oldttystate)
+//  }
+//
+//  // Get the current state
+//  op, err := c.ExecContainer("c1", req, &args)
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Wait for it to complete
+//  err = op.Wait()
+//  if err != nil {
+//    return err
+//  }
+//
+// Example - image copy
+//
+// This copies an image from a simplestreams server to a local LXD daemon
+//
+//  // Connect to LXD over the Unix socket
+//  c, err := lxd.ConnectLXDUnix("", nil)
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Connect to the remote SimpleStreams server
+//  d, err = lxd.ConnectSimpleStreams("https://images.linuxcontainers.org", nil)
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Resolve the alias
+//  alias, _, err := d.GetImageAlias("centos/7")
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Get the image information
+//  image, _, err := d.GetImage(alias.Target)
+//  if err != nil {
+//    return err
+//  }
+//
+//  // Ask LXD to copy the image from the remote server
+//  op, err := d.CopyImage(*image, c, nil)
+//  if err != nil {
+//    return err
+//  }
+//
+//  // And wait for it to finish
+//  err = op.Wait()
+//  if err != nil {
+//    return err
+//  }
+package lxd
diff --git a/client/events.go b/client/events.go
new file mode 100644
index 000000000..9738505c1
--- /dev/null
+++ b/client/events.go
@@ -0,0 +1,100 @@
+package lxd
+
+import (
+	"fmt"
+	"sync"
+)
+
+// The EventListener struct is used to interact with a LXD event stream
+type EventListener struct {
+	r            *ProtocolLXD
+	chActive     chan bool
+	disconnected bool
+	err          error
+
+	targets     []*EventTarget
+	targetsLock sync.Mutex
+}
+
+// The EventTarget struct is returned to the caller of AddHandler and used in RemoveHandler
+type EventTarget struct {
+	function func(interface{})
+	types    []string
+}
+
+// AddHandler adds a function to be called whenever an event is received
+func (e *EventListener) AddHandler(types []string, function func(interface{})) (*EventTarget, error) {
+	if function == nil {
+		return nil, fmt.Errorf("A valid function must be provided")
+	}
+
+	// Handle locking
+	e.targetsLock.Lock()
+	defer e.targetsLock.Unlock()
+
+	// Create a new target
+	target := EventTarget{
+		function: function,
+		types:    types,
+	}
+
+	// And add it to the targets
+	e.targets = append(e.targets, &target)
+
+	return &target, nil
+}
+
+// RemoveHandler removes a function to be called whenever an event is received
+func (e *EventListener) RemoveHandler(target *EventTarget) error {
+	if target == nil {
+		return fmt.Errorf("A valid event target must be provided")
+	}
+
+	// Handle locking
+	e.targetsLock.Lock()
+	defer e.targetsLock.Unlock()
+
+	// Locate and remove the function from the list
+	for i, entry := range e.targets {
+		if entry == target {
+			copy(e.targets[i:], e.targets[i+1:])
+			e.targets[len(e.targets)-1] = nil
+			e.targets = e.targets[:len(e.targets)-1]
+			return nil
+		}
+	}
+
+	return fmt.Errorf("Couldn't find this function and event types combination")
+}
+
+// Disconnect must be used once done listening for events
+func (e *EventListener) Disconnect() {
+	if e.disconnected {
+		return
+	}
+
+	// Handle locking
+	e.r.eventListenersLock.Lock()
+	defer e.r.eventListenersLock.Unlock()
+
+	// Locate and remove it from the global list
+	for i, listener := range e.r.eventListeners {
+		if listener == e {
+			copy(e.r.eventListeners[i:], e.r.eventListeners[i+1:])
+			e.r.eventListeners[len(e.r.eventListeners)-1] = nil
+			e.r.eventListeners = e.r.eventListeners[:len(e.r.eventListeners)-1]
+			break
+		}
+	}
+
+	// Turn off the handler
+	e.err = nil
+	e.disconnected = true
+	close(e.chActive)
+}
+
+// Wait hangs until the server disconnects the connection or Disconnect() is called
+func (e *EventListener) Wait() error {
+	<-e.chActive
+	return e.err
+}
diff --git a/client/interfaces.go b/client/interfaces.go
new file mode 100644
index 000000000..3ecbee28a
--- /dev/null
+++ b/client/interfaces.go
@@ -0,0 +1,208 @@
+package lxd
+
+import (
+	"io"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// The ImageServer type represents a read-only image server.
+type ImageServer interface {
+	// Image handling functions
+	GetImages() (images []api.Image, err error)
+	GetImageFingerprints() (fingerprints []string, err error)
+
+	GetImage(fingerprint string) (image *api.Image, ETag string, err error)
+	GetImageFile(fingerprint string, req ImageFileRequest) (resp *ImageFileResponse, err error)
+
+	GetPrivateImage(fingerprint string, secret string) (image *api.Image, ETag string, err error)
+	GetPrivateImageFile(fingerprint string, secret string, req ImageFileRequest) (resp *ImageFileResponse, err error)
+
+	GetImageAliases() (aliases []api.ImageAliasesEntry, err error)
+	GetImageAliasNames() (names []string, err error)
+
+	GetImageAlias(name string) (alias *api.ImageAliasesEntry, ETag string, err error)
+
+	CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (op *Operation, err error)
+}
+
+// The ContainerServer type represents a full featured LXD server.
+type ContainerServer interface {
+	// Server functions
+	GetServer() (server *api.Server, ETag string, err error)
+	UpdateServer(server api.ServerPut, ETag string) (err error)
+	HasExtension(extension string) bool
+
+	// Certificate functions
+	GetCertificateFingerprints() (fingerprints []string, err error)
+	GetCertificates() (certificates []api.Certificate, err error)
+	GetCertificate(fingerprint string) (certificate *api.Certificate, ETag string, err error)
+	CreateCertificate(certificate api.CertificatesPost) (err error)
+	DeleteCertificate(fingerprint string) (err error)
+
+	// Container functions
+	GetContainerNames() (names []string, err error)
+	GetContainers() (containers []api.Container, err error)
+	GetContainer(name string) (container *api.Container, ETag string, err error)
+	CreateContainer(container api.ContainersPost) (op *Operation, err error)
+	UpdateContainer(name string, container api.ContainerPut, ETag string) (op *Operation, err error)
+	RenameContainer(name string, container api.ContainerPost) (op *Operation, err error)
+	MigrateContainer(name string, container api.ContainerPost) (op *Operation, err error)
+	DeleteContainer(name string) (op *Operation, err error)
+
+	ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (*Operation, error)
+
+	GetContainerFile(containerName string, path string) (content io.ReadCloser, resp *ContainerFileResponse, err error)
+	CreateContainerFile(containerName string, path string, args ContainerFileArgs) (err error)
+	DeleteContainerFile(containerName string, path string) (err error)
+
+	GetContainerSnapshotNames(containerName string) (names []string, err error)
+	GetContainerSnapshots(containerName string) (snapshots []api.ContainerSnapshot, err error)
+	GetContainerSnapshot(containerName string, name string) (snapshot *api.ContainerSnapshot, ETag string, err error)
+	CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (op *Operation, err error)
+	RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
+	MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
+	DeleteContainerSnapshot(containerName string, name string) (op *Operation, err error)
+
+	GetContainerState(name string) (state *api.ContainerState, ETag string, err error)
+	UpdateContainerState(name string, state api.ContainerStatePut, ETag string) (op *Operation, err error)
+
+	GetContainerLogfiles(name string) (logfiles []string, err error)
+	GetContainerLogfile(name string, filename string) (content io.ReadCloser, err error)
+	DeleteContainerLogfile(name string, filename string) (err error)
+
+	// Event handling functions
+	GetEvents() (listener *EventListener, err error)
+
+	// Image functions
+	ImageServer
+	CreateImage(image api.ImagesPost) (op *Operation, err error)
+	UpdateImage(fingerprint string, image api.ImagePut, ETag string) (err error)
+	DeleteImage(fingerprint string) (op *Operation, err error)
+	CreateImageSecret(fingerprint string) (op *Operation, err error)
+	CreateImageAlias(alias api.ImageAliasesPost) (err error)
+	UpdateImageAlias(name string, alias api.ImageAliasesEntryPut, ETag string) (err error)
+	RenameImageAlias(name string, alias api.ImageAliasesEntryPost) (err error)
+	DeleteImageAlias(name string) (err error)
+
+	// Network functions ("network" API extension)
+	GetNetworkNames() (names []string, err error)
+	GetNetworks() (networks []api.Network, err error)
+	GetNetwork(name string) (network *api.Network, ETag string, err error)
+
+	// Operation functions
+	GetOperation(uuid string) (op *api.Operation, ETag string, err error)
+	DeleteOperation(uuid string) (err error)
+	GetOperationWebsocket(uuid string, secret string) (conn *websocket.Conn, err error)
+
+	// Profile functions
+	GetProfileNames() (names []string, err error)
+	GetProfiles() (profiles []api.Profile, err error)
+	GetProfile(name string) (profile *api.Profile, ETag string, err error)
+	CreateProfile(profile api.ProfilesPost) (err error)
+	UpdateProfile(name string, profile api.ProfilePut, ETag string) (err error)
+	RenameProfile(name string, profile api.ProfilePost) (err error)
+	DeleteProfile(name string) (err error)
+
+	// Internal functions (for internal use)
+	RawQuery(method string, path string, data interface{}, queryETag string) (resp *api.Response, ETag string, err error)
+	RawWebsocket(path string) (conn *websocket.Conn, err error)
+}
+
+// The ProgressData struct represents new progress information on an operation
+type ProgressData struct {
+	// Preferred string repreentation of progress (always set)
+	Text string
+
+	// Progress in percent
+	Percentage int
+
+	// Number of bytes transferred (for files)
+	TransferredBytes int64
+
+	// Total number of bytes (for files)
+	TotalBytes int64
+}
+
+// The ImageFileRequest struct is used for an image download request
+type ImageFileRequest struct {
+	// Writer for the metadata file
+	MetaFile io.WriteSeeker
+
+	// Writer for the rootfs file
+	RootfsFile io.WriteSeeker
+
+	// Progress handler (called whenever some progress is made)
+	ProgressHandler func(progress ProgressData)
+}
+
+// The ImageFileResponse struct is used as the response for image downloads
+type ImageFileResponse struct {
+	// Filename for the metadata file
+	MetaName string
+
+	// Size of the metadata file
+	MetaSize int64
+
+	// Filename for the rootfs file
+	RootfsName string
+
+	// Size of the rootfs file
+	RootfsSize int64
+}
+
+// The ImageCopyArgs struct is used to pass additional options during image copy
+type ImageCopyArgs struct {
+	// Aliases to add to the copied image.
+	Aliases []api.ImageAlias
+
+	// Whether to have LXD keep this image up to date
+	AutoUpdate bool
+
+	// Whether this image is to be made available to unauthenticated users
+	Public bool
+}
+
+// The ContainerExecArgs struct is used to pass additional options during container exec
+type ContainerExecArgs struct {
+	// Standard input
+	Stdin io.ReadCloser
+
+	// Standard output
+	Stdout io.WriteCloser
+
+	// Standard error
+	Stderr io.WriteCloser
+
+	// Control message handler (window resize, signals, ...)
+	Control func(conn *websocket.Conn)
+}
+
+// The ContainerFileArgs struct is used to pass the various options for a container file upload
+type ContainerFileArgs struct {
+	// File content
+	Content io.ReadSeeker
+
+	// User id that owns the file
+	UID int64
+
+	// Group id that owns the file
+	GID int64
+
+	// File permissions
+	Mode int
+}
+
+// The ContainerFileResponse struct is used as part of the response for a container file download
+type ContainerFileResponse struct {
+	// User id that owns the file
+	UID int64
+
+	// Group id that owns the file
+	GID int64
+
+	// File permissions
+	Mode int
+}
diff --git a/client/lxd.go b/client/lxd.go
new file mode 100644
index 000000000..2d7707fa4
--- /dev/null
+++ b/client/lxd.go
@@ -0,0 +1,201 @@
+package lxd
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+	"sync"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// ProtocolLXD represents a LXD API server
+type ProtocolLXD struct {
+	server *api.Server
+
+	eventListeners     []*EventListener
+	eventListenersLock sync.Mutex
+
+	http            *http.Client
+	httpHost        string
+	httpUserAgent   string
+	httpCertificate string
+}
+
+// RawQuery allows directly querying the LXD API
+//
+// This should only be used by internal LXD tools.
+func (r *ProtocolLXD) RawQuery(method string, path string, data interface{}, ETag string) (*api.Response, string, error) {
+	// Generate the URL
+	url := fmt.Sprintf("%s%s", r.httpHost, path)
+
+	return r.rawQuery(method, url, data, ETag)
+}
+
+// RawWebsocket allows directly connection to LXD API websockets
+//
+// This should only be used by internal LXD tools.
+func (r *ProtocolLXD) RawWebsocket(path string) (*websocket.Conn, error) {
+	// Generate the URL
+	url := fmt.Sprintf("%s%s", r.httpHost, path)
+
+	return r.rawWebsocket(url)
+}
+
+// Internal functions
+func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag string) (*api.Response, string, error) {
+	var req *http.Request
+	var err error
+
+	// Get a new HTTP request setup
+	if data != nil {
+		// Encode the provided data
+		buf := bytes.Buffer{}
+		err := json.NewEncoder(&buf).Encode(data)
+		if err != nil {
+			return nil, "", err
+		}
+
+		// Some data to be sent along with the request
+		req, err = http.NewRequest(method, url, &buf)
+		if err != nil {
+			return nil, "", err
+		}
+
+		// Set the encoding accordingly
+		req.Header.Set("Content-Type", "application/json")
+	} else {
+		// No data to be sent along with the request
+		req, err = http.NewRequest(method, url, nil)
+		if err != nil {
+			return nil, "", err
+		}
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Set the ETag
+	if ETag != "" {
+		req.Header.Set("If-Match", ETag)
+	}
+
+	// Send the request
+	resp, err := r.http.Do(req)
+	if err != nil {
+		return nil, "", err
+	}
+	defer resp.Body.Close()
+
+	// Get the ETag
+	etag := resp.Header.Get("ETag")
+
+	// Decode the response
+	decoder := json.NewDecoder(resp.Body)
+	response := api.Response{}
+
+	err = decoder.Decode(&response)
+	if err != nil {
+		// Check the return value for a cleaner error
+		if resp.StatusCode != http.StatusOK {
+			return nil, "", fmt.Errorf("Failed to fetch %s: %s", url, resp.Status)
+		}
+
+		return nil, "", err
+	}
+
+	// Handle errors
+	if response.Type == api.ErrorResponse {
+		return nil, "", fmt.Errorf(response.Error)
+	}
+
+	return &response, etag, nil
+}
+
+func (r *ProtocolLXD) query(method string, path string, data interface{}, ETag string) (*api.Response, string, error) {
+	// Generate the URL
+	url := fmt.Sprintf("%s/1.0%s", r.httpHost, path)
+
+	return r.rawQuery(method, url, data, ETag)
+}
+
+func (r *ProtocolLXD) queryStruct(method string, path string, data interface{}, ETag string, target interface{}) (string, error) {
+	resp, etag, err := r.query(method, path, data, ETag)
+	if err != nil {
+		return "", err
+	}
+
+	err = resp.MetadataAsStruct(&target)
+	if err != nil {
+		return "", err
+	}
+
+	return etag, nil
+}
+
+func (r *ProtocolLXD) queryOperation(method string, path string, data interface{}, ETag string) (*Operation, string, error) {
+	// Send the query
+	resp, etag, err := r.query(method, path, data, ETag)
+	if err != nil {
+		return nil, "", err
+	}
+
+	// Get to the operation
+	respOperation, err := resp.MetadataAsOperation()
+	if err != nil {
+		return nil, "", err
+	}
+
+	// Setup an Operation wrapper
+	op := Operation{
+		Operation: *respOperation,
+		r:         r,
+		chActive:  make(chan bool),
+	}
+
+	return &op, etag, nil
+}
+
+func (r *ProtocolLXD) rawWebsocket(url string) (*websocket.Conn, error) {
+	// Grab the http transport handler
+	httpTransport := r.http.Transport.(*http.Transport)
+
+	// Setup a new websocket dialer based on it
+	dialer := websocket.Dialer{
+		NetDial:         httpTransport.Dial,
+		TLSClientConfig: httpTransport.TLSClientConfig,
+		Proxy:           httpTransport.Proxy,
+	}
+
+	// Set the user agent
+	headers := http.Header{}
+	if r.httpUserAgent != "" {
+		headers.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Establish the connection
+	conn, _, err := dialer.Dial(url, headers)
+	if err != nil {
+		return nil, err
+	}
+
+	return conn, err
+}
+
+func (r *ProtocolLXD) websocket(path string) (*websocket.Conn, error) {
+	// Generate the URL
+	var url string
+	if strings.HasPrefix(r.httpHost, "https://") {
+		url = fmt.Sprintf("wss://%s/1.0%s", strings.TrimPrefix(r.httpHost, "https://"), path)
+	} else {
+		url = fmt.Sprintf("ws://%s/1.0%s", strings.TrimPrefix(r.httpHost, "http://"), path)
+	}
+
+	return r.rawWebsocket(url)
+}
diff --git a/client/lxd_certificates.go b/client/lxd_certificates.go
new file mode 100644
index 000000000..4eb275bcd
--- /dev/null
+++ b/client/lxd_certificates.go
@@ -0,0 +1,78 @@
+package lxd
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// Certificate handling functions
+
+// GetCertificateFingerprints returns a list of certificate fingerprints
+func (r *ProtocolLXD) GetCertificateFingerprints() ([]string, error) {
+	certificates := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/certificates", nil, "", &certificates)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	fingerprints := []string{}
+	for _, fingerprint := range fingerprints {
+		fields := strings.Split(fingerprint, "/certificates/")
+		fingerprints = append(fingerprints, fields[len(fields)-1])
+	}
+
+	return fingerprints, nil
+}
+
+// GetCertificates returns a list of certificates
+func (r *ProtocolLXD) GetCertificates() ([]api.Certificate, error) {
+	certificates := []api.Certificate{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/certificates?recursion=1", nil, "", &certificates)
+	if err != nil {
+		return nil, err
+	}
+
+	return certificates, nil
+}
+
+// GetCertificate returns the certificate entry for the provided fingerprint
+func (r *ProtocolLXD) GetCertificate(fingerprint string) (*api.Certificate, string, error) {
+	certificate := api.Certificate{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/certificates/%s", fingerprint), nil, "", &certificate)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &certificate, etag, nil
+}
+
+// CreateCertificate adds a new certificate to the LXD trust store
+func (r *ProtocolLXD) CreateCertificate(certificate api.CertificatesPost) error {
+	// Send the request
+	_, _, err := r.query("POST", "/certificates", certificate, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteCertificate removes a certificate from the LXD trust store
+func (r *ProtocolLXD) DeleteCertificate(fingerprint string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/certificates/%s", fingerprint), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
new file mode 100644
index 000000000..bddb7e7e2
--- /dev/null
+++ b/client/lxd_containers.go
@@ -0,0 +1,496 @@
+package lxd
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+)
+
+// Container handling functions
+
+// GetContainerNames returns a list of container names
+func (r *ProtocolLXD) GetContainerNames() ([]string, error) {
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/containers", nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, url := range urls {
+		fields := strings.Split(url, "/containers/")
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetContainers returns a list of containers
+func (r *ProtocolLXD) GetContainers() ([]api.Container, error) {
+	containers := []api.Container{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/containers?recursion=1", nil, "", &containers)
+	if err != nil {
+		return nil, err
+	}
+
+	return containers, nil
+}
+
+// GetContainer returns the container entry for the provided name
+func (r *ProtocolLXD) GetContainer(name string) (*api.Container, string, error) {
+	container := api.Container{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/containers/%s", name), nil, "", &container)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &container, etag, nil
+}
+
+// CreateContainer requests that LXD creates a new container
+func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("POST", "/containers", container, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// UpdateContainer updates the container definition
+func (r *ProtocolLXD) UpdateContainer(name string, container api.ContainerPut, ETag string) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("PUT", fmt.Sprintf("/containers/%s", name), container, ETag)
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// RenameContainer requests that LXD renames the container
+func (r *ProtocolLXD) RenameContainer(name string, container api.ContainerPost) (*Operation, error) {
+	// Sanity check
+	if container.Migration {
+		return nil, fmt.Errorf("Can't ask for a migration through RenameContainer")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("/containers/%s", name), container, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// MigrateContainer requests that LXD prepares for a container migration
+func (r *ProtocolLXD) MigrateContainer(name string, container api.ContainerPost) (*Operation, error) {
+	// Sanity check
+	if !container.Migration {
+		return nil, fmt.Errorf("Can't ask for a rename through MigrateContainer")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("/containers/%s", name), container, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// DeleteContainer requests that LXD deletes the container
+func (r *ProtocolLXD) DeleteContainer(name string) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/containers/%s", name), nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// ExecContainer requests that LXD spawns a command inside the container
+func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("/containers/%s/exec", containerName), exec, "")
+	if err != nil {
+		return nil, err
+	}
+
+	// Process additional arguments
+	if args != nil {
+		// Parse the fds
+		fds := map[string]string{}
+
+		value, ok := op.Metadata["fds"]
+		if ok {
+			values := value.(map[string]interface{})
+			for k, v := range values {
+				fds[k] = v.(string)
+			}
+		}
+
+		// Call the control handler with a connection to the control socket
+		if args.Control != nil && fds["control"] != "" {
+			conn, err := r.GetOperationWebsocket(op.ID, fds["control"])
+			if err != nil {
+				return nil, err
+			}
+
+			go args.Control(conn)
+		}
+
+		if exec.Interactive {
+			// Handle interactive sections
+			if args.Stdin != nil && args.Stdout != nil {
+				// Connect to the websocket
+				conn, err := r.GetOperationWebsocket(op.ID, fds["0"])
+				if err != nil {
+					return nil, err
+				}
+
+				// And attach stdin and stdout to it
+				go func() {
+					shared.WebsocketSendStream(conn, args.Stdin, -1)
+					<-shared.WebsocketRecvStream(args.Stdout, conn)
+					conn.Close()
+				}()
+			}
+		} else {
+			// Handle non-interactive sessions
+			dones := []chan bool{}
+			conns := []*websocket.Conn{}
+
+			// Handle stdin
+			if fds["0"] != "" {
+				conn, err := r.GetOperationWebsocket(op.ID, fds["0"])
+				if err != nil {
+					return nil, err
+				}
+
+				conns = append(conns, conn)
+				dones = append(dones, shared.WebsocketSendStream(conn, args.Stdin, -1))
+			}
+
+			// Handle stdout
+			if fds["1"] != "" {
+				conn, err := r.GetOperationWebsocket(op.ID, fds["1"])
+				if err != nil {
+					return nil, err
+				}
+
+				conns = append(conns, conn)
+				dones = append(dones, shared.WebsocketRecvStream(args.Stdout, conn))
+			}
+
+			// Handle stderr
+			if fds["2"] != "" {
+				conn, err := r.GetOperationWebsocket(op.ID, fds["2"])
+				if err != nil {
+					return nil, err
+				}
+
+				conns = append(conns, conn)
+				dones = append(dones, shared.WebsocketRecvStream(args.Stderr, conn))
+			}
+
+			// Wait for everything to be done
+			go func() {
+				for _, chDone := range dones {
+					<-chDone
+				}
+
+				if fds["0"] != "" {
+					args.Stdin.Close()
+				}
+
+				for _, conn := range conns {
+					conn.Close()
+				}
+			}()
+		}
+	}
+
+	return op, nil
+}
+
+// GetContainerFile retrieves the provided path from the container
+func (r *ProtocolLXD) GetContainerFile(containerName string, path string) (io.ReadCloser, *ContainerFileResponse, error) {
+	// Prepare the HTTP request
+	url := fmt.Sprintf("%s/1.0/containers/%s/files?path=%s", r.httpHost, containerName, path)
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.http.Do(req)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		return nil, nil, fmt.Errorf("Failed to fetch %s: %s", url, resp.Status)
+	}
+
+	// Parse the headers
+	uid, gid, mode := shared.ParseLXDFileHeaders(resp.Header)
+	fileResp := ContainerFileResponse{
+		UID:  uid,
+		GID:  gid,
+		Mode: mode,
+	}
+
+	return resp.Body, &fileResp, err
+}
+
+// CreateContainerFile tells LXD to create a file in the container
+func (r *ProtocolLXD) CreateContainerFile(containerName string, path string, args ContainerFileArgs) error {
+	// Prepare the HTTP request
+	url := fmt.Sprintf("%s/1.0/containers/%s/files?path=%s", r.httpHost, containerName, path)
+	req, err := http.NewRequest("POST", url, args.Content)
+	if err != nil {
+		return err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Set the various headers
+	req.Header.Set("X-LXD-uid", fmt.Sprintf("%d", args.UID))
+	req.Header.Set("X-LXD-gid", fmt.Sprintf("%d", args.GID))
+	req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", args.Mode))
+
+	// Send the request
+	resp, err := r.http.Do(req)
+	if err != nil {
+		return err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("Failed to upload to %s: %s", url, resp.Status)
+	}
+
+	return nil
+}
+
+// DeleteContainerFile deletes a file in the container
+func (r *ProtocolLXD) DeleteContainerFile(containerName string, path string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/containers/%s/files?path=%s", containerName, path), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// GetContainerSnapshotNames returns a list of snapshot names for the container
+func (r *ProtocolLXD) GetContainerSnapshotNames(containerName string) ([]string, error) {
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", fmt.Sprintf("/containers/%s/snapshots", containerName), nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, url := range urls {
+		fields := strings.Split(url, fmt.Sprintf("/containers/%s/snapshots/", containerName))
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetContainerSnapshots returns a list of snapshots for the container
+func (r *ProtocolLXD) GetContainerSnapshots(containerName string) ([]api.ContainerSnapshot, error) {
+	snapshots := []api.ContainerSnapshot{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", fmt.Sprintf("/containers/%s/snapshots?recursion=1", containerName), nil, "", snapshots)
+	if err != nil {
+		return nil, err
+	}
+
+	return snapshots, nil
+}
+
+// GetContainerSnapshot returns a Snapshot struct for the provided container and snapshot names
+func (r *ProtocolLXD) GetContainerSnapshot(containerName string, name string) (*api.ContainerSnapshot, string, error) {
+	snapshot := api.ContainerSnapshot{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/containers/%s/snapshots/%s", containerName, name), nil, "", &snapshot)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &snapshot, etag, nil
+}
+
+// CreateContainerSnapshot requests that LXD creates a new snapshot for the container
+func (r *ProtocolLXD) CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("/containers/%s/snapshots", containerName), snapshot, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// RenameContainerSnapshot requests that LXD renames the snapshot
+func (r *ProtocolLXD) RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (*Operation, error) {
+	// Sanity check
+	if container.Migration {
+		return nil, fmt.Errorf("Can't ask for a migration through RenameContainerSnapshot")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("/containers/%s/snapshots/%s", containerName, name), container, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// MigrateContainerSnapshot requests that LXD prepares for a snapshot migration
+func (r *ProtocolLXD) MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (*Operation, error) {
+	// Sanity check
+	if !container.Migration {
+		return nil, fmt.Errorf("Can't ask for a rename through MigrateContainerSnapshot")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("/containers/%s/snapshots/%s", containerName, name), container, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// DeleteContainerSnapshot requests that LXD deletes the container snapshot
+func (r *ProtocolLXD) DeleteContainerSnapshot(containerName string, name string) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/containers/%s/snapshots/%s", containerName, name), nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// GetContainerState returns a ContainerState entry for the provided container name
+func (r *ProtocolLXD) GetContainerState(name string) (*api.ContainerState, string, error) {
+	state := api.ContainerState{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/containers/%s/state", name), nil, "", &state)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &state, etag, nil
+}
+
+// UpdateContainerState updates the container to match the requested state
+func (r *ProtocolLXD) UpdateContainerState(name string, state api.ContainerStatePut, ETag string) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("PUT", fmt.Sprintf("/containers/%s/state", name), state, ETag)
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// GetContainerLogfiles returns a list of logfiles for the container
+func (r *ProtocolLXD) GetContainerLogfiles(name string) ([]string, error) {
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", fmt.Sprintf("/containers/%s/logs", name), nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	logfiles := []string{}
+	for _, url := range logfiles {
+		fields := strings.Split(url, fmt.Sprintf("/containers/%s/logs/", name))
+		logfiles = append(logfiles, fields[len(fields)-1])
+	}
+
+	return logfiles, nil
+}
+
+// GetContainerLogfile returns the content of the requested logfile
+//
+// Note that it's the caller's responsibility to close the returned ReadCloser
+func (r *ProtocolLXD) GetContainerLogfile(name string, filename string) (io.ReadCloser, error) {
+	// Prepare the HTTP request
+	url := fmt.Sprintf("%s/1.0/containers/%s/logs/%s", r.httpHost, name, filename)
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.http.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("Failed to fetch %s: %s", url, resp.Status)
+	}
+
+	return resp.Body, err
+}
+
+// DeleteContainerLogfile deletes the requested logfile
+func (r *ProtocolLXD) DeleteContainerLogfile(name string, filename string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/containers/%s/logs/%s", name, filename), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/client/lxd_events.go b/client/lxd_events.go
new file mode 100644
index 000000000..e63e78b95
--- /dev/null
+++ b/client/lxd_events.go
@@ -0,0 +1,105 @@
+package lxd
+
+import (
+	"encoding/json"
+
+	"github.com/lxc/lxd/shared"
+)
+
+// Event handling functions
+
+// GetEvents connects to the LXD monitoring interface
+func (r *ProtocolLXD) GetEvents() (*EventListener, error) {
+	// Prevent anything else from interacting with the listeners
+	r.eventListenersLock.Lock()
+	defer r.eventListenersLock.Unlock()
+
+	// Setup a new listener
+	listener := EventListener{
+		r:        r,
+		chActive: make(chan bool),
+	}
+
+	if r.eventListeners != nil {
+		// There is an existing Go routine setup, so just add another target
+		r.eventListeners = append(r.eventListeners, &listener)
+		return &listener, nil
+	}
+
+	// Initialize the list if needed
+	r.eventListeners = []*EventListener{}
+
+	// Setup a new connection with LXD
+	conn, err := r.websocket("/events")
+	if err != nil {
+		return nil, err
+	}
+
+	// Add the listener
+	r.eventListeners = append(r.eventListeners, &listener)
+
+	// And spawn the listener
+	go func() {
+		for {
+			r.eventListenersLock.Lock()
+			if len(r.eventListeners) == 0 {
+				// We don't need the connection anymore, disconnect
+				conn.Close()
+
+				r.eventListeners = nil
+				r.eventListenersLock.Unlock()
+				break
+			}
+			r.eventListenersLock.Unlock()
+
+			_, data, err := conn.ReadMessage()
+			if err != nil {
+				// Prevent anything else from interacting with the listeners
+				r.eventListenersLock.Lock()
+				defer r.eventListenersLock.Unlock()
+
+				// Tell all the current listeners about the failure
+				for _, listener := range r.eventListeners {
+					listener.err = err
+					listener.disconnected = true
+					close(listener.chActive)
+				}
+
+				// And remove them all from the list
+				r.eventListeners = []*EventListener{}
+				return
+			}
+
+			// Attempt to unpack the message
+			message := make(map[string]interface{})
+			err = json.Unmarshal(data, &message)
+			if err != nil {
+				continue
+			}
+
+			// Extract the message type
+			_, ok := message["type"]
+			if !ok {
+				continue
+			}
+			messageType := message["type"].(string)
+
+			// Send the message to all handlers
+			r.eventListenersLock.Lock()
+			for _, listener := range r.eventListeners {
+				listener.targetsLock.Lock()
+				for _, target := range listener.targets {
+					if target.types != nil && !shared.StringInSlice(messageType, target.types) {
+						continue
+					}
+
+					go target.function(message)
+				}
+				listener.targetsLock.Unlock()
+			}
+			r.eventListenersLock.Unlock()
+		}
+	}()
+
+	return &listener, nil
+}
diff --git a/client/lxd_images.go b/client/lxd_images.go
new file mode 100644
index 000000000..37a8f7eed
--- /dev/null
+++ b/client/lxd_images.go
@@ -0,0 +1,375 @@
+package lxd
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"io"
+	"mime"
+	"mime/multipart"
+	"net/http"
+	"strings"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/ioprogress"
+)
+
+// Image handling functions
+
+// GetImages returns a list of available images as Image structs
+func (r *ProtocolLXD) GetImages() ([]api.Image, error) {
+	images := []api.Image{}
+
+	_, err := r.queryStruct("GET", "/images?recursion=1", nil, "", &images)
+	if err != nil {
+		return nil, err
+	}
+
+	return images, nil
+}
+
+// GetImageFingerprints returns a list of available image fingerprints
+func (r *ProtocolLXD) GetImageFingerprints() ([]string, error) {
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/images", nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	fingerprints := []string{}
+	for _, url := range urls {
+		fields := strings.Split(url, "/images/")
+		fingerprints = append(fingerprints, fields[len(fields)-1])
+	}
+
+	return fingerprints, nil
+}
+
+// GetImage returns an Image struct for the provided fingerprint
+func (r *ProtocolLXD) GetImage(fingerprint string) (*api.Image, string, error) {
+	return r.GetPrivateImage(fingerprint, "")
+}
+
+// GetImageFile downloads an image from the server, returning an ImageFileRequest struct
+func (r *ProtocolLXD) GetImageFile(fingerprint string, req ImageFileRequest) (*ImageFileResponse, error) {
+	return r.GetPrivateImageFile(fingerprint, "", req)
+}
+
+// GetPrivateImage is similar to GetImage but allows passing a secret download token
+func (r *ProtocolLXD) GetPrivateImage(fingerprint string, secret string) (*api.Image, string, error) {
+	image := api.Image{}
+
+	// Build the API path
+	path := fmt.Sprintf("/images/%s", fingerprint)
+	if secret != "" {
+		path = fmt.Sprintf("%s?secret=%s", path, secret)
+	}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", path, nil, "", &image)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &image, etag, nil
+}
+
+// GetPrivateImageFile is similar to GetImageFile but allows passing a secret download token
+func (r *ProtocolLXD) GetPrivateImageFile(fingerprint string, secret string, req ImageFileRequest) (*ImageFileResponse, error) {
+	// Sanity checks
+	if req.MetaFile == nil && req.RootfsFile == nil {
+		return nil, fmt.Errorf("No file requested")
+	}
+
+	// Prepare the response
+	resp := ImageFileResponse{}
+
+	// Build the URL
+	url := fmt.Sprintf("%s/1.0/images/%s/export", r.httpHost, fingerprint)
+	if secret != "" {
+		url = fmt.Sprintf("%s?secret=%s", url, secret)
+	}
+
+	// Prepare the download request
+	request, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	if r.httpUserAgent != "" {
+		request.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Start the request
+	response, err := r.http.Do(request)
+	if err != nil {
+		return nil, err
+	}
+	defer response.Body.Close()
+
+	if response.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("Unable to fetch %s: %s", url, response.Status)
+	}
+
+	ctype, ctypeParams, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
+	if err != nil {
+		ctype = "application/octet-stream"
+	}
+
+	// Handle the data
+	body := response.Body
+	if req.ProgressHandler != nil {
+		body = &ioprogress.ProgressReader{
+			ReadCloser: response.Body,
+			Tracker: &ioprogress.ProgressTracker{
+				Length: response.ContentLength,
+				Handler: func(percent int64, speed int64) {
+					req.ProgressHandler(ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed, 2))})
+				},
+			},
+		}
+	}
+
+	// Hashing
+	sha256 := sha256.New()
+
+	// Deal with split images
+	if ctype == "multipart/form-data" {
+		if req.MetaFile == nil || req.RootfsFile == nil {
+			return nil, fmt.Errorf("Multi-part image but only one target file provided")
+		}
+
+		// Parse the POST data
+		mr := multipart.NewReader(body, ctypeParams["boundary"])
+
+		// Get the metadata tarball
+		part, err := mr.NextPart()
+		if err != nil {
+			return nil, err
+		}
+
+		if part.FormName() != "metadata" {
+			return nil, fmt.Errorf("Invalid multipart image")
+		}
+
+		size, err := io.Copy(io.MultiWriter(req.MetaFile, sha256), part)
+		if err != nil {
+			return nil, err
+		}
+		resp.MetaSize = size
+		resp.MetaName = part.FileName()
+
+		// Get the rootfs tarball
+		part, err = mr.NextPart()
+		if err != nil {
+			return nil, err
+		}
+
+		if part.FormName() != "rootfs" {
+			return nil, fmt.Errorf("Invalid multipart image")
+		}
+
+		size, err = io.Copy(io.MultiWriter(req.RootfsFile, sha256), part)
+		if err != nil {
+			return nil, err
+		}
+		resp.RootfsSize = size
+		resp.RootfsName = part.FileName()
+
+		// Check the hash
+		hash := fmt.Sprintf("%x", sha256.Sum(nil))
+		if hash != fingerprint {
+			return nil, fmt.Errorf("Image fingerprint doesn't match. Got %s expected %s", hash, fingerprint)
+		}
+
+		return &resp, nil
+	}
+
+	// Deal with unified images
+	_, cdParams, err := mime.ParseMediaType(response.Header.Get("Content-Disposition"))
+	if err != nil {
+		return nil, err
+	}
+
+	filename, ok := cdParams["filename"]
+	if !ok {
+		return nil, fmt.Errorf("No filename in Content-Disposition header")
+	}
+
+	size, err := io.Copy(io.MultiWriter(req.MetaFile, sha256), body)
+	if err != nil {
+		return nil, err
+	}
+	resp.MetaSize = size
+	resp.MetaName = filename
+
+	// Check the hash
+	hash := fmt.Sprintf("%x", sha256.Sum(nil))
+	if hash != fingerprint {
+		return nil, fmt.Errorf("Image fingerprint doesn't match. Got %s expected %s", hash, fingerprint)
+	}
+
+	return &resp, nil
+}
+
+// GetImageAliases returns the list of available aliases as ImageAliasesEntry structs
+func (r *ProtocolLXD) GetImageAliases() ([]api.ImageAliasesEntry, error) {
+	aliases := []api.ImageAliasesEntry{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/images/aliases?recursion=1", nil, "", aliases)
+	if err != nil {
+		return nil, err
+	}
+
+	return aliases, nil
+}
+
+// GetImageAliasNames returns the list of available alias names
+func (r *ProtocolLXD) GetImageAliasNames() ([]string, error) {
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/images/aliases", nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, url := range urls {
+		fields := strings.Split(url, "/images/aliases/")
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetImageAlias returns an existing alias as an ImageAliasesEntry struct
+func (r *ProtocolLXD) GetImageAlias(name string) (*api.ImageAliasesEntry, string, error) {
+	alias := api.ImageAliasesEntry{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/images/aliases/%s", name), nil, "", &alias)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &alias, etag, nil
+}
+
+// CreateImage requests that LXD creates, copies or import a new image
+func (r *ProtocolLXD) CreateImage(image api.ImagesPost) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("POST", "/images", image, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
+func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*Operation, error) {
+	// Prepare the copy request
+	req := api.ImagesPost{
+		Source: &api.ImagesPostSource{
+			ImageSource: api.ImageSource{
+				Certificate: r.httpCertificate,
+				Protocol:    "lxd",
+				Server:      r.httpHost,
+			},
+			Fingerprint: image.Fingerprint,
+			Mode:        "pull",
+			Type:        "image",
+		},
+	}
+
+	// Process the arguments
+	if args != nil {
+		req.AutoUpdate = args.AutoUpdate
+		req.Public = args.Public
+	}
+
+	return target.CreateImage(req)
+}
+
+// UpdateImage updates the image definition
+func (r *ProtocolLXD) UpdateImage(fingerprint string, image api.ImagePut, ETag string) error {
+	// Send the request
+	_, _, err := r.query("PUT", fmt.Sprintf("/images/%s", fingerprint), image, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteImage requests that LXD removes an image from the store
+func (r *ProtocolLXD) DeleteImage(fingerprint string) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/images/%s", fingerprint), nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// CreateImageSecret requests that LXD issues a temporary image secret
+func (r *ProtocolLXD) CreateImageSecret(fingerprint string) (*Operation, error) {
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/secret", fingerprint), nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// CreateImageAlias sets up a new image alias
+func (r *ProtocolLXD) CreateImageAlias(alias api.ImageAliasesPost) error {
+	// Send the request
+	_, _, err := r.query("POST", "/images/aliases", alias, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UpdateImageAlias updates the image alias definition
+func (r *ProtocolLXD) UpdateImageAlias(name string, alias api.ImageAliasesEntryPut, ETag string) error {
+	// Send the request
+	_, _, err := r.query("PUT", fmt.Sprintf("/images/aliases/%s", name), alias, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// RenameImageAlias renames an existing image alias
+func (r *ProtocolLXD) RenameImageAlias(name string, alias api.ImageAliasesEntryPost) error {
+	// Send the request
+	_, _, err := r.query("POST", fmt.Sprintf("/images/aliases/%s", name), alias, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteImageAlias removes an alias from the LXD image store
+func (r *ProtocolLXD) DeleteImageAlias(name string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/images/aliases/%s", name), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/client/lxd_networks.go b/client/lxd_networks.go
new file mode 100644
index 000000000..755bb2b84
--- /dev/null
+++ b/client/lxd_networks.go
@@ -0,0 +1,54 @@
+package lxd
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// GetNetworkNames returns a list of network names
+func (r *ProtocolLXD) GetNetworkNames() ([]string, error) {
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/networks", nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, url := range urls {
+		fields := strings.Split(url, "/networks/")
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetNetworks returns a list of Network struct
+func (r *ProtocolLXD) GetNetworks() ([]api.Network, error) {
+	networks := []api.Network{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/networks?recursion=1", nil, "", &networks)
+	if err != nil {
+		return nil, err
+	}
+
+	return networks, nil
+}
+
+// GetNetwork returns a Network entry for the provided name
+func (r *ProtocolLXD) GetNetwork(name string) (*api.Network, string, error) {
+	network := api.Network{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/networks/%s", name), nil, "", &network)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &network, etag, nil
+}
diff --git a/client/lxd_operations.go b/client/lxd_operations.go
new file mode 100644
index 000000000..fe39c02a6
--- /dev/null
+++ b/client/lxd_operations.go
@@ -0,0 +1,43 @@
+package lxd
+
+import (
+	"fmt"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// GetOperation returns an Operation entry for the provided uuid
+func (r *ProtocolLXD) GetOperation(uuid string) (*api.Operation, string, error) {
+	op := api.Operation{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/operations/%s", uuid), nil, "", &op)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &op, etag, nil
+}
+
+// GetOperationWebsocket returns a websocket connection for the provided operation
+func (r *ProtocolLXD) GetOperationWebsocket(uuid string, secret string) (*websocket.Conn, error) {
+	path := fmt.Sprintf("/operations/%s/websocket", uuid)
+	if secret != "" {
+		path = fmt.Sprintf("%s?secret=%s", path, secret)
+	}
+
+	return r.websocket(path)
+}
+
+// DeleteOperation deletes (cancels) a running operation
+func (r *ProtocolLXD) DeleteOperation(uuid string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/operations/%s", uuid), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/client/lxd_profiles.go b/client/lxd_profiles.go
new file mode 100644
index 000000000..a11f4cb3f
--- /dev/null
+++ b/client/lxd_profiles.go
@@ -0,0 +1,100 @@
+package lxd
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// Profile handling functions
+
+// GetProfileNames returns a list of available profile names
+func (r *ProtocolLXD) GetProfileNames() ([]string, error) {
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/profiles", nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, url := range urls {
+		fields := strings.Split(url, "/profiles/")
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetProfiles returns a list of available Profile structs
+func (r *ProtocolLXD) GetProfiles() ([]api.Profile, error) {
+	profiles := []api.Profile{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/profiles?recursion=1", nil, "", &profiles)
+	if err != nil {
+		return nil, err
+	}
+
+	return profiles, nil
+}
+
+// GetProfile returns a Profile entry for the provided name
+func (r *ProtocolLXD) GetProfile(name string) (*api.Profile, string, error) {
+	profile := api.Profile{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/profiles/%s", name), nil, "", &profile)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &profile, etag, nil
+}
+
+// CreateProfile defines a new container profile
+func (r *ProtocolLXD) CreateProfile(profile api.ProfilesPost) error {
+	// Send the request
+	_, _, err := r.query("POST", "/profiles", profile, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UpdateProfile updates the profile to match the provided Profile struct
+func (r *ProtocolLXD) UpdateProfile(name string, profile api.ProfilePut, ETag string) error {
+	// Send the request
+	_, _, err := r.query("PUT", fmt.Sprintf("/profiles/%s", name), profile, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// RenameProfile renames an existing profile entry
+func (r *ProtocolLXD) RenameProfile(name string, profile api.ProfilePost) error {
+	// Send the request
+	_, _, err := r.query("POST", fmt.Sprintf("/profiles/%s", name), profile, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteProfile deletes a profile
+func (r *ProtocolLXD) DeleteProfile(name string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/profiles/%s", name), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/client/lxd_server.go b/client/lxd_server.go
new file mode 100644
index 000000000..fbae67fb3
--- /dev/null
+++ b/client/lxd_server.go
@@ -0,0 +1,50 @@
+package lxd
+
+import (
+	"github.com/lxc/lxd/shared/api"
+)
+
+// Server handling functions
+
+// GetServer returns the server status as a Server struct
+func (r *ProtocolLXD) GetServer() (*api.Server, string, error) {
+	server := api.Server{}
+
+	// Return the cached entry if present
+	if r.server != nil {
+		return r.server, "", nil
+	}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", "", nil, "", &server)
+	if err != nil {
+		return nil, "", err
+	}
+
+	// Add the value to the cache
+	r.server = &server
+
+	return &server, etag, nil
+}
+
+// UpdateServer updates the server status to match the provided Server struct
+func (r *ProtocolLXD) UpdateServer(server api.ServerPut, ETag string) error {
+	// Send the request
+	_, _, err := r.query("PUT", "", server, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// HasExtension returns true if the server supports a given API extension
+func (r *ProtocolLXD) HasExtension(extension string) bool {
+	for _, entry := range r.server.APIExtensions {
+		if entry == extension {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/client/operations.go b/client/operations.go
new file mode 100644
index 000000000..3aedf807e
--- /dev/null
+++ b/client/operations.go
@@ -0,0 +1,221 @@
+package lxd
+
+import (
+	"encoding/json"
+	"fmt"
+	"sync"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// The Operation type represents an ongoing LXD operation (asynchronous processing)
+type Operation struct {
+	api.Operation
+
+	r            *ProtocolLXD
+	listener     *EventListener
+	listenerLock sync.Mutex
+	chActive     chan bool
+}
+
+// AddHandler adds a function to be called whenever an event is received
+func (op *Operation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
+	// Make sure we have a listener setup
+	err := op.setupListener()
+	if err != nil {
+		return nil, err
+	}
+
+	// Make sure we're not racing with ourselves
+	op.listenerLock.Lock()
+	defer op.listenerLock.Unlock()
+
+	// If we're done already, just return
+	if op.StatusCode.IsFinal() {
+		return nil, nil
+	}
+
+	// Wrap the function to filter unwanted messages
+	wrapped := func(data interface{}) {
+		newOp := op.extractOperation(data)
+		if newOp == nil {
+			return
+		}
+
+		function(*newOp)
+	}
+
+	return op.listener.AddHandler([]string{"operation"}, wrapped)
+}
+
+// Cancel will request that LXD cancels the operation (if supported)
+func (op *Operation) Cancel() error {
+	return op.r.DeleteOperation(op.ID)
+}
+
+// GetWebsocket returns a raw websocket connection from the operation
+func (op *Operation) GetWebsocket(secret string) (*websocket.Conn, error) {
+	return op.r.GetOperationWebsocket(op.ID, secret)
+}
+
+// RemoveHandler removes a function to be called whenever an event is received
+func (op *Operation) RemoveHandler(target *EventTarget) error {
+	// Make sure we're not racing with ourselves
+	op.listenerLock.Lock()
+	defer op.listenerLock.Unlock()
+
+	// If the listener is gone, just return
+	if op.listener == nil {
+		return nil
+	}
+
+	return op.listener.RemoveHandler(target)
+}
+
+// Refresh pulls the current version of the operation and updates the struct
+func (op *Operation) Refresh() error {
+	// Don't bother with a manual update if we are listening for events
+	if op.listener != nil {
+		return nil
+	}
+
+	// Get the current version of the operation
+	newOp, _, err := op.r.GetOperation(op.ID)
+	if err != nil {
+		return err
+	}
+
+	// Update the operation struct
+	op.Operation = *newOp
+
+	return nil
+}
+
+// Wait lets you wait until the operation reaches a final state
+func (op *Operation) Wait() error {
+	// Check if not done already
+	if op.StatusCode.IsFinal() {
+		if op.Err != "" {
+			return fmt.Errorf(op.Err)
+		}
+
+		return nil
+	}
+
+	// Make sure we have a listener setup
+	err := op.setupListener()
+	if err != nil {
+		return err
+	}
+
+	<-op.chActive
+
+	// We're done, parse the result
+	if op.Err != "" {
+		return fmt.Errorf(op.Err)
+	}
+
+	return nil
+}
+
+func (op *Operation) setupListener() error {
+	// Make sure we're not racing with ourselves
+	op.listenerLock.Lock()
+	defer op.listenerLock.Unlock()
+
+	// We already have a listener setup
+	if op.listener != nil {
+		return nil
+	}
+
+	// Get a new listener
+	listener, err := op.r.GetEvents()
+	if err != nil {
+		return err
+	}
+
+	// Setup the handler
+	chReady := make(chan bool)
+	_, err = listener.AddHandler([]string{"operation"}, func(data interface{}) {
+		<-chReady
+
+		// Get an operation struct out of this data
+		newOp := op.extractOperation(data)
+		if newOp == nil {
+			return
+		}
+
+		// Update the struct
+		op.Operation = *newOp
+
+		// And check if we're done
+		if op.StatusCode.IsFinal() {
+			// Make sure we're not racing with ourselves
+			op.listenerLock.Lock()
+			defer op.listenerLock.Unlock()
+
+			op.listener.Disconnect()
+			op.listener = nil
+			close(op.chActive)
+			return
+		}
+	})
+	if err != nil {
+		listener.Disconnect()
+		return err
+	}
+
+	// And do a manual refresh to avoid races
+	err = op.Refresh()
+	if err != nil {
+		listener.Disconnect()
+		return err
+	}
+
+	// Check if not done already
+	if op.StatusCode.IsFinal() {
+		listener.Disconnect()
+		close(op.chActive)
+
+		if op.Err != "" {
+			return fmt.Errorf(op.Err)
+		}
+
+		return nil
+	}
+
+	// Start processing background updates
+	op.listener = listener
+	close(chReady)
+
+	return nil
+}
+
+func (op *Operation) extractOperation(data interface{}) *api.Operation {
+	// Extract the metadata
+	meta, ok := data.(map[string]interface{})["metadata"]
+	if !ok {
+		return nil
+	}
+
+	// And attempt to decode it as JSON operation data
+	encoded, err := json.Marshal(meta)
+	if err != nil {
+		return nil
+	}
+
+	newOp := api.Operation{}
+	err = json.Unmarshal(encoded, &newOp)
+	if err != nil {
+		return nil
+	}
+
+	// And now check that it's what we want
+	if newOp.ID != op.ID {
+		return nil
+	}
+
+	return &newOp
+}
diff --git a/client/simplestreams.go b/client/simplestreams.go
new file mode 100644
index 000000000..a69a2ea8d
--- /dev/null
+++ b/client/simplestreams.go
@@ -0,0 +1,17 @@
+package lxd
+
+import (
+	"net/http"
+
+	"github.com/lxc/lxd/shared/simplestreams"
+)
+
+// ProtocolSimpleStreams implements a SimpleStreams API client
+type ProtocolSimpleStreams struct {
+	ssClient *simplestreams.SimpleStreams
+
+	http            *http.Client
+	httpHost        string
+	httpUserAgent   string
+	httpCertificate string
+}
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
new file mode 100644
index 000000000..1ff5fefbe
--- /dev/null
+++ b/client/simplestreams_images.go
@@ -0,0 +1,169 @@
+package lxd
+
+import (
+	"fmt"
+	"github.com/lxc/lxd/shared/api"
+	"strings"
+)
+
+// Image handling functions
+
+// GetImages returns a list of available images as Image structs
+func (r *ProtocolSimpleStreams) GetImages() ([]api.Image, error) {
+	return r.ssClient.ListImages()
+}
+
+// GetImageFingerprints returns a list of available image fingerprints
+func (r *ProtocolSimpleStreams) GetImageFingerprints() ([]string, error) {
+	// Get all the images from simplestreams
+	images, err := r.ssClient.ListImages()
+	if err != nil {
+		return nil, err
+	}
+
+	// And now extract just the fingerprints
+	fingerprints := []string{}
+	for _, img := range images {
+		fingerprints = append(fingerprints, img.Fingerprint)
+	}
+
+	return fingerprints, nil
+}
+
+// GetImage returns an Image struct for the provided fingerprint
+func (r *ProtocolSimpleStreams) GetImage(fingerprint string) (*api.Image, string, error) {
+	image, err := r.ssClient.GetImage(fingerprint)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return image, "", err
+}
+
+// GetImageFile downloads an image from the server, returning an ImageFileResponse struct
+func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRequest) (*ImageFileResponse, error) {
+	// Sanity checks
+	if req.MetaFile == nil && req.RootfsFile == nil {
+		return nil, fmt.Errorf("No file requested")
+	}
+
+	// Get the file list
+	files, err := r.ssClient.GetFiles(fingerprint)
+	if err != nil {
+		return nil, err
+	}
+
+	// Prepare the response
+	resp := ImageFileResponse{}
+
+	// Download the LXD image file
+	meta, ok := files["meta"]
+	if ok && req.MetaFile != nil {
+		// Try over http
+		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), meta.Path)
+
+		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
+		if err != nil {
+			// Try over https
+			url = fmt.Sprintf("%s/%s", r.httpHost, meta.Path)
+			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		parts := strings.Split(meta.Path, "/")
+		resp.MetaName = parts[len(parts)-1]
+		resp.MetaSize = size
+	}
+
+	// Download the rootfs
+	rootfs, ok := files["root"]
+	if ok && req.RootfsFile != nil {
+		// Try over http
+		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), rootfs.Path)
+
+		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+		if err != nil {
+			// Try over https
+			url = fmt.Sprintf("%s/%s", r.httpHost, rootfs.Path)
+			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		parts := strings.Split(rootfs.Path, "/")
+		resp.RootfsName = parts[len(parts)-1]
+		resp.RootfsSize = size
+	}
+
+	return &resp, nil
+}
+
+// GetPrivateImage isn't relevant for the simplestreams protocol
+func (r *ProtocolSimpleStreams) GetPrivateImage(fingerprint string, secret string) (*api.Image, string, error) {
+	return nil, "", fmt.Errorf("Private images aren't supported by the simplestreams protocol")
+}
+
+// GetPrivateImageFile isn't relevant for the simplestreams protocol
+func (r *ProtocolSimpleStreams) GetPrivateImageFile(fingerprint string, secret string, req ImageFileRequest) (*ImageFileResponse, error) {
+	return nil, fmt.Errorf("Private images aren't supported by the simplestreams protocol")
+}
+
+// GetImageAliases returns the list of available aliases as ImageAliasesEntry structs
+func (r *ProtocolSimpleStreams) GetImageAliases() ([]api.ImageAliasesEntry, error) {
+	return r.ssClient.ListAliases()
+}
+
+// GetImageAliasNames returns the list of available alias names
+func (r *ProtocolSimpleStreams) GetImageAliasNames() ([]string, error) {
+	// Get all the images from simplestreams
+	aliases, err := r.ssClient.ListAliases()
+	if err != nil {
+		return nil, err
+	}
+
+	// And now extract just the names
+	names := []string{}
+	for _, alias := range aliases {
+		names = append(names, alias.Name)
+	}
+
+	return names, nil
+}
+
+// GetImageAlias returns an existing alias as an ImageAliasesEntry struct
+func (r *ProtocolSimpleStreams) GetImageAlias(name string) (*api.ImageAliasesEntry, string, error) {
+	alias, err := r.ssClient.GetAlias(name)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return alias, "", err
+}
+
+// CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
+func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*Operation, error) {
+	// Prepare the copy request
+	req := api.ImagesPost{
+		Source: &api.ImagesPostSource{
+			ImageSource: api.ImageSource{
+				Certificate: r.httpCertificate,
+				Protocol:    "simplestreams",
+				Server:      r.httpHost,
+			},
+			Fingerprint: image.Fingerprint,
+			Mode:        "pull",
+			Type:        "image",
+		},
+	}
+
+	// Process the arguments
+	if args != nil {
+		req.AutoUpdate = args.AutoUpdate
+		req.Public = args.Public
+	}
+
+	return target.CreateImage(req)
+}
diff --git a/client/util.go b/client/util.go
new file mode 100644
index 000000000..a2f635c89
--- /dev/null
+++ b/client/util.go
@@ -0,0 +1,145 @@
+package lxd
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"net/url"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/ioprogress"
+)
+
+func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
+	// Get the TLS configuration
+	tlsConfig, err := shared.GetTLSConfigMem(tlsClientCert, tlsClientKey, tlsServerCert)
+	if err != nil {
+		return nil, err
+	}
+
+	// Define the http transport
+	transport := &http.Transport{
+		TLSClientConfig:   tlsConfig,
+		Dial:              shared.RFC3493Dialer,
+		Proxy:             shared.ProxyFromEnvironment,
+		DisableKeepAlives: true,
+	}
+
+	// Allow overriding the proxy
+	if proxy != nil {
+		transport.Proxy = proxy
+	}
+
+	// Define the http client
+	client := http.Client{
+		Transport: transport,
+	}
+
+	// Setup redirect policy
+	client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+		// Replicate the headers
+		req.Header = via[len(via)-1].Header
+
+		return nil
+	}
+
+	return &client, nil
+}
+
+func unixHTTPClient(path string) (*http.Client, error) {
+	// Setup a Unix socket dialer
+	unixDial := func(network, addr string) (net.Conn, error) {
+		raddr, err := net.ResolveUnixAddr("unix", path)
+		if err != nil {
+			return nil, err
+		}
+
+		return net.DialUnix("unix", nil, raddr)
+	}
+
+	// Define the http transport
+	transport := &http.Transport{
+		Dial:              unixDial,
+		DisableKeepAlives: true,
+	}
+
+	// Define the http client
+	client := http.Client{
+		Transport: transport,
+	}
+
+	// Setup redirect policy
+	client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+		// Replicate the headers
+		req.Header = via[len(via)-1].Header
+
+		return nil
+	}
+
+	return &client, nil
+}
+
+func downloadFileSha256(httpClient *http.Client, useragent string, progress func(progress ProgressData), filename string, url string, hash string, target io.WriteSeeker) (int64, error) {
+	// Always seek to the beginning
+	target.Seek(0, 0)
+
+	// Prepare the download request
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return -1, err
+	}
+
+	if useragent != "" {
+		req.Header.Set("User-Agent", useragent)
+	}
+
+	// Start the request
+	r, err := httpClient.Do(req)
+	if err != nil {
+		return -1, err
+	}
+	defer r.Body.Close()
+
+	if r.StatusCode != http.StatusOK {
+		return -1, fmt.Errorf("Unable to fetch %s: %s", url, r.Status)
+	}
+
+	// Handle the data
+	body := r.Body
+	if progress != nil {
+		body = &ioprogress.ProgressReader{
+			ReadCloser: r.Body,
+			Tracker: &ioprogress.ProgressTracker{
+				Length: r.ContentLength,
+				Handler: func(percent int64, speed int64) {
+					if filename != "" {
+						progress(ProgressData{Text: fmt.Sprintf("%s: %d%% (%s/s)", filename, percent, shared.GetByteSizeString(speed, 2))})
+					} else {
+						progress(ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed, 2))})
+					}
+				},
+			},
+		}
+	}
+
+	sha256 := sha256.New()
+	size, err := io.Copy(io.MultiWriter(target, sha256), body)
+	if err != nil {
+		return -1, err
+	}
+
+	result := fmt.Sprintf("%x", sha256.Sum(nil))
+	if result != hash {
+		return -1, fmt.Errorf("Hash mismatch for %s: %s != %s", url, result, hash)
+	}
+
+	return size, nil
+}
+
+type nullReadWriteCloser int
+
+func (nullReadWriteCloser) Close() error                { return nil }
+func (nullReadWriteCloser) Write(p []byte) (int, error) { return len(p), nil }
+func (nullReadWriteCloser) Read(p []byte) (int, error)  { return 0, io.EOF }

From 6d0bf13c7cbf575c7a59d0a8e7edf5800e6c9a43 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 31 Mar 2017 18:30:39 -0400
Subject: [PATCH 0821/1193] lxc/config: Add new config handling code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This isn't used for the client itself yet, but is used by lxd-benchmark
and can similarly be used by anyone who wants to use the same remote
syntax as our client supports.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config/config.go  |  39 +++++++++++++++
 lxc/config/default.go |  52 ++++++++++++++++++++
 lxc/config/file.go    |  73 +++++++++++++++++++++++++++
 lxc/config/remote.go  | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 298 insertions(+)
 create mode 100644 lxc/config/config.go
 create mode 100644 lxc/config/default.go
 create mode 100644 lxc/config/file.go
 create mode 100644 lxc/config/remote.go

diff --git a/lxc/config/config.go b/lxc/config/config.go
new file mode 100644
index 000000000..7e5f3df1b
--- /dev/null
+++ b/lxc/config/config.go
@@ -0,0 +1,39 @@
+package config
+
+import (
+	"fmt"
+	"path/filepath"
+)
+
+// Config holds settings to be used by a client or daemon
+type Config struct {
+	// DefaultRemote holds the remote daemon name from the Remotes map
+	// that the client should communicate with by default
+	DefaultRemote string `yaml:"default-remote"`
+
+	// Remotes defines a map of remote daemon names to the details for
+	// communication with the named daemon
+	Remotes map[string]Remote `yaml:"remotes"`
+
+	// Command line aliases for `lxc`
+	Aliases map[string]string `yaml:"aliases"`
+
+	// Configuration directory
+	ConfigDir string `yaml:"-"`
+
+	// The UserAgent to pass for all queries
+	UserAgent string `yaml:"-"`
+}
+
+// ConfigPath returns a joined path of the configuration directory and passed arguments
+func (c *Config) ConfigPath(paths ...string) string {
+	path := []string{c.ConfigDir}
+	path = append(path, paths...)
+
+	return filepath.Join(path...)
+}
+
+// ServerCertPath returns the path for the remote's server certificate
+func (c *Config) ServerCertPath(remote string) string {
+	return c.ConfigPath("servercerts", fmt.Sprintf("%s.crt", remote))
+}
diff --git a/lxc/config/default.go b/lxc/config/default.go
new file mode 100644
index 000000000..b16af972c
--- /dev/null
+++ b/lxc/config/default.go
@@ -0,0 +1,52 @@
+package config
+
+// LocalRemote is the default local remote (over the LXD unix socket)
+var LocalRemote = Remote{
+	Addr:   "unix://",
+	Static: true,
+	Public: false,
+}
+
+// ImagesRemote is the community image server (over simplestreams)
+var ImagesRemote = Remote{
+	Addr:     "https://images.linuxcontainers.org",
+	Public:   true,
+	Protocol: "simplestreams",
+}
+
+// UbuntuRemote is the Ubuntu image server (over simplestreams)
+var UbuntuRemote = Remote{
+	Addr:     "https://cloud-images.ubuntu.com/releases",
+	Static:   true,
+	Public:   true,
+	Protocol: "simplestreams",
+}
+
+// UbuntuDailyRemote is the Ubuntu daily image server (over simplestreams)
+var UbuntuDailyRemote = Remote{
+	Addr:     "https://cloud-images.ubuntu.com/daily",
+	Static:   true,
+	Public:   true,
+	Protocol: "simplestreams",
+}
+
+// StaticRemotes is the list of remotes which can't be removed
+var StaticRemotes = map[string]Remote{
+	"local":        LocalRemote,
+	"ubuntu":       UbuntuRemote,
+	"ubuntu-daily": UbuntuDailyRemote,
+}
+
+// DefaultRemotes is the list of default remotes
+var DefaultRemotes = map[string]Remote{
+	"images":       ImagesRemote,
+	"local":        LocalRemote,
+	"ubuntu":       UbuntuRemote,
+	"ubuntu-daily": UbuntuDailyRemote,
+}
+
+// DefaultConfig is the default configuration
+var DefaultConfig = Config{
+	Remotes:       DefaultRemotes,
+	DefaultRemote: "local",
+}
diff --git a/lxc/config/file.go b/lxc/config/file.go
new file mode 100644
index 000000000..220f29b2e
--- /dev/null
+++ b/lxc/config/file.go
@@ -0,0 +1,73 @@
+package config
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+
+	"gopkg.in/yaml.v2"
+
+	"github.com/lxc/lxd/shared"
+)
+
+// LoadConfig reads the configuration from the config path; if the path does
+// not exist, it returns a default configuration.
+func LoadConfig(path string) (*Config, error) {
+	// Open the config file
+	content, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to read the configuration file: %v", err)
+	}
+
+	// Decode the yaml document
+	c := Config{}
+	err = yaml.Unmarshal(content, &c)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to decode the configuration: %v", err)
+	}
+
+	// Set default values
+	if c.Remotes == nil {
+		c.Remotes = make(map[string]Remote)
+	}
+	c.ConfigDir = filepath.Dir(path)
+
+	// Apply the static remotes
+	for k, v := range StaticRemotes {
+		c.Remotes[k] = v
+	}
+
+	return &c, nil
+}
+
+// SaveConfig writes the provided configuration to the config file.
+func (c *Config) SaveConfig(path string) error {
+	// Create a new copy for the config file
+	conf := Config{}
+	err := shared.DeepCopy(c, conf)
+	if err != nil {
+		return fmt.Errorf("Unable to copy the configuration: %v", err)
+	}
+
+	// Remove the static remotes
+	for k := range StaticRemotes {
+		delete(conf.Remotes, k)
+	}
+
+	// Create the config file (or truncate an existing one)
+	f, err := os.Create(path)
+	if err != nil {
+		return fmt.Errorf("Unable to create the configuration file: %v", err)
+	}
+	defer f.Close()
+
+	// Write the new config
+	data, err := yaml.Marshal(c)
+	_, err = f.Write(data)
+	if err != nil {
+		return fmt.Errorf("Unable to write the configuration: %v", err)
+	}
+
+	return nil
+}
diff --git a/lxc/config/remote.go b/lxc/config/remote.go
new file mode 100644
index 000000000..51f3d3e97
--- /dev/null
+++ b/lxc/config/remote.go
@@ -0,0 +1,134 @@
+package config
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/shared"
+)
+
+// Remote holds details for communication with a remote daemon
+type Remote struct {
+	Addr     string `yaml:"addr"`
+	Public   bool   `yaml:"public"`
+	Protocol string `yaml:"protocol,omitempty"`
+	Static   bool   `yaml:"-"`
+}
+
+// ParseRemote splits remote and object
+func (c *Config) ParseRemote(raw string) (string, string, error) {
+	result := strings.SplitN(raw, ":", 2)
+	if len(result) == 1 {
+		return c.DefaultRemote, result[0], nil
+	}
+
+	_, ok := c.Remotes[result[0]]
+	if !ok {
+		return "", "", fmt.Errorf("The remote \"%s\" doesn't exist", result[0])
+	}
+
+	return result[0], result[1], nil
+}
+
+// GetContainerServer returns a ContainerServer struct for the remote
+func (c *Config) GetContainerServer(name string) (lxd.ContainerServer, error) {
+	// Get the remote
+	remote, ok := c.Remotes[name]
+	if !ok {
+		return nil, fmt.Errorf("The remote \"%s\" doesn't exist", name)
+	}
+
+	// Sanity checks
+	if remote.Public || remote.Protocol == "simplestreams" {
+		return nil, fmt.Errorf("The remote isn't a private LXD server")
+	}
+
+	// Get connection arguments
+	args := c.getConnectionArgs(name)
+
+	// Unix socket
+	if strings.HasPrefix(remote.Addr, "unix:") {
+		d, err := lxd.ConnectLXDUnix(strings.TrimPrefix(strings.TrimPrefix(remote.Addr, "unix:"), "//"), &args)
+		if err != nil {
+			return nil, err
+		}
+
+		return d, nil
+	}
+
+	// HTTPs
+	if args.TLSClientCert == "" || args.TLSClientKey == "" {
+		return nil, fmt.Errorf("Missing TLS client certificate and key")
+	}
+
+	d, err := lxd.ConnectLXD(remote.Addr, &args)
+	if err != nil {
+		return nil, err
+	}
+
+	return d, nil
+}
+
+// GetImageServer returns a ImageServer struct for the remote
+func (c *Config) GetImageServer(name string) (lxd.ImageServer, error) {
+	// Get the remote
+	remote, ok := c.Remotes[name]
+	if !ok {
+		return nil, fmt.Errorf("The remote \"%s\" doesn't exist", name)
+	}
+
+	// Get connection arguments
+	args := c.getConnectionArgs(name)
+
+	// Unix socket
+	if strings.HasPrefix(remote.Addr, "unix:") {
+		d, err := lxd.ConnectLXDUnix(strings.TrimPrefix(strings.TrimPrefix(remote.Addr, "unix:"), "//"), &args)
+		if err != nil {
+			return nil, err
+		}
+
+		return d, nil
+	}
+
+	// HTTPs (simplestreams)
+	if remote.Protocol == "simplestreams" {
+		d, err := lxd.ConnectSimpleStreams(remote.Addr, &args)
+		if err != nil {
+			return nil, err
+		}
+
+		return d, nil
+	}
+
+	// HTTPs (LXD)
+	d, err := lxd.ConnectPublicLXD(remote.Addr, &args)
+	if err != nil {
+		return nil, err
+	}
+
+	return d, nil
+}
+
+func (c *Config) getConnectionArgs(name string) lxd.ConnectionArgs {
+	args := lxd.ConnectionArgs{
+		UserAgent: c.UserAgent,
+	}
+
+	// Client certificate
+	if !shared.PathExists(c.ConfigPath("client.crt")) {
+		args.TLSClientCert = c.ConfigPath("client.crt")
+	}
+
+	// Client key
+	if !shared.PathExists(c.ConfigPath("client.key")) {
+		args.TLSClientKey = c.ConfigPath("client.key")
+	}
+
+	// Server certificate
+	if shared.PathExists(c.ServerCertPath(name)) {
+		args.TLSServerCert = c.ServerCertPath(name)
+	}
+
+	return args
+}

From b6a31171fa3815a4ee96b0d6a7311f68be4fb707 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 30 Mar 2017 17:22:15 -0400
Subject: [PATCH 0822/1193] doc: Update README.md for new API client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 8e3300e9e..b9e2a7847 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ To easily see what LXD is about, you can [try it online](https://linuxcontainers
 
 ## Status
 
-* GoDoc: [![GoDoc](https://godoc.org/github.com/lxc/lxd?status.svg)](https://godoc.org/github.com/lxc/lxd)
+* GoDoc: [![GoDoc](https://godoc.org/github.com/lxc/lxd/client?status.svg)](https://godoc.org/github.com/lxc/lxd/client)
 * Jenkins (Linux): [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/)
 * Travis (macOS): [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd/)
 * AppVeyor (Windows): [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd/)

From 2481fbaa7879dbae7d59da18be8c261d16c4157f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 30 Mar 2017 17:28:50 -0400
Subject: [PATCH 0823/1193] ci: Update for new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 .appveyor.yml | 3 ++-
 .travis.yml   | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/.appveyor.yml b/.appveyor.yml
index d9cbb6c17..ddc6fcfef 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -19,8 +19,9 @@ build_script:
 test_script:
 - cmd: |-
     go test -v ./
-    go test -v ./shared
+    go test -v ./client
     go test -v ./lxc
+    go test -v ./shared
 
 after_test:
   # powershell capture command output into environment variable
diff --git a/.travis.yml b/.travis.yml
index 73e98ada8..781302bb2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,8 +20,9 @@ install:
 script:
   - "make client"
   - "go test ./"
-  - "go test ./shared"
+  - "go test ./client"
   - "go test ./lxc"
+  - "go test ./shared"
 
 notifications:
   webhooks: https://linuxcontainers.org/webhook-lxcbot/

From a2f1fe0e2f2496760ad7a73de0de7ce5f4f32ab4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 31 Mar 2017 15:21:36 -0400
Subject: [PATCH 0824/1193] tests: Run golint on client/ and lxc/config/
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/static_analysis.sh | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index a26492dac..abb65f43b 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -36,6 +36,8 @@ test_static_analysis() {
 
     ## golint
     if which golint >/dev/null 2>&1; then
+      golint -set_exit_status client/
+      golint -set_exit_status lxc/config/
       golint -set_exit_status shared/api/
       golint -set_exit_status shared/gnuflag/
       golint -set_exit_status shared/i18n/

From bb7a55423397f69de92e6f2890fc37cc74553428 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 7 Mar 2017 00:21:07 -0500
Subject: [PATCH 0825/1193] Port main_activateifneded to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_activateifneeded.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index 7f831f8e9..da6de2b44 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -39,7 +39,7 @@ func cmdActivateIfNeeded() error {
 	value := daemonConfig["core.https_address"].Get()
 	if value != "" {
 		shared.LogDebugf("Daemon has core.https_address set, activating...")
-		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+		_, err := lxd.ConnectLXDUnix("", nil)
 		return err
 	}
 
@@ -67,13 +67,13 @@ func cmdActivateIfNeeded() error {
 
 		if c.IsRunning() {
 			shared.LogDebugf("Daemon has running containers, activating...")
-			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			_, err := lxd.ConnectLXDUnix("", nil)
 			return err
 		}
 
 		if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) {
 			shared.LogDebugf("Daemon has auto-started containers, activating...")
-			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			_, err := lxd.ConnectLXDUnix("", nil)
 			return err
 		}
 	}

From e3b044819d114690a985e982cecae66f9d458265 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 7 Mar 2017 02:23:23 -0500
Subject: [PATCH 0826/1193] Port main_callhook to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_callhook.go | 32 +++++++++-----------------------
 1 file changed, 9 insertions(+), 23 deletions(-)

diff --git a/lxd/main_callhook.go b/lxd/main_callhook.go
index 31e2231de..c8507fd83 100644
--- a/lxd/main_callhook.go
+++ b/lxd/main_callhook.go
@@ -2,15 +2,14 @@ package main
 
 import (
 	"fmt"
-	"net/http"
 	"os"
 	"time"
 
-	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/client"
 )
 
 func cmdCallHook(args []string) error {
+	// Parse the arguments
 	if len(args) < 4 {
 		return fmt.Errorf("Invalid arguments")
 	}
@@ -20,18 +19,14 @@ func cmdCallHook(args []string) error {
 	state := args[3]
 	target := ""
 
-	err := os.Setenv("LXD_DIR", path)
+	// Connect to LXD
+	c, err := lxd.ConnectLXDUnix(fmt.Sprintf("%s/unix.socket", path), nil)
 	if err != nil {
 		return err
 	}
 
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	url := fmt.Sprintf("%s/internal/containers/%s/on%s", c.BaseURL, id, state)
-
+	// Prepare the request URL
+	url := fmt.Sprintf("/internal/containers/%s/on%s", id, state)
 	if state == "stop" {
 		target = os.Getenv("LXC_TARGET")
 		if target == "" {
@@ -40,20 +35,10 @@ func cmdCallHook(args []string) error {
 		url = fmt.Sprintf("%s?target=%s", url, target)
 	}
 
-	req, err := http.NewRequest("GET", url, nil)
-	if err != nil {
-		return err
-	}
-
+	// Setup the request
 	hook := make(chan error, 1)
 	go func() {
-		raw, err := c.Http.Do(req)
-		if err != nil {
-			hook <- err
-			return
-		}
-
-		_, err = lxd.HoistResponse(raw, api.SyncResponse)
+		_, _, err := c.RawQuery("GET", url, nil, "")
 		if err != nil {
 			hook <- err
 			return
@@ -62,6 +47,7 @@ func cmdCallHook(args []string) error {
 		hook <- nil
 	}()
 
+	// Handle the timeout
 	select {
 	case err := <-hook:
 		if err != nil {

From 85245d348b91ce1860c41b921d8b17e0bcba8fdd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 7 Mar 2017 02:23:57 -0500
Subject: [PATCH 0827/1193] Port main_ready to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_ready.go | 19 +++----------------
 1 file changed, 3 insertions(+), 16 deletions(-)

diff --git a/lxd/main_ready.go b/lxd/main_ready.go
index 109323412..fb4f0dfe5 100644
--- a/lxd/main_ready.go
+++ b/lxd/main_ready.go
@@ -1,29 +1,16 @@
 package main
 
 import (
-	"net/http"
-
-	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/client"
 )
 
 func cmdReady() error {
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
-	if err != nil {
-		return err
-	}
-
-	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/ready", nil)
-	if err != nil {
-		return err
-	}
-
-	raw, err := c.Http.Do(req)
+	c, err := lxd.ConnectLXDUnix("", nil)
 	if err != nil {
 		return err
 	}
 
-	_, err = lxd.HoistResponse(raw, api.SyncResponse)
+	_, _, err = c.RawQuery("PUT", "/internal/ready", nil, "")
 	if err != nil {
 		return err
 	}

From 6285b0b9ff8eb8a160e0a74f97caac49a4799ec5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 7 Mar 2017 02:24:09 -0500
Subject: [PATCH 0828/1193] Port main_waitready to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_waitready.go | 20 +++-----------------
 1 file changed, 3 insertions(+), 17 deletions(-)

diff --git a/lxd/main_waitready.go b/lxd/main_waitready.go
index 74534b3e6..057abe758 100644
--- a/lxd/main_waitready.go
+++ b/lxd/main_waitready.go
@@ -2,11 +2,9 @@ package main
 
 import (
 	"fmt"
-	"net/http"
 	"time"
 
-	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/client"
 )
 
 func cmdWaitReady() error {
@@ -21,25 +19,13 @@ func cmdWaitReady() error {
 	finger := make(chan error, 1)
 	go func() {
 		for {
-			c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			c, err := lxd.ConnectLXDUnix("", nil)
 			if err != nil {
 				time.Sleep(500 * time.Millisecond)
 				continue
 			}
 
-			req, err := http.NewRequest("GET", c.BaseURL+"/internal/ready", nil)
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			raw, err := c.Http.Do(req)
-			if err != nil {
-				time.Sleep(500 * time.Millisecond)
-				continue
-			}
-
-			_, err = lxd.HoistResponse(raw, api.SyncResponse)
+			_, _, err = c.RawQuery("GET", "/internal/ready", nil, "")
 			if err != nil {
 				time.Sleep(500 * time.Millisecond)
 				continue

From a6869c24c14c16d7279a988134cf3d0cc4975b9c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Mar 2017 21:46:23 -0400
Subject: [PATCH 0829/1193] Port main_shutdown to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_shutdown.go | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/lxd/main_shutdown.go b/lxd/main_shutdown.go
index 74c380f4b..56be2aa12 100644
--- a/lxd/main_shutdown.go
+++ b/lxd/main_shutdown.go
@@ -2,10 +2,9 @@ package main
 
 import (
 	"fmt"
-	"net/http"
 	"time"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 )
 
 func cmdShutdown() error {
@@ -17,28 +16,30 @@ func cmdShutdown() error {
 		timeout = *argTimeout
 	}
 
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	c, err := lxd.ConnectLXDUnix("", nil)
 	if err != nil {
 		return err
 	}
 
-	req, err := http.NewRequest("PUT", c.BaseURL+"/internal/shutdown", nil)
+	_, _, err = c.RawQuery("PUT", "/internal/shutdown", nil, "")
 	if err != nil {
 		return err
 	}
 
-	_, err = c.Http.Do(req)
-	if err != nil {
-		return err
-	}
-
-	monitor := make(chan error, 1)
+	chMonitor := make(chan bool, 1)
 	go func() {
-		monitor <- c.Monitor(nil, func(m interface{}) {}, nil)
+		monitor, err := c.GetEvents()
+		if err != nil {
+			close(chMonitor)
+			return
+		}
+
+		monitor.Wait()
+		close(chMonitor)
 	}()
 
 	select {
-	case <-monitor:
+	case <-chMonitor:
 		break
 	case <-time.After(time.Second * time.Duration(timeout)):
 		return fmt.Errorf("LXD still running after %ds timeout.", timeout)

From 78b7a861c81af4886e3152e3062df3f21a78ecab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 22 Mar 2017 23:30:28 -0400
Subject: [PATCH 0830/1193] Port main_migratedumpsuccess to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_migratedumpsuccess.go | 24 ++++++++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/lxd/main_migratedumpsuccess.go b/lxd/main_migratedumpsuccess.go
index c4c8fb150..663e6f955 100644
--- a/lxd/main_migratedumpsuccess.go
+++ b/lxd/main_migratedumpsuccess.go
@@ -2,8 +2,10 @@ package main
 
 import (
 	"fmt"
+	"net/url"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/shared/api"
 )
 
 func cmdMigrateDumpSuccess(args []string) error {
@@ -11,16 +13,30 @@ func cmdMigrateDumpSuccess(args []string) error {
 		return fmt.Errorf("bad migrate dump success args %s", args)
 	}
 
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	c, err := lxd.ConnectLXDUnix("", nil)
 	if err != nil {
 		return err
 	}
 
-	conn, err := c.Websocket(args[1], args[2])
+	conn, err := c.RawWebsocket(fmt.Sprintf("/1.0/operations/%s/websocket?%s", args[1], url.Values{"secret": []string{args[2]}}))
 	if err != nil {
 		return err
 	}
 	conn.Close()
 
-	return c.WaitForSuccess(args[1])
+	resp, _, err := c.RawQuery("GET", fmt.Sprintf("/1.0/operations/%s/wait", args[1]), nil, "")
+	if err != nil {
+		return err
+	}
+
+	op, err := resp.MetadataAsOperation()
+	if err != nil {
+		return err
+	}
+
+	if op.StatusCode == api.Success {
+		return nil
+	}
+
+	return fmt.Errorf(op.Err)
 }

From de4eadd9075d7f7762282696384e40f98f4598e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 00:34:45 -0400
Subject: [PATCH 0831/1193] config: Fix SaveConfig's DeepCopy call
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config/file.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/config/file.go b/lxc/config/file.go
index 220f29b2e..716277445 100644
--- a/lxc/config/file.go
+++ b/lxc/config/file.go
@@ -45,7 +45,7 @@ func LoadConfig(path string) (*Config, error) {
 func (c *Config) SaveConfig(path string) error {
 	// Create a new copy for the config file
 	conf := Config{}
-	err := shared.DeepCopy(c, conf)
+	err := shared.DeepCopy(c, &conf)
 	if err != nil {
 		return fmt.Errorf("Unable to copy the configuration: %v", err)
 	}

From 44fb253c662a485af4d052bad80e5cd84d2878ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 5 Apr 2017 22:09:18 -0400
Subject: [PATCH 0832/1193] lxc/config: Fix path handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config/remote.go | 50 ++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 36 insertions(+), 14 deletions(-)

diff --git a/lxc/config/remote.go b/lxc/config/remote.go
index 51f3d3e97..36796cbdb 100644
--- a/lxc/config/remote.go
+++ b/lxc/config/remote.go
@@ -2,6 +2,7 @@ package config
 
 import (
 	"fmt"
+	"io/ioutil"
 	"strings"
 
 	"github.com/lxc/lxd/client"
@@ -45,11 +46,14 @@ func (c *Config) GetContainerServer(name string) (lxd.ContainerServer, error) {
 	}
 
 	// Get connection arguments
-	args := c.getConnectionArgs(name)
+	args, err := c.getConnectionArgs(name)
+	if err != nil {
+		return nil, err
+	}
 
 	// Unix socket
 	if strings.HasPrefix(remote.Addr, "unix:") {
-		d, err := lxd.ConnectLXDUnix(strings.TrimPrefix(strings.TrimPrefix(remote.Addr, "unix:"), "//"), &args)
+		d, err := lxd.ConnectLXDUnix(strings.TrimPrefix(strings.TrimPrefix(remote.Addr, "unix:"), "//"), args)
 		if err != nil {
 			return nil, err
 		}
@@ -62,7 +66,7 @@ func (c *Config) GetContainerServer(name string) (lxd.ContainerServer, error) {
 		return nil, fmt.Errorf("Missing TLS client certificate and key")
 	}
 
-	d, err := lxd.ConnectLXD(remote.Addr, &args)
+	d, err := lxd.ConnectLXD(remote.Addr, args)
 	if err != nil {
 		return nil, err
 	}
@@ -79,11 +83,14 @@ func (c *Config) GetImageServer(name string) (lxd.ImageServer, error) {
 	}
 
 	// Get connection arguments
-	args := c.getConnectionArgs(name)
+	args, err := c.getConnectionArgs(name)
+	if err != nil {
+		return nil, err
+	}
 
 	// Unix socket
 	if strings.HasPrefix(remote.Addr, "unix:") {
-		d, err := lxd.ConnectLXDUnix(strings.TrimPrefix(strings.TrimPrefix(remote.Addr, "unix:"), "//"), &args)
+		d, err := lxd.ConnectLXDUnix(strings.TrimPrefix(strings.TrimPrefix(remote.Addr, "unix:"), "//"), args)
 		if err != nil {
 			return nil, err
 		}
@@ -93,7 +100,7 @@ func (c *Config) GetImageServer(name string) (lxd.ImageServer, error) {
 
 	// HTTPs (simplestreams)
 	if remote.Protocol == "simplestreams" {
-		d, err := lxd.ConnectSimpleStreams(remote.Addr, &args)
+		d, err := lxd.ConnectSimpleStreams(remote.Addr, args)
 		if err != nil {
 			return nil, err
 		}
@@ -102,7 +109,7 @@ func (c *Config) GetImageServer(name string) (lxd.ImageServer, error) {
 	}
 
 	// HTTPs (LXD)
-	d, err := lxd.ConnectPublicLXD(remote.Addr, &args)
+	d, err := lxd.ConnectPublicLXD(remote.Addr, args)
 	if err != nil {
 		return nil, err
 	}
@@ -110,25 +117,40 @@ func (c *Config) GetImageServer(name string) (lxd.ImageServer, error) {
 	return d, nil
 }
 
-func (c *Config) getConnectionArgs(name string) lxd.ConnectionArgs {
+func (c *Config) getConnectionArgs(name string) (*lxd.ConnectionArgs, error) {
 	args := lxd.ConnectionArgs{
 		UserAgent: c.UserAgent,
 	}
 
 	// Client certificate
-	if !shared.PathExists(c.ConfigPath("client.crt")) {
-		args.TLSClientCert = c.ConfigPath("client.crt")
+	if shared.PathExists(c.ConfigPath("client.crt")) {
+		content, err := ioutil.ReadFile(c.ConfigPath("client.crt"))
+		if err != nil {
+			return nil, err
+		}
+
+		args.TLSClientCert = string(content)
 	}
 
 	// Client key
-	if !shared.PathExists(c.ConfigPath("client.key")) {
-		args.TLSClientKey = c.ConfigPath("client.key")
+	if shared.PathExists(c.ConfigPath("client.key")) {
+		content, err := ioutil.ReadFile(c.ConfigPath("client.key"))
+		if err != nil {
+			return nil, err
+		}
+
+		args.TLSClientKey = string(content)
 	}
 
 	// Server certificate
 	if shared.PathExists(c.ServerCertPath(name)) {
-		args.TLSServerCert = c.ServerCertPath(name)
+		content, err := ioutil.ReadFile(c.ServerCertPath(name))
+		if err != nil {
+			return nil, err
+		}
+
+		args.TLSServerCert = string(content)
 	}
 
-	return args
+	return &args, nil
 }

From 3905d0d74ae167287aaa2ea69b098a5f1ea2d36d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 14 Apr 2017 14:34:39 +0200
Subject: [PATCH 0833/1193] forkexec: remove os.FindProcess

Replace it by syscall.Wait4() in this case. This should remove the last traces
of FindProcess.

Closes #3037.
Possibly relates to #3157.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/main_forkexec.go | 27 ++++++++-------------------
 1 file changed, 8 insertions(+), 19 deletions(-)

diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
index 3492fac7b..bdbdcac64 100644
--- a/lxd/main_forkexec.go
+++ b/lxd/main_forkexec.go
@@ -103,30 +103,19 @@ func cmdForkExec(args []string) (int, error) {
 		return -1, fmt.Errorf("Failed sending PID of executing command: %q", err)
 	}
 
-	proc, err := os.FindProcess(status)
-	if err != nil {
+	var ws syscall.WaitStatus
+	wpid, err := syscall.Wait4(status, &ws, 0, nil)
+	if err != nil || wpid != status {
 		return -1, fmt.Errorf("Failed finding process: %q", err)
 	}
 
-	procState, err := proc.Wait()
-	if err != nil {
-		return -1, fmt.Errorf("Failed waiting on process %d: %q", status, err)
-	}
-
-	if procState.Success() {
-		return 0, nil
+	if ws.Exited() {
+		return ws.ExitStatus(), nil
 	}
 
-	exCode, ok := procState.Sys().(syscall.WaitStatus)
-	if ok {
-		if exCode.Signaled() {
-			// COMMENT(brauner): 128 + n == Fatal error signal "n"
-			return 128 + int(exCode.Signal()), nil
-		}
-
-		if exCode.Exited() {
-			return exCode.ExitStatus(), nil
-		}
+	if ws.Signaled() {
+		// 128 + n == Fatal error signal "n"
+		return 128 + int(ws.Signal()), nil
 	}
 
 	return -1, fmt.Errorf("Command failed")

From f5d98a1fb853cd991c72480a14557e844bbf0423 Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Sat, 15 Apr 2017 13:30:51 +0300
Subject: [PATCH 0834/1193] Move common data extraction to a helper function

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 lxc/list.go | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index 68e925957..cb520653e 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -309,8 +309,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 	cStatesWg.Wait()
 	cSnapshotsWg.Wait()
 
-	switch c.format {
-	case listFormatTable:
+	tableData := func() [][]string {
 		data := [][]string{}
 		for _, cInfo := range cinfos {
 			if !c.shouldShow(filters, &cInfo) {
@@ -323,14 +322,18 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 			}
 			data = append(data, col)
 		}
+		sort.Sort(byName(data))
+		return data
+	}
 
+	switch c.format {
+	case listFormatTable:
 		table := tablewriter.NewWriter(os.Stdout)
 		table.SetAutoWrapText(false)
 		table.SetAlignment(tablewriter.ALIGN_LEFT)
 		table.SetRowLine(true)
 		table.SetHeader(headers)
-		sort.Sort(byName(data))
-		table.AppendBulk(data)
+		table.AppendBulk(tableData())
 		table.Render()
 	case listFormatJSON:
 		data := make([]listContainerItem, len(cinfos))

From 308f5798c54bed51488c532762e66a58d94478f4 Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Sat, 15 Apr 2017 13:48:21 +0300
Subject: [PATCH 0835/1193] No need to sort, containers are extracted already
 sorted:

    "SELECT name FROM containers WHERE type=? ORDER BY name"
    lxd/db_containers.go:dbContainersList()
      lxd/containers_get.go:doContainersGet()
    ---web-request---
    resp, err := c.get("containers?recursion=1")
    client.go:ListContainers()
      ctslist, err := d.ListContainers()
      lxc/list.go:run()

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 lxc/list.go | 1 -
 1 file changed, 1 deletion(-)

diff --git a/lxc/list.go b/lxc/list.go
index cb520653e..634050f59 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -322,7 +322,6 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 			}
 			data = append(data, col)
 		}
-		sort.Sort(byName(data))
 		return data
 	}
 

From d3170294318d057d8494987a600870ebf2b14c98 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 17 Apr 2017 01:11:32 -0400
Subject: [PATCH 0836/1193] list: Re-add dropped sort.Sort call
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxc/list.go b/lxc/list.go
index 634050f59..aa301e9f5 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -322,6 +322,8 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 			}
 			data = append(data, col)
 		}
+
+		sort.Sort(byName(data))
 		return data
 	}
 

From c7e586d4970a68a9c98ecfd2c4c8457297a3247d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 23 Mar 2017 22:53:56 -0400
Subject: [PATCH 0837/1193] Port lxd-benchmark to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/lxd-benchmark/main.go | 84 +++++++++++++++++++++++++++++++---------------
 1 file changed, 57 insertions(+), 27 deletions(-)

diff --git a/test/lxd-benchmark/main.go b/test/lxd-benchmark/main.go
index 09fe3b044..686146249 100644
--- a/test/lxd-benchmark/main.go
+++ b/test/lxd-benchmark/main.go
@@ -8,7 +8,8 @@ import (
 	"sync"
 	"time"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -60,7 +61,7 @@ func run(args []string) error {
 	gnuflag.Parse(true)
 
 	// Connect to LXD
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	c, err := lxd.ConnectLXDUnix("", nil)
 	if err != nil {
 		return err
 	}
@@ -79,7 +80,7 @@ func logf(format string, args ...interface{}) {
 	fmt.Printf(fmt.Sprintf("[%s] %s\n", time.Now().Format(time.StampMilli), format), args...)
 }
 
-func spawnContainers(c *lxd.Client, count int, image string, privileged bool) error {
+func spawnContainers(c lxd.ContainerServer, count int, image string, privileged bool) error {
 	batch := *argParallel
 	if batch < 1 {
 		// Detect the number of parallel actions
@@ -95,7 +96,7 @@ func spawnContainers(c *lxd.Client, count int, image string, privileged bool) er
 	remainder := count % batch
 
 	// Print the test header
-	st, err := c.ServerStatus()
+	st, _, err := c.GetServer()
 	if err != nil {
 		return err
 	}
@@ -135,27 +136,47 @@ func spawnContainers(c *lxd.Client, count int, image string, privileged bool) er
 	var fingerprint string
 	if strings.Contains(image, ":") {
 		var remote string
-		remote, fingerprint = lxd.DefaultConfig.ParseRemoteAndContainer(image)
 
-		if fingerprint == "" {
-			fingerprint = "default"
+		defaultConfig := config.DefaultConfig
+		defaultConfig.UserAgent = version.UserAgent
+
+		remote, fingerprint, err = defaultConfig.ParseRemote(image)
+		if err != nil {
+			return err
 		}
 
-		d, err := lxd.NewClient(&lxd.DefaultConfig, remote)
+		d, err := defaultConfig.GetImageServer(remote)
 		if err != nil {
 			return err
 		}
 
-		target := d.GetAlias(fingerprint)
-		if target != "" {
-			fingerprint = target
+		if fingerprint == "" {
+			fingerprint = "default"
 		}
 
-		_, err = c.GetImageInfo(fingerprint)
+		alias, _, err := d.GetImageAlias(fingerprint)
+		if err == nil {
+			fingerprint = alias.Target
+		}
+
+		_, _, err = c.GetImage(fingerprint)
 		if err != nil {
 			logf("Importing image into local store: %s", fingerprint)
-			err := d.CopyImage(fingerprint, c, false, nil, false, false, nil)
+			image, _, err := d.GetImage(fingerprint)
 			if err != nil {
+				logf(fmt.Sprintf("Failed to import image: %s", err))
+				return err
+			}
+
+			op, err := d.CopyImage(*image, c, nil)
+			if err != nil {
+				logf(fmt.Sprintf("Failed to import image: %s", err))
+				return err
+			}
+
+			err = op.Wait()
+			if err != nil {
+				logf(fmt.Sprintf("Failed to import image: %s", err))
 				return err
 			}
 		} else {
@@ -183,26 +204,35 @@ func spawnContainers(c *lxd.Client, count int, image string, privileged bool) er
 		config["user.lxd-benchmark"] = "true"
 
 		// Create
-		resp, err := c.Init(name, "local", fingerprint, nil, config, false)
+		req := api.ContainersPost{
+			Name: name,
+			Source: api.ContainerSource{
+				Type:        "image",
+				Fingerprint: fingerprint,
+			},
+		}
+		req.Config = config
+
+		op, err := c.CreateContainer(req)
 		if err != nil {
 			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
 			return
 		}
 
-		err = c.WaitForSuccess(resp.Operation)
+		err = op.Wait()
 		if err != nil {
 			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
 			return
 		}
 
 		// Start
-		resp, err = c.Action(name, "start", -1, false, false)
+		op, err = c.UpdateContainerState(name, api.ContainerStatePut{Action: "start", Timeout: -1}, "")
 		if err != nil {
 			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
 			return
 		}
 
-		err = c.WaitForSuccess(resp.Operation)
+		err = op.Wait()
 		if err != nil {
 			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
 			return
@@ -210,13 +240,13 @@ func spawnContainers(c *lxd.Client, count int, image string, privileged bool) er
 
 		// Freeze
 		if *argFreeze {
-			resp, err = c.Action(name, "freeze", -1, false, false)
+			op, err := c.UpdateContainerState(name, api.ContainerStatePut{Action: "freeze", Timeout: -1}, "")
 			if err != nil {
 				logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
 				return
 			}
 
-			err = c.WaitForSuccess(resp.Operation)
+			err = op.Wait()
 			if err != nil {
 				logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
 				return
@@ -258,7 +288,7 @@ func spawnContainers(c *lxd.Client, count int, image string, privileged bool) er
 	return nil
 }
 
-func deleteContainers(c *lxd.Client) error {
+func deleteContainers(c lxd.ContainerServer) error {
 	batch := *argParallel
 	if batch < 1 {
 		// Detect the number of parallel actions
@@ -271,7 +301,7 @@ func deleteContainers(c *lxd.Client) error {
 	}
 
 	// List all the containers
-	allContainers, err := c.ListContainers()
+	allContainers, err := c.GetContainers()
 	if err != nil {
 		return err
 	}
@@ -300,27 +330,27 @@ func deleteContainers(c *lxd.Client) error {
 
 		// Stop
 		if ct.IsActive() {
-			resp, err := c.Action(ct.Name, "stop", -1, true, false)
+			op, err := c.UpdateContainerState(ct.Name, api.ContainerStatePut{Action: "stop", Timeout: -1, Force: true}, "")
 			if err != nil {
-				logf("Failed to delete container: %s", ct.Name)
+				logf(fmt.Sprintf("Failed to delete container '%s': %s", ct.Name, err))
 				return
 			}
 
-			err = c.WaitForSuccess(resp.Operation)
+			err = op.Wait()
 			if err != nil {
-				logf("Failed to delete container: %s", ct.Name)
+				logf(fmt.Sprintf("Failed to delete container '%s': %s", ct.Name, err))
 				return
 			}
 		}
 
 		// Delete
-		resp, err := c.Delete(ct.Name)
+		op, err := c.DeleteContainer(ct.Name)
 		if err != nil {
 			logf("Failed to delete container: %s", ct.Name)
 			return
 		}
 
-		err = c.WaitForSuccess(resp.Operation)
+		err = op.Wait()
 		if err != nil {
 			logf("Failed to delete container: %s", ct.Name)
 			return

From 5bd5afd91eddc43268f1dc960fde642f9d23638a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 14 Apr 2017 12:06:56 +0200
Subject: [PATCH 0838/1193] copy: wait asynchronously

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxc/copy.go | 27 +++++++++++++++++++++++++--
 1 file changed, 25 insertions(+), 2 deletions(-)

diff --git a/lxc/copy.go b/lxc/copy.go
index 5f5c8bdf9..a02a99181 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -165,6 +165,18 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 	 * course, if all the errors are websocket errors, let's just
 	 * report that.
 	 */
+	waitchan := make(chan map[int]error, 2)
+	wait := func(cli *lxd.Client, op string, ch chan map[int]error, senderid int) {
+		msg := make(map[int]error, 1)
+		err := cli.WaitForSuccess(op)
+		if err != nil {
+			msg[senderid] = err
+			ch <- msg
+		}
+
+		msg[senderid] = nil
+		ch <- msg
+	}
 	for _, addr := range addresses {
 		var migration *api.Response
 
@@ -174,11 +186,22 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 			continue
 		}
 
-		if err = dest.WaitForSuccess(migration.Operation); err != nil {
+		destOpId := 0
+		go wait(dest, migration.Operation, waitchan, destOpId)
+		sourceOpId := 1
+		go wait(source, sourceWSResponse.Operation, waitchan, sourceOpId)
+
+		opStatus := make([]map[int]error, 2)
+		for i := 0; i < cap(waitchan); i++ {
+			opStatus[i] = <-waitchan
+		}
+
+		if opStatus[0][destOpId] != nil {
 			continue
 		}
 
-		if err = source.WaitForSuccess(sourceWSResponse.Operation); err != nil {
+		err = opStatus[1][sourceOpId]
+		if err != nil {
 			return err
 		}
 

From 49e525a1bd7068f77b83461a6ad7ad1bd4c59c7a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Mar 2017 22:57:31 -0500
Subject: [PATCH 0839/1193] Port main_daemon to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/containers_post.go |  33 ++--
 lxd/daemon.go          |  69 +-------
 lxd/daemon_images.go   | 444 +++++++++++++++++++++----------------------------
 lxd/images.go          |  19 ++-
 lxd/migrate.go         |   8 +-
 lxd/remote.go          |  26 ---
 6 files changed, 228 insertions(+), 371 deletions(-)
 delete mode 100644 lxd/remote.go

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 44596de7b..a852f0380 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -83,20 +83,6 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 	}
 
 	run := func(op *operation) error {
-		if req.Source.Server != "" {
-			hash, err = d.ImageDownload(
-				op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret,
-				hash, true, daemonConfig["images.auto_update_cached"].GetBool())
-			if err != nil {
-				return err
-			}
-		}
-
-		_, imgInfo, err := dbImageGet(d.db, hash, false, false)
-		if err != nil {
-			return err
-		}
-
 		args := containerArgs{
 			Config:    req.Config,
 			Ctype:     cTypeRegular,
@@ -106,12 +92,27 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 			Profiles:  req.Profiles,
 		}
 
-		args.Architecture, err = osarch.ArchitectureId(imgInfo.Architecture)
+		var info *api.Image
+		if req.Source.Server != "" {
+			info, err = d.ImageDownload(
+				op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret,
+				hash, true, daemonConfig["images.auto_update_cached"].GetBool())
+			if err != nil {
+				return err
+			}
+		} else {
+			_, info, err = dbImageGet(d.db, hash, false, false)
+			if err != nil {
+				return err
+			}
+		}
+
+		args.Architecture, err = osarch.ArchitectureId(info.Architecture)
 		if err != nil {
 			return err
 		}
 
-		_, err = containerCreateFromImage(d, args, imgInfo.Fingerprint)
+		_, err = containerCreateFromImage(d, args, info.Fingerprint)
 		return err
 	}
 
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 46f193f12..bae04fa7a 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -29,9 +29,8 @@ import (
 	"github.com/syndtr/gocapability/capability"
 	"gopkg.in/tomb.v2"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
@@ -148,70 +147,6 @@ func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
 	return &myhttp, nil
 }
 
-func (d *Daemon) httpGetSync(url string, certificate string) (*api.Response, error) {
-	var err error
-
-	req, err := http.NewRequest("GET", url, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	req.Header.Set("User-Agent", version.UserAgent)
-
-	myhttp, err := d.httpClient(certificate)
-	if err != nil {
-		return nil, err
-	}
-
-	r, err := myhttp.Do(req)
-	if err != nil {
-		return nil, err
-	}
-
-	resp, err := lxd.ParseResponse(r)
-	if err != nil {
-		return nil, err
-	}
-
-	if resp.Type != api.SyncResponse {
-		return nil, fmt.Errorf("unexpected non-sync response")
-	}
-
-	return resp, nil
-}
-
-func (d *Daemon) httpGetFile(url string, certificate string) (*http.Response, error) {
-	var err error
-
-	myhttp, err := d.httpClient(certificate)
-	if err != nil {
-		return nil, err
-	}
-
-	req, err := http.NewRequest("GET", url, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	req.Header.Set("User-Agent", version.UserAgent)
-
-	raw, err := myhttp.Do(req)
-	if err != nil {
-		return nil, err
-	}
-
-	if raw.StatusCode != 200 {
-		_, err := lxd.HoistResponse(raw, api.ErrorResponse)
-		if err != nil {
-			return nil, err
-		}
-
-		return nil, fmt.Errorf("non-200 status with no error response?")
-	}
-
-	return raw, nil
-}
-
 func readMyCert() (string, string, error) {
 	certf := shared.VarPath("server.crt")
 	keyf := shared.VarPath("server.key")
@@ -953,7 +888,7 @@ func (d *Daemon) Init() error {
 		// If the socket exists, let's try to connect to it and see if there's
 		// a lxd running.
 		if shared.PathExists(localSocketPath) {
-			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+			_, err := lxd.ConnectLXDUnix("", nil)
 			if err != nil {
 				shared.LogDebugf("Detected stale unix socket, deleting")
 				// Connecting failed, so let's delete the socket and
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 0899139bf..e390e4915 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -1,11 +1,11 @@
 package main
 
 import (
+	"crypto/sha256"
 	"fmt"
 	"io"
 	"io/ioutil"
-	"mime"
-	"mime/multipart"
+	"net/http"
 	"os"
 	"path/filepath"
 	"strings"
@@ -14,10 +14,10 @@ import (
 
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
-	"github.com/lxc/lxd/shared/simplestreams"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -28,8 +28,9 @@ type imageStreamCacheEntry struct {
 	Aliases      []api.ImageAliasesEntry `yaml:"aliases"`
 	Certificate  string                  `yaml:"certificate"`
 	Fingerprints []string                `yaml:"fingerprints"`
-	expiry       time.Time
-	ss           *simplestreams.SimpleStreams
+
+	expiry time.Time
+	remote lxd.ImageServer
 }
 
 var imageStreamCache = map[string]*imageStreamCacheEntry{}
@@ -71,55 +72,64 @@ func imageLoadStreamCache(d *Daemon) error {
 	}
 
 	for url, entry := range imageStreamCache {
-		if entry.ss == nil {
-			myhttp, err := d.httpClient(entry.Certificate)
+		if entry.remote == nil {
+			remote, err := lxd.ConnectSimpleStreams(url, &lxd.ConnectionArgs{
+				TLSServerCert: entry.Certificate,
+				UserAgent:     version.UserAgent,
+				Proxy:         d.proxy,
+			})
 			if err != nil {
-				return err
+				continue
 			}
 
-			ss := simplestreams.NewClient(url, *myhttp, version.UserAgent)
-			entry.ss = ss
+			entry.remote = remote
 		}
 	}
 
 	return nil
 }
 
-// ImageDownload checks if we have that Image Fingerprint else
-// downloads the image from a remote server.
-func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) {
+// ImageDownload resolves the image fingerprint and if not in the database, downloads it
+func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (*api.Image, error) {
 	var err error
-	var ss *simplestreams.SimpleStreams
 	var ctxMap log.Ctx
 
+	var remote lxd.ImageServer
+	var info *api.Image
+
+	// Default protocol is LXD
 	if protocol == "" {
 		protocol = "lxd"
 	}
 
+	// Default the fingerprint to the alias string we received
 	fp := alias
 
-	// Expand aliases
+	// Attempt to resolve the alias
 	if protocol == "simplestreams" {
 		imageStreamCacheLock.Lock()
 		entry, _ := imageStreamCache[server]
 		if entry == nil || entry.expiry.Before(time.Now()) {
+			// Add a new entry to the cache
 			refresh := func() (*imageStreamCacheEntry, error) {
 				// Setup simplestreams client
-				myhttp, err := d.httpClient(certificate)
+				remote, err = lxd.ConnectSimpleStreams(server, &lxd.ConnectionArgs{
+					TLSServerCert: certificate,
+					UserAgent:     version.UserAgent,
+					Proxy:         d.proxy,
+				})
 				if err != nil {
 					return nil, err
 				}
 
-				ss = simplestreams.NewClient(server, *myhttp, version.UserAgent)
-
 				// Get all aliases
-				aliases, err := ss.ListAliases()
+				aliases, err := remote.GetImageAliases()
 				if err != nil {
 					return nil, err
 				}
 
 				// Get all fingerprints
-				images, err := ss.ListImages()
+				images, err := remote.GetImages()
 				if err != nil {
 					return nil, err
 				}
@@ -130,7 +140,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 				}
 
 				// Generate cache entry
-				entry = &imageStreamCacheEntry{ss: ss, Aliases: aliases, Certificate: certificate, Fingerprints: fingerprints, expiry: time.Now().Add(time.Hour)}
+				entry = &imageStreamCacheEntry{remote: remote, Aliases: aliases, Certificate: certificate, Fingerprints: fingerprints, expiry: time.Now().Add(time.Hour)}
 				imageStreamCache[server] = entry
 				imageSaveStreamCache()
 
@@ -148,53 +158,80 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			} else {
 				// Failed to fetch entry and nothing in cache
 				imageStreamCacheLock.Unlock()
-				return "", err
+				return nil, err
 			}
 		} else {
+			// use the existing entry
 			shared.LogDebug("Using SimpleStreams cache entry", log.Ctx{"server": server, "expiry": entry.expiry})
-			ss = entry.ss
+			remote = entry.remote
 		}
 		imageStreamCacheLock.Unlock()
 
-		// Expand aliases
-		for _, alias := range entry.Aliases {
-			if alias.Name != fp {
+		// Look for a matching alias
+		for _, entry := range entry.Aliases {
+			if entry.Name != fp {
 				continue
 			}
 
-			fp = alias.Target
+			fp = entry.Target
 			break
 		}
 
-		// Expand fingerprint
-		for _, fingerprint := range entry.Fingerprints {
-			if !strings.HasPrefix(fingerprint, fp) {
-				continue
+		// Expand partial fingerprints
+		matches := []string{}
+		for _, entry := range entry.Fingerprints {
+			if strings.HasPrefix(entry, fp) {
+				matches = append(matches, entry)
 			}
+		}
 
-			if fp == alias {
-				alias = fingerprint
-			}
-			fp = fingerprint
-			break
+		if len(matches) == 1 {
+			fp = matches[0]
+		} else if len(matches) > 1 {
+			return nil, fmt.Errorf("Provided partial image fingerprint matches more than one image")
 		}
 	} else if protocol == "lxd" {
-		target, err := remoteGetImageFingerprint(d, server, certificate, fp)
-		if err == nil && target != "" {
-			fp = target
+		// Setup LXD client
+		remote, err = lxd.ConnectPublicLXD(server, &lxd.ConnectionArgs{
+			TLSServerCert: certificate,
+			UserAgent:     version.UserAgent,
+			Proxy:         d.proxy,
+		})
+		if err != nil {
+			return nil, err
+		}
+
+		// For public images, handle aliases and initial metadata
+		if secret == "" {
+			// Look for a matching alias
+			entry, _, err := remote.GetImageAlias(fp)
+			if err == nil {
+				fp = entry.Target
+			}
+
+			// Expand partial fingerprints
+			info, _, err = remote.GetImage(fp)
+			if err != nil {
+				return nil, err
+			}
+
+			fp = info.Fingerprint
 		}
 	}
 
-	if _, _, err := dbImageGet(d.db, fp, false, false); err == nil {
+	// Check if the image already exists (partial hash match)
+	_, imgInfo, err := dbImageGet(d.db, fp, false, true)
+	if err == nil {
 		shared.LogDebug("Image already exists in the db", log.Ctx{"image": fp})
-		// already have it
-		return fp, nil
+		info = imgInfo
+
+		return info, nil
 	}
 
-	// Now check if we already downloading the image
+	// Deal with parallel downloads
 	imagesDownloadingLock.Lock()
 	if waitChannel, ok := imagesDownloading[fp]; ok {
-		// We already download the image
+		// We are already downloading the image
 		imagesDownloadingLock.Unlock()
 
 		shared.LogDebug(
@@ -202,23 +239,17 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			log.Ctx{"image": fp})
 
 		// Wait until the download finishes (channel closes)
-		if _, ok := <-waitChannel; ok {
-			shared.LogWarnf("Value transmitted over image lock semaphore?")
-		}
-
-		if _, _, err := dbImageGet(d.db, fp, false, true); err != nil {
-			shared.LogError(
-				"Previous download didn't succeed",
-				log.Ctx{"image": fp})
+		<-waitChannel
 
-			return "", fmt.Errorf("Previous download didn't succeed")
+		// Grab the database entry
+		_, imgInfo, err := dbImageGet(d.db, fp, false, true)
+		if err != nil {
+			// Other download failed, lets try again
+			shared.LogError("Other image download didn't succeed", log.Ctx{"image": fp})
+		} else {
+			// Other download succeeded, we're done
+			return imgInfo, nil
 		}
-
-		shared.LogDebug(
-			"Previous download succeeded",
-			log.Ctx{"image": fp})
-
-		return fp, nil
 	}
 
 	// Add the download to the queue
@@ -241,21 +272,28 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	} else {
 		ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server}
 	}
-
 	shared.LogInfo("Downloading image", ctxMap)
 
-	exporturl := server
-
-	var info api.Image
-	info.Fingerprint = fp
-
+	// Cleanup any leftover from a past attempt
 	destDir := shared.VarPath("images")
 	destName := filepath.Join(destDir, fp)
+
 	if shared.PathExists(destName) {
 		d.Storage.ImageDelete(fp)
 	}
 
-	progress := func(progressInt int64, speedInt int64) {
+	failure := true
+	cleanup := func() {
+		if failure {
+			os.Remove(destName)
+			os.Remove(destName + ".rootfs")
+			d.Storage.ImageDelete(fp)
+		}
+	}
+	defer cleanup()
+
+	// Setup a progress handler
+	progress := func(progress lxd.ProgressData) {
 		if op == nil {
 			return
 		}
@@ -265,264 +303,166 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			meta = make(map[string]interface{})
 		}
 
-		progress := fmt.Sprintf("%d%% (%s/s)", progressInt, shared.GetByteSizeString(speedInt, 2))
-
-		if meta["download_progress"] != progress {
-			meta["download_progress"] = progress
+		if meta["download_progress"] != progress.Text {
+			meta["download_progress"] = progress.Text
 			op.UpdateMetadata(meta)
 		}
 	}
 
-	if protocol == "lxd" {
-		/* grab the metadata from /1.0/images/%s */
-		var url string
-		if secret != "" {
-			url = fmt.Sprintf(
-				"%s/%s/images/%s?secret=%s",
-				server, version.APIVersion, fp, secret)
-		} else {
-			url = fmt.Sprintf("%s/%s/images/%s", server, version.APIVersion, fp)
+	if protocol == "lxd" || protocol == "simplestreams" {
+		// Create the target files
+		dest, err := os.Create(destName)
+		if err != nil {
+			return nil, err
 		}
+		defer dest.Close()
 
-		resp, err := d.httpGetSync(url, certificate)
+		destRootfs, err := os.Create(destName + ".rootfs")
 		if err != nil {
-			shared.LogError(
-				"Failed to download image metadata",
-				log.Ctx{"image": fp, "err": err})
+			return nil, err
+		}
+		defer destRootfs.Close()
 
-			return "", err
+		// Get the image information
+		if info == nil {
+			if secret != "" {
+				info, _, err = remote.GetPrivateImage(fp, secret)
+			} else {
+				info, _, err = remote.GetImage(fp)
+			}
+			if err != nil {
+				return nil, err
+			}
 		}
 
-		if err := resp.MetadataAsStruct(&info); err != nil {
-			return "", err
+		// Download the image
+		var resp *lxd.ImageFileResponse
+		request := lxd.ImageFileRequest{
+			MetaFile:        io.WriteSeeker(dest),
+			RootfsFile:      io.WriteSeeker(destRootfs),
+			ProgressHandler: progress,
 		}
 
-		/* now grab the actual file from /1.0/images/%s/export */
 		if secret != "" {
-			exporturl = fmt.Sprintf(
-				"%s/%s/images/%s/export?secret=%s",
-				server, version.APIVersion, fp, secret)
-
+			resp, err = remote.GetPrivateImageFile(fp, secret, request)
 		} else {
-			exporturl = fmt.Sprintf(
-				"%s/%s/images/%s/export",
-				server, version.APIVersion, fp)
+			resp, err = remote.GetImageFile(fp, request)
 		}
-	} else if protocol == "simplestreams" {
-		err := ss.Download(fp, "meta", destName, nil)
-		if err != nil {
-			return "", err
-		}
-
-		err = ss.Download(fp, "root", destName+".rootfs", progress)
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 
-		info, err := ss.GetImage(fp)
-		if err != nil {
-			return "", err
-		}
-
-		info.Public = false
-		info.AutoUpdate = autoUpdate
-
-		// Create the database entry
-		err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
-		if err != nil {
-			return "", err
-		}
-
-		if alias != fp {
-			id, _, err := dbImageGet(d.db, fp, false, true)
+		// Deal with unified images
+		if resp.RootfsSize == 0 {
+			err := os.Remove(destName + ".rootfs")
 			if err != nil {
-				return "", err
-			}
-
-			err = dbImageSourceInsert(d.db, id, server, protocol, "", alias)
-			if err != nil {
-				return "", err
+				return nil, err
 			}
 		}
-
-		shared.LogInfo("Image downloaded", ctxMap)
-
-		if forContainer {
-			return fp, dbImageLastAccessInit(d.db, fp)
-		}
-
-		return fp, nil
-	}
-
-	raw, err := d.httpGetFile(exporturl, certificate)
-	if err != nil {
-		shared.LogError(
-			"Failed to download image",
-			log.Ctx{"image": fp, "err": err})
-		return "", err
-	}
-	info.Size = raw.ContentLength
-
-	ctype, ctypeParams, err := mime.ParseMediaType(raw.Header.Get("Content-Type"))
-	if err != nil {
-		ctype = "application/octet-stream"
-	}
-
-	body := &ioprogress.ProgressReader{
-		ReadCloser: raw.Body,
-		Tracker: &ioprogress.ProgressTracker{
-			Length:  raw.ContentLength,
-			Handler: progress,
-		},
-	}
-
-	if ctype == "multipart/form-data" {
-		// Parse the POST data
-		mr := multipart.NewReader(body, ctypeParams["boundary"])
-
-		// Get the metadata tarball
-		part, err := mr.NextPart()
+	} else if protocol == "direct" {
+		// Setup HTTP client
+		httpClient, err := d.httpClient(certificate)
 		if err != nil {
-			shared.LogError(
-				"Invalid multipart image",
-				log.Ctx{"image": fp, "err": err})
-
-			return "", err
-		}
-
-		if part.FormName() != "metadata" {
-			shared.LogError(
-				"Invalid multipart image",
-				log.Ctx{"image": fp, "err": err})
-
-			return "", fmt.Errorf("Invalid multipart image")
+			return nil, err
 		}
 
-		destName = filepath.Join(destDir, info.Fingerprint)
-		f, err := os.Create(destName)
+		req, err := http.NewRequest("GET", server, nil)
 		if err != nil {
-			shared.LogError(
-				"Failed to save image",
-				log.Ctx{"image": fp, "err": err})
-
-			return "", err
+			return nil, err
 		}
 
-		_, err = io.Copy(f, part)
-		f.Close()
+		req.Header.Set("User-Agent", version.UserAgent)
 
+		// Make the request
+		raw, err := httpClient.Do(req)
 		if err != nil {
-			shared.LogError(
-				"Failed to save image",
-				log.Ctx{"image": fp, "err": err})
-
-			return "", err
+			return nil, err
 		}
 
-		// Get the rootfs tarball
-		part, err = mr.NextPart()
-		if err != nil {
-			shared.LogError(
-				"Invalid multipart image",
-				log.Ctx{"image": fp, "err": err})
-
-			return "", err
+		if raw.StatusCode != http.StatusOK {
+			return nil, fmt.Errorf("Unable to fetch %s: %s", server, raw.Status)
 		}
 
-		if part.FormName() != "rootfs" {
-			shared.LogError(
-				"Invalid multipart image",
-				log.Ctx{"image": fp})
-			return "", fmt.Errorf("Invalid multipart image")
+		// Progress handler
+		body := &ioprogress.ProgressReader{
+			ReadCloser: raw.Body,
+			Tracker: &ioprogress.ProgressTracker{
+				Length: raw.ContentLength,
+				Handler: func(percent int64, speed int64) {
+					progress(lxd.ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed, 2))})
+				},
+			},
 		}
 
-		destName = filepath.Join(destDir, info.Fingerprint+".rootfs")
-		f, err = os.Create(destName)
+		// Create the target files
+		f, err := os.Create(destName)
 		if err != nil {
-			shared.LogError(
-				"Failed to save image",
-				log.Ctx{"image": fp, "err": err})
-			return "", err
+			return nil, err
 		}
+		defer f.Close()
 
-		_, err = io.Copy(f, part)
-		f.Close()
+		// Hashing
+		sha256 := sha256.New()
 
+		// Download the image
+		size, err := io.Copy(io.MultiWriter(f, sha256), body)
 		if err != nil {
-			shared.LogError(
-				"Failed to save image",
-				log.Ctx{"image": fp, "err": err})
-			return "", err
+			return nil, err
 		}
-	} else {
-		destName = filepath.Join(destDir, info.Fingerprint)
 
-		f, err := os.Create(destName)
-		if err != nil {
-			shared.LogError(
-				"Failed to save image",
-				log.Ctx{"image": fp, "err": err})
-
-			return "", err
-		}
-
-		_, err = io.Copy(f, body)
-		f.Close()
-
-		if err != nil {
-			shared.LogError(
-				"Failed to save image",
-				log.Ctx{"image": fp, "err": err})
-			return "", err
+		// Validate hash
+		result := fmt.Sprintf("%x", sha256.Sum(nil))
+		if result != fp {
+			return nil, fmt.Errorf("Hash mismatch for %s: %s != %s", server, result, fp)
 		}
-	}
 
-	if protocol == "direct" {
+		// Parse the image
 		imageMeta, err := getImageMetadata(destName)
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 
+		info.Size = size
 		info.Architecture = imageMeta.Architecture
 		info.CreatedAt = time.Unix(imageMeta.CreationDate, 0)
 		info.ExpiresAt = time.Unix(imageMeta.ExpiryDate, 0)
 		info.Properties = imageMeta.Properties
 	}
 
-	// By default, make all downloaded images private
+	// Override visiblity
 	info.Public = false
 
-	if alias != fp && secret == "" {
-		info.AutoUpdate = autoUpdate
-	}
-
 	// Create the database entry
 	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
-		shared.LogError(
-			"Failed to create image",
-			log.Ctx{"image": fp, "err": err})
-
-		return "", err
+		return nil, fmt.Errorf("here: %v: %s", err, info.Fingerprint)
 	}
 
+	// Image is in the DB now, don't wipe on-disk files on failure
+	failure = false
+
 	if alias != fp {
 		id, _, err := dbImageGet(d.db, fp, false, true)
 		if err != nil {
-			return "", err
+			return nil, err
 		}
 
 		err = dbImageSourceInsert(d.db, id, server, protocol, "", alias)
 		if err != nil {
-			return "", err
+			return nil, err
 		}
-	}
 
-	shared.LogInfo("Image downloaded", ctxMap)
+		info.AutoUpdate = autoUpdate
+	}
 
+	// Mark the image as "cached" if downloading for a container
 	if forContainer {
-		return fp, dbImageLastAccessInit(d.db, fp)
+		err := dbImageLastAccessInit(d.db, fp)
+		if err != nil {
+			return nil, err
+		}
 	}
 
-	return fp, nil
+	shared.LogInfo("Image downloaded", ctxMap)
+	return info, nil
 }
diff --git a/lxd/images.go b/lxd/images.go
index 08c94ff3e..e16f6bede 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -331,12 +331,12 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image
 		return nil, fmt.Errorf("must specify one of alias or fingerprint for init from image")
 	}
 
-	hash, err = d.ImageDownload(op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret, hash, false, req.AutoUpdate)
+	info, err := d.ImageDownload(op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret, hash, false, req.AutoUpdate)
 	if err != nil {
 		return nil, err
 	}
 
-	id, info, err := dbImageGet(d.db, hash, false, false)
+	id, info, err := dbImageGet(d.db, info.Fingerprint, false, true)
 	if err != nil {
 		return nil, err
 	}
@@ -400,12 +400,12 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, e
 	}
 
 	// Import the image
-	hash, err = d.ImageDownload(op, url, "direct", "", "", hash, false, req.AutoUpdate)
+	info, err := d.ImageDownload(op, url, "direct", "", "", hash, false, req.AutoUpdate)
 	if err != nil {
 		return nil, err
 	}
 
-	id, info, err := dbImageGet(d.db, hash, false, false)
+	id, info, err := dbImageGet(d.db, info.Fingerprint, false, false)
 	if err != nil {
 		return nil, err
 	}
@@ -839,13 +839,16 @@ func autoUpdateImages(d *Daemon) {
 
 		shared.LogDebug("Processing image", log.Ctx{"fp": fp, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias})
 
-		hash, err := d.ImageDownload(nil, source.Server, source.Protocol, "", "", source.Alias, false, true)
+		newInfo, err := d.ImageDownload(nil, source.Server, source.Protocol, "", "", source.Alias, false, true)
+		if err != nil {
+			shared.LogError("Failed to update the image", log.Ctx{"err": err, "fp": fp})
+			continue
+		}
+
+		hash := newInfo.Fingerprint
 		if hash == fp {
 			shared.LogDebug("Already up to date", log.Ctx{"fp": fp})
 			continue
-		} else if err != nil {
-			shared.LogError("Failed to update the image", log.Ctx{"err": err, "fp": fp})
-			continue
 		}
 
 		newId, _, err := dbImageGet(d.db, hash, false, true)
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 2dcfd06fd..899ad9062 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -20,7 +20,6 @@ import (
 	"github.com/gorilla/websocket"
 	"gopkg.in/lxc/go-lxc.v2"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -593,7 +592,12 @@ func (c *migrationSink) connectWithSecret(secret string) (*websocket.Conn, error
 	// The URL is a https URL to the operation, mangle to be a wss URL to the secret
 	wsUrl := fmt.Sprintf("wss://%s/websocket?%s", strings.TrimPrefix(c.url, "https://"), query.Encode())
 
-	return lxd.WebsocketDial(c.dialer, wsUrl)
+	conn, _, err := c.dialer.Dial(wsUrl, http.Header{})
+	if err != nil {
+		return nil, err
+	}
+
+	return conn, err
 }
 
 func (c *migrationSink) Do(migrateOp *operation) error {
diff --git a/lxd/remote.go b/lxd/remote.go
deleted file mode 100644
index da8bfc4ca..000000000
--- a/lxd/remote.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package main
-
-import (
-	"fmt"
-
-	"github.com/lxc/lxd/shared/api"
-	"github.com/lxc/lxd/shared/version"
-)
-
-func remoteGetImageFingerprint(d *Daemon, server string, certificate string, alias string) (string, error) {
-	url := fmt.Sprintf(
-		"%s/%s/images/aliases/%s",
-		server, version.APIVersion, alias)
-
-	resp, err := d.httpGetSync(url, certificate)
-	if err != nil {
-		return "", err
-	}
-
-	var result api.ImageAliasesEntry
-	if err = resp.MetadataAsStruct(&result); err != nil {
-		return "", fmt.Errorf("Error reading alias")
-	}
-
-	return result.Target, nil
-}

From e50491cadbd61ce22670328a51c1cca40dc49810 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 17 Mar 2017 16:40:57 +0100
Subject: [PATCH 0840/1193] Port main_init to new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_init.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 51 insertions(+), 11 deletions(-)

diff --git a/lxd/main_init.go b/lxd/main_init.go
index 26a0157b5..d89afae0a 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -12,7 +12,7 @@ import (
 
 	"golang.org/x/crypto/ssh/terminal"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -149,19 +149,59 @@ func cmdInit() error {
 		}
 	}
 
-	// Confirm that LXD is online
-	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
+	// Connect to LXD
+	c, err := lxd.ConnectLXDUnix("", nil)
 	if err != nil {
 		return fmt.Errorf("Unable to talk to LXD: %s", err)
 	}
 
+	setServerConfig := func(key string, value string) error {
+		server, etag, err := c.GetServer()
+		if err != nil {
+			return err
+		}
+
+		if server.Config == nil {
+			server.Config = map[string]interface{}{}
+		}
+
+		server.Config[key] = value
+
+		err = c.UpdateServer(server.Writable(), etag)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	setProfileConfigItem := func(profileName string, key string, value string) error {
+		profile, etag, err := c.GetProfile(profileName)
+		if err != nil {
+			return err
+		}
+
+		if profile.Config == nil {
+			profile.Config = map[string]string{}
+		}
+
+		profile.Config[key] = value
+
+		err = c.UpdateProfile(profileName, profile.Writable(), etag)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
 	// Check that we have no containers or images in the store
-	containers, err := c.ListContainers()
+	containers, err := c.GetContainerNames()
 	if err != nil {
 		return fmt.Errorf("Unable to list the LXD containers: %s", err)
 	}
 
-	images, err := c.ListImages()
+	images, err := c.GetImageFingerprints()
 	if err != nil {
 		return fmt.Errorf("Unable to list the LXD images: %s", err)
 	}
@@ -332,7 +372,7 @@ they otherwise would.
 
 	// Unset all storage keys, core.https_address and core.trust_password
 	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
-		_, err = c.SetServerConfig(key, "")
+		err = setServerConfig(key, "")
 		if err != nil {
 			return err
 		}
@@ -378,31 +418,31 @@ they otherwise would.
 		}
 
 		// Configure LXD to use the pool
-		_, err = c.SetServerConfig("storage.zfs_pool_name", storagePool)
+		err = setServerConfig("storage.zfs_pool_name", storagePool)
 		if err != nil {
 			return err
 		}
 	}
 
 	if defaultPrivileged == 0 {
-		err = c.SetProfileConfigItem("default", "security.privileged", "")
+		err = setProfileConfigItem("default", "security.privileged", "")
 		if err != nil {
 			return err
 		}
 	} else if defaultPrivileged == 1 {
-		err = c.SetProfileConfigItem("default", "security.privileged", "true")
+		err = setProfileConfigItem("default", "security.privileged", "true")
 		if err != nil {
 		}
 	}
 
 	if networkAddress != "" {
-		_, err = c.SetServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
+		err = setServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
 		if err != nil {
 			return err
 		}
 
 		if trustPassword != "" {
-			_, err = c.SetServerConfig("core.trust_password", trustPassword)
+			err = setServerConfig("core.trust_password", trustPassword)
 			if err != nil {
 				return err
 			}

From f1443ce39121a77fd48ddb98ccc1f96d9241e294 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 5 Apr 2017 23:09:30 -0400
Subject: [PATCH 0841/1193] shared/logger: Create new package for logger
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

And move everything over to it.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go                        |  11 +-
 doc/debugging.md                 |   4 +-
 lxc/exec.go                      |   4 +-
 lxc/exec_unix.go                 |   6 +-
 lxc/exec_windows.go              |   4 +-
 lxc/main.go                      |   3 +-
 lxc/remote.go                    |   3 +-
 lxd/api_internal.go              |   6 +-
 lxd/apparmor.go                  |   5 +-
 lxd/certificates.go              |   7 +-
 lxd/container_exec.go            |  19 ++--
 lxd/container_lxc.go             | 229 ++++++++++++++++++++-------------------
 lxd/container_put.go             |   5 +-
 lxd/containers.go                |   7 +-
 lxd/containers_get.go            |   8 +-
 lxd/containers_post.go           |   7 +-
 lxd/daemon.go                    | 137 +++++++++++------------
 lxd/daemon_config.go             |   3 +-
 lxd/daemon_images.go             |  15 +--
 lxd/db.go                        |  36 +++---
 lxd/db_containers.go             |   7 +-
 lxd/db_test.go                   |   6 +-
 lxd/db_update.go                 |  21 ++--
 lxd/debug.go                     |   6 +-
 lxd/devices.go                   |  27 ++---
 lxd/devlxd.go                    |   5 +-
 lxd/events.go                    |   5 +-
 lxd/images.go                    |  47 ++++----
 lxd/main.go                      |   3 +-
 lxd/main_activateifneeded.go     |  11 +-
 lxd/main_daemon.go               |   9 +-
 lxd/migrate.go                   |   9 +-
 lxd/operations.go                |  27 ++---
 lxd/patches.go                   |   5 +-
 lxd/profiles.go                  |   5 +-
 lxd/rsync.go                     |   5 +-
 lxd/storage.go                   |  15 +--
 lxd/storage_btrfs.go             |  15 +--
 lxd/storage_lvm.go               |   9 +-
 lxd/storage_zfs.go               |  11 +-
 shared/json.go                   |   6 +-
 shared/{ => logger}/log.go       |  24 ++--
 shared/{ => logger}/log_debug.go |  24 ++--
 shared/logging/log.go            |  10 +-
 shared/network.go                |  36 +++---
 shared/network_linux.go          |   8 +-
 shared/util_linux.go             |  20 ++--
 47 files changed, 467 insertions(+), 428 deletions(-)
 rename shared/{ => logger}/log.go (73%)
 rename shared/{ => logger}/log_debug.go (84%)

diff --git a/client.go b/client.go
index 2a723bda8..d8cacf0b6 100644
--- a/client.go
+++ b/client.go
@@ -26,6 +26,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/simplestreams"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -72,7 +73,7 @@ func ParseResponse(r *http.Response) (*api.Response, error) {
 	if err != nil {
 		return nil, err
 	}
-	shared.LogDebugf("Raw response: %s", string(s))
+	logger.Debugf("Raw response: %s", string(s))
 
 	if err := json.Unmarshal(s, &ret); err != nil {
 		return nil, err
@@ -368,7 +369,7 @@ func (c *Client) doUpdateMethod(method string, base string, args interface{}, rt
 		return nil, err
 	}
 
-	shared.LogDebugf("%s %s to %s", method, buf.String(), uri)
+	logger.Debugf("%s %s to %s", method, buf.String(), uri)
 
 	req, err := http.NewRequest(method, uri, &buf)
 	if err != nil {
@@ -517,7 +518,7 @@ func (c *Client) AmTrusted() bool {
 		return false
 	}
 
-	shared.LogDebugf("%s", resp)
+	logger.Debugf("%s", resp)
 
 	meta, err := resp.MetadataAsMap()
 	if err != nil {
@@ -538,7 +539,7 @@ func (c *Client) IsPublic() bool {
 		return false
 	}
 
-	shared.LogDebugf("%s", resp)
+	logger.Debugf("%s", resp)
 
 	meta, err := resp.MetadataAsMap()
 	if err != nil {
@@ -2128,7 +2129,7 @@ func (c *Client) SetProfileConfigItem(profile, key, value string) error {
 
 	st, err := c.ProfileConfig(profile)
 	if err != nil {
-		shared.LogDebugf("Error getting profile %s to update", profile)
+		logger.Debugf("Error getting profile %s to update", profile)
 		return err
 	}
 
diff --git a/doc/debugging.md b/doc/debugging.md
index 0f91c76ef..27bd20be3 100644
--- a/doc/debugging.md
+++ b/doc/debugging.md
@@ -6,8 +6,8 @@ Adding `--debug` flag to any client command will give extra information
 about internals. If there is no useful info, it can be added with the
 logging call:
 
-    shared.LogDebugf("Hello: %s", "Debug")
-    
+    logger.Debugf("Hello: %s", "Debug")
+
 #### lxc monitor
 
 This command will monitor messages as they appear on remote server.
diff --git a/lxc/exec.go b/lxc/exec.go
index 4c6c4ce3c..d76787975 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -11,10 +11,10 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/termios"
 )
 
@@ -62,7 +62,7 @@ func (c *execCmd) sendTermSize(control *websocket.Conn) error {
 		return err
 	}
 
-	shared.LogDebugf("Window size is now: %dx%d", width, height)
+	logger.Debugf("Window size is now: %dx%d", width, height)
 
 	w, err := control.NextWriter(websocket.TextMessage)
 	if err != nil {
diff --git a/lxc/exec_unix.go b/lxc/exec_unix.go
index 0d4d95217..ee7bb41de 100644
--- a/lxc/exec_unix.go
+++ b/lxc/exec_unix.go
@@ -11,7 +11,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 func (c *execCmd) getStdout() io.WriteCloser {
@@ -29,11 +29,11 @@ func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) {
 	for {
 		sig := <-ch
 
-		shared.LogDebugf("Received '%s signal', updating window geometry.", sig)
+		logger.Debugf("Received '%s signal', updating window geometry.", sig)
 
 		err := c.sendTermSize(control)
 		if err != nil {
-			shared.LogDebugf("error setting term size %s", err)
+			logger.Debugf("error setting term size %s", err)
 			break
 		}
 	}
diff --git a/lxc/exec_windows.go b/lxc/exec_windows.go
index 950c615cd..21ec1b8f6 100644
--- a/lxc/exec_windows.go
+++ b/lxc/exec_windows.go
@@ -10,7 +10,7 @@ import (
 	"github.com/mattn/go-colorable"
 
 	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 // Windows doesn't process ANSI sequences natively, so we wrap
@@ -38,6 +38,6 @@ func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) {
 	// won't work quite correctly.
 	err := c.sendTermSize(control)
 	if err != nil {
-		shared.LogDebugf("error setting term size %s", err)
+		logger.Debugf("error setting term size %s", err)
 	}
 }
diff --git a/lxc/main.go b/lxc/main.go
index eddda95aa..be7d30fa8 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -12,6 +12,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 )
 
@@ -119,7 +120,7 @@ func run() error {
 	os.Args = os.Args[1:]
 	gnuflag.Parse(true)
 
-	shared.Log, err = logging.GetLogger("", "", *verbose, *debug, nil)
+	logger.Log, err = logging.GetLogger("", "", *verbose, *debug, nil)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/remote.go b/lxc/remote.go
index c25d8f659..d7ceb8311 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -20,6 +20,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 type remoteCmd struct {
@@ -284,7 +285,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 
 func (c *remoteCmd) removeCertificate(config *lxd.Config, remote string) {
 	certf := config.ServerCertPath(remote)
-	shared.LogDebugf("Trying to remove %s", certf)
+	logger.Debugf("Trying to remove %s", certf)
 
 	os.Remove(certf)
 }
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 51c11e36e..48a0dcf60 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/gorilla/mux"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -59,7 +59,7 @@ func internalContainerOnStart(d *Daemon, r *http.Request) Response {
 
 	err = c.OnStart()
 	if err != nil {
-		shared.Log.Error("start hook failed", log.Ctx{"container": c.Name(), "err": err})
+		logger.Log.Error("start hook failed", log.Ctx{"container": c.Name(), "err": err})
 		return SmartError(err)
 	}
 
@@ -84,7 +84,7 @@ func internalContainerOnStop(d *Daemon, r *http.Request) Response {
 
 	err = c.OnStop(target)
 	if err != nil {
-		shared.Log.Error("stop hook failed", log.Ctx{"container": c.Name(), "err": err})
+		logger.Log.Error("stop hook failed", log.Ctx{"container": c.Name(), "err": err})
 		return SmartError(err)
 	}
 
diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index b11be5b0d..f4563b2f8 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -395,7 +396,7 @@ func runApparmor(command string, c container) error {
 	}...)
 
 	if err != nil {
-		shared.LogError("Running apparmor",
+		logger.Error("Running apparmor",
 			log.Ctx{"action": command, "output": output, "err": err})
 	}
 
@@ -472,7 +473,7 @@ func AADestroy(c container) error {
 	if aaStacking {
 		p := path.Join("/sys/kernel/security/apparmor/policy/namespaces", AANamespace(c))
 		if err := os.Remove(p); err != nil {
-			shared.LogError("error removing apparmor namespace", log.Ctx{"err": err, "ns": p})
+			logger.Error("error removing apparmor namespace", log.Ctx{"err": err, "ns": p})
 		}
 	}
 
diff --git a/lxd/certificates.go b/lxd/certificates.go
index 903a88ad8..dfbe11e2c 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -53,20 +54,20 @@ func readSavedClientCAList(d *Daemon) {
 
 	dbCerts, err := dbCertsGet(d.db)
 	if err != nil {
-		shared.LogInfof("Error reading certificates from database: %s", err)
+		logger.Infof("Error reading certificates from database: %s", err)
 		return
 	}
 
 	for _, dbCert := range dbCerts {
 		certBlock, _ := pem.Decode([]byte(dbCert.Certificate))
 		if certBlock == nil {
-			shared.LogInfof("Error decoding certificate for %s: %s", dbCert.Name, err)
+			logger.Infof("Error decoding certificate for %s: %s", dbCert.Name, err)
 			continue
 		}
 
 		cert, err := x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
-			shared.LogInfof("Error reading certificate for %s: %s", dbCert.Name, err)
+			logger.Infof("Error reading certificate for %s: %s", dbCert.Name, err)
 			continue
 		}
 		d.clientCerts = append(d.clientCerts, *cert)
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index d4f2635e7..987246f4a 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -17,6 +17,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -158,7 +159,7 @@ func (s *execWs) Do(op *operation) error {
 				}
 
 				if err != nil {
-					shared.LogDebugf("Got error getting next reader %s", err)
+					logger.Debugf("Got error getting next reader %s", err)
 					er, ok := err.(*websocket.CloseError)
 					if !ok {
 						break
@@ -171,42 +172,42 @@ func (s *execWs) Do(op *operation) error {
 					// If an abnormal closure occured, kill the attached process.
 					err := syscall.Kill(attachedChildPid, syscall.SIGKILL)
 					if err != nil {
-						shared.LogDebugf("Failed to send SIGKILL to pid %d.", attachedChildPid)
+						logger.Debugf("Failed to send SIGKILL to pid %d.", attachedChildPid)
 					} else {
-						shared.LogDebugf("Sent SIGKILL to pid %d.", attachedChildPid)
+						logger.Debugf("Sent SIGKILL to pid %d.", attachedChildPid)
 					}
 					return
 				}
 
 				buf, err := ioutil.ReadAll(r)
 				if err != nil {
-					shared.LogDebugf("Failed to read message %s", err)
+					logger.Debugf("Failed to read message %s", err)
 					break
 				}
 
 				command := api.ContainerExecControl{}
 
 				if err := json.Unmarshal(buf, &command); err != nil {
-					shared.LogDebugf("Failed to unmarshal control socket command: %s", err)
+					logger.Debugf("Failed to unmarshal control socket command: %s", err)
 					continue
 				}
 
 				if command.Command == "window-resize" {
 					winchWidth, err := strconv.Atoi(command.Args["width"])
 					if err != nil {
-						shared.LogDebugf("Unable to extract window width: %s", err)
+						logger.Debugf("Unable to extract window width: %s", err)
 						continue
 					}
 
 					winchHeight, err := strconv.Atoi(command.Args["height"])
 					if err != nil {
-						shared.LogDebugf("Unable to extract window height: %s", err)
+						logger.Debugf("Unable to extract window height: %s", err)
 						continue
 					}
 
 					err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight)
 					if err != nil {
-						shared.LogDebugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
+						logger.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
 						continue
 					}
 				}
@@ -435,7 +436,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 
 		err = op.UpdateMetadata(metadata)
 		if err != nil {
-			shared.LogError("error updating metadata for cmd", log.Ctx{"err": err, "cmd": post.Command})
+			logger.Error("error updating metadata for cmd", log.Ctx{"err": err, "cmd": post.Command})
 		}
 		return cmdErr
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index e6eba3a09..f2f2d456b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -27,6 +27,7 @@ import (
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -199,7 +200,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	ctxMap := log.Ctx{"name": c.name,
 		"ephemeral": c.ephemeral}
 
-	shared.LogInfo("Creating container", ctxMap)
+	logger.Info("Creating container", ctxMap)
 
 	// No need to detect storage here, its a new container.
 	c.storage = d.Storage
@@ -208,7 +209,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	err := c.init()
 	if err != nil {
 		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
+		logger.Error("Failed creating container", ctxMap)
 		return nil, err
 	}
 
@@ -237,7 +238,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		err = c.Update(updateArgs, false)
 		if err != nil {
 			c.Delete()
-			shared.LogError("Failed creating container", ctxMap)
+			logger.Error("Failed creating container", ctxMap)
 			return nil, err
 		}
 	}
@@ -246,14 +247,14 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	err = containerValidConfig(c.daemon, c.expandedConfig, false, true)
 	if err != nil {
 		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
+		logger.Error("Failed creating container", ctxMap)
 		return nil, err
 	}
 
 	err = containerValidDevices(c.expandedDevices, false, true)
 	if err != nil {
 		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
+		logger.Error("Failed creating container", ctxMap)
 		return nil, err
 	}
 
@@ -271,7 +272,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 
 		if err != nil {
 			c.Delete()
-			shared.LogError("Failed creating container", ctxMap)
+			logger.Error("Failed creating container", ctxMap)
 			return nil, err
 		}
 	}
@@ -281,7 +282,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		idmapBytes, err := json.Marshal(idmap.Idmap)
 		if err != nil {
 			c.Delete()
-			shared.LogError("Failed creating container", ctxMap)
+			logger.Error("Failed creating container", ctxMap)
 			return nil, err
 		}
 		jsonIdmap = string(idmapBytes)
@@ -292,14 +293,14 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	err = c.ConfigKeySet("volatile.idmap.next", jsonIdmap)
 	if err != nil {
 		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
+		logger.Error("Failed creating container", ctxMap)
 		return nil, err
 	}
 
 	err = c.ConfigKeySet("volatile.idmap.base", fmt.Sprintf("%v", base))
 	if err != nil {
 		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
+		logger.Error("Failed creating container", ctxMap)
 		return nil, err
 	}
 
@@ -311,7 +312,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		err = c.ConfigKeySet("volatile.last_state.idmap", jsonIdmap)
 		if err != nil {
 			c.Delete()
-			shared.LogError("Failed creating container", ctxMap)
+			logger.Error("Failed creating container", ctxMap)
 			return nil, err
 		}
 	}
@@ -320,11 +321,11 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	err = c.init()
 	if err != nil {
 		c.Delete()
-		shared.LogError("Failed creating container", ctxMap)
+		logger.Error("Failed creating container", ctxMap)
 		return nil, err
 	}
 
-	shared.LogInfo("Created container", ctxMap)
+	logger.Info("Created container", ctxMap)
 
 	return c, nil
 }
@@ -1467,7 +1468,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	}
 
 	if !reflect.DeepEqual(idmap, lastIdmap) {
-		shared.LogDebugf("Container idmap changed, remapping")
+		logger.Debugf("Container idmap changed, remapping")
 
 		err := c.StorageStart()
 		if err != nil {
@@ -1681,7 +1682,7 @@ func (c *containerLXC) Start(stateful bool) error {
 		"ephemeral":     c.ephemeral,
 		"stateful":      stateful}
 
-	shared.LogInfo("Starting container", ctxMap)
+	logger.Info("Starting container", ctxMap)
 
 	// If stateful, restore now
 	if stateful {
@@ -1699,11 +1700,11 @@ func (c *containerLXC) Start(stateful bool) error {
 
 		err = dbContainerSetStateful(c.daemon.db, c.id, false)
 		if err != nil {
-			shared.LogError("Failed starting container", ctxMap)
+			logger.Error("Failed starting container", ctxMap)
 			return err
 		}
 
-		shared.LogInfo("Started container", ctxMap)
+		logger.Info("Started container", ctxMap)
 
 		return err
 	} else if c.stateful {
@@ -1731,7 +1732,7 @@ func (c *containerLXC) Start(stateful bool) error {
 	// Capture debug output
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.LogDebugf("forkstart: %s", line)
+			logger.Debugf("forkstart: %s", line)
 		}
 	}
 
@@ -1763,7 +1764,7 @@ func (c *containerLXC) Start(stateful bool) error {
 			}
 		}
 
-		shared.LogError("Failed starting container", ctxMap)
+		logger.Error("Failed starting container", ctxMap)
 
 		// Return the actual error
 		return fmt.Errorf(
@@ -1774,7 +1775,7 @@ func (c *containerLXC) Start(stateful bool) error {
 			err, lxcLog)
 	}
 
-	shared.LogInfo("Started container", ctxMap)
+	logger.Info("Started container", ctxMap)
 
 	return nil
 }
@@ -1832,7 +1833,7 @@ func (c *containerLXC) OnStart() error {
 			c.fromHook = false
 			err := c.setNetworkPriority()
 			if err != nil {
-				shared.LogError("Failed to apply network priority", log.Ctx{"container": c.name, "err": err})
+				logger.Error("Failed to apply network priority", log.Ctx{"container": c.name, "err": err})
 			}
 		}(c)
 	}
@@ -1852,7 +1853,7 @@ func (c *containerLXC) OnStart() error {
 			c.fromHook = false
 			err = c.setNetworkLimits(name, m)
 			if err != nil {
-				shared.LogError("Failed to apply network limits", log.Ctx{"container": c.name, "err": err})
+				logger.Error("Failed to apply network limits", log.Ctx{"container": c.name, "err": err})
 			}
 		}(c, name, m)
 	}
@@ -1887,7 +1888,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		"ephemeral":     c.ephemeral,
 		"stateful":      stateful}
 
-	shared.LogInfo("Stopping container", ctxMap)
+	logger.Info("Stopping container", ctxMap)
 
 	// Handle stateful stop
 	if stateful {
@@ -1898,7 +1899,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		err := os.MkdirAll(stateDir, 0700)
 		if err != nil {
 			op.Done(err)
-			shared.LogError("Failed stopping container", ctxMap)
+			logger.Error("Failed stopping container", ctxMap)
 			return err
 		}
 
@@ -1906,7 +1907,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		err = c.Migrate(lxc.MIGRATE_DUMP, stateDir, "snapshot", true, false)
 		if err != nil {
 			op.Done(err)
-			shared.LogError("Failed stopping container", ctxMap)
+			logger.Error("Failed stopping container", ctxMap)
 			return err
 		}
 
@@ -1914,12 +1915,12 @@ func (c *containerLXC) Stop(stateful bool) error {
 		err = dbContainerSetStateful(c.daemon.db, c.id, true)
 		if err != nil {
 			op.Done(err)
-			shared.LogError("Failed stopping container", ctxMap)
+			logger.Error("Failed stopping container", ctxMap)
 			return err
 		}
 
 		op.Done(nil)
-		shared.LogInfo("Stopped container", ctxMap)
+		logger.Info("Stopped container", ctxMap)
 		return nil
 	}
 
@@ -1927,7 +1928,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 	err = c.initLXC()
 	if err != nil {
 		op.Done(err)
-		shared.LogError("Failed stopping container", ctxMap)
+		logger.Error("Failed stopping container", ctxMap)
 		return err
 	}
 
@@ -1946,17 +1947,17 @@ func (c *containerLXC) Stop(stateful bool) error {
 
 	if err := c.c.Stop(); err != nil {
 		op.Done(err)
-		shared.LogError("Failed stopping container", ctxMap)
+		logger.Error("Failed stopping container", ctxMap)
 		return err
 	}
 
 	err = op.Wait()
 	if err != nil && c.IsRunning() {
-		shared.LogError("Failed stopping container", ctxMap)
+		logger.Error("Failed stopping container", ctxMap)
 		return err
 	}
 
-	shared.LogInfo("Stopped container", ctxMap)
+	logger.Info("Stopped container", ctxMap)
 	return nil
 }
 
@@ -1980,29 +1981,29 @@ func (c *containerLXC) Shutdown(timeout time.Duration) error {
 		"ephemeral":     c.ephemeral,
 		"timeout":       timeout}
 
-	shared.LogInfo("Shutting down container", ctxMap)
+	logger.Info("Shutting down container", ctxMap)
 
 	// Load the go-lxc struct
 	err = c.initLXC()
 	if err != nil {
 		op.Done(err)
-		shared.LogError("Failed shutting down container", ctxMap)
+		logger.Error("Failed shutting down container", ctxMap)
 		return err
 	}
 
 	if err := c.c.Shutdown(timeout); err != nil {
 		op.Done(err)
-		shared.LogError("Failed shutting down container", ctxMap)
+		logger.Error("Failed shutting down container", ctxMap)
 		return err
 	}
 
 	err = op.Wait()
 	if err != nil && c.IsRunning() {
-		shared.LogError("Failed shutting down container", ctxMap)
+		logger.Error("Failed shutting down container", ctxMap)
 		return err
 	}
 
-	shared.LogInfo("Shut down container", ctxMap)
+	logger.Info("Shut down container", ctxMap)
 
 	return nil
 }
@@ -2010,7 +2011,7 @@ func (c *containerLXC) Shutdown(timeout time.Duration) error {
 func (c *containerLXC) OnStop(target string) error {
 	// Validate target
 	if !shared.StringInSlice(target, []string{"stop", "reboot"}) {
-		shared.LogError("Container sent invalid target to OnStop", log.Ctx{"container": c.Name(), "target": target})
+		logger.Error("Container sent invalid target to OnStop", log.Ctx{"container": c.Name(), "target": target})
 		return fmt.Errorf("Invalid stop target: %s", target)
 	}
 
@@ -2041,7 +2042,7 @@ func (c *containerLXC) OnStop(target string) error {
 			"ephemeral": c.ephemeral,
 			"stateful":  false}
 
-		shared.LogInfo(fmt.Sprintf("Container initiated %s", target), ctxMap)
+		logger.Info(fmt.Sprintf("Container initiated %s", target), ctxMap)
 	}
 
 	go func(c *containerLXC, target string, op *lxcContainerOperation) {
@@ -2059,19 +2060,19 @@ func (c *containerLXC) OnStop(target string) error {
 		// Unload the apparmor profile
 		err = AADestroy(c)
 		if err != nil {
-			shared.LogError("Failed to destroy apparmor namespace", log.Ctx{"container": c.Name(), "err": err})
+			logger.Error("Failed to destroy apparmor namespace", log.Ctx{"container": c.Name(), "err": err})
 		}
 
 		// Clean all the unix devices
 		err = c.removeUnixDevices()
 		if err != nil {
-			shared.LogError("Unable to remove unix devices", log.Ctx{"container": c.Name(), "err": err})
+			logger.Error("Unable to remove unix devices", log.Ctx{"container": c.Name(), "err": err})
 		}
 
 		// Clean all the disk devices
 		err = c.removeDiskDevices()
 		if err != nil {
-			shared.LogError("Unable to remove disk devices", log.Ctx{"container": c.Name(), "err": err})
+			logger.Error("Unable to remove disk devices", log.Ctx{"container": c.Name(), "err": err})
 		}
 
 		// Reboot the container
@@ -2087,7 +2088,7 @@ func (c *containerLXC) OnStop(target string) error {
 		// Record current state
 		err = dbContainerSetState(c.daemon.db, c.id, "STOPPED")
 		if err != nil {
-			shared.LogError("Failed to set container state", log.Ctx{"container": c.Name(), "err": err})
+			logger.Error("Failed to set container state", log.Ctx{"container": c.Name(), "err": err})
 		}
 
 		// Destroy ephemeral containers
@@ -2115,24 +2116,24 @@ func (c *containerLXC) Freeze() error {
 		return fmt.Errorf("The container isn't running")
 	}
 
-	shared.LogInfo("Freezing container", ctxMap)
+	logger.Info("Freezing container", ctxMap)
 
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
 		ctxMap["err"] = err
-		shared.LogError("Failed freezing container", ctxMap)
+		logger.Error("Failed freezing container", ctxMap)
 		return err
 	}
 
 	err = c.c.Freeze()
 	if err != nil {
 		ctxMap["err"] = err
-		shared.LogError("Failed freezing container", ctxMap)
+		logger.Error("Failed freezing container", ctxMap)
 		return err
 	}
 
-	shared.LogInfo("Froze container", ctxMap)
+	logger.Info("Froze container", ctxMap)
 
 	return err
 }
@@ -2152,21 +2153,21 @@ func (c *containerLXC) Unfreeze() error {
 		return fmt.Errorf("The container isn't running")
 	}
 
-	shared.LogInfo("Unfreezing container", ctxMap)
+	logger.Info("Unfreezing container", ctxMap)
 
 	// Load the go-lxc struct
 	err := c.initLXC()
 	if err != nil {
-		shared.LogError("Failed unfreezing container", ctxMap)
+		logger.Error("Failed unfreezing container", ctxMap)
 		return err
 	}
 
 	err = c.c.Unfreeze()
 	if err != nil {
-		shared.LogError("Failed unfreezing container", ctxMap)
+		logger.Error("Failed unfreezing container", ctxMap)
 	}
 
-	shared.LogInfo("Unfroze container", ctxMap)
+	logger.Info("Unfroze container", ctxMap)
 
 	return err
 }
@@ -2328,12 +2329,12 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 		"ephemeral":        c.ephemeral,
 		"source container": sourceContainer.Name()}
 
-	shared.LogInfo("Restoring container", ctxMap)
+	logger.Info("Restoring container", ctxMap)
 
 	// Restore the rootfs
 	err = c.storage.ContainerRestore(c, sourceContainer)
 	if err != nil {
-		shared.LogError("Failed restoring container filesystem", ctxMap)
+		logger.Error("Failed restoring container filesystem", ctxMap)
 		return err
 	}
 
@@ -2348,7 +2349,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 
 	err = c.Update(args, false)
 	if err != nil {
-		shared.LogError("Failed restoring container configuration", ctxMap)
+		logger.Error("Failed restoring container configuration", ctxMap)
 		return err
 	}
 
@@ -2363,25 +2364,25 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 		// this in snapshots.
 		err2 := os.RemoveAll(c.StatePath())
 		if err2 != nil {
-			shared.LogError("Failed to delete snapshot state", log.Ctx{"path": c.StatePath(), "err": err2})
+			logger.Error("Failed to delete snapshot state", log.Ctx{"path": c.StatePath(), "err": err2})
 		}
 
 		if err != nil {
-			shared.LogInfo("Failed restoring container", ctxMap)
+			logger.Info("Failed restoring container", ctxMap)
 			return err
 		}
 
-		shared.LogInfo("Restored container", ctxMap)
+		logger.Info("Restored container", ctxMap)
 		return nil
 	}
 
 	// Restart the container
 	if wasRunning {
-		shared.LogInfo("Restored container", ctxMap)
+		logger.Info("Restored container", ctxMap)
 		return c.Start(false)
 	}
 
-	shared.LogInfo("Restored container", ctxMap)
+	logger.Info("Restored container", ctxMap)
 
 	return nil
 }
@@ -2407,18 +2408,18 @@ func (c *containerLXC) Delete() error {
 		"creation date": c.creationDate,
 		"ephemeral":     c.ephemeral}
 
-	shared.LogInfo("Deleting container", ctxMap)
+	logger.Info("Deleting container", ctxMap)
 
 	if c.IsSnapshot() {
 		// Remove the snapshot
 		if err := c.storage.ContainerSnapshotDelete(c); err != nil {
-			shared.LogWarn("Failed to delete snapshot", log.Ctx{"name": c.Name(), "err": err})
+			logger.Warn("Failed to delete snapshot", log.Ctx{"name": c.Name(), "err": err})
 			return err
 		}
 	} else {
 		// Remove all snapshot
 		if err := containerDeleteSnapshots(c.daemon, c.Name()); err != nil {
-			shared.LogWarn("Failed to delete snapshots", log.Ctx{"name": c.Name(), "err": err})
+			logger.Warn("Failed to delete snapshots", log.Ctx{"name": c.Name(), "err": err})
 			return err
 		}
 
@@ -2428,7 +2429,7 @@ func (c *containerLXC) Delete() error {
 		// Delete the container from disk
 		if shared.PathExists(c.Path()) {
 			if err := c.storage.ContainerDelete(c); err != nil {
-				shared.LogError("Failed deleting container storage", ctxMap)
+				logger.Error("Failed deleting container storage", ctxMap)
 				return err
 			}
 		}
@@ -2436,11 +2437,11 @@ func (c *containerLXC) Delete() error {
 
 	// Remove the database record
 	if err := dbContainerRemove(c.daemon.db, c.Name()); err != nil {
-		shared.LogError("Failed deleting container entry", ctxMap)
+		logger.Error("Failed deleting container entry", ctxMap)
 		return err
 	}
 
-	shared.LogInfo("Deleted container", ctxMap)
+	logger.Info("Deleted container", ctxMap)
 
 	return nil
 }
@@ -2452,7 +2453,7 @@ func (c *containerLXC) Rename(newName string) error {
 		"ephemeral":     c.ephemeral,
 		"newname":       newName}
 
-	shared.LogInfo("Renaming container", ctxMap)
+	logger.Info("Renaming container", ctxMap)
 
 	// Sanity checks
 	if !c.IsSnapshot() && !shared.ValidHostname(newName) {
@@ -2471,7 +2472,7 @@ func (c *containerLXC) Rename(newName string) error {
 	if shared.PathExists(c.LogPath()) {
 		err := os.Rename(c.LogPath(), shared.LogPath(newName))
 		if err != nil {
-			shared.LogError("Failed renaming container", ctxMap)
+			logger.Error("Failed renaming container", ctxMap)
 			return err
 		}
 	}
@@ -2479,19 +2480,19 @@ func (c *containerLXC) Rename(newName string) error {
 	// Rename the storage entry
 	if c.IsSnapshot() {
 		if err := c.storage.ContainerSnapshotRename(c, newName); err != nil {
-			shared.LogError("Failed renaming container", ctxMap)
+			logger.Error("Failed renaming container", ctxMap)
 			return err
 		}
 	} else {
 		if err := c.storage.ContainerRename(c, newName); err != nil {
-			shared.LogError("Failed renaming container", ctxMap)
+			logger.Error("Failed renaming container", ctxMap)
 			return err
 		}
 	}
 
 	// Rename the database entry
 	if err := dbContainerRename(c.daemon.db, oldName, newName); err != nil {
-		shared.LogError("Failed renaming container", ctxMap)
+		logger.Error("Failed renaming container", ctxMap)
 		return err
 	}
 
@@ -2499,7 +2500,7 @@ func (c *containerLXC) Rename(newName string) error {
 		// Rename all the snapshots
 		results, err := dbContainerGetSnapshots(c.daemon.db, oldName)
 		if err != nil {
-			shared.LogError("Failed renaming container", ctxMap)
+			logger.Error("Failed renaming container", ctxMap)
 			return err
 		}
 
@@ -2508,7 +2509,7 @@ func (c *containerLXC) Rename(newName string) error {
 			baseSnapName := filepath.Base(sname)
 			newSnapshotName := newName + shared.SnapshotDelimiter + baseSnapName
 			if err := dbContainerRename(c.daemon.db, sname, newSnapshotName); err != nil {
-				shared.LogError("Failed renaming container", ctxMap)
+				logger.Error("Failed renaming container", ctxMap)
 				return err
 			}
 		}
@@ -2520,7 +2521,7 @@ func (c *containerLXC) Rename(newName string) error {
 	// Invalidate the go-lxc cache
 	c.c = nil
 
-	shared.LogInfo("Renamed container", ctxMap)
+	logger.Info("Renamed container", ctxMap)
 
 	return nil
 }
@@ -3250,12 +3251,12 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		return fmt.Errorf("Cannot export a running container as an image")
 	}
 
-	shared.LogInfo("Exporting container", ctxMap)
+	logger.Info("Exporting container", ctxMap)
 
 	// Start the storage
 	err := c.StorageStart()
 	if err != nil {
-		shared.LogError("Failed exporting container", ctxMap)
+		logger.Error("Failed exporting container", ctxMap)
 		return err
 	}
 	defer c.StorageStop()
@@ -3263,13 +3264,13 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 	// Unshift the container
 	idmap, err := c.LastIdmapSet()
 	if err != nil {
-		shared.LogError("Failed exporting container", ctxMap)
+		logger.Error("Failed exporting container", ctxMap)
 		return err
 	}
 
 	if idmap != nil {
 		if err := idmap.UnshiftRootfs(c.RootfsPath()); err != nil {
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 
@@ -3293,7 +3294,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 
 		err = c.tarStoreFile(linkmap, offset, tw, path, fi)
 		if err != nil {
-			shared.LogDebugf("Error tarring up %s: %s", path, err)
+			logger.Debugf("Error tarring up %s: %s", path, err)
 			return err
 		}
 		return nil
@@ -3306,7 +3307,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		tempDir, err := ioutil.TempDir("", "lxd_lxd_metadata_")
 		if err != nil {
 			tw.Close()
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 		defer os.RemoveAll(tempDir)
@@ -3318,7 +3319,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 			parent, err := containerLoadByName(c.daemon, parentName)
 			if err != nil {
 				tw.Close()
-				shared.LogError("Failed exporting container", ctxMap)
+				logger.Error("Failed exporting container", ctxMap)
 				return err
 			}
 
@@ -3330,7 +3331,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		if arch == "" {
 			arch, err = osarch.ArchitectureName(c.daemon.architectures[0])
 			if err != nil {
-				shared.LogError("Failed exporting container", ctxMap)
+				logger.Error("Failed exporting container", ctxMap)
 				return err
 			}
 		}
@@ -3344,7 +3345,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		data, err := yaml.Marshal(&meta)
 		if err != nil {
 			tw.Close()
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 
@@ -3353,22 +3354,22 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		err = ioutil.WriteFile(fnam, data, 0644)
 		if err != nil {
 			tw.Close()
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 
 		fi, err := os.Lstat(fnam)
 		if err != nil {
 			tw.Close()
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 
 		tmpOffset := len(path.Dir(fnam)) + 1
 		if err := c.tarStoreFile(linkmap, tmpOffset, tw, fnam, fi); err != nil {
 			tw.Close()
-			shared.LogDebugf("Error writing to tarfile: %s", err)
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Debugf("Error writing to tarfile: %s", err)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 	} else {
@@ -3377,7 +3378,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 			content, err := ioutil.ReadFile(fnam)
 			if err != nil {
 				tw.Close()
-				shared.LogError("Failed exporting container", ctxMap)
+				logger.Error("Failed exporting container", ctxMap)
 				return err
 			}
 
@@ -3385,7 +3386,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 			err = yaml.Unmarshal(content, &metadata)
 			if err != nil {
 				tw.Close()
-				shared.LogError("Failed exporting container", ctxMap)
+				logger.Error("Failed exporting container", ctxMap)
 				return err
 			}
 			metadata.Properties = properties
@@ -3394,7 +3395,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 			tempDir, err := ioutil.TempDir("", "lxd_lxd_metadata_")
 			if err != nil {
 				tw.Close()
-				shared.LogError("Failed exporting container", ctxMap)
+				logger.Error("Failed exporting container", ctxMap)
 				return err
 			}
 			defer os.RemoveAll(tempDir)
@@ -3402,7 +3403,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 			data, err := yaml.Marshal(&metadata)
 			if err != nil {
 				tw.Close()
-				shared.LogError("Failed exporting container", ctxMap)
+				logger.Error("Failed exporting container", ctxMap)
 				return err
 			}
 
@@ -3411,7 +3412,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 			err = ioutil.WriteFile(fnam, data, 0644)
 			if err != nil {
 				tw.Close()
-				shared.LogError("Failed exporting container", ctxMap)
+				logger.Error("Failed exporting container", ctxMap)
 				return err
 			}
 		}
@@ -3420,8 +3421,8 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		fi, err := os.Lstat(fnam)
 		if err != nil {
 			tw.Close()
-			shared.LogDebugf("Error statting %s during export", fnam)
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Debugf("Error statting %s during export", fnam)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 
@@ -3433,8 +3434,8 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		}
 		if err != nil {
 			tw.Close()
-			shared.LogDebugf("Error writing to tarfile: %s", err)
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Debugf("Error writing to tarfile: %s", err)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 	}
@@ -3443,7 +3444,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 	fnam = c.RootfsPath()
 	err = filepath.Walk(fnam, writeToTar)
 	if err != nil {
-		shared.LogError("Failed exporting container", ctxMap)
+		logger.Error("Failed exporting container", ctxMap)
 		return err
 	}
 
@@ -3452,18 +3453,18 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 	if shared.PathExists(fnam) {
 		err = filepath.Walk(fnam, writeToTar)
 		if err != nil {
-			shared.LogError("Failed exporting container", ctxMap)
+			logger.Error("Failed exporting container", ctxMap)
 			return err
 		}
 	}
 
 	err = tw.Close()
 	if err != nil {
-		shared.LogError("Failed exporting container", ctxMap)
+		logger.Error("Failed exporting container", ctxMap)
 		return err
 	}
 
-	shared.LogInfo("Exported container", ctxMap)
+	logger.Info("Exported container", ctxMap)
 	return nil
 }
 
@@ -3506,7 +3507,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 		return fmt.Errorf("Unable to perform container live migration. CRIU isn't installed.")
 	}
 
-	shared.LogInfo("Migrating container", ctxMap)
+	logger.Info("Migrating container", ctxMap)
 
 	prettyCmd := ""
 	switch cmd {
@@ -3518,7 +3519,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 		prettyCmd = "restore"
 	default:
 		prettyCmd = "unknown"
-		shared.LogWarn("unknown migrate call", log.Ctx{"cmd": cmd})
+		logger.Warn("unknown migrate call", log.Ctx{"cmd": cmd})
 	}
 
 	preservesInodes := c.storage.PreservesInodes()
@@ -3584,7 +3585,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 
 		if string(out) != "" {
 			for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-				shared.LogDebugf("forkmigrate: %s", line)
+				logger.Debugf("forkmigrate: %s", line)
 			}
 		}
 
@@ -3632,20 +3633,20 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 
 	collectErr := collectCRIULogFile(c, stateDir, function, prettyCmd)
 	if collectErr != nil {
-		shared.LogError("Error collecting checkpoint log file", log.Ctx{"err": collectErr})
+		logger.Error("Error collecting checkpoint log file", log.Ctx{"err": collectErr})
 	}
 
 	if migrateErr != nil {
 		log, err2 := getCRIULogErrors(stateDir, prettyCmd)
 		if err2 == nil {
-			shared.LogInfo("Failed migrating container", ctxMap)
+			logger.Info("Failed migrating container", ctxMap)
 			migrateErr = fmt.Errorf("%s %s failed\n%s", function, prettyCmd, log)
 		}
 
 		return migrateErr
 	}
 
-	shared.LogInfo("Migrated container", ctxMap)
+	logger.Info("Migrated container", ctxMap)
 
 	return nil
 }
@@ -3838,7 +3839,7 @@ func (c *containerLXC) FileExists(path string) error {
 		}
 
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.LogDebugf("forkcheckfile: %s", line)
+			logger.Debugf("forkcheckfile: %s", line)
 		}
 	}
 
@@ -3937,7 +3938,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int64, int64, o
 			continue
 		}
 
-		shared.LogDebugf("forkgetfile: %s", line)
+		logger.Debugf("forkgetfile: %s", line)
 	}
 
 	if err != nil {
@@ -4145,7 +4146,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	r, w, err := shared.Pipe()
 	defer r.Close()
 	if err != nil {
-		shared.LogErrorf("s", err)
+		logger.Errorf("%s", err)
 		return nil, -1, -1, err
 	}
 
@@ -4159,7 +4160,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 
 	attachedPid := -1
 	if err := json.NewDecoder(r).Decode(&attachedPid); err != nil {
-		shared.LogErrorf("Failed to retrieve PID of executing child process: %s", err)
+		logger.Errorf("Failed to retrieve PID of executing child process: %s", err)
 		return nil, -1, -1, err
 	}
 
@@ -4276,7 +4277,7 @@ func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
 
 	// Process forkgetnet response
 	if err != nil {
-		shared.LogError("Error calling 'lxd forkgetnet", log.Ctx{"container": c.name, "output": string(out), "pid": pid})
+		logger.Error("Error calling 'lxd forkgetnet", log.Ctx{"container": c.name, "output": string(out), "pid": pid})
 		return result
 	}
 
@@ -4284,7 +4285,7 @@ func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
 
 	err = json.Unmarshal([]byte(out), &networks)
 	if err != nil {
-		shared.LogError("Failure to read forkgetnet json", log.Ctx{"container": c.name, "err": err})
+		logger.Error("Failure to read forkgetnet json", log.Ctx{"container": c.name, "err": err})
 		return result
 	}
 
@@ -4490,7 +4491,7 @@ func (c *containerLXC) insertMount(source, target, fstype string, flags int) err
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.LogDebugf("forkmount: %s", line)
+			logger.Debugf("forkmount: %s", line)
 		}
 	}
 
@@ -4520,7 +4521,7 @@ func (c *containerLXC) removeMount(mount string) error {
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
-			shared.LogDebugf("forkumount: %s", line)
+			logger.Debugf("forkumount: %s", line)
 		}
 	}
 
@@ -4654,7 +4655,7 @@ func (c *containerLXC) createUnixDevice(m types.Device) (string, error) {
 		if idmapset != nil {
 			if err := idmapset.ShiftFile(devPath); err != nil {
 				// uidshift failing is weird, but not a big problem.  Log and proceed
-				shared.LogDebugf("Failed to uidshift device %s: %s\n", m["path"], err)
+				logger.Debugf("Failed to uidshift device %s: %s\n", m["path"], err)
 			}
 		}
 	} else {
@@ -4835,7 +4836,7 @@ func (c *containerLXC) removeUnixDevices() error {
 		devicePath := filepath.Join(c.DevicesPath(), f.Name())
 		err := os.Remove(devicePath)
 		if err != nil {
-			shared.LogError("failed removing unix device", log.Ctx{"err": err, "path": devicePath})
+			logger.Error("failed removing unix device", log.Ctx{"err": err, "path": devicePath})
 		}
 	}
 
@@ -5342,7 +5343,7 @@ func (c *containerLXC) removeDiskDevices() error {
 		diskPath := filepath.Join(c.DevicesPath(), f.Name())
 		err := os.Remove(diskPath)
 		if err != nil {
-			shared.LogError("Failed to remove disk device path", log.Ctx{"err": err, "path": diskPath})
+			logger.Error("Failed to remove disk device path", log.Ctx{"err": err, "path": diskPath})
 		}
 	}
 
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 8ab75112a..425531cd3 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -80,7 +81,7 @@ func containerSnapRestore(d *Daemon, name string, snap string) error {
 		snap = name + shared.SnapshotDelimiter + snap
 	}
 
-	shared.LogInfo(
+	logger.Info(
 		"RESTORE => Restoring snapshot",
 		log.Ctx{
 			"snapshot":  snap,
@@ -88,7 +89,7 @@ func containerSnapRestore(d *Daemon, name string, snap string) error {
 
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		shared.LogError(
+		logger.Error(
 			"RESTORE => loadcontainerLXD() failed",
 			log.Ctx{
 				"container": name,
diff --git a/lxd/containers.go b/lxd/containers.go
index 63f31429d..03e79f81d 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -7,6 +7,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -168,7 +169,7 @@ func containersShutdown(d *Daemon) error {
 }
 
 func containerDeleteSnapshots(d *Daemon, cname string) error {
-	shared.LogDebug("containerDeleteSnapshots",
+	logger.Debug("containerDeleteSnapshots",
 		log.Ctx{"container": cname})
 
 	results, err := dbContainerGetSnapshots(d.db, cname)
@@ -179,7 +180,7 @@ func containerDeleteSnapshots(d *Daemon, cname string) error {
 	for _, sname := range results {
 		sc, err := containerLoadByName(d, sname)
 		if err != nil {
-			shared.LogError(
+			logger.Error(
 				"containerDeleteSnapshots: Failed to load the snapshotcontainer",
 				log.Ctx{"container": cname, "snapshot": sname})
 
@@ -187,7 +188,7 @@ func containerDeleteSnapshots(d *Daemon, cname string) error {
 		}
 
 		if err := sc.Delete(); err != nil {
-			shared.LogError(
+			logger.Error(
 				"containerDeleteSnapshots: Failed to delete a snapshotcontainer",
 				log.Ctx{"container": cname, "snapshot": sname, "err": err})
 		}
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 38e9deb8d..87a45f5da 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -5,8 +5,8 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -17,7 +17,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 			return SyncResponse(true, result)
 		}
 		if !isDbLockedError(err) {
-			shared.LogDebugf("DBERR: containersGet: error %q", err)
+			logger.Debugf("DBERR: containersGet: error %q", err)
 			return InternalError(err)
 		}
 		// 1 s may seem drastic, but we really don't want to thrash
@@ -25,8 +25,8 @@ func containersGet(d *Daemon, r *http.Request) Response {
 		time.Sleep(100 * time.Millisecond)
 	}
 
-	shared.LogDebugf("DBERR: containersGet, db is locked")
-	shared.PrintStack()
+	logger.Debugf("DBERR: containersGet, db is locked")
+	logger.PrintStack()
 	return InternalError(fmt.Errorf("DB is locked"))
 }
 
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index a852f0380..4efea4001 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -14,6 +14,7 @@ import (
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/osarch"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -279,7 +280,7 @@ func createFromCopy(d *Daemon, req *api.ContainersPost) Response {
 
 	for key, value := range sourceConfig {
 		if len(key) > 8 && key[0:8] == "volatile" && !shared.StringInSlice(key[9:], []string{"base_image", "last_state.idmap"}) {
-			shared.LogDebug("Skipping volatile key from copy source",
+			logger.Debug("Skipping volatile key from copy source",
 				log.Ctx{"key": key})
 			continue
 		}
@@ -345,7 +346,7 @@ func createFromCopy(d *Daemon, req *api.ContainersPost) Response {
 }
 
 func containersPost(d *Daemon, r *http.Request) Response {
-	shared.LogDebugf("Responding to container create")
+	logger.Debugf("Responding to container create")
 
 	req := api.ContainersPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -370,7 +371,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 				return InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries"))
 			}
 		}
-		shared.LogDebugf("No name provided, creating %s", req.Name)
+		logger.Debugf("No name provided, creating %s", req.Name)
 	}
 
 	if req.Devices == nil {
diff --git a/lxd/daemon.go b/lxd/daemon.go
index bae04fa7a..849cbecd3 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -31,6 +31,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
@@ -150,7 +151,7 @@ func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
 func readMyCert() (string, string, error) {
 	certf := shared.VarPath("server.crt")
 	keyf := shared.VarPath("server.key")
-	shared.LogDebug("Looking for existing certificates", log.Ctx{"cert": certf, "key": keyf})
+	logger.Debug("Looking for existing certificates", log.Ctx{"cert": certf, "key": keyf})
 	err := shared.FindOrGenCert(certf, keyf, false)
 
 	return certf, keyf, err
@@ -205,19 +206,19 @@ func (d *Daemon) createCmd(version string, c Command) {
 		w.Header().Set("Content-Type", "application/json")
 
 		if d.isTrustedClient(r) {
-			shared.LogDebug(
+			logger.Debug(
 				"handling",
 				log.Ctx{"method": r.Method, "url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else if r.Method == "GET" && c.untrustedGet {
-			shared.LogDebug(
+			logger.Debug(
 				"allowing untrusted GET",
 				log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else if r.Method == "POST" && c.untrustedPost {
-			shared.LogDebug(
+			logger.Debug(
 				"allowing untrusted POST",
 				log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		} else {
-			shared.LogWarn(
+			logger.Warn(
 				"rejecting request from untrusted client",
 				log.Ctx{"ip": r.RemoteAddr})
 			Forbidden.Render(w)
@@ -264,7 +265,7 @@ func (d *Daemon) createCmd(version string, c Command) {
 		if err := resp.Render(w); err != nil {
 			err := InternalError(err).Render(w)
 			if err != nil {
-				shared.LogErrorf("Failed writing error for error, giving up")
+				logger.Errorf("Failed writing error for error, giving up")
 			}
 		}
 
@@ -291,21 +292,21 @@ func (d *Daemon) SetupStorageDriver() error {
 	if lvmVgName != "" {
 		d.Storage, err = newStorage(d, storageTypeLvm)
 		if err != nil {
-			shared.LogErrorf("Could not initialize storage type LVM: %s - falling back to dir", err)
+			logger.Errorf("Could not initialize storage type LVM: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
 	} else if zfsPoolName != "" {
 		d.Storage, err = newStorage(d, storageTypeZfs)
 		if err != nil {
-			shared.LogErrorf("Could not initialize storage type ZFS: %s - falling back to dir", err)
+			logger.Errorf("Could not initialize storage type ZFS: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
 	} else if d.BackingFs == "btrfs" {
 		d.Storage, err = newStorage(d, storageTypeBtrfs)
 		if err != nil {
-			shared.LogErrorf("Could not initialize storage type btrfs: %s - falling back to dir", err)
+			logger.Errorf("Could not initialize storage type btrfs: %s - falling back to dir", err)
 		} else {
 			return nil
 		}
@@ -479,8 +480,8 @@ func (d *Daemon) Init() error {
 	}
 
 	/* Setup logging if that wasn't done before */
-	if shared.Log == nil {
-		shared.Log, err = logging.GetLogger("", "", true, true, nil)
+	if logger.Log == nil {
+		logger.Log, err = logging.GetLogger("", "", true, true, nil)
 		if err != nil {
 			return err
 		}
@@ -488,13 +489,13 @@ func (d *Daemon) Init() error {
 
 	/* Print welcome message */
 	if d.MockMode {
-		shared.LogInfo(fmt.Sprintf("LXD %s is starting in mock mode", version.Version),
+		logger.Info(fmt.Sprintf("LXD %s is starting in mock mode", version.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	} else if d.SetupMode {
-		shared.LogInfo(fmt.Sprintf("LXD %s is starting in setup mode", version.Version),
+		logger.Info(fmt.Sprintf("LXD %s is starting in setup mode", version.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	} else {
-		shared.LogInfo(fmt.Sprintf("LXD %s is starting in normal mode", version.Version),
+		logger.Info(fmt.Sprintf("LXD %s is starting in normal mode", version.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	}
 
@@ -505,31 +506,31 @@ func (d *Daemon) Init() error {
 	if aaAvailable && os.Getenv("LXD_SECURITY_APPARMOR") == "false" {
 		aaAvailable = false
 		aaAdmin = false
-		shared.LogWarnf("AppArmor support has been manually disabled")
+		logger.Warnf("AppArmor support has been manually disabled")
 	}
 
 	if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") {
 		aaAvailable = false
 		aaAdmin = false
-		shared.LogWarnf("AppArmor support has been disabled because of lack of kernel support")
+		logger.Warnf("AppArmor support has been disabled because of lack of kernel support")
 	}
 
 	_, err = exec.LookPath("apparmor_parser")
 	if aaAvailable && err != nil {
 		aaAvailable = false
 		aaAdmin = false
-		shared.LogWarnf("AppArmor support has been disabled because 'apparmor_parser' couldn't be found")
+		logger.Warnf("AppArmor support has been disabled because 'apparmor_parser' couldn't be found")
 	}
 
 	/* Detect AppArmor admin support */
 	if aaAdmin && !haveMacAdmin() {
 		aaAdmin = false
-		shared.LogWarnf("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.")
+		logger.Warnf("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.")
 	}
 
 	if aaAdmin && runningInUserns {
 		aaAdmin = false
-		shared.LogWarnf("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.")
+		logger.Warnf("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.")
 	}
 
 	/* Detect AppArmor confinment */
@@ -537,7 +538,7 @@ func (d *Daemon) Init() error {
 		profile := aaProfile()
 		if profile != "unconfined" && profile != "" {
 			aaConfined = true
-			shared.LogWarnf("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.")
+			logger.Warnf("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.")
 		}
 	}
 
@@ -562,13 +563,13 @@ func (d *Daemon) Init() error {
 			parts := strings.Split(strings.TrimSpace(content), ".")
 
 			if len(parts) == 0 {
-				shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
+				logger.Warn("unknown apparmor domain version", log.Ctx{"version": content})
 				return false
 			}
 
 			major, err := strconv.Atoi(parts[0])
 			if err != nil {
-				shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
+				logger.Warn("unknown apparmor domain version", log.Ctx{"version": content})
 				return false
 			}
 
@@ -576,7 +577,7 @@ func (d *Daemon) Init() error {
 			if len(parts) == 2 {
 				minor, err = strconv.Atoi(parts[1])
 				if err != nil {
-					shared.LogWarn("unknown apparmor domain version", log.Ctx{"version": content})
+					logger.Warn("unknown apparmor domain version", log.Ctx{"version": content})
 					return false
 				}
 			}
@@ -590,42 +591,42 @@ func (d *Daemon) Init() error {
 	/* Detect CGroup support */
 	cgBlkioController = shared.PathExists("/sys/fs/cgroup/blkio/")
 	if !cgBlkioController {
-		shared.LogWarnf("Couldn't find the CGroup blkio controller, I/O limits will be ignored.")
+		logger.Warnf("Couldn't find the CGroup blkio controller, I/O limits will be ignored.")
 	}
 
 	cgCpuController = shared.PathExists("/sys/fs/cgroup/cpu/")
 	if !cgCpuController {
-		shared.LogWarnf("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.")
+		logger.Warnf("Couldn't find the CGroup CPU controller, CPU time limits will be ignored.")
 	}
 
 	cgCpusetController = shared.PathExists("/sys/fs/cgroup/cpuset/")
 	if !cgCpusetController {
-		shared.LogWarnf("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.")
+		logger.Warnf("Couldn't find the CGroup CPUset controller, CPU pinning will be ignored.")
 	}
 
 	cgDevicesController = shared.PathExists("/sys/fs/cgroup/devices/")
 	if !cgDevicesController {
-		shared.LogWarnf("Couldn't find the CGroup devices controller, device access control won't work.")
+		logger.Warnf("Couldn't find the CGroup devices controller, device access control won't work.")
 	}
 
 	cgMemoryController = shared.PathExists("/sys/fs/cgroup/memory/")
 	if !cgMemoryController {
-		shared.LogWarnf("Couldn't find the CGroup memory controller, memory limits will be ignored.")
+		logger.Warnf("Couldn't find the CGroup memory controller, memory limits will be ignored.")
 	}
 
 	cgNetPrioController = shared.PathExists("/sys/fs/cgroup/net_prio/")
 	if !cgNetPrioController {
-		shared.LogWarnf("Couldn't find the CGroup network class controller, network limits will be ignored.")
+		logger.Warnf("Couldn't find the CGroup network class controller, network limits will be ignored.")
 	}
 
 	cgPidsController = shared.PathExists("/sys/fs/cgroup/pids/")
 	if !cgPidsController {
-		shared.LogWarnf("Couldn't find the CGroup pids controller, process limits will be ignored.")
+		logger.Warnf("Couldn't find the CGroup pids controller, process limits will be ignored.")
 	}
 
 	cgSwapAccounting = shared.PathExists("/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes")
 	if !cgSwapAccounting {
-		shared.LogWarnf("CGroup memory swap accounting is disabled, swap limits will be ignored.")
+		logger.Warnf("CGroup memory swap accounting is disabled, swap limits will be ignored.")
 	}
 
 	/* Get the list of supported architectures */
@@ -689,30 +690,30 @@ func (d *Daemon) Init() error {
 	/* Detect the filesystem */
 	d.BackingFs, err = filesystemDetect(d.lxcpath)
 	if err != nil {
-		shared.LogError("Error detecting backing fs", log.Ctx{"err": err})
+		logger.Error("Error detecting backing fs", log.Ctx{"err": err})
 	}
 
 	/* Read the uid/gid allocation */
 	d.IdmapSet, err = shared.DefaultIdmapSet()
 	if err != nil {
-		shared.LogWarn("Error reading default uid/gid map", log.Ctx{"err": err.Error()})
-		shared.LogWarnf("Only privileged containers will be able to run")
+		logger.Warn("Error reading default uid/gid map", log.Ctx{"err": err.Error()})
+		logger.Warnf("Only privileged containers will be able to run")
 		d.IdmapSet = nil
 	} else {
 		kernelIdmapSet, err := shared.CurrentIdmapSet()
 		if err == nil {
-			shared.LogInfof("Kernel uid/gid map:")
+			logger.Infof("Kernel uid/gid map:")
 			for _, lxcmap := range kernelIdmapSet.ToLxcString() {
-				shared.LogInfof(strings.TrimRight(" - "+lxcmap, "\n"))
+				logger.Infof(strings.TrimRight(" - "+lxcmap, "\n"))
 			}
 		}
 
 		if len(d.IdmapSet.Idmap) == 0 {
-			shared.LogWarnf("No available uid/gid map could be found")
-			shared.LogWarnf("Only privileged containers will be able to run")
+			logger.Warnf("No available uid/gid map could be found")
+			logger.Warnf("Only privileged containers will be able to run")
 			d.IdmapSet = nil
 		} else {
-			shared.LogInfof("Configured LXD uid/gid map:")
+			logger.Infof("Configured LXD uid/gid map:")
 			for _, lxcmap := range d.IdmapSet.Idmap {
 				suffix := ""
 
@@ -721,14 +722,14 @@ func (d *Daemon) Init() error {
 				}
 
 				for _, lxcEntry := range lxcmap.ToLxcString() {
-					shared.LogInfof(" - %s%s", strings.TrimRight(lxcEntry, "\n"), suffix)
+					logger.Infof(" - %s%s", strings.TrimRight(lxcEntry, "\n"), suffix)
 				}
 			}
 
 			err = d.IdmapSet.Usable()
 			if err != nil {
-				shared.LogWarnf("One or more uid/gid map entry isn't usable (typically due to nesting)")
-				shared.LogWarnf("Only privileged containers will be able to run")
+				logger.Warnf("One or more uid/gid map entry isn't usable (typically due to nesting)")
+				logger.Warnf("Only privileged containers will be able to run")
 				d.IdmapSet = nil
 			}
 		}
@@ -770,14 +771,14 @@ func (d *Daemon) Init() error {
 	go func() {
 		t := time.NewTicker(24 * time.Hour)
 		for {
-			shared.LogInfof("Expiring log files")
+			logger.Infof("Expiring log files")
 
 			err := d.ExpireLogs()
 			if err != nil {
-				shared.LogError("Failed to expire logs", log.Ctx{"err": err})
+				logger.Error("Failed to expire logs", log.Ctx{"err": err})
 			}
 
-			shared.LogInfof("Done expiring log files")
+			logger.Infof("Done expiring log files")
 			<-t.C
 		}
 	}()
@@ -801,7 +802,7 @@ func (d *Daemon) Init() error {
 	}
 
 	/* Setup /dev/lxd */
-	shared.LogInfof("Starting /dev/lxd handler")
+	logger.Infof("Starting /dev/lxd handler")
 	d.devlxd, err = createAndBindDevLxd()
 	if err != nil {
 		return err
@@ -862,7 +863,7 @@ func (d *Daemon) Init() error {
 	}
 
 	d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		shared.LogInfo("Sending top level 404", log.Ctx{"url": r.URL})
+		logger.Info("Sending top level 404", log.Ctx{"url": r.URL})
 		w.Header().Set("Content-Type", "application/json")
 		NotFound.Render(w)
 	})
@@ -870,7 +871,7 @@ func (d *Daemon) Init() error {
 	// Prepare the list of listeners
 	listeners := d.GetListeners()
 	if len(listeners) > 0 {
-		shared.LogInfof("LXD is socket activated")
+		logger.Infof("LXD is socket activated")
 
 		for _, listener := range listeners {
 			if shared.PathExists(listener.Addr().String()) {
@@ -881,7 +882,7 @@ func (d *Daemon) Init() error {
 			}
 		}
 	} else {
-		shared.LogInfof("LXD isn't socket activated")
+		logger.Infof("LXD isn't socket activated")
 
 		localSocketPath := shared.VarPath("unix.socket")
 
@@ -890,7 +891,7 @@ func (d *Daemon) Init() error {
 		if shared.PathExists(localSocketPath) {
 			_, err := lxd.ConnectLXDUnix("", nil)
 			if err != nil {
-				shared.LogDebugf("Detected stale unix socket, deleting")
+				logger.Debugf("Detected stale unix socket, deleting")
 				// Connecting failed, so let's delete the socket and
 				// listen on it ourselves.
 				err = os.Remove(localSocketPath)
@@ -942,10 +943,10 @@ func (d *Daemon) Init() error {
 
 		tcpl, err := tls.Listen("tcp", listenAddr, d.tlsConfig)
 		if err != nil {
-			shared.LogError("cannot listen on https socket, skipping...", log.Ctx{"err": err})
+			logger.Error("cannot listen on https socket, skipping...", log.Ctx{"err": err})
 		} else {
 			if d.TCPSocket != nil {
-				shared.LogInfof("Replacing inherited TCP socket with configured one")
+				logger.Infof("Replacing inherited TCP socket with configured one")
 				d.TCPSocket.Socket.Close()
 			}
 			d.TCPSocket = &Socket{Socket: tcpl, CloseOnExit: true}
@@ -953,14 +954,14 @@ func (d *Daemon) Init() error {
 	}
 
 	// Bind the REST API
-	shared.LogInfof("REST API daemon:")
+	logger.Infof("REST API daemon:")
 	if d.UnixSocket != nil {
-		shared.LogInfo(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
+		logger.Info(" - binding Unix socket", log.Ctx{"socket": d.UnixSocket.Socket.Addr()})
 		d.tomb.Go(func() error { return http.Serve(d.UnixSocket.Socket, &lxdHttpServer{d.mux, d}) })
 	}
 
 	if d.TCPSocket != nil {
-		shared.LogInfo(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
+		logger.Info(" - binding TCP socket", log.Ctx{"socket": d.TCPSocket.Socket.Addr()})
 		d.tomb.Go(func() error { return http.Serve(d.TCPSocket.Socket, &lxdHttpServer{d.mux, d}) })
 	}
 
@@ -1041,10 +1042,10 @@ func (d *Daemon) Ready() error {
 func (d *Daemon) CheckTrustState(cert x509.Certificate) bool {
 	for k, v := range d.clientCerts {
 		if bytes.Compare(cert.Raw, v.Raw) == 0 {
-			shared.LogDebug("Found cert", log.Ctx{"k": k})
+			logger.Debug("Found cert", log.Ctx{"k": k})
 			return true
 		}
-		shared.Log.Debug("Client cert != key", log.Ctx{"k": k})
+		logger.Log.Debug("Client cert != key", log.Ctx{"k": k})
 	}
 	return false
 }
@@ -1077,42 +1078,42 @@ func (d *Daemon) Stop() error {
 	forceStop := false
 
 	d.tomb.Kill(errStop)
-	shared.LogInfof("Stopping REST API handler:")
+	logger.Infof("Stopping REST API handler:")
 	for _, socket := range []*Socket{d.TCPSocket, d.UnixSocket} {
 		if socket == nil {
 			continue
 		}
 
 		if socket.CloseOnExit {
-			shared.LogInfo(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()})
+			logger.Info(" - closing socket", log.Ctx{"socket": socket.Socket.Addr()})
 			socket.Socket.Close()
 		} else {
-			shared.LogInfo(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()})
+			logger.Info(" - skipping socket-activated socket", log.Ctx{"socket": socket.Socket.Addr()})
 			forceStop = true
 		}
 	}
 
-	shared.LogInfof("Stopping /dev/lxd handler")
+	logger.Infof("Stopping /dev/lxd handler")
 	d.devlxd.Close()
-	shared.LogInfof("Stopped /dev/lxd handler")
+	logger.Infof("Stopped /dev/lxd handler")
 
 	if n, err := d.numRunningContainers(); err != nil || n == 0 {
-		shared.LogInfof("Unmounting temporary filesystems")
+		logger.Infof("Unmounting temporary filesystems")
 
 		syscall.Unmount(shared.VarPath("devlxd"), syscall.MNT_DETACH)
 		syscall.Unmount(shared.VarPath("shmounts"), syscall.MNT_DETACH)
 
-		shared.LogInfof("Done unmounting temporary filesystems")
+		logger.Infof("Done unmounting temporary filesystems")
 	} else {
-		shared.LogDebugf("Not unmounting temporary filesystems (containers are still running)")
+		logger.Debugf("Not unmounting temporary filesystems (containers are still running)")
 	}
 
-	shared.LogInfof("Closing the database")
+	logger.Infof("Closing the database")
 	d.db.Close()
 
-	shared.LogInfof("Saving simplestreams cache")
+	logger.Infof("Saving simplestreams cache")
 	imageSaveStreamCache()
-	shared.LogInfof("Saved simplestreams cache")
+	logger.Infof("Saved simplestreams cache")
 
 	if d.MockMode || forceStop {
 		return nil
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 11ce7454c..37dcb2080 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -15,6 +15,7 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 var daemonConfigLock sync.Mutex
@@ -204,7 +205,7 @@ func daemonConfigInit(db *sql.DB) error {
 	for k, v := range dbValues {
 		_, ok := daemonConfig[k]
 		if !ok {
-			shared.LogError("Found invalid configuration key in database", log.Ctx{"key": k})
+			logger.Error("Found invalid configuration key in database", log.Ctx{"key": k})
 		}
 
 		daemonConfig[k].currentValue = v
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index e390e4915..e8498c16f 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -18,6 +18,7 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -153,7 +154,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 				entry = newEntry
 			} else if entry != nil {
 				// Failed to fetch entry but existing cache
-				shared.LogWarn("Unable to refresh cache, using stale entry", log.Ctx{"server": server})
+				logger.Warn("Unable to refresh cache, using stale entry", log.Ctx{"server": server})
 				entry.expiry = time.Now().Add(time.Hour)
 			} else {
 				// Failed to fetch entry and nothing in cache
@@ -162,7 +163,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			}
 		} else {
 			// use the existing entry
-			shared.LogDebug("Using SimpleStreams cache entry", log.Ctx{"server": server, "expiry": entry.expiry})
+			logger.Debug("Using SimpleStreams cache entry", log.Ctx{"server": server, "expiry": entry.expiry})
 			remote = entry.remote
 		}
 		imageStreamCacheLock.Unlock()
@@ -222,7 +223,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	// Check if the image already exists (partial hash match)
 	_, imgInfo, err := dbImageGet(d.db, fp, false, true)
 	if err == nil {
-		shared.LogDebug("Image already exists in the db", log.Ctx{"image": fp})
+		logger.Debug("Image already exists in the db", log.Ctx{"image": fp})
 		info = imgInfo
 
 		return info, nil
@@ -234,7 +235,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		// We are already downloading the image
 		imagesDownloadingLock.Unlock()
 
-		shared.LogDebug(
+		logger.Debug(
 			"Already downloading the image, waiting for it to succeed",
 			log.Ctx{"image": fp})
 
@@ -245,7 +246,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		_, imgInfo, err := dbImageGet(d.db, fp, false, true)
 		if err != nil {
 			// Other download failed, lets try again
-			shared.LogError("Other image download didn't succeed", log.Ctx{"image": fp})
+			logger.Error("Other image download didn't succeed", log.Ctx{"image": fp})
 		} else {
 			// Other download succeeded, we're done
 			return imgInfo, nil
@@ -272,7 +273,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	} else {
 		ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server}
 	}
-	shared.LogInfo("Downloading image", ctxMap)
+	logger.Info("Downloading image", ctxMap)
 
 	// Cleanup any leftover from a past attempt
 	destDir := shared.VarPath("images")
@@ -463,6 +464,6 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 	}
 
-	shared.LogInfo("Image downloaded", ctxMap)
+	logger.Info("Image downloaded", ctxMap)
 	return info, nil
 }
diff --git a/lxd/db.go b/lxd/db.go
index c65e51ba7..9c4a5f15f 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -7,7 +7,7 @@ import (
 
 	"github.com/mattn/go-sqlite3"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 var (
@@ -294,14 +294,14 @@ func dbBegin(db *sql.DB) (*sql.Tx, error) {
 			return tx, nil
 		}
 		if !isDbLockedError(err) {
-			shared.LogDebugf("DbBegin: error %q", err)
+			logger.Debugf("DbBegin: error %q", err)
 			return nil, err
 		}
 		time.Sleep(30 * time.Millisecond)
 	}
 
-	shared.LogDebugf("DbBegin: DB still locked")
-	shared.PrintStack()
+	logger.Debugf("DbBegin: DB still locked")
+	logger.PrintStack()
 	return nil, fmt.Errorf("DB is locked")
 }
 
@@ -312,14 +312,14 @@ func txCommit(tx *sql.Tx) error {
 			return nil
 		}
 		if !isDbLockedError(err) {
-			shared.LogDebugf("Txcommit: error %q", err)
+			logger.Debugf("Txcommit: error %q", err)
 			return err
 		}
 		time.Sleep(30 * time.Millisecond)
 	}
 
-	shared.LogDebugf("Txcommit: db still locked")
-	shared.PrintStack()
+	logger.Debugf("Txcommit: db still locked")
+	logger.PrintStack()
 	return fmt.Errorf("DB is locked")
 }
 
@@ -338,8 +338,8 @@ func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interfac
 		time.Sleep(30 * time.Millisecond)
 	}
 
-	shared.LogDebugf("DbQueryRowScan: query %q args %q, DB still locked", q, args)
-	shared.PrintStack()
+	logger.Debugf("DbQueryRowScan: query %q args %q, DB still locked", q, args)
+	logger.PrintStack()
 	return fmt.Errorf("DB is locked")
 }
 
@@ -350,14 +350,14 @@ func dbQuery(db *sql.DB, q string, args ...interface{}) (*sql.Rows, error) {
 			return result, nil
 		}
 		if !isDbLockedError(err) {
-			shared.LogDebugf("DbQuery: query %q error %q", q, err)
+			logger.Debugf("DbQuery: query %q error %q", q, err)
 			return nil, err
 		}
 		time.Sleep(30 * time.Millisecond)
 	}
 
-	shared.LogDebugf("DbQuery: query %q args %q, DB still locked", q, args)
-	shared.PrintStack()
+	logger.Debugf("DbQuery: query %q args %q, DB still locked", q, args)
+	logger.PrintStack()
 	return nil, fmt.Errorf("DB is locked")
 }
 
@@ -430,14 +430,14 @@ func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{
 			return result, nil
 		}
 		if !isDbLockedError(err) {
-			shared.LogDebugf("DbQuery: query %q error %q", q, err)
+			logger.Debugf("DbQuery: query %q error %q", q, err)
 			return nil, err
 		}
 		time.Sleep(30 * time.Millisecond)
 	}
 
-	shared.LogDebugf("DbQueryscan: query %q inargs %q, DB still locked", q, inargs)
-	shared.PrintStack()
+	logger.Debugf("DbQueryscan: query %q inargs %q, DB still locked", q, inargs)
+	logger.PrintStack()
 	return nil, fmt.Errorf("DB is locked")
 }
 
@@ -448,13 +448,13 @@ func dbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) {
 			return result, nil
 		}
 		if !isDbLockedError(err) {
-			shared.LogDebugf("DbExec: query %q error %q", q, err)
+			logger.Debugf("DbExec: query %q error %q", q, err)
 			return nil, err
 		}
 		time.Sleep(30 * time.Millisecond)
 	}
 
-	shared.LogDebugf("DbExec: query %q args %q, DB still locked", q, args)
-	shared.PrintStack()
+	logger.Debugf("DbExec: query %q args %q, DB still locked", q, args)
+	logger.PrintStack()
 	return nil, fmt.Errorf("DB is locked")
 }
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index 843f0184a..749f53dda 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -195,7 +196,7 @@ func dbContainerConfigInsert(tx *sql.Tx, id int, config map[string]string) error
 	for k, v := range config {
 		_, err := stmt.Exec(id, k, v)
 		if err != nil {
-			shared.LogDebugf("Error adding configuration item %s = %s to container %d",
+			logger.Debugf("Error adding configuration item %s = %s to container %d",
 				k, v, id)
 			return err
 		}
@@ -240,7 +241,7 @@ func dbContainerProfilesInsert(tx *sql.Tx, id int, profiles []string) error {
 	for _, p := range profiles {
 		_, err = stmt.Exec(id, p, applyOrder)
 		if err != nil {
-			shared.LogDebugf("Error adding profile %s to container: %s",
+			logger.Debugf("Error adding profile %s to container: %s",
 				p, err)
 			return err
 		}
@@ -372,7 +373,7 @@ func dbContainerRename(db *sql.DB, oldName string, newName string) error {
 	}
 	defer stmt.Close()
 
-	shared.LogDebug(
+	logger.Debug(
 		"Calling SQL Query",
 		log.Ctx{
 			"query":   "UPDATE containers SET name = ? WHERE name = ?",
diff --git a/lxd/db_test.go b/lxd/db_test.go
index 0d3a861cb..513d7f08a 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -7,8 +7,8 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/lxd/types"
-	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 )
 
@@ -30,9 +30,9 @@ const DB_FIXTURES string = `
 //  This Helper will initialize a test in-memory DB.
 func createTestDb(t *testing.T) (db *sql.DB) {
 	// Setup logging if main() hasn't been called/when testing
-	if shared.Log == nil {
+	if logger.Log == nil {
 		var err error
-		shared.Log, err = logging.GetLogger("", "", true, true, nil)
+		logger.Log, err = logging.GetLogger("", "", true, true, nil)
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 0f4177cc4..3d3697fc3 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -11,6 +11,7 @@ import (
 	"syscall"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -75,7 +76,7 @@ type dbUpdate struct {
 func (u *dbUpdate) apply(currentVersion int, d *Daemon) error {
 	// Get the current schema version
 
-	shared.LogDebugf("Updating DB schema from %d to %d", currentVersion, u.version)
+	logger.Debugf("Updating DB schema from %d to %d", currentVersion, u.version)
 
 	err := u.run(currentVersion, u.version, d)
 	if err != nil {
@@ -100,7 +101,7 @@ func dbUpdatesApplyAll(d *Daemon) error {
 		}
 
 		if !d.MockMode && !backup {
-			shared.LogInfof("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
+			logger.Infof("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
 			err := shared.FileCopy(shared.VarPath("lxd.db"), shared.VarPath("lxd.db.bak"))
 			if err != nil {
 				return err
@@ -308,7 +309,7 @@ func dbUpdateFromV18(currentVersion int, version int, d *Daemon) error {
 		// Deal with completely broken values
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
-			shared.LogDebugf("Invalid container memory limit, id=%d value=%s, removing.", id, value)
+			logger.Debugf("Invalid container memory limit, id=%d value=%s, removing.", id, value)
 			_, err = d.db.Exec("DELETE FROM containers_config WHERE id=?;", id)
 			if err != nil {
 				return err
@@ -345,7 +346,7 @@ func dbUpdateFromV18(currentVersion int, version int, d *Daemon) error {
 		// Deal with completely broken values
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
-			shared.LogDebugf("Invalid profile memory limit, id=%d value=%s, removing.", id, value)
+			logger.Debugf("Invalid profile memory limit, id=%d value=%s, removing.", id, value)
 			_, err = d.db.Exec("DELETE FROM profiles_config WHERE id=?;", id)
 			if err != nil {
 				return err
@@ -411,11 +412,11 @@ func dbUpdateFromV15(currentVersion int, version int, d *Daemon) error {
 		newLVName = strings.Replace(newLVName, shared.SnapshotDelimiter, "-", -1)
 
 		if cName == newLVName {
-			shared.LogDebug("No need to rename, skipping", log.Ctx{"cName": cName, "newLVName": newLVName})
+			logger.Debug("No need to rename, skipping", log.Ctx{"cName": cName, "newLVName": newLVName})
 			continue
 		}
 
-		shared.LogDebug("About to rename cName in lv upgrade", log.Ctx{"lvLinkPath": lvLinkPath, "cName": cName, "newLVName": newLVName})
+		logger.Debug("About to rename cName in lv upgrade", log.Ctx{"lvLinkPath": lvLinkPath, "cName": cName, "newLVName": newLVName})
 
 		output, err := shared.RunCommand("lvrename", vgName, cName, newLVName)
 		if err != nil {
@@ -501,7 +502,7 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 		oldPath := shared.VarPath("containers", snappieces[0], "snapshots", snappieces[1])
 		newPath := shared.VarPath("snapshots", snappieces[0], snappieces[1])
 		if shared.PathExists(oldPath) && !shared.PathExists(newPath) {
-			shared.LogInfo(
+			logger.Info(
 				"Moving snapshot",
 				log.Ctx{
 					"snapshot": cName,
@@ -514,7 +515,7 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 			// snapshots/<container>/<snap0>
 			output, err := storageRsyncCopy(oldPath, newPath)
 			if err != nil {
-				shared.LogError(
+				logger.Error(
 					"Failed rsync snapshot",
 					log.Ctx{
 						"snapshot": cName,
@@ -526,7 +527,7 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 
 			// Remove containers/<container>/snapshots/<snap0>
 			if err := os.RemoveAll(oldPath); err != nil {
-				shared.LogError(
+				logger.Error(
 					"Failed to remove the old snapshot path",
 					log.Ctx{
 						"snapshot": cName,
@@ -569,7 +570,7 @@ func dbUpdateFromV10(currentVersion int, version int, d *Daemon) error {
 			return err
 		}
 
-		shared.LogDebugf("Restarting all the containers following directory rename")
+		logger.Debugf("Restarting all the containers following directory rename")
 		containersShutdown(d)
 		containersRestart(d)
 	}
diff --git a/lxd/debug.go b/lxd/debug.go
index f1a0fd7c4..d255e346c 100644
--- a/lxd/debug.go
+++ b/lxd/debug.go
@@ -6,13 +6,13 @@ import (
 	"runtime/pprof"
 	"syscall"
 
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 func doMemDump(memProfile string) {
 	f, err := os.Create(memProfile)
 	if err != nil {
-		shared.LogDebugf("Error opening memory profile file '%s': %s", err)
+		logger.Debugf("Error opening memory profile file '%s': %s", err)
 		return
 	}
 	pprof.WriteHeapProfile(f)
@@ -24,7 +24,7 @@ func memProfiler(memProfile string) {
 	signal.Notify(ch, syscall.SIGUSR1)
 	for {
 		sig := <-ch
-		shared.LogDebugf("Received '%s signal', dumping memory.", sig)
+		logger.Debugf("Received '%s signal', dumping memory.", sig)
 		doMemDump(memProfile)
 	}
 }
diff --git a/lxd/devices.go b/lxd/devices.go
index 5895bb968..4600f7f3d 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -18,6 +18,7 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -186,24 +187,24 @@ func deviceTaskBalance(d *Daemon) {
 		// Older kernel - use cpuset.cpus
 		effectiveCpus, err = cGroupGet("cpuset", "/", "cpuset.cpus")
 		if err != nil {
-			shared.LogErrorf("Error reading host's cpuset.cpus")
+			logger.Errorf("Error reading host's cpuset.cpus")
 			return
 		}
 	}
 	err = cGroupSet("cpuset", "/lxc", "cpuset.cpus", effectiveCpus)
 	if err != nil && shared.PathExists("/sys/fs/cgroup/cpuset/lxc") {
-		shared.LogWarn("Error setting lxd's cpuset.cpus", log.Ctx{"err": err})
+		logger.Warn("Error setting lxd's cpuset.cpus", log.Ctx{"err": err})
 	}
 	cpus, err := parseCpuset(effectiveCpus)
 	if err != nil {
-		shared.LogError("Error parsing host's cpu set", log.Ctx{"cpuset": effectiveCpus, "err": err})
+		logger.Error("Error parsing host's cpu set", log.Ctx{"cpuset": effectiveCpus, "err": err})
 		return
 	}
 
 	// Iterate through the containers
 	containers, err := dbContainersList(d.db, cTypeRegular)
 	if err != nil {
-		shared.LogError("problem loading containers list", log.Ctx{"err": err})
+		logger.Error("problem loading containers list", log.Ctx{"err": err})
 		return
 	}
 	fixedContainers := map[int][]container{}
@@ -267,7 +268,7 @@ func deviceTaskBalance(d *Daemon) {
 	for cpu, ctns := range fixedContainers {
 		c, ok := usage[cpu]
 		if !ok {
-			shared.LogErrorf("Internal error: container using unavailable cpu")
+			logger.Errorf("Internal error: container using unavailable cpu")
 			continue
 		}
 		id := c.strId
@@ -316,7 +317,7 @@ func deviceTaskBalance(d *Daemon) {
 		sort.Strings(set)
 		err := ctn.CGroupSet("cpuset.cpus", strings.Join(set, ","))
 		if err != nil {
-			shared.LogError("balance: Unable to set cpuset", log.Ctx{"name": ctn.Name(), "err": err, "value": strings.Join(set, ",")})
+			logger.Error("balance: Unable to set cpuset", log.Ctx{"name": ctn.Name(), "err": err, "value": strings.Join(set, ",")})
 		}
 	}
 }
@@ -360,7 +361,7 @@ func deviceNetworkPriority(d *Daemon, netif string) {
 func deviceEventListener(d *Daemon) {
 	chNetlinkCPU, chNetlinkNetwork, err := deviceNetlinkListener()
 	if err != nil {
-		shared.LogErrorf("scheduler: couldn't setup netlink listener")
+		logger.Errorf("scheduler: couldn't setup netlink listener")
 		return
 	}
 
@@ -368,7 +369,7 @@ func deviceEventListener(d *Daemon) {
 		select {
 		case e := <-chNetlinkCPU:
 			if len(e) != 2 {
-				shared.LogErrorf("Scheduler: received an invalid cpu hotplug event")
+				logger.Errorf("Scheduler: received an invalid cpu hotplug event")
 				continue
 			}
 
@@ -376,11 +377,11 @@ func deviceEventListener(d *Daemon) {
 				continue
 			}
 
-			shared.LogDebugf("Scheduler: cpu: %s is now %s: re-balancing", e[0], e[1])
+			logger.Debugf("Scheduler: cpu: %s is now %s: re-balancing", e[0], e[1])
 			deviceTaskBalance(d)
 		case e := <-chNetlinkNetwork:
 			if len(e) != 2 {
-				shared.LogErrorf("Scheduler: received an invalid network hotplug event")
+				logger.Errorf("Scheduler: received an invalid network hotplug event")
 				continue
 			}
 
@@ -388,11 +389,11 @@ func deviceEventListener(d *Daemon) {
 				continue
 			}
 
-			shared.LogDebugf("Scheduler: network: %s has been added: updating network priorities", e[0])
+			logger.Debugf("Scheduler: network: %s has been added: updating network priorities", e[0])
 			deviceNetworkPriority(d, e[0])
 		case e := <-deviceSchedRebalance:
 			if len(e) != 3 {
-				shared.LogErrorf("Scheduler: received an invalid rebalance event")
+				logger.Errorf("Scheduler: received an invalid rebalance event")
 				continue
 			}
 
@@ -400,7 +401,7 @@ func deviceEventListener(d *Daemon) {
 				continue
 			}
 
-			shared.LogDebugf("Scheduler: %s %s %s: re-balancing", e[0], e[1], e[2])
+			logger.Debugf("Scheduler: %s %s %s: re-balancing", e[0], e[1], e[2])
 			deviceTaskBalance(d)
 		}
 	}
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 73597e792..c2ece9d30 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -16,6 +16,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -218,7 +219,7 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) {
 	case http.StateNew:
 		cred, err := getCred(unixConn)
 		if err != nil {
-			shared.LogDebugf("Error getting ucred for conn %s", err)
+			logger.Debugf("Error getting ucred for conn %s", err)
 		} else {
 			m.m[unixConn] = cred
 		}
@@ -239,7 +240,7 @@ func (m *ConnPidMapper) ConnStateHandler(conn net.Conn, state http.ConnState) {
 	case http.StateClosed:
 		delete(m.m, unixConn)
 	default:
-		shared.LogDebugf("Unknown state for connection %s", state)
+		logger.Debugf("Unknown state for connection %s", state)
 	}
 }
 
diff --git a/lxd/events.go b/lxd/events.go
index 5778ae927..df3bdc600 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -13,6 +13,7 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 type eventsHandler struct {
@@ -87,7 +88,7 @@ func eventsSocket(r *http.Request, w http.ResponseWriter) error {
 	eventListeners[listener.id] = &listener
 	eventsLock.Unlock()
 
-	shared.LogDebugf("New events listener: %s", listener.id)
+	logger.Debugf("New events listener: %s", listener.id)
 
 	<-listener.active
 
@@ -96,7 +97,7 @@ func eventsSocket(r *http.Request, w http.ResponseWriter) error {
 	eventsLock.Unlock()
 
 	listener.connection.Close()
-	shared.LogDebugf("Disconnected events listener: %s", listener.id)
+	logger.Debugf("Disconnected events listener: %s", listener.id)
 
 	return nil
 }
diff --git a/lxd/images.go b/lxd/images.go
index e16f6bede..c375031d2 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -24,6 +24,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
@@ -137,8 +138,8 @@ func unpack(d *Daemon, file string, path string) error {
 		}
 
 		co := output
-		shared.LogDebugf("Unpacking failed")
-		shared.LogDebugf(co)
+		logger.Debugf("Unpacking failed")
+		logger.Debugf(co)
 
 		// Truncate the output to a single line for inclusion in the error
 		// message.  The first line isn't guaranteed to pinpoint the issue,
@@ -428,7 +429,7 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, e
 func getImgPostInfo(d *Daemon, r *http.Request, builddir string, post *os.File) (*api.Image, error) {
 	info := api.Image{}
 	var imageMeta *imageMetadata
-	logger := logging.AddContext(shared.Log, log.Ctx{"function": "getImgPostInfo"})
+	logger := logging.AddContext(logger.Log, log.Ctx{"function": "getImgPostInfo"})
 
 	public, _ := strconv.Atoi(r.Header.Get("X-LXD-public"))
 	info.Public = public == 1
@@ -636,7 +637,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		}
 
 		if err := os.RemoveAll(path); err != nil {
-			shared.LogDebugf("Error deleting temporary directory \"%s\": %s", path, err)
+			logger.Debugf("Error deleting temporary directory \"%s\": %s", path, err)
 		}
 	}
 
@@ -813,18 +814,18 @@ func imagesGet(d *Daemon, r *http.Request) Response {
 var imagesCmd = Command{name: "images", post: imagesPost, untrustedGet: true, get: imagesGet}
 
 func autoUpdateImages(d *Daemon) {
-	shared.LogInfof("Updating images")
+	logger.Infof("Updating images")
 
 	images, err := dbImagesGet(d.db, false)
 	if err != nil {
-		shared.LogError("Unable to retrieve the list of images", log.Ctx{"err": err})
+		logger.Error("Unable to retrieve the list of images", log.Ctx{"err": err})
 		return
 	}
 
 	for _, fp := range images {
 		id, info, err := dbImageGet(d.db, fp, false, true)
 		if err != nil {
-			shared.LogError("Error loading image", log.Ctx{"err": err, "fp": fp})
+			logger.Error("Error loading image", log.Ctx{"err": err, "fp": fp})
 			continue
 		}
 
@@ -837,66 +838,66 @@ func autoUpdateImages(d *Daemon) {
 			continue
 		}
 
-		shared.LogDebug("Processing image", log.Ctx{"fp": fp, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias})
+		logger.Debug("Processing image", log.Ctx{"fp": fp, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias})
 
 		newInfo, err := d.ImageDownload(nil, source.Server, source.Protocol, "", "", source.Alias, false, true)
 		if err != nil {
-			shared.LogError("Failed to update the image", log.Ctx{"err": err, "fp": fp})
+			logger.Error("Failed to update the image", log.Ctx{"err": err, "fp": fp})
 			continue
 		}
 
 		hash := newInfo.Fingerprint
 		if hash == fp {
-			shared.LogDebug("Already up to date", log.Ctx{"fp": fp})
+			logger.Debug("Already up to date", log.Ctx{"fp": fp})
 			continue
 		}
 
 		newId, _, err := dbImageGet(d.db, hash, false, true)
 		if err != nil {
-			shared.LogError("Error loading image", log.Ctx{"err": err, "fp": hash})
+			logger.Error("Error loading image", log.Ctx{"err": err, "fp": hash})
 			continue
 		}
 
 		err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedAt)
 		if err != nil {
-			shared.LogError("Error setting last use date", log.Ctx{"err": err, "fp": hash})
+			logger.Error("Error setting last use date", log.Ctx{"err": err, "fp": hash})
 			continue
 		}
 
 		err = dbImageAliasesMove(d.db, id, newId)
 		if err != nil {
-			shared.LogError("Error moving aliases", log.Ctx{"err": err, "fp": hash})
+			logger.Error("Error moving aliases", log.Ctx{"err": err, "fp": hash})
 			continue
 		}
 
 		err = doDeleteImage(d, fp)
 		if err != nil {
-			shared.LogError("Error deleting image", log.Ctx{"err": err, "fp": fp})
+			logger.Error("Error deleting image", log.Ctx{"err": err, "fp": fp})
 		}
 	}
 
-	shared.LogInfof("Done updating images")
+	logger.Infof("Done updating images")
 }
 
 func pruneExpiredImages(d *Daemon) {
-	shared.LogInfof("Pruning expired images")
+	logger.Infof("Pruning expired images")
 
 	// Get the list of expires images
 	expiry := daemonConfig["images.remote_cache_expiry"].GetInt64()
 	images, err := dbImagesGetExpired(d.db, expiry)
 	if err != nil {
-		shared.LogError("Unable to retrieve the list of expired images", log.Ctx{"err": err})
+		logger.Error("Unable to retrieve the list of expired images", log.Ctx{"err": err})
 		return
 	}
 
 	// Delete them
 	for _, fp := range images {
 		if err := doDeleteImage(d, fp); err != nil {
-			shared.LogError("Error deleting image", log.Ctx{"err": err, "fp": fp})
+			logger.Error("Error deleting image", log.Ctx{"err": err, "fp": fp})
 		}
 	}
 
-	shared.LogInfof("Done pruning expired images")
+	logger.Infof("Done pruning expired images")
 }
 
 func doDeleteImage(d *Daemon, fingerprint string) error {
@@ -909,11 +910,11 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	// look at the path
 	s, err := storageForImage(d, imgInfo)
 	if err != nil {
-		shared.LogError("error detecting image storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
+		logger.Error("error detecting image storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
 	} else {
 		// Remove the image from storage backend
 		if err = s.ImageDelete(imgInfo.Fingerprint); err != nil {
-			shared.LogError("error deleting the image from storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
+			logger.Error("error deleting the image from storage backend", log.Ctx{"fingerprint": imgInfo.Fingerprint, "err": err})
 		}
 	}
 
@@ -922,7 +923,7 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	if shared.PathExists(fname) {
 		err = os.Remove(fname)
 		if err != nil {
-			shared.LogDebugf("Error deleting image file %s: %s", fname, err)
+			logger.Debugf("Error deleting image file %s: %s", fname, err)
 		}
 	}
 
@@ -931,7 +932,7 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	if shared.PathExists(fname) {
 		err = os.Remove(fname)
 		if err != nil {
-			shared.LogDebugf("Error deleting image file %s: %s", fname, err)
+			logger.Debugf("Error deleting image file %s: %s", fname, err)
 		}
 	}
 
diff --git a/lxd/main.go b/lxd/main.go
index fc119c308..afcc7b378 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -183,7 +184,7 @@ func run() error {
 
 	handler := eventsHandler{}
 	var err error
-	shared.Log, err = logging.GetLogger(syslog, *argLogfile, *argVerbose, *argDebug, handler)
+	logger.Log, err = logging.GetLogger(syslog, *argLogfile, *argVerbose, *argDebug, handler)
 	if err != nil {
 		fmt.Printf("%s", err)
 		return nil
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index da6de2b44..753d25dc8 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 func cmdActivateIfNeeded() error {
@@ -20,7 +21,7 @@ func cmdActivateIfNeeded() error {
 	}
 
 	if !shared.PathExists(shared.VarPath("lxd.db")) {
-		shared.LogDebugf("No DB, so no need to start the daemon now.")
+		logger.Debugf("No DB, so no need to start the daemon now.")
 		return nil
 	}
 
@@ -38,7 +39,7 @@ func cmdActivateIfNeeded() error {
 	// Look for network socket
 	value := daemonConfig["core.https_address"].Get()
 	if value != "" {
-		shared.LogDebugf("Daemon has core.https_address set, activating...")
+		logger.Debugf("Daemon has core.https_address set, activating...")
 		_, err := lxd.ConnectLXDUnix("", nil)
 		return err
 	}
@@ -66,18 +67,18 @@ func cmdActivateIfNeeded() error {
 		autoStart := config["boot.autostart"]
 
 		if c.IsRunning() {
-			shared.LogDebugf("Daemon has running containers, activating...")
+			logger.Debugf("Daemon has running containers, activating...")
 			_, err := lxd.ConnectLXDUnix("", nil)
 			return err
 		}
 
 		if lastState == "RUNNING" || lastState == "Running" || shared.IsTrue(autoStart) {
-			shared.LogDebugf("Daemon has auto-started containers, activating...")
+			logger.Debugf("Daemon has auto-started containers, activating...")
 			_, err := lxd.ConnectLXDUnix("", nil)
 			return err
 		}
 	}
 
-	shared.LogDebugf("No need to start the daemon now.")
+	logger.Debugf("No need to start the daemon now.")
 	return nil
 }
diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
index 4d6d85157..9153fb587 100644
--- a/lxd/main_daemon.go
+++ b/lxd/main_daemon.go
@@ -11,6 +11,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 func cmdDaemon() error {
@@ -45,7 +46,7 @@ func cmdDaemon() error {
 		go func() {
 			for {
 				time.Sleep(time.Duration(*argPrintGoroutinesEvery) * time.Second)
-				shared.PrintStack()
+				logger.PrintStack()
 			}
 		}()
 	}
@@ -70,7 +71,7 @@ func cmdDaemon() error {
 		signal.Notify(ch, syscall.SIGPWR)
 		sig := <-ch
 
-		shared.LogInfof("Received '%s signal', shutting down containers.", sig)
+		logger.Infof("Received '%s signal', shutting down containers.", sig)
 
 		containersShutdown(d)
 
@@ -81,7 +82,7 @@ func cmdDaemon() error {
 	go func() {
 		<-d.shutdownChan
 
-		shared.LogInfof("Asked to shutdown by API, shutting down containers.")
+		logger.Infof("Asked to shutdown by API, shutting down containers.")
 
 		containersShutdown(d)
 
@@ -96,7 +97,7 @@ func cmdDaemon() error {
 		signal.Notify(ch, syscall.SIGTERM)
 		sig := <-ch
 
-		shared.LogInfof("Received '%s signal', exiting.", sig)
+		logger.Infof("Received '%s signal', exiting.", sig)
 		ret = d.Stop()
 		wg.Done()
 	}()
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 899ad9062..385f05fa5 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -21,6 +21,7 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 type migrationFields struct {
@@ -135,7 +136,7 @@ func (c *migrationFields) controlChannel() <-chan MigrationControl {
 		msg := MigrationControl{}
 		err := c.recv(&msg)
 		if err != nil {
-			shared.LogDebugf("Got error reading migration control socket %s", err)
+			logger.Debugf("Got error reading migration control socket %s", err)
 			close(ch)
 			return
 		}
@@ -490,7 +491,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				return abort(err)
 			/* the dump finished, let's continue on to the restore */
 			case <-dumpDone:
-				shared.LogDebugf("Dump finished, continuing with restore...")
+				logger.Debugf("Dump finished, continuing with restore...")
 			}
 		} else {
 			defer os.RemoveAll(checkpointDir)
@@ -531,7 +532,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		restoreSuccess <- *msg.Success
 		err := <-dumpSuccess
 		if err != nil {
-			shared.LogErrorf("dump failed after successful restore?: %q", err)
+			logger.Errorf("dump failed after successful restore?: %q", err)
 		}
 	}
 
@@ -774,7 +775,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 				// The source can only tell us it failed (e.g. if
 				// checkpointing failed). We have to tell the source
 				// whether or not the restore was successful.
-				shared.LogDebugf("Unknown message %v from source", msg)
+				logger.Debugf("Unknown message %v from source", msg)
 				err = c.src.container.TemplateApply("copy")
 				if err != nil {
 					c.src.container.Delete()
diff --git a/lxd/operations.go b/lxd/operations.go
index 517652b1c..161ff036f 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
 )
 
@@ -118,7 +119,7 @@ func (op *operation) Run() (chan error, error) {
 				op.done()
 				chanRun <- err
 
-				shared.LogDebugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err)
+				logger.Debugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err)
 
 				_, md, _ := op.Render()
 				eventSend("operation", md)
@@ -132,7 +133,7 @@ func (op *operation) Run() (chan error, error) {
 			chanRun <- nil
 
 			op.lock.Lock()
-			shared.LogDebugf("Success for %s operation: %s", op.class.String(), op.id)
+			logger.Debugf("Success for %s operation: %s", op.class.String(), op.id)
 			_, md, _ := op.Render()
 			eventSend("operation", md)
 			op.lock.Unlock()
@@ -140,7 +141,7 @@ func (op *operation) Run() (chan error, error) {
 	}
 	op.lock.Unlock()
 
-	shared.LogDebugf("Started %s operation: %s", op.class.String(), op.id)
+	logger.Debugf("Started %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
@@ -172,7 +173,7 @@ func (op *operation) Cancel() (chan error, error) {
 				op.lock.Unlock()
 				chanCancel <- err
 
-				shared.LogDebugf("Failed to cancel %s operation: %s: %s", op.class.String(), op.id, err)
+				logger.Debugf("Failed to cancel %s operation: %s: %s", op.class.String(), op.id, err)
 				_, md, _ := op.Render()
 				eventSend("operation", md)
 				return
@@ -184,13 +185,13 @@ func (op *operation) Cancel() (chan error, error) {
 			op.done()
 			chanCancel <- nil
 
-			shared.LogDebugf("Cancelled %s operation: %s", op.class.String(), op.id)
+			logger.Debugf("Cancelled %s operation: %s", op.class.String(), op.id)
 			_, md, _ := op.Render()
 			eventSend("operation", md)
 		}(op, oldStatus, chanCancel)
 	}
 
-	shared.LogDebugf("Cancelling %s operation: %s", op.class.String(), op.id)
+	logger.Debugf("Cancelling %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
@@ -202,7 +203,7 @@ func (op *operation) Cancel() (chan error, error) {
 		chanCancel <- nil
 	}
 
-	shared.LogDebugf("Cancelled %s operation: %s", op.class.String(), op.id)
+	logger.Debugf("Cancelled %s operation: %s", op.class.String(), op.id)
 	_, md, _ = op.Render()
 	eventSend("operation", md)
 
@@ -227,17 +228,17 @@ func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error
 		if err != nil {
 			chanConnect <- err
 
-			shared.LogDebugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err)
+			logger.Debugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err)
 			return
 		}
 
 		chanConnect <- nil
 
-		shared.LogDebugf("Handled %s operation: %s", op.class.String(), op.id)
+		logger.Debugf("Handled %s operation: %s", op.class.String(), op.id)
 	}(op, chanConnect)
 	op.lock.Unlock()
 
-	shared.LogDebugf("Connected %s operation: %s", op.class.String(), op.id)
+	logger.Debugf("Connected %s operation: %s", op.class.String(), op.id)
 
 	return chanConnect, nil
 }
@@ -316,7 +317,7 @@ func (op *operation) UpdateResources(opResources map[string][]string) error {
 	op.resources = opResources
 	op.lock.Unlock()
 
-	shared.LogDebugf("Updated resources for %s operation: %s", op.class.String(), op.id)
+	logger.Debugf("Updated resources for %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
@@ -342,7 +343,7 @@ func (op *operation) UpdateMetadata(opMetadata interface{}) error {
 	op.metadata = newMetadata
 	op.lock.Unlock()
 
-	shared.LogDebugf("Updated metadata for %s operation: %s", op.class.String(), op.id)
+	logger.Debugf("Updated metadata for %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
@@ -397,7 +398,7 @@ func operationCreate(opClass operationClass, opResources map[string][]string, op
 	operations[op.id] = &op
 	operationsLock.Unlock()
 
-	shared.LogDebugf("New %s operation: %s", op.class.String(), op.id)
+	logger.Debugf("New %s operation: %s", op.class.String(), op.id)
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
diff --git a/lxd/patches.go b/lxd/patches.go
index ae1e258ac..94f9655e0 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -4,6 +4,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -36,7 +37,7 @@ type patch struct {
 }
 
 func (p *patch) apply(d *Daemon) error {
-	shared.LogDebugf("Applying patch: %s", p.name)
+	logger.Debugf("Applying patch: %s", p.name)
 
 	err := p.run(p.name, d)
 	if err != nil {
@@ -95,7 +96,7 @@ func patchInvalidProfileNames(name string, d *Daemon) error {
 
 	for _, profile := range profiles {
 		if strings.Contains(profile, "/") || shared.StringInSlice(profile, []string{".", ".."}) {
-			shared.LogInfo("Removing unreachable profile (invalid name)", log.Ctx{"name": profile})
+			logger.Info("Removing unreachable profile (invalid name)", log.Ctx{"name": profile})
 			err := dbProfileDelete(d.db, profile)
 			if err != nil {
 				return err
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 2e93a46c5..4900e071f 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -35,7 +36,7 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 		} else {
 			profile, err := doProfileGet(d, name)
 			if err != nil {
-				shared.LogError("Failed to get profile", log.Ctx{"profile": name})
+				logger.Error("Failed to get profile", log.Ctx{"profile": name})
 				continue
 			}
 			resultMap[i] = profile
@@ -126,7 +127,7 @@ func getContainersWithProfile(d *Daemon, profile string) []container {
 	for _, name := range output {
 		c, err := containerLoadByName(d, name)
 		if err != nil {
-			shared.LogError("Failed opening container", log.Ctx{"container": name})
+			logger.Error("Failed opening container", log.Ctx{"container": name})
 			continue
 		}
 		results = append(results, c)
diff --git a/lxd/rsync.go b/lxd/rsync.go
index b02ecc926..6aa24f1b7 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -11,6 +11,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
@@ -110,7 +111,7 @@ func RsyncSend(path string, conn *websocket.Conn) error {
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.LogErrorf("Rsync send failed: %s: %s: %s", path, err, string(output))
+		logger.Errorf("Rsync send failed: %s: %s: %s", path, err, string(output))
 	}
 
 	<-readDone
@@ -161,7 +162,7 @@ func RsyncRecv(path string, conn *websocket.Conn) error {
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.LogErrorf("Rsync receive failed: %s: %s: %s", path, err, string(output))
+		logger.Errorf("Rsync receive failed: %s: %s: %s", path, err, string(output))
 	}
 
 	<-readDone
diff --git a/lxd/storage.go b/lxd/storage.go
index f8736a069..a1a2c5720 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -14,6 +14,7 @@ import (
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -54,7 +55,7 @@ func filesystemDetect(path string) (string, error) {
 	case filesystemSuperMagicNfs:
 		return "nfs", nil
 	default:
-		shared.LogDebugf("Unknown backing filesystem type: 0x%x", fs.Type)
+		logger.Debugf("Unknown backing filesystem type: 0x%x", fs.Type)
 		return string(fs.Type), nil
 	}
 }
@@ -289,12 +290,12 @@ type storageShared struct {
 	sTypeName    string
 	sTypeVersion string
 
-	log shared.Logger
+	log logger.Logger
 }
 
 func (ss *storageShared) initShared() error {
 	ss.log = logging.AddContext(
-		shared.Log,
+		logger.Log,
 		log.Ctx{"driver": fmt.Sprintf("storage/%s", ss.sTypeName)},
 	)
 	return nil
@@ -316,7 +317,7 @@ func (ss *storageShared) shiftRootfs(c container) error {
 	dpath := c.Path()
 	rpath := c.RootfsPath()
 
-	shared.LogDebug("Shifting root filesystem",
+	logger.Debug("Shifting root filesystem",
 		log.Ctx{"container": c.Name(), "rootfs": rpath})
 
 	idmapset, err := c.IdmapSet()
@@ -330,7 +331,7 @@ func (ss *storageShared) shiftRootfs(c container) error {
 
 	err = idmapset.ShiftRootfs(rpath)
 	if err != nil {
-		shared.LogDebugf("Shift of rootfs %s failed: %s", rpath, err)
+		logger.Debugf("Shift of rootfs %s failed: %s", rpath, err)
 		return err
 	}
 
@@ -375,13 +376,13 @@ func (ss *storageShared) setUnprivUserAcl(c container, destPath string) error {
 
 type storageLogWrapper struct {
 	w   storage
-	log shared.Logger
+	log logger.Logger
 }
 
 func (lw *storageLogWrapper) Init(config map[string]interface{}) (storage, error) {
 	_, err := lw.w.Init(config)
 	lw.log = logging.AddContext(
-		shared.Log,
+		logger.Log,
 		log.Ctx{"driver": fmt.Sprintf("storage/%s", lw.w.GetStorageTypeName())},
 	)
 
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 9b3c21147..5b3bf21c5 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -16,6 +16,7 @@ import (
 	"github.com/pborman/uuid"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -787,12 +788,12 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
-		shared.LogError("problem reading btrfs send stderr", log.Ctx{"err": err})
+		logger.Error("problem reading btrfs send stderr", log.Ctx{"err": err})
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.LogError("problem with btrfs send", log.Ctx{"output": string(output)})
+		logger.Error("problem with btrfs send", log.Ctx{"output": string(output)})
 	}
 
 	return err
@@ -942,7 +943,7 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 		// Remove the existing pre-created subvolume
 		err := s.subvolsDelete(targetPath)
 		if err != nil {
-			shared.LogErrorf("Failed to delete pre-created BTRFS subvolume: %s.", btrfsPath)
+			logger.Errorf("Failed to delete pre-created BTRFS subvolume: %s.", btrfsPath)
 			return err
 		}
 
@@ -965,12 +966,12 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 
 		output, err := ioutil.ReadAll(stderr)
 		if err != nil {
-			shared.LogDebugf("problem reading btrfs receive stderr %s", err)
+			logger.Debugf("problem reading btrfs receive stderr %s", err)
 		}
 
 		err = cmd.Wait()
 		if err != nil {
-			shared.LogError("problem with btrfs receive", log.Ctx{"output": string(output)})
+			logger.Error("problem with btrfs receive", log.Ctx{"output": string(output)})
 			return err
 		}
 
@@ -979,13 +980,13 @@ func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots [
 
 			err := s.subvolsSnapshot(cPath, targetPath, false)
 			if err != nil {
-				shared.LogError("problem with btrfs snapshot", log.Ctx{"err": err})
+				logger.Error("problem with btrfs snapshot", log.Ctx{"err": err})
 				return err
 			}
 
 			err = s.subvolsDelete(cPath)
 			if err != nil {
-				shared.LogError("problem with btrfs delete", log.Ctx{"err": err})
+				logger.Error("problem with btrfs delete", log.Ctx{"err": err})
 				return err
 			}
 		}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index e04e111ec..cac29f91c 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -13,6 +13,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	log "gopkg.in/inconshreveable/log15.v2"
 )
@@ -20,7 +21,7 @@ import (
 func storageLVMCheckVolumeGroup(vgName string) error {
 	output, err := shared.RunCommand("vgdisplay", "-s", vgName)
 	if err != nil {
-		shared.LogDebug("vgdisplay failed to find vg", log.Ctx{"output": string(output)})
+		logger.Debug("vgdisplay failed to find vg", log.Ctx{"output": string(output)})
 		return fmt.Errorf("LVM volume group '%s' not found", vgName)
 	}
 
@@ -557,7 +558,7 @@ func (s *storageLvm) createSnapshotContainer(
 
 	srcName := containerNameToLVName(sourceContainer.Name())
 	destName := containerNameToLVName(snapshotContainer.Name())
-	shared.LogDebug(
+	logger.Debug(
 		"Creating snapshot",
 		log.Ctx{"srcName": srcName, "destName": destName})
 
@@ -649,7 +650,7 @@ func (s *storageLvm) ContainerSnapshotStart(container container) error {
 	srcName := containerNameToLVName(container.Name())
 	destName := containerNameToLVName(container.Name() + "/rw")
 
-	shared.LogDebug(
+	logger.Debug(
 		"Creating snapshot",
 		log.Ctx{"srcName": srcName, "destName": destName})
 
@@ -732,7 +733,7 @@ func (s *storageLvm) ImageCreate(fingerprint string) error {
 	fstype := daemonConfig["storage.lvm_fstype"].Get()
 	err = tryMount(lvpath, tempLVMountPoint, fstype, 0, "discard")
 	if err != nil {
-		shared.LogInfof("Error mounting image LV for unpacking: %v", err)
+		logger.Infof("Error mounting image LV for unpacking: %v", err)
 		return fmt.Errorf("Error mounting image LV: %v", err)
 	}
 
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 21977fe85..58ede0a53 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -13,6 +13,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 
 	"github.com/pborman/uuid"
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -1255,12 +1256,12 @@ func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zf
 
 	output, err := ioutil.ReadAll(stderr)
 	if err != nil {
-		shared.LogError("problem reading zfs send stderr", log.Ctx{"err": err})
+		logger.Error("problem reading zfs send stderr", log.Ctx{"err": err})
 	}
 
 	err = cmd.Wait()
 	if err != nil {
-		shared.LogError("problem with zfs send", log.Ctx{"output": string(output)})
+		logger.Error("problem with zfs send", log.Ctx{"output": string(output)})
 	}
 
 	return err
@@ -1402,12 +1403,12 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*
 
 		output, err := ioutil.ReadAll(stderr)
 		if err != nil {
-			shared.LogDebug("problem reading zfs recv stderr %s", log.Ctx{"err": err})
+			logger.Debug("problem reading zfs recv stderr %s", log.Ctx{"err": err})
 		}
 
 		err = cmd.Wait()
 		if err != nil {
-			shared.LogError("problem with zfs recv", log.Ctx{"output": string(output)})
+			logger.Error("problem with zfs recv", log.Ctx{"output": string(output)})
 		}
 		return err
 	}
@@ -1451,7 +1452,7 @@ func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*
 		/* clean up our migration-send snapshots that we got from recv. */
 		zfsSnapshots, err := s.zfsListSnapshots(fmt.Sprintf("containers/%s", container.Name()))
 		if err != nil {
-			shared.LogError("failed listing snapshots post migration", log.Ctx{"err": err})
+			logger.Error("failed listing snapshots post migration", log.Ctx{"err": err})
 			return
 		}
 
diff --git a/shared/json.go b/shared/json.go
index 644d0f783..09f106665 100644
--- a/shared/json.go
+++ b/shared/json.go
@@ -4,6 +4,8 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+
+	"github.com/lxc/lxd/shared/logger"
 )
 
 type Jmap map[string]interface{}
@@ -51,11 +53,11 @@ func (m Jmap) GetBool(key string) (bool, error) {
 func DebugJson(r *bytes.Buffer) {
 	pretty := &bytes.Buffer{}
 	if err := json.Indent(pretty, r.Bytes(), "\t", "\t"); err != nil {
-		LogDebugf("error indenting json: %s", err)
+		logger.Debugf("error indenting json: %s", err)
 		return
 	}
 
 	// Print the JSON without the last "\n"
 	str := pretty.String()
-	LogDebugf("\n\t%s", str[0:len(str)-1])
+	logger.Debugf("\n\t%s", str[0:len(str)-1])
 }
diff --git a/shared/log.go b/shared/logger/log.go
similarity index 73%
rename from shared/log.go
rename to shared/logger/log.go
index ec464e06b..b811db027 100644
--- a/shared/log.go
+++ b/shared/logger/log.go
@@ -1,6 +1,6 @@
 // +build !logdebug
 
-package shared
+package logger
 
 import (
 	"fmt"
@@ -30,31 +30,31 @@ func init() {
 }
 
 // General wrappers around Logger interface functions.
-func LogDebug(msg string, ctx interface{}) {
+func Debug(msg string, ctx interface{}) {
 	if Log != nil {
 		Log.Debug(msg, ctx)
 	}
 }
 
-func LogInfo(msg string, ctx interface{}) {
+func Info(msg string, ctx interface{}) {
 	if Log != nil {
 		Log.Info(msg, ctx)
 	}
 }
 
-func LogWarn(msg string, ctx interface{}) {
+func Warn(msg string, ctx interface{}) {
 	if Log != nil {
 		Log.Warn(msg, ctx)
 	}
 }
 
-func LogError(msg string, ctx interface{}) {
+func Error(msg string, ctx interface{}) {
 	if Log != nil {
 		Log.Error(msg, ctx)
 	}
 }
 
-func LogCrit(msg string, ctx interface{}) {
+func Crit(msg string, ctx interface{}) {
 	if Log != nil {
 		Log.Crit(msg, ctx)
 	}
@@ -62,31 +62,31 @@ func LogCrit(msg string, ctx interface{}) {
 
 // Wrappers around Logger interface functions that send a string to the Logger
 // by running it through fmt.Sprintf().
-func LogInfof(format string, args ...interface{}) {
+func Infof(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Info(fmt.Sprintf(format, args...))
 	}
 }
 
-func LogDebugf(format string, args ...interface{}) {
+func Debugf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Debug(fmt.Sprintf(format, args...))
 	}
 }
 
-func LogWarnf(format string, args ...interface{}) {
+func Warnf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Warn(fmt.Sprintf(format, args...))
 	}
 }
 
-func LogErrorf(format string, args ...interface{}) {
+func Errorf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Error(fmt.Sprintf(format, args...))
 	}
 }
 
-func LogCritf(format string, args ...interface{}) {
+func Critf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Crit(fmt.Sprintf(format, args...))
 	}
@@ -95,5 +95,5 @@ func LogCritf(format string, args ...interface{}) {
 func PrintStack() {
 	buf := make([]byte, 1<<16)
 	runtime.Stack(buf, true)
-	LogErrorf("%s", buf)
+	Errorf("%s", buf)
 }
diff --git a/shared/log_debug.go b/shared/logger/log_debug.go
similarity index 84%
rename from shared/log_debug.go
rename to shared/logger/log_debug.go
index fdded57f0..0cc1ab149 100644
--- a/shared/log_debug.go
+++ b/shared/logger/log_debug.go
@@ -1,6 +1,6 @@
 // +build logdebug
 
-package shared
+package logger
 
 import (
 	"fmt"
@@ -30,7 +30,7 @@ func init() {
 }
 
 // General wrappers around Logger interface functions.
-func LogDebug(msg string, ctx interface{}) {
+func Debug(msg string, ctx interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
@@ -38,7 +38,7 @@ func LogDebug(msg string, ctx interface{}) {
 	}
 }
 
-func LogInfo(msg string, ctx interface{}) {
+func Info(msg string, ctx interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
@@ -46,7 +46,7 @@ func LogInfo(msg string, ctx interface{}) {
 	}
 }
 
-func LogWarn(msg string, ctx interface{}) {
+func Warn(msg string, ctx interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
@@ -54,7 +54,7 @@ func LogWarn(msg string, ctx interface{}) {
 	}
 }
 
-func LogError(msg string, ctx interface{}) {
+func Error(msg string, ctx interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
@@ -62,7 +62,7 @@ func LogError(msg string, ctx interface{}) {
 	}
 }
 
-func LogCrit(msg string, ctx interface{}) {
+func Crit(msg string, ctx interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
@@ -72,7 +72,7 @@ func LogCrit(msg string, ctx interface{}) {
 
 // Wrappers around Logger interface functions that send a string to the Logger
 // by running it through fmt.Sprintf().
-func LogInfof(format string, args ...interface{}) {
+func Infof(format string, args ...interface{}) {
 	if Log != nil {
 		msg := fmt.Sprintf(format, args...)
 		pc, fn, line, _ := runtime.Caller(1)
@@ -81,7 +81,7 @@ func LogInfof(format string, args ...interface{}) {
 	}
 }
 
-func LogDebugf(format string, args ...interface{}) {
+func Debugf(format string, args ...interface{}) {
 	if Log != nil {
 		msg := fmt.Sprintf(format, args...)
 		pc, fn, line, _ := runtime.Caller(1)
@@ -90,7 +90,7 @@ func LogDebugf(format string, args ...interface{}) {
 	}
 }
 
-func LogWarnf(format string, args ...interface{}) {
+func Warnf(format string, args ...interface{}) {
 	if Log != nil {
 		msg := fmt.Sprintf(format, args...)
 		pc, fn, line, _ := runtime.Caller(1)
@@ -99,7 +99,7 @@ func LogWarnf(format string, args ...interface{}) {
 	}
 }
 
-func LogErrorf(format string, args ...interface{}) {
+func Errorf(format string, args ...interface{}) {
 	if Log != nil {
 		msg := fmt.Sprintf(format, args...)
 		pc, fn, line, _ := runtime.Caller(1)
@@ -108,7 +108,7 @@ func LogErrorf(format string, args ...interface{}) {
 	}
 }
 
-func LogCritf(format string, args ...interface{}) {
+func Critf(format string, args ...interface{}) {
 	if Log != nil {
 		msg := fmt.Sprintf(format, args...)
 		pc, fn, line, _ := runtime.Caller(1)
@@ -120,5 +120,5 @@ func LogCritf(format string, args ...interface{}) {
 func PrintStack() {
 	buf := make([]byte, 1<<16)
 	runtime.Stack(buf, true)
-	LogErrorf("%s", buf)
+	Errorf("%s", buf)
 }
diff --git a/shared/logging/log.go b/shared/logging/log.go
index f07302859..15b8ac9cd 100644
--- a/shared/logging/log.go
+++ b/shared/logging/log.go
@@ -5,14 +5,14 @@ import (
 	"os"
 	"path/filepath"
 
-	"github.com/lxc/lxd/shared"
+	log "gopkg.in/inconshreveable/log15.v2"
 	"gopkg.in/inconshreveable/log15.v2/term"
 
-	log "gopkg.in/inconshreveable/log15.v2"
+	"github.com/lxc/lxd/shared/logger"
 )
 
-// GetLogger returns a logger suitable for using as shared.Log.
-func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHandler log.Handler) (shared.Logger, error) {
+// GetLogger returns a logger suitable for using as logger.Log.
+func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHandler log.Handler) (logger.Logger, error) {
 	Log := log.New()
 
 	var handlers []log.Handler
@@ -81,7 +81,7 @@ func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHa
 	return Log, nil
 }
 
-func AddContext(logger shared.Logger, ctx log.Ctx) shared.Logger {
+func AddContext(logger logger.Logger, ctx log.Ctx) logger.Logger {
 	log15logger, ok := logger.(log.Logger)
 	if !ok {
 		logger.Error("couldn't downcast logger to add context", log.Ctx{"logger": log15logger, "ctx": ctx})
diff --git a/shared/network.go b/shared/network.go
index 1e01d8ea4..c979e3b55 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -12,6 +12,8 @@ import (
 	"time"
 
 	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared/logger"
 )
 
 func RFC3493Dialer(network, address string) (net.Conn, error) {
@@ -140,14 +142,14 @@ func WebsocketSendStream(conn *websocket.Conn, r io.Reader, bufferSize int) chan
 
 			w, err := conn.NextWriter(websocket.BinaryMessage)
 			if err != nil {
-				LogDebugf("Got error getting next writer %s", err)
+				logger.Debugf("Got error getting next writer %s", err)
 				break
 			}
 
 			_, err = w.Write(buf)
 			w.Close()
 			if err != nil {
-				LogDebugf("Got err writing %s", err)
+				logger.Debugf("Got err writing %s", err)
 				break
 			}
 		}
@@ -165,23 +167,23 @@ func WebsocketRecvStream(w io.Writer, conn *websocket.Conn) chan bool {
 		for {
 			mt, r, err := conn.NextReader()
 			if mt == websocket.CloseMessage {
-				LogDebugf("Got close message for reader")
+				logger.Debugf("Got close message for reader")
 				break
 			}
 
 			if mt == websocket.TextMessage {
-				LogDebugf("got message barrier")
+				logger.Debugf("got message barrier")
 				break
 			}
 
 			if err != nil {
-				LogDebugf("Got error getting next reader %s, %s", err, w)
+				logger.Debugf("Got error getting next reader %s, %s", err, w)
 				break
 			}
 
 			buf, err := ioutil.ReadAll(r)
 			if err != nil {
-				LogDebugf("Got error writing to writer %s", err)
+				logger.Debugf("Got error writing to writer %s", err)
 				break
 			}
 
@@ -191,11 +193,11 @@ func WebsocketRecvStream(w io.Writer, conn *websocket.Conn) chan bool {
 
 			i, err := w.Write(buf)
 			if i != len(buf) {
-				LogDebugf("Didn't write all of buf")
+				logger.Debugf("Didn't write all of buf")
 				break
 			}
 			if err != nil {
-				LogDebugf("Error writing buf %s", err)
+				logger.Debugf("Error writing buf %s", err)
 				break
 			}
 		}
@@ -215,21 +217,21 @@ func defaultReader(conn *websocket.Conn, r io.ReadCloser, readDone chan<- bool)
 		buf, ok := <-in
 		if !ok {
 			r.Close()
-			LogDebugf("sending write barrier")
+			logger.Debugf("sending write barrier")
 			conn.WriteMessage(websocket.TextMessage, []byte{})
 			readDone <- true
 			return
 		}
 		w, err := conn.NextWriter(websocket.BinaryMessage)
 		if err != nil {
-			LogDebugf("Got error getting next writer %s", err)
+			logger.Debugf("Got error getting next writer %s", err)
 			break
 		}
 
 		_, err = w.Write(buf)
 		w.Close()
 		if err != nil {
-			LogDebugf("Got err writing %s", err)
+			logger.Debugf("Got err writing %s", err)
 			break
 		}
 	}
@@ -243,32 +245,32 @@ func defaultWriter(conn *websocket.Conn, w io.WriteCloser, writeDone chan<- bool
 	for {
 		mt, r, err := conn.NextReader()
 		if err != nil {
-			LogDebugf("Got error getting next reader %s, %s", err, w)
+			logger.Debugf("Got error getting next reader %s, %s", err, w)
 			break
 		}
 
 		if mt == websocket.CloseMessage {
-			LogDebugf("Got close message for reader")
+			logger.Debugf("Got close message for reader")
 			break
 		}
 
 		if mt == websocket.TextMessage {
-			LogDebugf("Got message barrier, resetting stream")
+			logger.Debugf("Got message barrier, resetting stream")
 			break
 		}
 
 		buf, err := ioutil.ReadAll(r)
 		if err != nil {
-			LogDebugf("Got error writing to writer %s", err)
+			logger.Debugf("Got error writing to writer %s", err)
 			break
 		}
 		i, err := w.Write(buf)
 		if i != len(buf) {
-			LogDebugf("Didn't write all of buf")
+			logger.Debugf("Didn't write all of buf")
 			break
 		}
 		if err != nil {
-			LogDebugf("Error writing buf %s", err)
+			logger.Debugf("Error writing buf %s", err)
 			break
 		}
 	}
diff --git a/shared/network_linux.go b/shared/network_linux.go
index 3bee79274..09ecd9841 100644
--- a/shared/network_linux.go
+++ b/shared/network_linux.go
@@ -6,6 +6,8 @@ import (
 	"io"
 
 	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared/logger"
 )
 
 func WebsocketExecMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser, exited chan bool, fd int) (chan bool, chan bool) {
@@ -20,21 +22,21 @@ func WebsocketExecMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser
 			buf, ok := <-in
 			if !ok {
 				r.Close()
-				LogDebugf("sending write barrier")
+				logger.Debugf("sending write barrier")
 				conn.WriteMessage(websocket.TextMessage, []byte{})
 				readDone <- true
 				return
 			}
 			w, err := conn.NextWriter(websocket.BinaryMessage)
 			if err != nil {
-				LogDebugf("Got error getting next writer %s", err)
+				logger.Debugf("Got error getting next writer %s", err)
 				break
 			}
 
 			_, err = w.Write(buf)
 			w.Close()
 			if err != nil {
-				LogDebugf("Got err writing %s", err)
+				logger.Debugf("Got err writing %s", err)
 				break
 			}
 		}
diff --git a/shared/util_linux.go b/shared/util_linux.go
index ebdfa0ac4..007d5556e 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -14,6 +14,8 @@ import (
 	"sync/atomic"
 	"syscall"
 	"unsafe"
+
+	"github.com/lxc/lxd/shared/logger"
 )
 
 // #cgo LDFLAGS: -lutil -lpthread
@@ -565,13 +567,13 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 
 		ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP))
 		if ret < 0 {
-			LogErrorf("Failed to poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) on file descriptor: %s.", err)
+			logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) on file descriptor: %s.", err)
 		} else if ret > 0 {
 			if (revents & POLLERR) > 0 {
-				LogWarnf("Detected poll(POLLERR) event.")
+				logger.Warnf("Detected poll(POLLERR) event.")
 			}
 		} else if ret == 0 {
-			LogDebugf("No data in stdout: exiting.")
+			logger.Debugf("No data in stdout: exiting.")
 			once.Do(closeChannel)
 			return
 		}
@@ -593,7 +595,7 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 				// COMMENT(brauner):
 				// This condition is only reached in cases where we are massively f*cked since we even handle
 				// EINTR in the underlying C wrapper around poll(). So let's exit here.
-				LogErrorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
+				logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
 				return
 			}
 
@@ -603,13 +605,13 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 			// keep on reading from the pty file descriptor until we get a simple POLLHUP back.
 			both := ((revents & (POLLIN | POLLPRI)) > 0) && ((revents & (POLLHUP | POLLRDHUP)) > 0)
 			if both {
-				LogDebugf("Detected poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) event.")
+				logger.Debugf("Detected poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) event.")
 				read := buf[offset : offset+readSize]
 				nr, err = r.Read(read)
 			}
 
 			if (revents & POLLERR) > 0 {
-				LogWarnf("Detected poll(POLLERR) event: exiting.")
+				logger.Warnf("Detected poll(POLLERR) event: exiting.")
 				return
 			}
 
@@ -670,10 +672,10 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 					//   stdout is written out.
 					ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP))
 					if ret < 0 {
-						LogErrorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
+						logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
 						return
 					} else if (revents & (POLLHUP | POLLRDHUP)) == 0 {
-						LogDebugf("Exiting but background processes are still running.")
+						logger.Debugf("Exiting but background processes are still running.")
 						return
 					}
 				}
@@ -685,7 +687,7 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 			// The attached process has exited and we have read all data that may have
 			// been buffered.
 			if ((revents & (POLLHUP | POLLRDHUP)) > 0) && !both {
-				LogDebugf("Detected poll(POLLHUP) event: exiting.")
+				logger.Debugf("Detected poll(POLLHUP) event: exiting.")
 				return
 			}
 

From 2a3d24e15ca68e6c13aec10caedc63481e3038cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Apr 2017 21:55:48 -0400
Subject: [PATCH 0842/1193] shared/logger: Add pretty formatting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/logger/format.go    | 15 +++++++++++++++
 shared/logger/log.go       | 20 ++++++++++----------
 shared/logger/log_debug.go | 20 ++++++++++----------
 3 files changed, 35 insertions(+), 20 deletions(-)
 create mode 100644 shared/logger/format.go

diff --git a/shared/logger/format.go b/shared/logger/format.go
new file mode 100644
index 000000000..16f2e83c5
--- /dev/null
+++ b/shared/logger/format.go
@@ -0,0 +1,15 @@
+package logger
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+func Pretty(input interface{}) string {
+	pretty, err := json.MarshalIndent(input, "\t", "\t")
+	if err != nil {
+		return fmt.Sprintf("%s", input)
+	}
+
+	return fmt.Sprintf("\n\t%s", pretty)
+}
diff --git a/shared/logger/log.go b/shared/logger/log.go
index b811db027..60148d497 100644
--- a/shared/logger/log.go
+++ b/shared/logger/log.go
@@ -30,33 +30,33 @@ func init() {
 }
 
 // General wrappers around Logger interface functions.
-func Debug(msg string, ctx interface{}) {
+func Debug(msg string, ctx ...interface{}) {
 	if Log != nil {
-		Log.Debug(msg, ctx)
+		Log.Debug(msg, ctx...)
 	}
 }
 
-func Info(msg string, ctx interface{}) {
+func Info(msg string, ctx ...interface{}) {
 	if Log != nil {
-		Log.Info(msg, ctx)
+		Log.Info(msg, ctx...)
 	}
 }
 
-func Warn(msg string, ctx interface{}) {
+func Warn(msg string, ctx ...interface{}) {
 	if Log != nil {
-		Log.Warn(msg, ctx)
+		Log.Warn(msg, ctx...)
 	}
 }
 
-func Error(msg string, ctx interface{}) {
+func Error(msg string, ctx ...interface{}) {
 	if Log != nil {
-		Log.Error(msg, ctx)
+		Log.Error(msg, ctx...)
 	}
 }
 
-func Crit(msg string, ctx interface{}) {
+func Crit(msg string, ctx ...interface{}) {
 	if Log != nil {
-		Log.Crit(msg, ctx)
+		Log.Crit(msg, ctx...)
 	}
 }
 
diff --git a/shared/logger/log_debug.go b/shared/logger/log_debug.go
index 0cc1ab149..49185537b 100644
--- a/shared/logger/log_debug.go
+++ b/shared/logger/log_debug.go
@@ -30,43 +30,43 @@ func init() {
 }
 
 // General wrappers around Logger interface functions.
-func Debug(msg string, ctx interface{}) {
+func Debug(msg string, ctx ...interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
-		Log.Debug(msg, ctx)
+		Log.Debug(msg, ctx...)
 	}
 }
 
-func Info(msg string, ctx interface{}) {
+func Info(msg string, ctx ...interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
-		Log.Info(msg, ctx)
+		Log.Info(msg, ctx...)
 	}
 }
 
-func Warn(msg string, ctx interface{}) {
+func Warn(msg string, ctx ...interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
-		Log.Warn(msg, ctx)
+		Log.Warn(msg, ctx...)
 	}
 }
 
-func Error(msg string, ctx interface{}) {
+func Error(msg string, ctx ...interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
-		Log.Error(msg, ctx)
+		Log.Error(msg, ctx...)
 	}
 }
 
-func Crit(msg string, ctx interface{}) {
+func Crit(msg string, ctx ...interface{}) {
 	if Log != nil {
 		pc, fn, line, _ := runtime.Caller(1)
 		msg := fmt.Sprintf("%s: %d: %s: %s", fn, line, runtime.FuncForPC(pc).Name(), msg)
-		Log.Crit(msg, ctx)
+		Log.Crit(msg, ctx...)
 	}
 }
 

From 46be9fe17ec5f388a59fc0d9381bf30885517219 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Apr 2017 21:56:09 -0400
Subject: [PATCH 0843/1193] client: Add basic logging code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go |  9 +++++++++
 client/lxd.go        | 24 ++++++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/client/connection.go b/client/connection.go
index 6ed4e14b0..5e6c063db 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -6,6 +6,7 @@ import (
 	"os"
 	"path/filepath"
 
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/simplestreams"
 )
 
@@ -33,6 +34,8 @@ type ConnectionArgs struct {
 //
 // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
 func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
+	logger.Infof("Connecting to a remote LXD over HTTPs")
+
 	// Use empty args if not specified
 	if args == nil {
 		args = &ConnectionArgs{}
@@ -66,6 +69,8 @@ func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 // If the path argument is empty, then $LXD_DIR/unix.socket will be used.
 // If that one isn't set either, then the path will default to /var/lib/lxd/unix.socket.
 func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error) {
+	logger.Infof("Connecting to a local LXD over a Unix socket")
+
 	// Use empty args if not specified
 	if args == nil {
 		args = &ConnectionArgs{}
@@ -110,6 +115,8 @@ func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error)
 //
 // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
 func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
+	logger.Infof("Connecting to a remote public LXD over HTTPs")
+
 	// Use empty args if not specified
 	if args == nil {
 		args = &ConnectionArgs{}
@@ -142,6 +149,8 @@ func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 //
 // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
 func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error) {
+	logger.Infof("Connecting to a remote simplestreams server")
+
 	// Use empty args if not specified
 	if args == nil {
 		args = &ConnectionArgs{}
diff --git a/client/lxd.go b/client/lxd.go
index 2d7707fa4..8b2f01acd 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -11,6 +11,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 // ProtocolLXD represents a LXD API server
@@ -51,6 +52,13 @@ func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag
 	var req *http.Request
 	var err error
 
+	// Log the request
+	logger.Info("Sending request to LXD",
+		"method", method,
+		"url", url,
+		"etag", ETag,
+	)
+
 	// Get a new HTTP request setup
 	if data != nil {
 		// Encode the provided data
@@ -68,6 +76,11 @@ func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag
 
 		// Set the encoding accordingly
 		req.Header.Set("Content-Type", "application/json")
+
+		// Log the data
+		if data != nil {
+			logger.Debugf(logger.Pretty(data))
+		}
 	} else {
 		// No data to be sent along with the request
 		req, err = http.NewRequest(method, url, nil)
@@ -136,6 +149,10 @@ func (r *ProtocolLXD) queryStruct(method string, path string, data interface{},
 		return "", err
 	}
 
+	// Log the data
+	logger.Debugf("Got response struct from LXD")
+	logger.Debugf(logger.Pretty(target))
+
 	return etag, nil
 }
 
@@ -159,6 +176,10 @@ func (r *ProtocolLXD) queryOperation(method string, path string, data interface{
 		chActive:  make(chan bool),
 	}
 
+	// Log the data
+	logger.Debugf("Got operation from LXD")
+	logger.Debugf(logger.Pretty(op.Operation))
+
 	return &op, etag, nil
 }
 
@@ -185,6 +206,9 @@ func (r *ProtocolLXD) rawWebsocket(url string) (*websocket.Conn, error) {
 		return nil, err
 	}
 
+	// Log the data
+	logger.Debugf("Connected to the websocket")
+
 	return conn, err
 }
 

From 52b13df5c83711fe6d989a4912d3a801af42d2d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 18 Apr 2017 19:30:06 -0400
Subject: [PATCH 0844/1193] Drop use of logger.Log when not needed
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/api_internal.go | 4 ++--
 lxd/daemon.go       | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 48a0dcf60..39cd19041 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -59,7 +59,7 @@ func internalContainerOnStart(d *Daemon, r *http.Request) Response {
 
 	err = c.OnStart()
 	if err != nil {
-		logger.Log.Error("start hook failed", log.Ctx{"container": c.Name(), "err": err})
+		logger.Error("start hook failed", log.Ctx{"container": c.Name(), "err": err})
 		return SmartError(err)
 	}
 
@@ -84,7 +84,7 @@ func internalContainerOnStop(d *Daemon, r *http.Request) Response {
 
 	err = c.OnStop(target)
 	if err != nil {
-		logger.Log.Error("stop hook failed", log.Ctx{"container": c.Name(), "err": err})
+		logger.Error("stop hook failed", log.Ctx{"container": c.Name(), "err": err})
 		return SmartError(err)
 	}
 
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 849cbecd3..567656fa4 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1045,7 +1045,7 @@ func (d *Daemon) CheckTrustState(cert x509.Certificate) bool {
 			logger.Debug("Found cert", log.Ctx{"k": k})
 			return true
 		}
-		logger.Log.Debug("Client cert != key", log.Ctx{"k": k})
+		logger.Debug("Client cert != key", log.Ctx{"k": k})
 	}
 	return false
 }

From 2a109aff96f02b8d37bb5ed1138555c63e925e27 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 20 Apr 2017 23:09:29 -0400
Subject: [PATCH 0845/1193] Fix bad cherry-pick
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

a5089d6239e32f3eed469fd4c5a0357c4216fd06 was incorrectly cherry-picked
leading in the ImageCreate call being dropped.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go |  6 ++++++
 lxd/images.go        | 12 ++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index e8498c16f..56b18d1a4 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -433,6 +433,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	// Override visiblity
 	info.Public = false
 
+	// Create storage entry
+	err = d.Storage.ImageCreate(info.Fingerprint)
+	if err != nil {
+		return nil, err
+	}
+
 	// Create the database entry
 	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
diff --git a/lxd/images.go b/lxd/images.go
index c375031d2..a0f4cbf0d 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -311,6 +311,12 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, builddir st
 	info.Architecture, _ = osarch.ArchitectureName(c.Architecture())
 	info.Properties = req.Properties
 
+	// Create storage entry
+	err = d.Storage.ImageCreate(info.Fingerprint)
+	if err != nil {
+		return nil, err
+	}
+
 	// Create the database entry
 	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
@@ -613,6 +619,12 @@ func getImgPostInfo(d *Daemon, r *http.Request, builddir string, post *os.File)
 		}
 	}
 
+	// Create storage entry
+	err = d.Storage.ImageCreate(info.Fingerprint)
+	if err != nil {
+		return nil, err
+	}
+
 	// Create the database entry
 	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {

From 7a5f5a0513611deb68839fbee9e96b72f7beac91 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 19 Apr 2017 12:27:07 +0200
Subject: [PATCH 0846/1193] Use flake8 instead of separate pyflakes and pep8

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 README.md                      |  3 ++-
 test/main.sh                   |  3 ++-
 test/suites/static_analysis.sh | 13 ++++++++++---
 3 files changed, 14 insertions(+), 5 deletions(-)
 mode change 100644 => 100755 test/suites/static_analysis.sh

diff --git a/README.md b/README.md
index b9e2a7847..97dea4882 100644
--- a/README.md
+++ b/README.md
@@ -107,7 +107,8 @@ host boot, but are needed if you'd like to use a particular backend:
 
 To run the testsuite, you'll also need:
 
-    sudo apt-get install curl gettext jq sqlite3 uuid-runtime pyflakes pep8 shellcheck bzr
+    sudo apt-get install curl gettext jq sqlite3 uuid-runtime bzr
+
 
 ### Building the tools
 
diff --git a/test/main.sh b/test/main.sh
index ba4391656..9129792c8 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -20,7 +20,8 @@ if [ -n "${LXD_DEBUG:-}" ]; then
 fi
 
 echo "==> Checking for dependencies"
-for dep in lxd lxc curl jq git xgettext sqlite3 msgmerge msgfmt shuf setfacl uuidgen pyflakes3 pep8 shellcheck; do
+deps="lxd lxc curl jq git xgettext sqlite3 msgmerge msgfmt shuf setfacl uuidgen"
+for dep in $deps; do
   which "${dep}" >/dev/null 2>&1 || (echo "Missing dependency: ${dep}" >&2 && exit 1)
 done
 
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
old mode 100644
new mode 100755
index abb65f43b..e63154970
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -10,11 +10,18 @@ test_static_analysis() {
 
     cd ../
     # Python3 static analysis
-    pep8 test/deps/import-busybox scripts/lxd-setup-lvm-storage
-    pyflakes3 test/deps/import-busybox scripts/lxd-setup-lvm-storage
+    if which flake8 >/dev/null 2>&1; then
+      flake8 test/deps/import-busybox scripts/lxd-setup-lvm-storage
+    else
+      echo "flake8 not found, python static analysis disabled"
+    fi  
 
     # Shell static analysis
-    shellcheck lxd-bridge/lxd-bridge test/main.sh test/suites/* test/backends/*
+    if which shellcheck >/dev/null 2>&1; then
+      shellcheck test/main.sh test/suites/* test/backends/*
+    else
+      echo "shellcheck not found, shell static analysis disabled"
+    fi  
 
     # Go static analysis
     ## Functions starting by empty line

From 7fd5b93cf9a97d9a38087cbdc429fffac808c099 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 19 Apr 2017 22:55:07 +0200
Subject: [PATCH 0847/1193] rsync: make our netcat handle EAGAIN

In particular, some filesystems give this, and golang's io.Copy doesn't
understand it and calls it an error. So let's instead just mask it and have
io.Copy try again.

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/main_netcat.go | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go
index f5be1b3c4..cc1bc8358 100644
--- a/lxd/main_netcat.go
+++ b/lxd/main_netcat.go
@@ -6,6 +6,7 @@ import (
 	"net"
 	"os"
 	"sync"
+	"syscall"
 )
 
 // Netcat is called with:
@@ -34,7 +35,7 @@ func cmdNetcat(args []string) error {
 	wg.Add(1)
 
 	go func() {
-		io.Copy(os.Stdout, conn)
+		io.Copy(os.Stdout, hideAgainReader{conn})
 		conn.Close()
 		wg.Done()
 	}()
@@ -47,3 +48,19 @@ func cmdNetcat(args []string) error {
 
 	return nil
 }
+
+type retryOnEagainReader struct {
+	r io.Reader
+}
+
+func (hr retryOnEagainReader) Read(p []byte) (int, error) {
+	n, err := hr.r.Read(p)
+	if err != nil {
+		// golang's io.Copy doesn't understand EAGAIN, so let's mask it
+		if errno, ok := err.(syscall.Errno); ok && errno == syscall.EAGAIN {
+			return n, nil
+		}
+	}
+
+	return n, err
+}

From 6b91b5beaa1816d568d5a6caf0c55a27ed0a8c51 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 19 Apr 2017 23:06:39 +0200
Subject: [PATCH 0848/1193] rsync: handle EAGAIN properly

Go's io.Copy() is not able to handle EAGAIN but Read() and Write() are. We need
to keep retrying on EAGAIN.

Closes #3168.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/main_netcat.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 55 insertions(+), 8 deletions(-)

diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go
index cc1bc8358..7de62f875 100644
--- a/lxd/main_netcat.go
+++ b/lxd/main_netcat.go
@@ -35,7 +35,7 @@ func cmdNetcat(args []string) error {
 	wg.Add(1)
 
 	go func() {
-		io.Copy(os.Stdout, hideAgainReader{conn})
+		io.Copy(eagainWriter{os.Stdout}, eagainReader{conn})
 		conn.Close()
 		wg.Done()
 	}()
@@ -49,18 +49,65 @@ func cmdNetcat(args []string) error {
 	return nil
 }
 
-type retryOnEagainReader struct {
+type eagainReader struct {
 	r io.Reader
 }
 
-func (hr retryOnEagainReader) Read(p []byte) (int, error) {
-	n, err := hr.r.Read(p)
-	if err != nil {
-		// golang's io.Copy doesn't understand EAGAIN, so let's mask it
-		if errno, ok := err.(syscall.Errno); ok && errno == syscall.EAGAIN {
-			return n, nil
+func (er eagainReader) Read(p []byte) (int, error) {
+	// keep retrying on EAGAIN
+again:
+	n, err := er.r.Read(p)
+	if err == nil {
+		return n, nil
+	}
+
+	var errno error
+	// EAGAIN errors can hide in os.PathError. My best explanation for this
+	// weirdness is that os.Stdout refers to /dev/stdout which is a path.
+	sysErr, ok := err.(*os.PathError)
+	if ok {
+		errno = sysErr.Err
+	} else {
+		tmpErrno, ok := err.(syscall.Errno)
+		if ok {
+			errno = tmpErrno
+		}
+	}
+
+	if errno == syscall.EAGAIN {
+		goto again
+	}
+
+	return n, err
+}
+
+type eagainWriter struct {
+	w io.Writer
+}
+
+func (ew eagainWriter) Write(p []byte) (int, error) {
+	// keep retrying on EAGAIN
+again:
+	n, err := ew.w.Write(p)
+	if err == nil {
+		return n, nil
+	}
+
+	var errno error
+	// EAGAIN errors can hide in os.PathError. My best explanation for this
+	// weirdness is that os.Stdout refers to /dev/stdout which is a path.
+	sysErr, ok := err.(*os.PathError)
+	if ok {
+		errno = sysErr.Err
+	} else {
+		tmpErrno, ok := err.(syscall.Errno)
+		if ok {
+			errno = tmpErrno
 		}
 	}
+	if errno == syscall.EAGAIN {
+		goto again
+	}
 
 	return n, err
 }

From 6bcd01c396a41d985f8a0f120709d48e69b8daa1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 20 Apr 2017 00:17:42 -0400
Subject: [PATCH 0849/1193] Fix AppArmor stack handling with nesting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This does the following
 - Re-work detection code to always fill all the various AppArmor variables
 - Add detection of LXD being run in an AppArmor stacked configuration
 - If running stacked (nesting), then load a per-container profile but
   don't attempt to setup a second level of stacking as this isn't
   supported by AppArmor.
 - Treat a security.privileged=true container inside an unprivileged
   container the same as running an unprivileged container.

Closes #3172

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go      |  11 +++--
 lxd/container_lxc.go |   4 +-
 lxd/daemon.go        | 132 ++++++++++++++++++++++++++-------------------------
 3 files changed, 75 insertions(+), 72 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index f4563b2f8..47a188139 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -319,7 +319,7 @@ func getAAProfileContent(c container) string {
 		profile += "  mount fstype=cgroup -> /sys/fs/cgroup/**,\n"
 	}
 
-	if aaStacking {
+	if aaStacking && !aaStacked {
 		profile += "\n  ### Feature: apparmor stacking\n"
 		profile += `  ### Configuration: apparmor profile loading (in namespace)
   deny /sys/k[^e]*{,/**} wklx,
@@ -357,12 +357,12 @@ func getAAProfileContent(c container) string {
 		// Apply nesting bits
 		profile += "\n  ### Configuration: nesting\n"
 		profile += strings.TrimLeft(AA_PROFILE_NESTING, "\n")
-		if !aaStacking || c.IsPrivileged() {
+		if !aaStacking || aaStacked {
 			profile += fmt.Sprintf("  change_profile -> \"%s\",\n", AAProfileFull(c))
 		}
 	}
 
-	if !c.IsPrivileged() {
+	if !c.IsPrivileged() || runningInUserns {
 		// Apply unprivileged bits
 		profile += "\n  ### Configuration: unprivileged containers\n"
 		profile += strings.TrimLeft(AA_PROFILE_UNPRIVILEGED, "\n")
@@ -404,7 +404,7 @@ func runApparmor(command string, c container) error {
 }
 
 func mkApparmorNamespace(namespace string) error {
-	if !aaStacking {
+	if !aaStacking || aaStacked {
 		return nil
 	}
 
@@ -470,7 +470,7 @@ func AADestroy(c container) error {
 		return nil
 	}
 
-	if aaStacking {
+	if aaStacking && !aaStacked {
 		p := path.Join("/sys/kernel/security/apparmor/policy/namespaces", AANamespace(c))
 		if err := os.Remove(p); err != nil {
 			logger.Error("error removing apparmor namespace", log.Ctx{"err": err, "ns": p})
@@ -508,6 +508,7 @@ func aaProfile() string {
 	if err == nil {
 		return strings.TrimSpace(string(contents))
 	}
+
 	return ""
 }
 
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f2f2d456b..f20423eef 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -709,7 +709,7 @@ func (c *containerLXC) initLXC() error {
 
 	// Base config
 	toDrop := "sys_time sys_module sys_rawio"
-	if !aaStacking {
+	if !aaStacking || aaStacked {
 		toDrop = toDrop + " mac_admin mac_override"
 	}
 
@@ -928,7 +928,7 @@ func (c *containerLXC) initLXC() error {
 			 * the old way of nesting, i.e. using the parent's
 			 * profile.
 			 */
-			if aaStacking {
+			if aaStacking && !aaStacked {
 				profile = fmt.Sprintf("%s//&:%s:", profile, AANamespace(c))
 			}
 
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 567656fa4..b0d35b26a 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -40,10 +40,11 @@ import (
 )
 
 // AppArmor
-var aaAdmin = true
-var aaAvailable = true
+var aaAvailable = false
+var aaAdmin = false
 var aaConfined = false
 var aaStacking = false
+var aaStacked = false
 
 // CGroup
 var cgBlkioController = false
@@ -502,90 +503,91 @@ func (d *Daemon) Init() error {
 	/* Detect user namespaces */
 	runningInUserns = shared.RunningInUserNS()
 
-	/* Detect AppArmor support */
-	if aaAvailable && os.Getenv("LXD_SECURITY_APPARMOR") == "false" {
-		aaAvailable = false
-		aaAdmin = false
+	/* Detect AppArmor availability */
+	_, err = exec.LookPath("apparmor_parser")
+	if os.Getenv("LXD_SECURITY_APPARMOR") == "false" {
 		logger.Warnf("AppArmor support has been manually disabled")
-	}
-
-	if aaAvailable && !shared.IsDir("/sys/kernel/security/apparmor") {
-		aaAvailable = false
-		aaAdmin = false
+	} else if !shared.IsDir("/sys/kernel/security/apparmor") {
 		logger.Warnf("AppArmor support has been disabled because of lack of kernel support")
-	}
-
-	_, err = exec.LookPath("apparmor_parser")
-	if aaAvailable && err != nil {
-		aaAvailable = false
-		aaAdmin = false
+	} else if err != nil {
 		logger.Warnf("AppArmor support has been disabled because 'apparmor_parser' couldn't be found")
+	} else {
+		aaAvailable = true
 	}
 
-	/* Detect AppArmor admin support */
-	if aaAdmin && !haveMacAdmin() {
-		aaAdmin = false
-		logger.Warnf("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.")
-	}
+	/* Detect AppArmor stacking support */
+	aaCanStack := func() bool {
+		contentBytes, err := ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/stack")
+		if err != nil {
+			return false
+		}
 
-	if aaAdmin && runningInUserns {
-		aaAdmin = false
-		logger.Warnf("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container.")
-	}
+		if string(contentBytes) != "yes\n" {
+			return false
+		}
 
-	/* Detect AppArmor confinment */
-	if !aaConfined {
-		profile := aaProfile()
-		if profile != "unconfined" && profile != "" {
-			aaConfined = true
-			logger.Warnf("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.")
+		contentBytes, err = ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/version")
+		if err != nil {
+			return false
 		}
-	}
 
-	if aaAvailable {
-		canStack := func() bool {
-			contentBytes, err := ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/stack")
-			if err != nil {
-				return false
-			}
+		content := string(contentBytes)
 
-			if string(contentBytes) != "yes\n" {
-				return false
-			}
+		parts := strings.Split(strings.TrimSpace(content), ".")
 
-			contentBytes, err = ioutil.ReadFile("/sys/kernel/security/apparmor/features/domain/version")
-			if err != nil {
-				return false
-			}
-
-			content := string(contentBytes)
-
-			parts := strings.Split(strings.TrimSpace(content), ".")
+		if len(parts) == 0 {
+			logger.Warn("unknown apparmor domain version", log.Ctx{"version": content})
+			return false
+		}
 
-			if len(parts) == 0 {
-				logger.Warn("unknown apparmor domain version", log.Ctx{"version": content})
-				return false
-			}
+		major, err := strconv.Atoi(parts[0])
+		if err != nil {
+			logger.Warn("unknown apparmor domain version", log.Ctx{"version": content})
+			return false
+		}
 
-			major, err := strconv.Atoi(parts[0])
+		minor := 0
+		if len(parts) == 2 {
+			minor, err = strconv.Atoi(parts[1])
 			if err != nil {
 				logger.Warn("unknown apparmor domain version", log.Ctx{"version": content})
 				return false
 			}
+		}
 
-			minor := 0
-			if len(parts) == 2 {
-				minor, err = strconv.Atoi(parts[1])
-				if err != nil {
-					logger.Warn("unknown apparmor domain version", log.Ctx{"version": content})
-					return false
-				}
-			}
+		return major >= 1 && minor >= 2
+	}
+
+	aaStacking = aaCanStack()
+
+	/* Detect existing AppArmor stack */
+	if shared.PathExists("/sys/kernel/security/apparmor/.ns_stacked") {
+		contentBytes, err := ioutil.ReadFile("/sys/kernel/security/apparmor/.ns_stacked")
+		if err == nil && string(contentBytes) == "yes\n" {
+			aaStacked = true
+		}
+	}
 
-			return major >= 1 && minor >= 2
+	/* Detect AppArmor admin support */
+	if !haveMacAdmin() {
+		if aaAvailable {
+			logger.Warnf("Per-container AppArmor profiles are disabled because the mac_admin capability is missing.")
 		}
+	} else if runningInUserns && !aaStacked {
+		if aaAvailable {
+			logger.Warnf("Per-container AppArmor profiles are disabled because LXD is running in an unprivileged container without stacking.")
+		}
+	} else {
+		aaAdmin = true
+	}
 
-		aaStacking = canStack()
+	/* Detect AppArmor confinment */
+	profile := aaProfile()
+	if profile != "unconfined" && profile != "" {
+		if aaAvailable {
+			logger.Warnf("Per-container AppArmor profiles are disabled because LXD is already protected by AppArmor.")
+		}
+		aaConfined = true
 	}
 
 	/* Detect CGroup support */

From 45fe6a55e0093bc7d44047fea878dfa44d2e6056 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 20 Apr 2017 11:45:00 +0200
Subject: [PATCH 0850/1193] util_linux: add function to detect errno

Closes #2494.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util_linux.go | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 007d5556e..8a234d676 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -702,3 +702,23 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 
 	return ch
 }
+
+// Detect whether err is an errno.
+func GetErrno(err error) (errno error, iserrno bool) {
+	sysErr, ok := err.(*os.SyscallError)
+	if ok {
+		return sysErr.Err, true
+	}
+
+	pathErr, ok := err.(*os.PathError)
+	if ok {
+		return pathErr.Err, true
+	}
+
+	tmpErrno, ok := err.(syscall.Errno)
+	if ok {
+		return tmpErrno, true
+	}
+
+	return nil, false
+}

From 52af9738bef6963fb960fc8f35a38c54e594019c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 21 Apr 2017 01:20:40 +0200
Subject: [PATCH 0851/1193] netcat: switch to new helper

Closes #2494.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/main_netcat.go | 37 +++++++------------------------------
 1 file changed, 7 insertions(+), 30 deletions(-)

diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go
index 7de62f875..4e96b6e1b 100644
--- a/lxd/main_netcat.go
+++ b/lxd/main_netcat.go
@@ -41,7 +41,7 @@ func cmdNetcat(args []string) error {
 	}()
 
 	go func() {
-		io.Copy(conn, os.Stdin)
+		io.Copy(eagainWriter{conn}, eagainReader{os.Stdin})
 	}()
 
 	wg.Wait()
@@ -54,27 +54,15 @@ type eagainReader struct {
 }
 
 func (er eagainReader) Read(p []byte) (int, error) {
-	// keep retrying on EAGAIN
 again:
 	n, err := er.r.Read(p)
 	if err == nil {
 		return n, nil
 	}
 
-	var errno error
-	// EAGAIN errors can hide in os.PathError. My best explanation for this
-	// weirdness is that os.Stdout refers to /dev/stdout which is a path.
-	sysErr, ok := err.(*os.PathError)
-	if ok {
-		errno = sysErr.Err
-	} else {
-		tmpErrno, ok := err.(syscall.Errno)
-		if ok {
-			errno = tmpErrno
-		}
-	}
-
-	if errno == syscall.EAGAIN {
+	// keep retrying on EAGAIN
+	errno, ok := shared.GetErrno(err)
+	if ok && errno == syscall.EAGAIN {
 		goto again
 	}
 
@@ -86,26 +74,15 @@ type eagainWriter struct {
 }
 
 func (ew eagainWriter) Write(p []byte) (int, error) {
-	// keep retrying on EAGAIN
 again:
 	n, err := ew.w.Write(p)
 	if err == nil {
 		return n, nil
 	}
 
-	var errno error
-	// EAGAIN errors can hide in os.PathError. My best explanation for this
-	// weirdness is that os.Stdout refers to /dev/stdout which is a path.
-	sysErr, ok := err.(*os.PathError)
-	if ok {
-		errno = sysErr.Err
-	} else {
-		tmpErrno, ok := err.(syscall.Errno)
-		if ok {
-			errno = tmpErrno
-		}
-	}
-	if errno == syscall.EAGAIN {
+	// keep retrying on EAGAIN
+	errno, ok := shared.GetErrno(err)
+	if ok && errno == syscall.EAGAIN {
 		goto again
 	}
 

From 3d1ab10b1066fe52497ca08eb4dee46b7d71afac Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 21 Apr 2017 01:21:50 +0200
Subject: [PATCH 0852/1193] container: add containerGetParentAndSnapshotName()

Returns the parent container name, snapshot name, and whether it actually was a
snapshot name.

Closes #2494.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container.go | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/lxd/container.go b/lxd/container.go
index 6e2e54f3c..d2dd0cce1 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -18,6 +18,18 @@ import (
 )
 
 // Helper functions
+
+// Returns the parent container name, snapshot name, and whether it actually was
+// a snapshot name.
+func containerGetParentAndSnapshotName(name string) (string, string, bool) {
+	fields := strings.SplitN(name, shared.SnapshotDelimiter, 2)
+	if len(fields) == 1 {
+		return name, "", false
+	}
+
+	return fields[0], fields[1], true
+}
+
 func containerPath(name string, isSnapshot bool) string {
 	if isSnapshot {
 		return shared.VarPath("snapshots", name)

From 15b13fe6b5f2edc20ff96395d571decf8a42fc4c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 21 Apr 2017 01:23:14 +0200
Subject: [PATCH 0853/1193] netcat: implement logging

Closes #2494.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/main_netcat.go   | 27 ++++++++++++++++++++++++---
 lxd/migrate.go       |  3 ++-
 lxd/rsync.go         | 30 +++++++++++++-----------------
 lxd/storage.go       |  9 ++++++---
 shared/util_linux.go |  7 ++++++-
 5 files changed, 51 insertions(+), 25 deletions(-)

diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go
index 4e96b6e1b..1cf9ee0a1 100644
--- a/lxd/main_netcat.go
+++ b/lxd/main_netcat.go
@@ -7,6 +7,8 @@ import (
 	"os"
 	"sync"
 	"syscall"
+
+	"github.com/lxc/lxd/shared"
 )
 
 // Netcat is called with:
@@ -17,17 +19,30 @@ import (
 // after the path to the unix socket are ignored, so that this can be passed
 // directly to rsync as the sync command.
 func cmdNetcat(args []string) error {
-	if len(args) < 2 {
+	if len(args) < 3 {
 		return fmt.Errorf("Bad arguments %q", args)
 	}
 
+	logPath := shared.LogPath(args[2], "netcat.log")
+	if shared.PathExists(logPath) {
+		os.Remove(logPath)
+	}
+
+	logFile, err := os.OpenFile(logPath, os.O_WRONLY|os.O_CREATE|os.O_SYNC, 0644)
+	if err != nil {
+		return err
+	}
+	defer logFile.Close()
+
 	uAddr, err := net.ResolveUnixAddr("unix", args[1])
 	if err != nil {
+		logFile.WriteString(fmt.Sprintf("Could not resolve unix domain socket \"%s\": %s.\n", args[1], err))
 		return err
 	}
 
 	conn, err := net.DialUnix("unix", nil, uAddr)
 	if err != nil {
+		logFile.WriteString(fmt.Sprintf("Could not dial unix domain socket \"%s\": %s.\n", args[1], err))
 		return err
 	}
 
@@ -35,13 +50,19 @@ func cmdNetcat(args []string) error {
 	wg.Add(1)
 
 	go func() {
-		io.Copy(eagainWriter{os.Stdout}, eagainReader{conn})
+		_, err := io.Copy(eagainWriter{os.Stdout}, eagainReader{conn})
+		if err != nil {
+			logFile.WriteString(fmt.Sprintf("Error while copying from stdout to unix domain socket \"%s\": %s.\n", args[1], err))
+		}
 		conn.Close()
 		wg.Done()
 	}()
 
 	go func() {
-		io.Copy(eagainWriter{conn}, eagainReader{os.Stdin})
+		_, err := io.Copy(eagainWriter{conn}, eagainReader{os.Stdin})
+		if err != nil {
+			logFile.WriteString(fmt.Sprintf("Error while copying from unix domain socket \"%s\" to stdin: %s.\n", args[1], err))
+		}
 	}()
 
 	wg.Wait()
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 385f05fa5..d3e8cd4c4 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -508,7 +508,8 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 		 * no reason to do these in parallel. In the future when we're using
 		 * p.haul's protocol, it will make sense to do these in parallel.
 		 */
-		err = RsyncSend(shared.AddSlash(checkpointDir), s.criuConn)
+		ctName, _, _ := containerGetParentAndSnapshotName(s.container.Name())
+		err = RsyncSend(ctName, shared.AddSlash(checkpointDir), s.criuConn)
 		if err != nil {
 			return abort(err)
 		}
diff --git a/lxd/rsync.go b/lxd/rsync.go
index 6aa24f1b7..ca982996e 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -5,27 +5,16 @@ import (
 	"io"
 	"io/ioutil"
 	"net"
-	"os"
 	"os/exec"
 
 	"github.com/gorilla/websocket"
+	"github.com/pborman/uuid"
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 )
 
-func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
-	/*
-	 * It's sort of unfortunate, but there's no library call to get a
-	 * temporary name, so we get the file and close it and use its name.
-	 */
-	f, err := ioutil.TempFile("", "lxd_rsync_")
-	if err != nil {
-		return nil, nil, nil, err
-	}
-	f.Close()
-	os.Remove(f.Name())
-
+func rsyncSendSetup(name string, path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	/*
 	 * The way rsync works, it invokes a subprocess that does the actual
 	 * talking (given to it by a -E argument). Since there isn't an easy
@@ -40,7 +29,14 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	 * stdin/stdout, but that also seemed messy. In any case, this seems to
 	 * work just fine.
 	 */
-	l, err := net.Listen("unix", f.Name())
+	auds := fmt.Sprintf("@lxd/%s", uuid.NewRandom().String())
+	// We simply copy a part of the uuid if it's longer than the allowed
+	// maximum. That should be safe enough for our purposes.
+	if len(auds) > shared.ABSTRACT_UNIX_SOCK_LEN-1 {
+		auds = auds[:shared.ABSTRACT_UNIX_SOCK_LEN-1]
+	}
+
+	l, err := net.Listen("unix", auds)
 	if err != nil {
 		return nil, nil, nil, err
 	}
@@ -56,7 +52,7 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	 * command (i.e. the command to run on --server). However, we're
 	 * hardcoding that at the other end, so we can just ignore it.
 	 */
-	rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, f.Name())
+	rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s %s\"", execPath, auds, name)
 	cmd := exec.Command(
 		"rsync",
 		"-arvP",
@@ -90,8 +86,8 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 
 // RsyncSend sets up the sending half of an rsync, to recursively send the
 // directory pointed to by path over the websocket.
-func RsyncSend(path string, conn *websocket.Conn) error {
-	cmd, dataSocket, stderr, err := rsyncSendSetup(path)
+func RsyncSend(name string, path string, conn *websocket.Conn) error {
+	cmd, dataSocket, stderr, err := rsyncSendSetup(name, path)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/storage.go b/lxd/storage.go
index a1a2c5720..8b41c7729 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -627,6 +627,7 @@ func (s rsyncStorageSourceDriver) Snapshots() []container {
 }
 
 func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn) error {
+	ctName, _, _ := containerGetParentAndSnapshotName(s.container.Name())
 	for _, send := range s.snapshots {
 		if err := send.StorageStart(); err != nil {
 			return err
@@ -634,17 +635,19 @@ func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn) error {
 		defer send.StorageStop()
 
 		path := send.Path()
-		if err := RsyncSend(shared.AddSlash(path), conn); err != nil {
+		if err := RsyncSend(ctName, shared.AddSlash(path), conn); err != nil {
 			return err
 		}
 	}
 
-	return RsyncSend(shared.AddSlash(s.container.Path()), conn)
+	return RsyncSend(ctName, shared.AddSlash(s.container.Path()), conn)
 }
 
 func (s rsyncStorageSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) error {
+	ctName, _, _ := containerGetParentAndSnapshotName(s.container.Name())
+
 	/* resync anything that changed between our first send and the checkpoint */
-	return RsyncSend(shared.AddSlash(s.container.Path()), conn)
+	return RsyncSend(ctName, shared.AddSlash(s.container.Path()), conn)
 }
 
 func (s rsyncStorageSourceDriver) Cleanup() {
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 8a234d676..90ec824e2 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -31,9 +31,10 @@ import (
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 #include <sys/stat.h>
 #include <sys/types.h>
-#include <unistd.h>
+#include <sys/un.h>
 
 #ifndef AT_SYMLINK_FOLLOW
 #define AT_SYMLINK_FOLLOW    0x400
@@ -43,6 +44,8 @@ import (
 #define AT_EMPTY_PATH       0x1000
 #endif
 
+#define ABSTRACT_UNIX_SOCK_LEN sizeof(((struct sockaddr_un *)0)->sun_path)
+
 // This is an adaption from https://codereview.appspot.com/4589049, to be
 // included in the stdlib with the stdlib's license.
 
@@ -212,6 +215,8 @@ again:
 */
 import "C"
 
+const ABSTRACT_UNIX_SOCK_LEN int = C.ABSTRACT_UNIX_SOCK_LEN
+
 const POLLIN int = C.POLLIN
 const POLLPRI int = C.POLLPRI
 const POLLNVAL int = C.POLLNVAL

From 9593ccc928ca4f0b45feaec3c2e51f49bf344448 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 20 Apr 2017 23:10:57 -0400
Subject: [PATCH 0854/1193] Drop leftover debug statement
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 56b18d1a4..d9a909e4a 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -442,7 +442,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	// Create the database entry
 	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
-		return nil, fmt.Errorf("here: %v: %s", err, info.Fingerprint)
+		return nil, err
 	}
 
 	// Image is in the DB now, don't wipe on-disk files on failure

From 8b1c453c7cfbc160c9679f3522a19a233a99ab89 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 20 Apr 2017 23:50:08 -0400
Subject: [PATCH 0855/1193] image: Trailing whitespace
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/image.go b/lxc/image.go
index 659842b7a..482ec10e8 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -99,7 +99,7 @@ lxc image export [<remote>:]<image> [target]
     split images) as found in the database will be used for the exported
     image.  If the target is a file (not a directory and not stdout), then
     the appropriate extension will be appended to the provided file name
-    based on the algorithm used to compress the image. 
+    based on the algorithm used to compress the image.
 
 lxc image info [<remote>:]<image>
     Print everything LXD knows about a given image.

From 7273965fc2ab821055ee5517bd194d1e4ee9e181 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 20 Apr 2017 23:52:11 -0400
Subject: [PATCH 0856/1193] vagrant: Trailing whitespace
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/vagrant/install-lxd.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/vagrant/install-lxd.sh b/scripts/vagrant/install-lxd.sh
index 7e59f788f..d2fee6e34 100644
--- a/scripts/vagrant/install-lxd.sh
+++ b/scripts/vagrant/install-lxd.sh
@@ -11,7 +11,7 @@ sudo apt-get -y install xz-utils tar acl curl gettext \
 sudo apt-get -y install lxc lxc-dev mercurial git pkg-config \
     protobuf-compiler golang-goprotobuf-dev squashfs-tools
 
-# setup env 
+# setup env
 [ -e uid_gid_setup ] || \
     echo "root:1000000:65536" | sudo tee -a /etc/subuid /etc/subgid && \
     touch uid_gid_setup

From e9396533f8c34a46281904849175c486240de797 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 20 Apr 2017 23:52:26 -0400
Subject: [PATCH 0857/1193] tests: Trailing whitespaces
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/static_analysis.sh | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index e63154970..ade38400e 100755
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -14,14 +14,18 @@ test_static_analysis() {
       flake8 test/deps/import-busybox scripts/lxd-setup-lvm-storage
     else
       echo "flake8 not found, python static analysis disabled"
-    fi  
+    fi
 
     # Shell static analysis
     if which shellcheck >/dev/null 2>&1; then
       shellcheck test/main.sh test/suites/* test/backends/*
     else
       echo "shellcheck not found, shell static analysis disabled"
+<<<<<<< HEAD
     fi  
+=======
+    fi
+>>>>>>> 7050be73... tests: Trailing whitespaces
 
     # Go static analysis
     ## Functions starting by empty line

From 552e2b376b631bbc61635e4afaac0a04c902bd5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 21 Apr 2017 00:41:33 -0400
Subject: [PATCH 0858/1193] tests: Testsuites are sourced, not executed
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/static_analysis.sh | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 mode change 100755 => 100644 test/suites/static_analysis.sh

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
old mode 100755
new mode 100644

From 34830bad1cd7ec0555ed21732359c6f553af4947 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at gmail.com>
Date: Mon, 24 Apr 2017 11:55:35 +0200
Subject: [PATCH 0859/1193] Make sure a client certificate is generated

The serverconfig.sh relies on client.crt being generated, running
`lxc remote add` (via ensure_has_localhost_remote) does the trick.

With this branch "sudo -E ./main.sh server_config" will no longer
fail.

Signed-off-by: Free Ekanayaka <free.ekanayaka at gmail.com>
---
 test/suites/serverconfig.sh | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/suites/serverconfig.sh b/test/suites/serverconfig.sh
index 1df24c83a..d9cd0c5b2 100644
--- a/test/suites/serverconfig.sh
+++ b/test/suites/serverconfig.sh
@@ -4,6 +4,7 @@ test_server_config() {
   LXD_SERVERCONFIG_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
   spawn_lxd "${LXD_SERVERCONFIG_DIR}"
 
+  ensure_has_localhost_remote "${LXD_ADDR}"
   lxc config set core.trust_password 123456
 
   config=$(lxc config show)

From f90d50331041b11ad3feb5cd9ba47cd1c948d6dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 24 Apr 2017 12:15:36 -0400
Subject: [PATCH 0860/1193] lxc: Show all commands in "man lxc"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3214

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go    | 3 +--
 lxc/manpage.go | 2 +-
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/lxc/main.go b/lxc/main.go
index be7d30fa8..4c60244da 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -62,8 +62,7 @@ func run() error {
 	}
 
 	if len(os.Args) >= 2 && (os.Args[1] == "--all") {
-		os.Args[1] = "help"
-		os.Args = append(os.Args, "--all")
+		os.Args = []string{os.Args[0], "help", "--all"}
 	}
 
 	if shared.StringInSlice("--version", os.Args) {
diff --git a/lxc/manpage.go b/lxc/manpage.go
index 5dc07c1d0..697f015d4 100644
--- a/lxc/manpage.go
+++ b/lxc/manpage.go
@@ -50,7 +50,7 @@ func (c *manpageCmd) run(_ *lxd.Config, args []string) error {
 	}
 
 	// Generate the main manpage
-	err = help2man(execName, "LXD - client", filepath.Join(args[0], fmt.Sprintf("lxc.1")))
+	err = help2man(fmt.Sprintf("%s --all", execName), "LXD - client", filepath.Join(args[0], fmt.Sprintf("lxc.1")))
 	if err != nil {
 		return fmt.Errorf(i18n.G("Failed to generate 'lxc.1': %v"), err)
 	}

From 46b2abf23b734803cc6e0713413410421250bf36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 3 May 2017 10:39:34 -0400
Subject: [PATCH 0861/1193] Fix bad cherry-pick
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/static_analysis.sh | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index ade38400e..0e0c57595 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -21,11 +21,7 @@ test_static_analysis() {
       shellcheck test/main.sh test/suites/* test/backends/*
     else
       echo "shellcheck not found, shell static analysis disabled"
-<<<<<<< HEAD
-    fi  
-=======
     fi
->>>>>>> 7050be73... tests: Trailing whitespaces
 
     # Go static analysis
     ## Functions starting by empty line

From 0525b3e99027e6c725bef9630be90feaf3fb48cb Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 9 May 2017 22:56:22 +0200
Subject: [PATCH 0862/1193] client: fix profiles list

Closes #3304.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client.go | 33 ++++++++++-----------------------
 1 file changed, 10 insertions(+), 23 deletions(-)

diff --git a/client.go b/client.go
index d8cacf0b6..f087d4277 100644
--- a/client.go
+++ b/client.go
@@ -2157,37 +2157,24 @@ func (c *Client) ListProfiles() ([]string, error) {
 		return nil, fmt.Errorf("This function isn't supported by public remotes.")
 	}
 
-	resp, err := c.get("profiles")
+	resp, err := c.get("profiles?recursion=1")
 	if err != nil {
 		return nil, err
 	}
 
-	var result []string
-
-	if err := resp.MetadataAsStruct(&result); err != nil {
+	profiles := []api.Profile{}
+	if err := resp.MetadataAsStruct(&profiles); err != nil {
 		return nil, err
 	}
 
-	names := []string{}
-
-	for _, url := range result {
-		toScan := strings.Replace(url, "/", " ", -1)
-		urlVersion := ""
-		name := ""
-		count, err := fmt.Sscanf(toScan, " %s profiles %s", &urlVersion, &name)
-		if err != nil {
-			return nil, err
-		}
-
-		if count != 2 {
-			return nil, fmt.Errorf("bad profile url %s", url)
-		}
-
-		if urlVersion != version.APIVersion {
-			return nil, fmt.Errorf("bad version in profile url")
-		}
+	if len(profiles) == 0 {
+		return nil, nil
+	}
 
-		names = append(names, name)
+	// spare a few allocation cycles
+	names := make([]string, len(profiles))
+	for i := 0; i < len(profiles); i++ {
+		names[i] = profiles[i].Name
 	}
 
 	return names, nil

From 50cb78e89fa9ccae0778b56d5c6e9a7377a5c37f Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at gmail.com>
Date: Wed, 26 Apr 2017 12:13:17 +0200
Subject: [PATCH 0863/1193] Drop unnecessary else statement

This branch drops an unnecessary else statement, as highlighted by a
linting hint in my emacs setup. Essentially since the "if" part ends
up with a return, there's no need for "else".

Signed-off-by: Free Ekanayaka <free.ekanayaka at gmail.com>
---
 lxc/init.go | 25 ++++++++++++-------------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index 089f319a6..15a115cbe 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -195,21 +195,20 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 
 	if err != nil {
 		return err
-	} else {
-		op, err := resp.MetadataAsOperation()
-		if err != nil {
-			return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
-		}
+	}
+	op, err := resp.MetadataAsOperation()
+	if err != nil {
+		return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
+	}
 
-		containers, ok := op.Resources["containers"]
-		if !ok || len(containers) == 0 {
-			return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
-		}
+	containers, ok := op.Resources["containers"]
+	if !ok || len(containers) == 0 {
+		return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
+	}
 
-		if len(containers) == 1 && name == "" {
-			fields := strings.Split(containers[0], "/")
-			fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
-		}
+	if len(containers) == 1 && name == "" {
+		fields := strings.Split(containers[0], "/")
+		fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
 	}
 	return nil
 }

From e0eab0fe0648b6c9d6166ece90bd7193f79de4b9 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 26 Apr 2017 10:54:58 +0200
Subject: [PATCH 0864/1193] Add yaml-mode marker in template for "lxc edit"
 actions.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 shared/util.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/shared/util.go b/shared/util.go
index 5ac2ace25..53f7003e0 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -513,6 +513,7 @@ func ValidHostname(name string) bool {
 	return true
 }
 
+// Spawn the editor with a temporary YAML file for editing configs
 func TextEditor(inPath string, inContent []byte) ([]byte, error) {
 	var f *os.File
 	var err error
@@ -552,7 +553,8 @@ func TextEditor(inPath string, inContent []byte) ([]byte, error) {
 		f.Write(inContent)
 		f.Close()
 
-		path = f.Name()
+		path = fmt.Sprintf("%s.yaml", f.Name())
+		os.Rename(f.Name(), path)
 		defer os.Remove(path)
 	} else {
 		path = inPath

From 700a90fc6db15eda883a37997383a3ac305abfb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 26 Apr 2017 20:45:06 -0400
Subject: [PATCH 0865/1193] lxc: Implement progress tracking for operations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/utils.go | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/lxc/utils.go b/lxc/utils.go
index 0611f9b6c..e9c01873a 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -4,6 +4,8 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
 )
 
@@ -46,6 +48,24 @@ func (p *ProgressRenderer) Update(status string) {
 	fmt.Print(msg)
 }
 
+func (p *ProgressRenderer) UpdateProgress(progress lxd.ProgressData) {
+	p.Update(progress.Text)
+}
+
+func (p *ProgressRenderer) UpdateOp(op api.Operation) {
+	if op.Metadata == nil {
+		return
+	}
+
+	for _, key := range []string{"fs_progress", "download_progress"} {
+		value, ok := op.Metadata[key]
+		if ok {
+			p.Update(value.(string))
+			break
+		}
+	}
+}
+
 // Image fingerprint and alias sorting
 type SortImage [][]string
 

From 527096285403a5745f6e04e9115051379dcfc46f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Apr 2017 13:10:11 -0400
Subject: [PATCH 0866/1193] lxc-to-lxd: Don't crash on missing mount file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3237

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 7224cc160..6c3ddd3c7 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -163,7 +163,7 @@ def config_parse(path):
             if key == "lxc.mount":
                 if not os.path.exists(value):
                     print("Container fstab file doesn't exist, skipping...")
-                    return False
+                    continue
 
                 with open(value, "r") as fd:
                     for line in fd:

From b349438ac40b8b0fd79b56aeb1e56b0076ae2d36 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free at ekanayaka.io>
Date: Thu, 27 Apr 2017 15:04:46 +0200
Subject: [PATCH 0867/1193] Add a new shared/cmd package with initial command
 I/O logic

This is a proof of concept of some unit-testable code that I'd like
to later use for making cmdInit testable (as per #2834).

It's small so should it be easy to review and because I wish to have
some feedback on the approach before moving forward. I hope that the
branch gives a feel of what further branches would look like.

As a general note, I feel it'd be important to improve coverage of the
parts that can be unit tested easily (so basically most higher-level
application logic except for system-level interactions). I think this
can be done incrementally without too much disruption, if we agree.

Signed-off-by: Free Ekanayaka <free at ekanayaka.io>
---
 shared/cmd/context.go          | 53 ++++++++++++++++++++++++++++++++++++++++++
 shared/cmd/context_test.go     | 47 +++++++++++++++++++++++++++++++++++++
 shared/cmd/doc.go              | 11 +++++++++
 test/suites/static_analysis.sh |  1 +
 4 files changed, 112 insertions(+)
 create mode 100644 shared/cmd/context.go
 create mode 100644 shared/cmd/context_test.go
 create mode 100644 shared/cmd/doc.go

diff --git a/shared/cmd/context.go b/shared/cmd/context.go
new file mode 100644
index 000000000..c265f8d62
--- /dev/null
+++ b/shared/cmd/context.go
@@ -0,0 +1,53 @@
+package cmd
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"strings"
+
+	"github.com/lxc/lxd/shared"
+)
+
+// Context captures the environment the sub-command is being run in,
+// such as in/out/err streams and command line arguments.
+type Context struct {
+	stdin  *bufio.Reader
+	stdout io.Writer
+	stderr io.Writer
+}
+
+// NewContext creates a new command context with the given parameters.
+func NewContext(stdin io.Reader, stdout, stderr io.Writer) *Context {
+	return &Context{
+		stdin:  bufio.NewReader(stdin),
+		stdout: stdout,
+		stderr: stderr,
+	}
+}
+
+// AskBool asks a question an expect a yes/no answer.
+func (c *Context) AskBool(question string, defaultAnswer string) bool {
+	for {
+		fmt.Fprintf(c.stdout, question)
+		answer := c.readAnswer(defaultAnswer)
+
+		if shared.StringInSlice(strings.ToLower(answer), []string{"yes", "y"}) {
+			return true
+		} else if shared.StringInSlice(strings.ToLower(answer), []string{"no", "n"}) {
+			return false
+		}
+
+		fmt.Fprintf(c.stderr, "Invalid input, try again.\n\n")
+	}
+}
+
+// Read the user's answer from the input stream, trimming newline and providing a default.
+func (c *Context) readAnswer(defaultAnswer string) string {
+	answer, _ := c.stdin.ReadString('\n')
+	answer = strings.TrimSuffix(answer, "\n")
+	if answer == "" {
+		answer = defaultAnswer
+	}
+	return answer
+}
diff --git a/shared/cmd/context_test.go b/shared/cmd/context_test.go
new file mode 100644
index 000000000..b57743fff
--- /dev/null
+++ b/shared/cmd/context_test.go
@@ -0,0 +1,47 @@
+package cmd_test
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+
+	"github.com/lxc/lxd/shared/cmd"
+)
+
+// AskBool returns a boolean result depending on the user input.
+func TestAskBool(t *testing.T) {
+	cases := []struct {
+		question      string
+		defaultAnswer string
+		output        string
+		error         string
+		input         string
+		result        bool
+	}{
+		{"Do you code?", "yes", "Do you code?", "", "\n", true},
+		{"Do you code?", "yes", "Do you code?", "", "yes\n", true},
+		{"Do you code?", "yes", "Do you code?", "", "y\n", true},
+		{"Do you code?", "yes", "Do you code?", "", "no\n", false},
+		{"Do you code?", "yes", "Do you code?", "", "n\n", false},
+		{"Do you code?", "yes", "Do you code?Do you code?", "Invalid input, try again.\n\n", "foo\nyes\n", true},
+	}
+	for _, c := range cases {
+		stdin := strings.NewReader(c.input)
+		stdout := new(bytes.Buffer)
+		stderr := new(bytes.Buffer)
+		context := cmd.NewContext(stdin, stdout, stderr)
+		result := context.AskBool(c.question, c.defaultAnswer)
+
+		if result != c.result {
+			t.Errorf("Expected '%v' result got '%v'", c.result, result)
+		}
+
+		if output := stdout.String(); output != c.output {
+			t.Errorf("Expected '%s' output got '%s'", c.output, output)
+		}
+
+		if error := stderr.String(); error != c.error {
+			t.Errorf("Expected '%s' error got '%s'", c.error, error)
+		}
+	}
+}
diff --git a/shared/cmd/doc.go b/shared/cmd/doc.go
new file mode 100644
index 000000000..02dab7258
--- /dev/null
+++ b/shared/cmd/doc.go
@@ -0,0 +1,11 @@
+/*
+
+The package cmd implements a simple abstraction around a "sub-command" for
+a main executable (e.g. "lxd init", where "init" is the sub-command).
+
+It is designed to make unit-testing easier, since OS-specific parts like
+standard in/out can be set in tests.
+
+*/
+
+package cmd
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 0e0c57595..2721058b5 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -46,6 +46,7 @@ test_static_analysis() {
       golint -set_exit_status client/
       golint -set_exit_status lxc/config/
       golint -set_exit_status shared/api/
+      golint -set_exit_status shared/cmd/
       golint -set_exit_status shared/gnuflag/
       golint -set_exit_status shared/i18n/
       golint -set_exit_status shared/ioprogress/

From fce7b284c472116014802f20d9c8b86f9caa1b42 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Apr 2017 19:52:19 -0400
Subject: [PATCH 0868/1193] client: Remove unneeded condition
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/client/lxd.go b/client/lxd.go
index 8b2f01acd..3f8663b38 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -78,9 +78,7 @@ func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag
 		req.Header.Set("Content-Type", "application/json")
 
 		// Log the data
-		if data != nil {
-			logger.Debugf(logger.Pretty(data))
-		}
+		logger.Debugf(logger.Pretty(data))
 	} else {
 		// No data to be sent along with the request
 		req, err = http.NewRequest(method, url, nil)

From dcb33dfbc01a164669d4527c7fb9adcfd2ae292d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Apr 2017 19:52:51 -0400
Subject: [PATCH 0869/1193] client: Support partial fingerprints
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_images.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index 37a8f7eed..fc8c72699 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -181,7 +181,7 @@ func (r *ProtocolLXD) GetPrivateImageFile(fingerprint string, secret string, req
 
 		// Check the hash
 		hash := fmt.Sprintf("%x", sha256.Sum(nil))
-		if hash != fingerprint {
+		if !strings.HasPrefix(hash, fingerprint) {
 			return nil, fmt.Errorf("Image fingerprint doesn't match. Got %s expected %s", hash, fingerprint)
 		}
 
@@ -208,7 +208,7 @@ func (r *ProtocolLXD) GetPrivateImageFile(fingerprint string, secret string, req
 
 	// Check the hash
 	hash := fmt.Sprintf("%x", sha256.Sum(nil))
-	if hash != fingerprint {
+	if !strings.HasPrefix(hash, fingerprint) {
 		return nil, fmt.Errorf("Image fingerprint doesn't match. Got %s expected %s", hash, fingerprint)
 	}
 

From 22be2d0b3dcaf361a8301ae5e77878f9f1e64996 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 00:04:35 -0400
Subject: [PATCH 0870/1193] client: Don't return cache on GetServer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

If someone calls GetServer directly, give them the real value.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_server.go | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/client/lxd_server.go b/client/lxd_server.go
index fbae67fb3..b358e22de 100644
--- a/client/lxd_server.go
+++ b/client/lxd_server.go
@@ -10,11 +10,6 @@ import (
 func (r *ProtocolLXD) GetServer() (*api.Server, string, error) {
 	server := api.Server{}
 
-	// Return the cached entry if present
-	if r.server != nil {
-		return r.server, "", nil
-	}
-
 	// Fetch the raw value
 	etag, err := r.queryStruct("GET", "", nil, "", &server)
 	if err != nil {

From 34aafed0f213c65f89aa657c99a44bcf00712e83 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 00:35:39 -0400
Subject: [PATCH 0871/1193] client: Track the server certificate, not client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/client/connection.go b/client/connection.go
index 5e6c063db..a9597613d 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -45,7 +45,7 @@ func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 	server := ProtocolLXD{
 		httpHost:        url,
 		httpUserAgent:   args.UserAgent,
-		httpCertificate: args.TLSClientCert,
+		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client
@@ -126,7 +126,7 @@ func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 	server := ProtocolLXD{
 		httpHost:        url,
 		httpUserAgent:   args.UserAgent,
-		httpCertificate: args.TLSClientCert,
+		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client
@@ -160,7 +160,7 @@ func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error)
 	server := ProtocolSimpleStreams{
 		httpHost:        url,
 		httpUserAgent:   args.UserAgent,
-		httpCertificate: args.TLSClientCert,
+		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client

From f6bf210ed169aa2ac8305a102d3338cbd2b11a0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 00:44:06 -0400
Subject: [PATCH 0872/1193] client: Fix private image handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_images.go | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index fc8c72699..644879b33 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -288,6 +288,16 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 		},
 	}
 
+	// Generate secret token if needed
+	if !image.Public {
+		op, err := r.CreateImageSecret(image.Fingerprint)
+		if err != nil {
+			return nil, err
+		}
+
+		req.Source.Secret = op.Metadata["secret"].(string)
+	}
+
 	// Process the arguments
 	if args != nil {
 		req.AutoUpdate = args.AutoUpdate

From fb37ab6d25b46bd8d1d1ff56b3bcfaea7008e6fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 00:54:07 -0400
Subject: [PATCH 0873/1193] client: Always pass pointer to queryStruct
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_containers.go | 2 +-
 client/lxd_images.go     | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index bddb7e7e2..de9fd5f65 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -335,7 +335,7 @@ func (r *ProtocolLXD) GetContainerSnapshots(containerName string) ([]api.Contain
 	snapshots := []api.ContainerSnapshot{}
 
 	// Fetch the raw value
-	_, err := r.queryStruct("GET", fmt.Sprintf("/containers/%s/snapshots?recursion=1", containerName), nil, "", snapshots)
+	_, err := r.queryStruct("GET", fmt.Sprintf("/containers/%s/snapshots?recursion=1", containerName), nil, "", &snapshots)
 	if err != nil {
 		return nil, err
 	}
diff --git a/client/lxd_images.go b/client/lxd_images.go
index 644879b33..1d296886f 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -220,7 +220,7 @@ func (r *ProtocolLXD) GetImageAliases() ([]api.ImageAliasesEntry, error) {
 	aliases := []api.ImageAliasesEntry{}
 
 	// Fetch the raw value
-	_, err := r.queryStruct("GET", "/images/aliases?recursion=1", nil, "", aliases)
+	_, err := r.queryStruct("GET", "/images/aliases?recursion=1", nil, "", &aliases)
 	if err != nil {
 		return nil, err
 	}

From b101dab4b23eb7462b7cf61516bf044e69c47cc2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 02:01:02 -0400
Subject: [PATCH 0874/1193] client: Fix race condition in operation handler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

As a goroutine is spawned for every event and we force queueing while we
get the initial state we need to make sure it's impossible for two
handlers to run concurently when touching shared resources.

We also need to cope with the fact that one of the handlers may have
decided to disconnect the listener and so should just return in such
case.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/operations.go | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/client/operations.go b/client/operations.go
index 3aedf807e..cd958fbe9 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -147,15 +147,20 @@ func (op *Operation) setupListener() error {
 			return
 		}
 
+		// We don't want concurency while processing events
+		op.listenerLock.Lock()
+		defer op.listenerLock.Unlock()
+
+		// Check if we're done already (because of another event)
+		if op.listener == nil {
+			return
+		}
+
 		// Update the struct
 		op.Operation = *newOp
 
 		// And check if we're done
 		if op.StatusCode.IsFinal() {
-			// Make sure we're not racing with ourselves
-			op.listenerLock.Lock()
-			defer op.listenerLock.Unlock()
-
 			op.listener.Disconnect()
 			op.listener = nil
 			close(op.chActive)

From 66f228282532abe2f0e50b8ecb5af6412be3b99b Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Fri, 21 Apr 2017 09:26:15 +0200
Subject: [PATCH 0875/1193] Extract profiles documentation to profiles.md.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 doc/profiles.md | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)
 create mode 100644 doc/profiles.md

diff --git a/doc/profiles.md b/doc/profiles.md
new file mode 100644
index 000000000..ee52e5249
--- /dev/null
+++ b/doc/profiles.md
@@ -0,0 +1,17 @@
+# Profiles
+Profiles can store any configuration that a container can (key/value or
+devices) and any number of profiles can be applied to a container.
+
+Profiles are applied in the order they are specified so the last profile to
+specify a specific key wins.
+
+In any case, resource-specific configuration always overrides that coming from
+the profiles.
+
+If not present, LXD will create a "default" profile.
+
+The "default" profile is set for any new container created which doesn't
+specify a different profiles list.
+
+
+See [container configuration](containers.md) for valid configuration options.

From de1c64666523af79021ea5f95f38ebf726417f67 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Fri, 21 Apr 2017 09:27:25 +0200
Subject: [PATCH 0876/1193] Extract containers documentation to containers.md.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 doc/containers.md | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 212 insertions(+)
 create mode 100644 doc/containers.md

diff --git a/doc/containers.md b/doc/containers.md
new file mode 100644
index 000000000..b5321ebd2
--- /dev/null
+++ b/doc/containers.md
@@ -0,0 +1,212 @@
+# Container configuration
+## Properties
+The following are direct container properties and can't be part of a profile:
+ - name
+ - architecture
+
+Name is the container name and can only be changed by renaming the container.
+
+## Key/value configuration
+The key/value configuration is namespaced with the following namespaces
+currently supported:
+ - boot (boot related options, timing, dependencies, ...)
+ - environment (environment variables)
+ - limits (resource limits)
+ - raw (raw container configuration overrides)
+ - security (security policies)
+ - user (storage for user properties, searchable)
+ - volatile (used internally by LXD to store settings that are specific to a specific container instance)
+
+The currently supported keys are:
+
+Key                         | Type      | Default       | Live update   | Description
+:--                         | :---      | :------       | :----------   | :----------
+boot.autostart              | boolean   | -             | n/a           | Always start the container when LXD starts (if not set, restore last state)
+boot.autostart.delay        | integer   | 0             | n/a           | Number of seconds to wait after the container started before starting the next one
+boot.autostart.priority     | integer   | 0             | n/a           | What order to start the containers in (starting with highest)
+environment.\*              | string    | -             | yes (exec)    | key/value environment variables to export to the container and set on exec
+limits.cpu                  | string    | - (all)       | yes           | Number or range of CPUs to expose to the container
+limits.cpu.allowance        | string    | 100%          | yes           | How much of the CPU can be used. Can be a percentage (e.g. 50%) for a soft limit or hard a chunk of time (25ms/100ms)
+limits.cpu.priority         | integer   | 10 (maximum)  | yes           | CPU scheduling priority compared to other containers sharing the same CPUs (overcommit) (integer between 0 and 10)
+limits.disk.priority        | integer   | 5 (medium)    | yes           | When under load, how much priority to give to the container's I/O requests (integer between 0 and 10)
+limits.memory               | string    | - (all)       | yes           | Percentage of the host's memory or fixed value in bytes (supports kB, MB, GB, TB, PB and EB suffixes)
+limits.memory.enforce       | string    | hard          | yes           | If hard, container can't exceed its memory limit. If soft, the container can exceed its memory limit when extra host memory is available.
+limits.memory.swap          | boolean   | true          | yes           | Whether to allow some of the container's memory to be swapped out to disk
+limits.memory.swap.priority | integer   | 10 (maximum)  | yes           | The higher this is set, the least likely the container is to be swapped to disk (integer between 0 and 10)
+limits.network.priority     | integer   | 0 (minimum)   | yes           | When under load, how much priority to give to the container's network requests (integer between 0 and 10)
+limits.processes            | integer   | - (max)       | yes           | Maximum number of processes that can run in the container
+linux.kernel\_modules       | string    | -             | yes           | Comma separated list of kernel modules to load before starting the container
+raw.apparmor                | blob      | -             | yes           | Apparmor profile entries to be appended to the generated profile
+raw.lxc                     | blob      | -             | no            | Raw LXC configuration to be appended to the generated one
+raw.idmap                   | blob      | -             | no            | Raw idmap configuration (e.g. "both 1000 1000")
+security.idmap.isolated     | boolean   | false         | no            | Use an idmap for this container that is unique among containers with isolated set.
+security.idmap.size         | integer   | -             | no            | The size of the idmap to use
+security.nesting            | boolean   | false         | yes           | Support running lxd (nested) inside the container
+security.privileged         | boolean   | false         | no            | Runs the container in privileged mode
+user.\*                     | string    | -             | n/a           | Free form user key/value storage (can be used in search)
+
+The following volatile keys are currently internally used by LXD:
+
+Key                         | Type      | Default       | Description
+:--                         | :---      | :------       | :----------
+volatile.\<name\>.hwaddr    | string    | -             | Network device MAC address (when no hwaddr property is set on the device itself)
+volatile.\<name\>.name      | string    | -             | Network device name (when no name propery is set on the device itself)
+volatile.apply\_template    | string    | -             | The name of a template hook which should be triggered upon next startup
+volatile.base\_image        | string    | -             | The hash of the image the container was created from, if any.
+volatile.idmap.base         | integer   | -             | The first id in the container's primary idmap range
+volatile.idmap.next         | string    | -             | The idmap to use next time the container starts
+volatile.last\_state.idmap  | string    | -             | Serialized container uid/gid map
+volatile.last\_state.power  | string    | -             | Container state as of last host shutdown
+
+
+Additionally, those user keys have become common with images (support isn't guaranteed):
+
+Key                         | Type          | Default           | Description
+:--                         | :---          | :------           | :----------
+user.network\_mode          | string        | dhcp              | One of "dhcp" or "link-local". Used to configure network in supported images.
+user.meta-data              | string        | -                 | Cloud-init meta-data, content is appended to seed value.
+user.user-data              | string        | #!cloud-config    | Cloud-init user-data, content is used as seed value.
+user.vendor-data            | string        | #!cloud-config    | Cloud-init vendor-data, content is used as seed value.
+user.network-config         | string        | DHCP on eth0      | Cloud-init network-config, content is used as seed value.
+
+Note that while a type is defined above as a convenience, all values are
+stored as strings and should be exported over the REST API as strings
+(which makes it possible to support any extra values without breaking
+backward compatibility).
+
+Those keys can be set using the lxc tool with:
+
+    lxc config set <container> <key> <value>
+
+Volatile keys can't be set by the user and can only be set directly against a container.
+
+The raw keys allow direct interaction with the backend features that LXD
+itself uses, setting those may very well break LXD in non-obvious ways
+and should whenever possible be avoided.
+
+
+## Devices configuration
+LXD will always provide the container with the basic devices which are
+required for a standard POSIX system to work. These aren't visible in
+container or profile configuration and may not be overriden.
+
+Those includes:
+ - /dev/null (character device)
+ - /dev/zero (character device)
+ - /dev/full (character device)
+ - /dev/console (character device)
+ - /dev/tty (character device)
+ - /dev/random (character device)
+ - /dev/urandom (character device)
+ - /dev/net/tun (character device)
+ - /dev/fuse (character device)
+ - lo (network interface)
+
+Anything else has to be defined in the container configuration or in one
+of its profiles. The default profile will typically contain a network
+interface to become eth0 in the container.
+
+To add extra devices to a container, device entries can be added
+directly to a container, or to a profile.
+
+Devices may be added or removed while the container is running.
+
+Every device entry is identified by a unique name. If the same name is
+used in a subsequent profile or in the container's own configuration,
+the whole entry is overriden by the new definition.
+
+Device entries are added through:
+    lxc config device add <container> <name> <type> [key=value]...
+    lxc profile device add <profile> <name> <type> [key=value]...
+
+### Device types
+LXD supports the following device types:
+
+ID (database)   | Name          | Description
+:--             | :--           | :--
+0               | none          | Inheritance blocker
+1               | nic           | Network interface
+2               | disk          | Mountpoint inside the container
+3               | unix-char     | Unix character device
+4               | unix-block    | Unix block device
+
+### Type: none
+A none type device doesn't have any property and doesn't create anything inside the container.
+
+It's only purpose it to stop inheritance of devices coming from profiles.
+
+To do so, just add a none type device with the same name of the one you wish to skip inheriting.
+It can be added in a profile being applied after the profile it originated from or directly on the container.
+
+### Type: nic
+LXD supports different kind of network devices:
+ - physical: Straight physical device passthrough from the host. The targeted device will vanish from the host and appear in the container.
+ - bridged: Uses an existing bridge on the host and creates a virtual device pair to connect the host bridge to the container.
+ - macvlan: Sets up a new network device based on an existing one but using a different MAC address.
+ - p2p: Creates a virtual device pair, putting one side in the container and leaving the other side on the host.
+
+Different network interface types have different additional properties, the current list is:
+
+Key             | Type      | Default           | Required  | Used by                       | Description
+:--             | :--       | :--               | :--       | :--                           | :--
+nictype         | string    | -                 | yes       | all                           | The device type, one of "physical", "bridged", "macvlan" or "p2p"
+limits.ingress  | string    | -                 | no        | bridged, p2p                  | I/O limit in bit/s (supports kbit, Mbit, Gbit suffixes)
+limits.egress   | string    | -                 | no        | bridged, p2p                  | I/O limit in bit/s (supports kbit, Mbit, Gbit suffixes)
+limits.max      | string    | -                 | no        | bridged, p2p                  | Same as modifying both limits.read and limits.write
+name            | string    | kernel assigned   | no        | all                           | The name of the interface inside the container
+host\_name      | string    | randomly assigned | no        | bridged, p2p, macvlan         | The name of the interface inside the host
+hwaddr          | string    | randomly assigned | no        | all                           | The MAC address of the new interface
+mtu             | integer   | parent MTU        | no        | all                           | The MTU of the new interface
+parent          | string    | -                 | yes       | physical, bridged, macvlan    | The name of the host device or bridge
+
+### Type: disk
+Disk entries are essentially mountpoints inside the container. They can
+either be a bind-mount of an existing file or directory on the host, or
+if the source is a block device, a regular mount.
+
+The following properties exist:
+
+Key             | Type      | Default           | Required  | Description
+:--             | :--       | :--               | :--       | :--
+limits.read     | string    | -                 | no        | I/O limit in byte/s (supports kB, MB, GB, TB, PB and EB suffixes) or in iops (must be suffixed with "iops")
+limits.write    | string    | -                 | no        | I/O limit in byte/s (supports kB, MB, GB, TB, PB and EB suffixes) or in iops (must be suffixed with "iops")
+limits.max      | string    | -                 | no        | Same as modifying both limits.read and limits.write
+path            | string    | -                 | yes       | Path inside the container where the disk will be mounted
+source          | string    | -                 | yes       | Path on the host, either to a file/directory or to a block device
+optional        | boolean   | false             | no        | Controls whether to fail if the source doesn't exist
+readonly        | boolean   | false             | no        | Controls whether to make the mount read-only
+size            | string    | -                 | no        | Disk size in bytes (supports kB, MB, GB, TB, PB and EB suffixes). This is only supported for the rootfs (/).
+recursive       | boolean   | false             | no        | Whether or not to recursively mount the source path
+
+If multiple disks, backed by the same block device, have I/O limits set,
+the average of the limits will be used.
+
+### Type: unix-char
+Unix character device entries simply make the requested character device
+appear in the container's /dev and allow read/write operations to it.
+
+The following properties exist:
+
+Key         | Type      | Default           | Required  | Description
+:--         | :--       | :--               | :--       | :--
+path        | string    | -                 | yes       | Path inside the container
+major       | int       | device on host    | no        | Device major number
+minor       | int       | device on host    | no        | Device minor number
+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
+
+### Type: unix-block
+Unix block device entries simply make the requested block device
+appear in the container's /dev and allow read/write operations to it.
+
+The following properties exist:
+
+Key         | Type      | Default           | Required  | Description
+:--         | :--       | :--               | :--       | :--
+path        | string    | -                 | yes       | Path inside the container
+major       | int       | device on host    | no        | Device major number
+minor       | int       | device on host    | no        | Device minor number
+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

From 61a18bda21cd6658cbb2b3309f380adf5088aaa9 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Fri, 21 Apr 2017 09:29:22 +0200
Subject: [PATCH 0877/1193] Extract server documentation to server.md.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 doc/server.md | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 doc/server.md

diff --git a/doc/server.md b/doc/server.md
new file mode 100644
index 000000000..d182830f1
--- /dev/null
+++ b/doc/server.md
@@ -0,0 +1,30 @@
+# Server configuration
+The key/value configuration is namespaced with the following namespaces
+currently supported:
+ - core (core daemon configuration)
+ - images (image configuration)
+ - storage (storage configuration)
+
+Key                             | Type          | Default                   | Description
+:--                             | :---          | :------                   | :----------
+core.https\_address             | string        | -                         | Address to bind for the remote API
+core.https\_allowed\_origin     | string        | -                         | Access-Control-Allow-Origin http header value
+core.https\_allowed\_methods    | string        | -                         | Access-Control-Allow-Methods http header value
+core.https\_allowed\_headers    | string        | -                         | Access-Control-Allow-Headers http header value
+core.proxy\_https               | string        | -                         | https proxy to use, if any (falls back to HTTPS\_PROXY environment variable)
+core.proxy\_http                | string        | -                         | http proxy to use, if any (falls back to HTTP\_PROXY environment variable)
+core.proxy\_ignore\_hosts       | string        | -                         | hosts which don't need the proxy for use (similar format to NO\_PROXY, e.g. 1.2.3.4,1.2.3.5, falls back to NO\_PROXY environment variable)
+core.trust\_password            | string        | -                         | Password to be provided by clients to setup a trust
+storage.lvm\_vg\_name           | string        | -                         | LVM Volume Group name to be used for container and image storage. A default Thin Pool is created using 100% of the free space in the Volume Group, unless `storage.lvm_thinpool_name` is set.
+storage.lvm\_thinpool\_name     | string        | "LXDPool"                 | LVM Thin Pool to use within the Volume Group specified in `storage.lvm_vg_name`, if the default pool parameters are undesirable.
+storage.lvm\_fstype             | string        | ext4                      | Format LV with filesystem, for now it's value can be only ext4 (default) or xfs.
+storage.lvm\_volume\_size       | string        | 10GiB                     | Size of the logical volume
+storage.zfs\_pool\_name         | string        | -                         | ZFS pool name
+images.compression\_algorithm   | string        | gzip                      | Compression algorithm to use for new images (bzip2, gzip, lzma, xz or none)
+images.remote\_cache\_expiry    | integer       | 10                        | Number of days after which an unused cached remote image will be flushed
+images.auto\_update\_interval   | integer       | 6                         | Interval in hours at which to look for update to cached images (0 disables it)
+images.auto\_update\_cached     | boolean       | true                      | Whether to automatically update any image that LXD caches
+
+Those keys can be set using the lxc tool with:
+
+    lxc config set <key> <value>

From e03fb9c5a988baeda425a5f54fff6c58074a1bba Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Fri, 21 Apr 2017 09:38:15 +0200
Subject: [PATCH 0878/1193] Update configuration.md with links to other
 documents.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 doc/configuration.md | 275 +--------------------------------------------------
 1 file changed, 4 insertions(+), 271 deletions(-)

diff --git a/doc/configuration.md b/doc/configuration.md
index cb5d0b035..a1be2d00b 100644
--- a/doc/configuration.md
+++ b/doc/configuration.md
@@ -1,273 +1,6 @@
 # Introduction
-Current LXD stores the following kind of configurations:
- - Server configuration (the LXD daemon itself)
- - Container configuration
+Current LXD stores configurations for a few components:
 
-The server configuration is a simple set of key and values.
-
-The container configuration is a bit more complex as it uses both
-key/value configuration and some more complex configuration structures
-for devices, network interfaces and storage volumes.
-
-# Server configuration
-## Key/value configuration
-The key/value configuration is namespaced with the following namespaces
-currently supported:
- - core (core daemon configuration)
- - images (image configuration)
- - storage (storage configuration)
-
-Key                             | Type          | Default                   | Description
-:--                             | :---          | :------                   | :----------
-core.https\_address             | string        | -                         | Address to bind for the remote API
-core.https\_allowed\_origin     | string        | -                         | Access-Control-Allow-Origin http header value
-core.https\_allowed\_methods    | string        | -                         | Access-Control-Allow-Methods http header value
-core.https\_allowed\_headers    | string        | -                         | Access-Control-Allow-Headers http header value
-core.proxy\_https               | string        | -                         | https proxy to use, if any (falls back to HTTPS\_PROXY environment variable)
-core.proxy\_http                | string        | -                         | http proxy to use, if any (falls back to HTTP\_PROXY environment variable)
-core.proxy\_ignore\_hosts       | string        | -                         | hosts which don't need the proxy for use (similar format to NO\_PROXY, e.g. 1.2.3.4,1.2.3.5, falls back to NO\_PROXY environment variable)
-core.trust\_password            | string        | -                         | Password to be provided by clients to setup a trust
-storage.lvm\_vg\_name           | string        | -                         | LVM Volume Group name to be used for container and image storage. A default Thin Pool is created using 100% of the free space in the Volume Group, unless `storage.lvm_thinpool_name` is set.
-storage.lvm\_thinpool\_name     | string        | "LXDPool"                 | LVM Thin Pool to use within the Volume Group specified in `storage.lvm_vg_name`, if the default pool parameters are undesirable.
-storage.lvm\_fstype             | string        | ext4                      | Format LV with filesystem, for now it's value can be only ext4 (default) or xfs.
-storage.lvm\_volume\_size       | string        | 10GiB                     | Size of the logical volume
-storage.zfs\_pool\_name         | string        | -                         | ZFS pool name
-images.compression\_algorithm   | string        | gzip                      | Compression algorithm to use for new images (bzip2, gzip, lzma, xz or none)
-images.remote\_cache\_expiry    | integer       | 10                        | Number of days after which an unused cached remote image will be flushed
-images.auto\_update\_interval   | integer       | 6                         | Interval in hours at which to look for update to cached images (0 disables it)
-images.auto\_update\_cached     | boolean       | true                      | Whether to automatically update any image that LXD caches
-
-Those keys can be set using the lxc tool with:
-
-    lxc config set <key> <value>
-
-
-# Container configuration
-## Properties
-The following are direct container properties and can't be part of a profile:
- - name
- - architecture
-
-Name is the container name and can only be changed by renaming the container.
-
-## Key/value configuration
-The key/value configuration is namespaced with the following namespaces
-currently supported:
- - boot (boot related options, timing, dependencies, ...)
- - environment (environment variables)
- - limits (resource limits)
- - raw (raw container configuration overrides)
- - security (security policies)
- - user (storage for user properties, searchable)
- - volatile (used internally by LXD to store settings that are specific to a specific container instance)
-
-The currently supported keys are:
-
-Key                         | Type      | Default       | Live update   | Description
-:--                         | :---      | :------       | :----------   | :----------
-boot.autostart              | boolean   | -             | n/a           | Always start the container when LXD starts (if not set, restore last state)
-boot.autostart.delay        | integer   | 0             | n/a           | Number of seconds to wait after the container started before starting the next one
-boot.autostart.priority     | integer   | 0             | n/a           | What order to start the containers in (starting with highest)
-environment.\*              | string    | -             | yes (exec)    | key/value environment variables to export to the container and set on exec
-limits.cpu                  | string    | - (all)       | yes           | Number or range of CPUs to expose to the container
-limits.cpu.allowance        | string    | 100%          | yes           | How much of the CPU can be used. Can be a percentage (e.g. 50%) for a soft limit or hard a chunk of time (25ms/100ms)
-limits.cpu.priority         | integer   | 10 (maximum)  | yes           | CPU scheduling priority compared to other containers sharing the same CPUs (overcommit) (integer between 0 and 10)
-limits.disk.priority        | integer   | 5 (medium)    | yes           | When under load, how much priority to give to the container's I/O requests (integer between 0 and 10)
-limits.memory               | string    | - (all)       | yes           | Percentage of the host's memory or fixed value in bytes (supports kB, MB, GB, TB, PB and EB suffixes)
-limits.memory.enforce       | string    | hard          | yes           | If hard, container can't exceed its memory limit. If soft, the container can exceed its memory limit when extra host memory is available.
-limits.memory.swap          | boolean   | true          | yes           | Whether to allow some of the container's memory to be swapped out to disk
-limits.memory.swap.priority | integer   | 10 (maximum)  | yes           | The higher this is set, the least likely the container is to be swapped to disk (integer between 0 and 10)
-limits.network.priority     | integer   | 0 (minimum)   | yes           | When under load, how much priority to give to the container's network requests (integer between 0 and 10)
-limits.processes            | integer   | - (max)       | yes           | Maximum number of processes that can run in the container
-linux.kernel\_modules       | string    | -             | yes           | Comma separated list of kernel modules to load before starting the container
-raw.apparmor                | blob      | -             | yes           | Apparmor profile entries to be appended to the generated profile
-raw.lxc                     | blob      | -             | no            | Raw LXC configuration to be appended to the generated one
-raw.idmap                   | blob      | -             | no            | Raw idmap configuration (e.g. "both 1000 1000")
-security.idmap.isolated     | boolean   | false         | no            | Use an idmap for this container that is unique among containers with isolated set.
-security.idmap.size         | integer   | -             | no            | The size of the idmap to use
-security.nesting            | boolean   | false         | yes           | Support running lxd (nested) inside the container
-security.privileged         | boolean   | false         | no            | Runs the container in privileged mode
-user.\*                     | string    | -             | n/a           | Free form user key/value storage (can be used in search)
-
-The following volatile keys are currently internally used by LXD:
-
-Key                         | Type      | Default       | Description
-:--                         | :---      | :------       | :----------
-volatile.\<name\>.hwaddr    | string    | -             | Network device MAC address (when no hwaddr property is set on the device itself)
-volatile.\<name\>.name      | string    | -             | Network device name (when no name propery is set on the device itself)
-volatile.apply\_template    | string    | -             | The name of a template hook which should be triggered upon next startup
-volatile.base\_image        | string    | -             | The hash of the image the container was created from, if any.
-volatile.idmap.base         | integer   | -             | The first id in the container's primary idmap range
-volatile.idmap.next         | string    | -             | The idmap to use next time the container starts
-volatile.last\_state.idmap  | string    | -             | Serialized container uid/gid map
-volatile.last\_state.power  | string    | -             | Container state as of last host shutdown
-
-
-Additionally, those user keys have become common with images (support isn't guaranteed):
-
-Key                         | Type          | Default           | Description
-:--                         | :---          | :------           | :----------
-user.network\_mode          | string        | dhcp              | One of "dhcp" or "link-local". Used to configure network in supported images.
-user.meta-data              | string        | -                 | Cloud-init meta-data, content is appended to seed value.
-user.user-data              | string        | #!cloud-config    | Cloud-init user-data, content is used as seed value.
-user.vendor-data            | string        | #!cloud-config    | Cloud-init vendor-data, content is used as seed value.
-user.network-config         | string        | DHCP on eth0      | Cloud-init network-config, content is used as seed value.
-
-Note that while a type is defined above as a convenience, all values are
-stored as strings and should be exported over the REST API as strings
-(which makes it possible to support any extra values without breaking
-backward compatibility).
-
-Those keys can be set using the lxc tool with:
-
-    lxc config set <container> <key> <value>
-
-Volatile keys can't be set by the user and can only be set directly against a container.
-
-The raw keys allow direct interaction with the backend features that LXD
-itself uses, setting those may very well break LXD in non-obvious ways
-and should whenever possible be avoided.
-
-
-## Devices configuration
-LXD will always provide the container with the basic devices which are
-required for a standard POSIX system to work. These aren't visible in
-container or profile configuration and may not be overriden.
-
-Those includes:
- - /dev/null (character device)
- - /dev/zero (character device)
- - /dev/full (character device)
- - /dev/console (character device)
- - /dev/tty (character device)
- - /dev/random (character device)
- - /dev/urandom (character device)
- - /dev/net/tun (character device)
- - /dev/fuse (character device)
- - lo (network interface)
-
-Anything else has to be defined in the container configuration or in one
-of its profiles. The default profile will typically contain a network
-interface to become eth0 in the container.
-
-To add extra devices to a container, device entries can be added
-directly to a container, or to a profile.
-
-Devices may be added or removed while the container is running.
-
-Every device entry is identified by a unique name. If the same name is
-used in a subsequent profile or in the container's own configuration,
-the whole entry is overriden by the new definition.
-
-Device entries are added through:
-    lxc config device add <container> <name> <type> [key=value]...
-    lxc profile device add <profile> <name> <type> [key=value]...
-
-### Device types
-LXD supports the following device types:
-
-ID (database)   | Name          | Description
-:--             | :--           | :--
-0               | none          | Inheritance blocker
-1               | nic           | Network interface
-2               | disk          | Mountpoint inside the container
-3               | unix-char     | Unix character device
-4               | unix-block    | Unix block device
-
-### Type: none
-A none type device doesn't have any property and doesn't create anything inside the container.
-
-It's only purpose it to stop inheritance of devices coming from profiles.
-
-To do so, just add a none type device with the same name of the one you wish to skip inheriting.
-It can be added in a profile being applied after the profile it originated from or directly on the container.
-
-### Type: nic
-LXD supports different kind of network devices:
- - physical: Straight physical device passthrough from the host. The targeted device will vanish from the host and appear in the container.
- - bridged: Uses an existing bridge on the host and creates a virtual device pair to connect the host bridge to the container.
- - macvlan: Sets up a new network device based on an existing one but using a different MAC address.
- - p2p: Creates a virtual device pair, putting one side in the container and leaving the other side on the host.
-
-Different network interface types have different additional properties, the current list is:
-
-Key             | Type      | Default           | Required  | Used by                       | Description
-:--             | :--       | :--               | :--       | :--                           | :--
-nictype         | string    | -                 | yes       | all                           | The device type, one of "physical", "bridged", "macvlan" or "p2p"
-limits.ingress  | string    | -                 | no        | bridged, p2p                  | I/O limit in bit/s (supports kbit, Mbit, Gbit suffixes)
-limits.egress   | string    | -                 | no        | bridged, p2p                  | I/O limit in bit/s (supports kbit, Mbit, Gbit suffixes)
-limits.max      | string    | -                 | no        | bridged, p2p                  | Same as modifying both limits.read and limits.write
-name            | string    | kernel assigned   | no        | all                           | The name of the interface inside the container
-host\_name      | string    | randomly assigned | no        | bridged, p2p, macvlan         | The name of the interface inside the host
-hwaddr          | string    | randomly assigned | no        | all                           | The MAC address of the new interface
-mtu             | integer   | parent MTU        | no        | all                           | The MTU of the new interface
-parent          | string    | -                 | yes       | physical, bridged, macvlan    | The name of the host device or bridge
-
-### Type: disk
-Disk entries are essentially mountpoints inside the container. They can
-either be a bind-mount of an existing file or directory on the host, or
-if the source is a block device, a regular mount.
-
-The following properties exist:
-
-Key             | Type      | Default           | Required  | Description
-:--             | :--       | :--               | :--       | :--
-limits.read     | string    | -                 | no        | I/O limit in byte/s (supports kB, MB, GB, TB, PB and EB suffixes) or in iops (must be suffixed with "iops")
-limits.write    | string    | -                 | no        | I/O limit in byte/s (supports kB, MB, GB, TB, PB and EB suffixes) or in iops (must be suffixed with "iops")
-limits.max      | string    | -                 | no        | Same as modifying both limits.read and limits.write
-path            | string    | -                 | yes       | Path inside the container where the disk will be mounted
-source          | string    | -                 | yes       | Path on the host, either to a file/directory or to a block device
-optional        | boolean   | false             | no        | Controls whether to fail if the source doesn't exist
-readonly        | boolean   | false             | no        | Controls whether to make the mount read-only
-size            | string    | -                 | no        | Disk size in bytes (supports kB, MB, GB, TB, PB and EB suffixes). This is only supported for the rootfs (/).
-recursive       | boolean   | false             | no        | Whether or not to recursively mount the source path
-
-If multiple disks, backed by the same block device, have I/O limits set,
-the average of the limits will be used.
-
-### Type: unix-char
-Unix character device entries simply make the requested character device
-appear in the container's /dev and allow read/write operations to it.
-
-The following properties exist:
-
-Key         | Type      | Default           | Required  | Description
-:--         | :--       | :--               | :--       | :--
-path        | string    | -                 | yes       | Path inside the container
-major       | int       | device on host    | no        | Device major number
-minor       | int       | device on host    | no        | Device minor number
-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
-
-### Type: unix-block
-Unix block device entries simply make the requested block device
-appear in the container's /dev and allow read/write operations to it.
-
-The following properties exist:
-
-Key         | Type      | Default           | Required  | Description
-:--         | :--       | :--               | :--       | :--
-path        | string    | -                 | yes       | Path inside the container
-major       | int       | device on host    | no        | Device major number
-minor       | int       | device on host    | no        | Device minor number
-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
-
-## Profiles
-Profiles can store any configuration that a container can (key/value or devices)
-and any number of profiles can be applied to a container.
-
-Profiles are applied in the order they are specified so the last profile
-to specify a specific key wins.
-
-In any case, resource-specific configuration always overrides that
-coming from the profiles.
-
-
-If not present, LXD will create a "default" profile which comes with a
-network interface connected to LXD's default bridge (lxdbr0).
-
-The "default" profile is set for any new container created which doesn't
-specify a different profiles list.
+- [Server](server.md)
+- [Containers](containers.md)
+- [Profiles](profiles.md)

From bd3be01ce3c55c106b4001d27b41d37a9fce47c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 21 Apr 2017 18:26:17 -0400
Subject: [PATCH 0879/1193] Allow for stable host interface names
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3143

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/containers.md    | 21 +++++++++++----------
 lxd/container.go     |  4 ++++
 lxd/container_lxc.go |  8 +++++++-
 3 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/doc/containers.md b/doc/containers.md
index b5321ebd2..3f1b9e438 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -47,16 +47,17 @@ user.\*                     | string    | -             | n/a           | Free f
 
 The following volatile keys are currently internally used by LXD:
 
-Key                         | Type      | Default       | Description
-:--                         | :---      | :------       | :----------
-volatile.\<name\>.hwaddr    | string    | -             | Network device MAC address (when no hwaddr property is set on the device itself)
-volatile.\<name\>.name      | string    | -             | Network device name (when no name propery is set on the device itself)
-volatile.apply\_template    | string    | -             | The name of a template hook which should be triggered upon next startup
-volatile.base\_image        | string    | -             | The hash of the image the container was created from, if any.
-volatile.idmap.base         | integer   | -             | The first id in the container's primary idmap range
-volatile.idmap.next         | string    | -             | The idmap to use next time the container starts
-volatile.last\_state.idmap  | string    | -             | Serialized container uid/gid map
-volatile.last\_state.power  | string    | -             | Container state as of last host shutdown
+Key                             | Type      | Default       | Description
+:--                             | :---      | :------       | :----------
+volatile.\<name\>.hwaddr        | string    | -             | Network device MAC address (when no hwaddr property is set on the device itself)
+volatile.\<name\>.name          | string    | -             | Network device name (when no name propery is set on the device itself)
+volatile.\<name\>.host\_name    | string    | -             | Network device name on the host (for nictype=bridged or nictype=p2p)
+volatile.apply\_template        | string    | -             | The name of a template hook which should be triggered upon next startup
+volatile.base\_image            | string    | -             | The hash of the image the container was created from, if any.
+volatile.idmap.base             | integer   | -             | The first id in the container's primary idmap range
+volatile.idmap.next             | string    | -             | The idmap to use next time the container starts
+volatile.last\_state.idmap      | string    | -             | Serialized container uid/gid map
+volatile.last\_state.power      | string    | -             | Container state as of last host shutdown
 
 
 Additionally, those user keys have become common with images (support isn't guaranteed):
diff --git a/lxd/container.go b/lxd/container.go
index d2dd0cce1..46f8d5df1 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -216,6 +216,10 @@ func containerValidConfigKey(key string, value string) error {
 		if strings.HasSuffix(key, ".name") {
 			return nil
 		}
+
+		if strings.HasSuffix(key, ".host_name") {
+			return nil
+		}
 	}
 
 	if strings.HasPrefix(key, "environment.") {
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f20423eef..fd5b2f4ea 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5043,7 +5043,7 @@ func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Dev
 		newDevice["hwaddr"] = volatileHwaddr
 	}
 
-	// File in the name
+	// Fill in the name
 	if m["name"] == "" {
 		configKey := fmt.Sprintf("volatile.%s.name", name)
 		volatileName := c.localConfig[configKey]
@@ -5073,6 +5073,12 @@ func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Dev
 		newDevice["name"] = volatileName
 	}
 
+	// Fill in the host name (but don't generate a static one ourselves)
+	if m["host_name"] == "" && shared.StringInSlice(m["nictype"], []string{"bridged", "p2p"}) {
+		configKey := fmt.Sprintf("volatile.%s.host_name", name)
+		newDevice["host_name"] = c.localConfig[configKey]
+	}
+
 	return newDevice, nil
 }
 

From f01ed508dea498da98f6bc1ad84106420a0d3ac0 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 24 Apr 2017 14:56:41 +0200
Subject: [PATCH 0880/1193] doc: add not about escaping btrfs qgroups

Closes #3135.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 doc/storage-backends.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/doc/storage-backends.md b/doc/storage-backends.md
index 09387481e..736b3f12f 100644
--- a/doc/storage-backends.md
+++ b/doc/storage-backends.md
@@ -39,6 +39,12 @@ rsync is used to transfer the container content across.
  - The btrfs backend is automatically used if /var/lib/lxd is on a btrfs filesystem.
  - Uses a subvolume per container, image and snapshot, creating btrfs snapshots when creating a new object.
  - When using for nesting, the host btrfs filesystem must be mounted with the "user\_subvol\_rm\_allowed" mount option.
+ - btrfs supports storage quotas via qgroups. While btrfs qgroups are
+   hierarchical, new subvolumes will not automatically be added to the qgroups
+   of their parent subvolumes. This means that users can trivially escape any
+   quotas that are set. If adherence to strict quotas is a necessity users
+   should be mindful of this and maybe consider using a zfs storage pool with
+   refquotas.
 
 ### LVM
 

From 38248026217ff0b8081712d49ab340be9e87e82a Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 25 Apr 2017 16:24:17 +0200
Subject: [PATCH 0881/1193] tree-wide: use containerGetParentAndSnapshotName()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go      | 2 +-
 lxd/container_snapshot.go | 3 +--
 lxd/db_update.go          | 6 +++---
 3 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fd5b2f4ea..6be4121cd 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3315,7 +3315,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		// Get the container's architecture
 		var arch string
 		if c.IsSnapshot() {
-			parentName := strings.SplitN(c.name, shared.SnapshotDelimiter, 2)[0]
+			parentName, _, _ := containerGetParentAndSnapshotName(c.name)
 			parent, err := containerLoadByName(c.daemon, parentName)
 			if err != nil {
 				tw.Close()
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index ca5a930c0..b57a1624c 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net/http"
 	"strconv"
-	"strings"
 
 	"github.com/gorilla/mux"
 
@@ -36,7 +35,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	resultMap := []*api.ContainerSnapshot{}
 
 	for _, snap := range snaps {
-		snapName := strings.SplitN(snap.Name(), shared.SnapshotDelimiter, 2)[1]
+		_, snapName, _ := containerGetParentAndSnapshotName(snap.Name())
 		if recursion == 0 {
 			url := fmt.Sprintf("/%s/containers/%s/snapshots/%s", version.APIVersion, cname, snapName)
 			resultString = append(resultString, url)
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 3d3697fc3..b10530216 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -498,9 +498,9 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 	errors := 0
 
 	for _, cName := range cNames {
-		snappieces := strings.SplitN(cName, shared.SnapshotDelimiter, 2)
-		oldPath := shared.VarPath("containers", snappieces[0], "snapshots", snappieces[1])
-		newPath := shared.VarPath("snapshots", snappieces[0], snappieces[1])
+		snapParentName, snapOnlyName, _ := containerGetParentAndSnapshotName(cName)
+		oldPath := shared.VarPath("containers", snapParentName, "snapshots", snapOnlyName)
+		newPath := shared.VarPath("snapshots", snapParentName, snapOnlyName)
 		if shared.PathExists(oldPath) && !shared.PathExists(newPath) {
 			logger.Info(
 				"Moving snapshot",

From 68e9e3a652c153846c76ea60d50a928797ce8d6c Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at gmail.com>
Date: Wed, 26 Apr 2017 09:45:53 +0200
Subject: [PATCH 0882/1193] Honor the LXD_BACKEND environment variable in
 storage tests

Instead of unconditionally run storage tests depending on the
availability of the underlying backend tools, run them depending on
the value of LXD_BACKEND.

There's a small hack detecting whether we are running as
lxd-github-pull-test Jenkins job, and in that case LXD_BACKEND gets
automatically set to $backend. I think it'd be best to change the
Jenkins configuration instead, and I'm not sure what other jobs would
need a change too (beside lxd-github-pull-test).

Signed-off-by: Free Ekanayaka <free.ekanayaka at gmail.com>
---
 test/main.sh | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/test/main.sh b/test/main.sh
index 9129792c8..8d780e4e8 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -49,15 +49,25 @@ local_tcp_port() {
 }
 
 # import all the backends
-for backend in backends/*.sh; do
+for backend_sh in backends/*.sh; do
   # shellcheck disable=SC1090
-  . "${backend}"
+  . "${backend_sh}"
 done
 
 if [ -z "${LXD_BACKEND:-}" ]; then
-  LXD_BACKEND=dir
+
+    # XXX The Jenkins lxd-github-pull-test job sets "backend" as environment
+    #     variable as opposed to LXD_BACKEND, so we want to honor that. This
+    #     should probably be fixed in the Jenkins configuration.
+    if [ -n "${JENKINS_URL:-}" ] && [ -n "${backend:-}" ]; then
+	LXD_BACKEND="${backend}"
+    else
+	LXD_BACKEND=dir
+    fi
 fi
 
+echo "==> Using storage backend ${LXD_BACKEND}"
+
 spawn_lxd() {
   set +x
   # LXD_DIR is local here because since $(lxc) is actually a function, it

From a86bd1f9408a3a67cc144efb2ab8de9ef50beed2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Apr 2017 11:44:22 -0400
Subject: [PATCH 0883/1193] tests: Remove invalid test for Jenkins
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Jenkins does set LXD_BACKEND

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/test/main.sh b/test/main.sh
index 8d780e4e8..dfd1c489a 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -54,16 +54,9 @@ for backend_sh in backends/*.sh; do
   . "${backend_sh}"
 done
 
+# Set default backend to dir
 if [ -z "${LXD_BACKEND:-}" ]; then
-
-    # XXX The Jenkins lxd-github-pull-test job sets "backend" as environment
-    #     variable as opposed to LXD_BACKEND, so we want to honor that. This
-    #     should probably be fixed in the Jenkins configuration.
-    if [ -n "${JENKINS_URL:-}" ] && [ -n "${backend:-}" ]; then
-	LXD_BACKEND="${backend}"
-    else
-	LXD_BACKEND=dir
-    fi
+    LXD_BACKEND=dir
 fi
 
 echo "==> Using storage backend ${LXD_BACKEND}"

From 02cfd8407355088a4b88b938563fb619320af7ce Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 27 Apr 2017 20:22:27 +0200
Subject: [PATCH 0884/1193] Allow random storage backend selection.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 test/README.md                 |  2 +-
 test/backends/btrfs.sh         |  4 ---
 test/backends/lvm.sh           |  4 ---
 test/backends/zfs.sh           |  4 ---
 test/main.sh                   | 75 +++++++++++++++++++++++++++++++-----------
 test/suites/basic.sh           | 10 ++++--
 test/suites/database_update.sh |  2 ++
 test/suites/filemanip.sh       |  2 +-
 test/suites/image.sh           | 11 +++++++
 test/suites/init.sh            |  2 +-
 test/suites/migration.sh       | 29 +++++++++++++---
 test/suites/remote.sh          | 11 +++++++
 test/suites/security.sh        |  2 +-
 test/suites/snapshots.sh       | 32 +++++++++++-------
 14 files changed, 135 insertions(+), 55 deletions(-)

diff --git a/test/README.md b/test/README.md
index d0357e784..3c543b601 100644
--- a/test/README.md
+++ b/test/README.md
@@ -12,7 +12,7 @@ To run only the integration tests, run from the test directory:
 
 Name                            | Default                   | Description
 :--                             | :---                      | :----------
-LXD\_BACKEND                    | dir                       | What backend to test against (btrfs, dir, lvm or zfs)
+LXD\_BACKEND                    | dir                       | What backend to test against (btrfs, dir, lvm, zfs, or random)
 LXD\_CONCURRENT                 | 0                         | Run concurency tests, very CPU intensive
 LXD\_DEBUG                      | 0                         | Run lxd, lxc and the shell in debug mode (very verbose)
 LXD\_INSPECT                    | 0                         | Don't teardown the test environment on failure
diff --git a/test/backends/btrfs.sh b/test/backends/btrfs.sh
index efbcc460d..ee42bb933 100644
--- a/test/backends/btrfs.sh
+++ b/test/backends/btrfs.sh
@@ -8,10 +8,6 @@ btrfs_setup() {
 
   echo "==> Setting up btrfs backend in ${LXD_DIR}"
 
-  if ! which btrfs >/dev/null 2>&1; then
-    echo "Couldn't find the btrfs binary"; false
-  fi
-
   truncate -s 100G "${TEST_DIR}/$(basename "${LXD_DIR}").btrfs"
   mkfs.btrfs "${TEST_DIR}/$(basename "${LXD_DIR}").btrfs"
 
diff --git a/test/backends/lvm.sh b/test/backends/lvm.sh
index 862e3301f..01ed29ebe 100644
--- a/test/backends/lvm.sh
+++ b/test/backends/lvm.sh
@@ -8,10 +8,6 @@ lvm_setup() {
 
   echo "==> Setting up lvm backend in ${LXD_DIR}"
 
-  if ! which lvm >/dev/null 2>&1; then
-    echo "Couldn't find the lvm binary"; false
-  fi
-
   truncate -s 4G "${TEST_DIR}/$(basename "${LXD_DIR}").lvm"
   pvloopdev=$(losetup --show -f "${TEST_DIR}/$(basename "${LXD_DIR}").lvm")
   if [ ! -e "${pvloopdev}" ]; then
diff --git a/test/backends/zfs.sh b/test/backends/zfs.sh
index f33802e9c..8aa303490 100644
--- a/test/backends/zfs.sh
+++ b/test/backends/zfs.sh
@@ -8,10 +8,6 @@ zfs_setup() {
 
   echo "==> Setting up ZFS backend in ${LXD_DIR}"
 
-  if ! which zfs >/dev/null 2>&1; then
-    echo "Couldn't find zfs binary"; false
-  fi
-
   truncate -s 100G "${LXD_DIR}/zfspool"
   # prefix lxdtest- here, as zfs pools must start with a letter, but tempdir
   # won't necessarily generate one that does.
diff --git a/test/main.sh b/test/main.sh
index dfd1c489a..e4b6265ac 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -48,36 +48,81 @@ local_tcp_port() {
   done
 }
 
-# import all the backends
-for backend_sh in backends/*.sh; do
-  # shellcheck disable=SC1090
-  . "${backend_sh}"
-done
+# return a list of available storage backends
+available_storage_backends() {
+  # shellcheck disable=2039
+  local backend backends
+
+  backends="dir"
+  for backend in btrfs lvm zfs; do
+    if which $backend >/dev/null 2>&1; then
+      backends="$backends $backend"
+    fi
+  done
+  echo "$backends"
+}
+
+# whether a storage backend is available
+storage_backend_available() {
+  # shellcheck disable=2039
+  local backends
+  backends="$(available_storage_backends)"
+  [ "${backends#*$1}" != "$backends" ]
+}
+
+# choose a random available backend, excluding LXD_BACKEND
+random_storage_backend() {
+    # shellcheck disable=2046
+    shuf -e $(available_storage_backends) | head -n 1
+}
+
+# return the storage backend being used by a LXD instance
+storage_backend() {
+    cat "$1/lxd.backend"
+}
 
 # Set default backend to dir
 if [ -z "${LXD_BACKEND:-}" ]; then
     LXD_BACKEND=dir
 fi
 
+echo "==> Available storage backends: $(available_storage_backends | sort)"
+if [ "$LXD_BACKEND" != "random" ] && ! storage_backend_available "$LXD_BACKEND"; then
+  echo "Storage backage \"$LXD_BACKEND\" is not available"
+  exit 1
+fi
 echo "==> Using storage backend ${LXD_BACKEND}"
 
+# import storage backends
+for backend in $(available_storage_backends); do
+  # shellcheck disable=SC1090
+  . "backends/${backend}.sh"
+done
+
 spawn_lxd() {
   set +x
   # LXD_DIR is local here because since $(lxc) is actually a function, it
   # overwrites the environment and we would lose LXD_DIR's value otherwise.
 
   # shellcheck disable=2039
-  local LXD_DIR
+  local LXD_DIR lxddir lxd_backend
 
   lxddir=${1}
   shift
 
+  if [ "$LXD_BACKEND" = "random" ]; then
+    lxd_backend="$(random_storage_backend)"
+  else
+    lxd_backend="$LXD_BACKEND"
+  fi
+
   # Copy pre generated Certs
   cp deps/server.crt "${lxddir}"
   cp deps/server.key "${lxddir}"
 
   # setup storage
-  "$LXD_BACKEND"_setup "${lxddir}"
+  "$lxd_backend"_setup "${lxddir}"
+  echo "$lxd_backend" > "${lxddir}/lxd.backend"
 
   echo "==> Spawning lxd in ${lxddir}"
   # shellcheck disable=SC2086
@@ -107,7 +152,7 @@ spawn_lxd() {
   fi
 
   echo "==> Configuring storage backend"
-  "$LXD_BACKEND"_configure "${lxddir}"
+  "$lxd_backend"_configure "${lxddir}"
 }
 
 lxc() {
@@ -195,12 +240,13 @@ kill_lxd() {
   # overwrites the environment and we would lose LXD_DIR's value otherwise.
 
   # shellcheck disable=2039
-  local LXD_DIR
+  local LXD_DIR daemon_dir daemon_pid check_leftovers lxd_backend
 
   daemon_dir=${1}
   LXD_DIR=${daemon_dir}
   daemon_pid=$(cat "${daemon_dir}/lxd.pid")
   check_leftovers="false"
+  lxd_backend=$(storage_backend "$daemon_dir")
   echo "==> Killing LXD at ${daemon_dir}"
 
   if [ -e "${daemon_dir}/unix.socket" ]; then
@@ -276,7 +322,7 @@ kill_lxd() {
   fi
 
   # teardown storage
-  "$LXD_BACKEND"_teardown "${daemon_dir}"
+  "$lxd_backend"_teardown "${daemon_dir}"
 
   # Wipe the daemon directory
   wipe "${daemon_dir}"
@@ -425,7 +471,6 @@ fi
 LXD_CONF=$(mktemp -d -p "${TEST_DIR}" XXX)
 export LXD_CONF
 
-# Setup the first LXD
 LXD_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
 export LXD_DIR
 chmod +x "${LXD_DIR}"
@@ -433,14 +478,6 @@ spawn_lxd "${LXD_DIR}"
 LXD_ADDR=$(cat "${LXD_DIR}/lxd.addr")
 export LXD_ADDR
 
-# Setup the second LXD
-LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
-chmod +x "${LXD2_DIR}"
-spawn_lxd "${LXD2_DIR}"
-LXD2_ADDR=$(cat "${LXD2_DIR}/lxd.addr")
-export LXD2_ADDR
-
-
 run_test() {
   TEST_CURRENT=${1}
   TEST_CURRENT_DESCRIPTION=${2:-${1}}
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 990b5a4fc..3eb09f465 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -12,6 +12,10 @@ gen_third_cert() {
 }
 
 test_basic_usage() {
+  # shellcheck disable=2039
+  local lxd_backend
+  lxd_backend=$(storage_backend "$LXD_DIR")
+
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
 
@@ -131,9 +135,9 @@ test_basic_usage() {
   # the container's filesystem. That's ok though: the logic we're trying to
   # test here is independent of storage backend, so running it for just one
   # backend (or all non-lvm backends) is enough.
-  if [ "${LXD_BACKEND}" != "lvm" ]; then
+  if [ "$lxd_backend" = "lvm" ]; then
     lxc init testimage nometadata
-    rm "${LXD_DIR}/containers/nometadata/metadata.yaml"
+    rm -f "${LXD_DIR}/containers/nometadata/metadata.yaml"
     lxc publish nometadata --alias=nometadata-image
     lxc image delete nometadata-image
     lxc delete nometadata
@@ -285,7 +289,7 @@ test_basic_usage() {
   rm "${LXD_DIR}/out"
 
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "$lxd_backend" = "dir" ]; then
     content=$(cat "${LXD_DIR}/containers/foo/rootfs/tmp/foo")
     [ "${content}" = "foo" ]
   fi
diff --git a/test/suites/database_update.sh b/test/suites/database_update.sh
index 6f39bee89..aff744166 100644
--- a/test/suites/database_update.sh
+++ b/test/suites/database_update.sh
@@ -19,4 +19,6 @@ test_database_update(){
   expected_cascades=11
   cascades=$(sqlite3 "${MIGRATE_DB}" ".dump" | grep -c "ON DELETE CASCADE")
   [ "${cascades}" -eq "${expected_cascades}" ] || { echo "FAIL: Wrong number of ON DELETE CASCADE foreign keys. Found: ${cascades}, exected: ${expected_cascades}"; false; }
+
+  kill_lxd "$LXD_MIGRATE_DIR"
 }
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index efe5a1b66..51a046b2b 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -21,7 +21,7 @@ test_filemanip() {
 
   lxc delete filemanip -f
 
-  if [ "${LXD_BACKEND}" != "lvm" ]; then
+  if [ "$(storage_backend "$LXD_DIR")" != "lvm" ]; then
     lxc launch testimage idmap -c "raw.idmap=\"both 0 0\""
     [ "$(stat -c %u "${LXD_DIR}/containers/idmap/rootfs")" = "0" ]
     lxc delete idmap --force
diff --git a/test/suites/image.sh b/test/suites/image.sh
index 0b749ca33..0155d0bfa 100644
--- a/test/suites/image.sh
+++ b/test/suites/image.sh
@@ -1,9 +1,17 @@
 #!/bin/sh
 
 test_image_expiry() {
+  # shellcheck disable=2039
+  local LXD2_DIR LXD2_ADDR
+  LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD2_DIR}"
+  spawn_lxd "${LXD2_DIR}"
+  LXD2_ADDR=$(cat "${LXD2_DIR}/lxd.addr")
+
   ensure_import_testimage
 
   if ! lxc_remote remote list | grep -q l1; then
+    # shellcheck disable=2153
     lxc_remote remote add l1 "${LXD_ADDR}" --accept-certificate --password foo
   fi
   if ! lxc_remote remote list | grep -q l2; then
@@ -28,4 +36,7 @@ test_image_expiry() {
   lxc_remote remote set-default l2
   lxc_remote config set images.remote_cache_expiry 10
   lxc_remote remote set-default local
+
+  lxc_remote remote remove l2
+  kill_lxd "$LXD2_DIR"
 }
diff --git a/test/suites/init.sh b/test/suites/init.sh
index abb89e9ff..e999a9d4f 100644
--- a/test/suites/init.sh
+++ b/test/suites/init.sh
@@ -8,7 +8,7 @@ test_lxd_autoinit() {
   # naming. This can cause naming conflicts when multiple test-suites are run on
   # a single runner.
 
-  if [ "${LXD_BACKEND}" = "zfs" ]; then
+  if [ "$(storage_backend "$LXD_DIR")" = "zfs" ]; then
     # lxd init --auto --storage-backend zfs --storage-pool <name>
     LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
     chmod +x "${LXD_INIT_DIR}"
diff --git a/test/suites/migration.sh b/test/suites/migration.sh
index 593b9b6ea..fd5533438 100644
--- a/test/suites/migration.sh
+++ b/test/suites/migration.sh
@@ -1,9 +1,25 @@
 #!/bin/sh
 
 test_migration() {
+  # setup a second LXD
+  # shellcheck disable=2039
+  local LXD2_DIR LXD2_ADDR lxd_backend
+
+  # shellcheck disable=2153
+  lxd_backend=$(storage_backend "$LXD_DIR")
+
+  LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD2_DIR}"
+  spawn_lxd "${LXD2_DIR}"
+  LXD2_ADDR=$(cat "${LXD2_DIR}/lxd.addr")
+
+  # shellcheck disable=2153
+  lxd2_backend=$(storage_backend "$LXD2_DIR")
+
   ensure_import_testimage
 
   if ! lxc_remote remote list | grep -q l1; then
+    # shellcheck disable=2153
     lxc_remote remote add l1 "${LXD_ADDR}" --accept-certificate --password foo
   fi
   if ! lxc_remote remote list | grep -q l2; then
@@ -19,29 +35,29 @@ test_migration() {
   lxc_remote config show l2:nonlive/snap0 | grep user.tester | grep foo
 
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" != "lvm" ]; then
+  if [ "${lxd2_backend}" != "lvm" ]; then
     [ -d "${LXD2_DIR}/containers/nonlive/rootfs" ]
   fi
   [ ! -d "${LXD_DIR}/containers/nonlive" ]
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd2_backend}" = "dir" ]; then
     [ -d "${LXD2_DIR}/snapshots/nonlive/snap0/rootfs/bin" ]
   fi
 
   lxc_remote copy l2:nonlive l1:nonlive2
   [ -d "${LXD_DIR}/containers/nonlive2" ]
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" != "lvm" ]; then
+  if [ "${lxd2_backend}" != "lvm" ]; then
     [ -d "${LXD2_DIR}/containers/nonlive/rootfs/bin" ]
   fi
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ -d "${LXD_DIR}/snapshots/nonlive2/snap0/rootfs/bin" ]
   fi
 
   lxc_remote copy l1:nonlive2/snap0 l2:nonlive3
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" != "lvm" ]; then
+  if [ "${lxd2_backend}" != "lvm" ]; then
     [ -d "${LXD2_DIR}/containers/nonlive3/rootfs/bin" ]
   fi
   lxc_remote delete l2:nonlive3 --force
@@ -65,6 +81,7 @@ test_migration() {
   lxc_remote delete l2:nonlive --force
 
   if ! which criu >/dev/null 2>&1; then
+    kill_lxd "$LXD2_DIR"
     echo "==> SKIP: live migration with CRIU (missing binary)"
     return
   fi
@@ -77,4 +94,6 @@ test_migration() {
   lxc_remote stop --stateful l1:migratee
   lxc_remote start l1:migratee
   lxc_remote delete --force l1:migratee
+
+  kill_lxd "$LXD2_DIR"
 }
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index 869b8fee7..71472ac1c 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -12,6 +12,7 @@ gen_second_cert() {
 }
 
 test_remote_url() {
+  # shellcheck disable=2153
   for url in "${LXD_ADDR}" "https://${LXD_ADDR}"; do
     lxc_remote remote add test "${url}" --accept-certificate --password foo
     lxc_remote finger test:
@@ -21,6 +22,7 @@ test_remote_url() {
     lxc_remote remote remove test
   done
 
+  # shellcheck disable=2153
   urls="${LXD_DIR}/unix.socket unix:${LXD_DIR}/unix.socket unix://${LXD_DIR}/unix.socket"
   if [ -z "${LXD_OFFLINE:-}" ]; then
     urls="images.linuxcontainers.org https://images.linuxcontainers.org ${urls}"
@@ -80,6 +82,13 @@ test_remote_admin() {
 }
 
 test_remote_usage() {
+  # shellcheck disable=2039
+  local LXD2_DIR LXD2_ADDR
+  LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD2_DIR}"
+  spawn_lxd "${LXD2_DIR}"
+  LXD2_ADDR=$(cat "${LXD2_DIR}/lxd.addr")
+
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
 
@@ -141,4 +150,6 @@ test_remote_usage() {
   lxc_remote info lxd2:c1
   lxc_remote stop lxd2:c1 --force
   lxc_remote delete lxd2:c1
+
+  kill_lxd "$LXD2_DIR"
 }
diff --git a/test/suites/security.sh b/test/suites/security.sh
index ce2524f7e..f2a96e40a 100644
--- a/test/suites/security.sh
+++ b/test/suites/security.sh
@@ -5,7 +5,7 @@ test_security() {
   ensure_has_localhost_remote "${LXD_ADDR}"
 
   # CVE-2016-1581
-  if [ "${LXD_BACKEND}" = "zfs" ]; then
+  if [ "$(storage_backend "$LXD_DIR")" = "zfs" ]; then
     LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
     chmod +x "${LXD_INIT_DIR}"
     spawn_lxd "${LXD_INIT_DIR}"
diff --git a/test/suites/snapshots.sh b/test/suites/snapshots.sh
index b1bb5eb36..5ce7e5296 100644
--- a/test/suites/snapshots.sh
+++ b/test/suites/snapshots.sh
@@ -4,62 +4,66 @@ test_snapshots() {
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
 
+  # shellcheck disable=2039
+  local lxd_backend
+  lxd_backend=$(storage_backend "$LXD_DIR")
+
   lxc init testimage foo
 
   lxc snapshot foo
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ -d "${LXD_DIR}/snapshots/foo/snap0" ]
   fi
 
   lxc snapshot foo
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ -d "${LXD_DIR}/snapshots/foo/snap1" ]
   fi
 
   lxc snapshot foo tester
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ -d "${LXD_DIR}/snapshots/foo/tester" ]
   fi
 
   lxc copy foo/tester foosnap1
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" != "lvm" ]; then
+  if [ "${lxd_backend}" != "lvm" ]; then
     [ -d "${LXD_DIR}/containers/foosnap1/rootfs" ]
   fi
 
   lxc delete foo/snap0
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ ! -d "${LXD_DIR}/snapshots/foo/snap0" ]
   fi
 
   # no CLI for this, so we use the API directly (rename a snapshot)
   wait_for "${LXD_ADDR}" my_curl -X POST "https://${LXD_ADDR}/1.0/containers/foo/snapshots/tester" -d "{\"name\":\"tester2\"}"
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ ! -d "${LXD_DIR}/snapshots/foo/tester" ]
   fi
 
   lxc move foo/tester2 foo/tester-two
   lxc delete foo/tester-two
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ ! -d "${LXD_DIR}/snapshots/foo/tester-two" ]
   fi
 
   lxc snapshot foo namechange
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ -d "${LXD_DIR}/snapshots/foo/namechange" ]
   fi
   lxc move foo foople
   [ ! -d "${LXD_DIR}/containers/foo" ]
   [ -d "${LXD_DIR}/containers/foople" ]
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "${lxd_backend}" = "dir" ]; then
     [ -d "${LXD_DIR}/snapshots/foople/namechange" ]
     [ -d "${LXD_DIR}/snapshots/foople/namechange" ]
   fi
@@ -71,6 +75,10 @@ test_snapshots() {
 }
 
 test_snap_restore() {
+  # shellcheck disable=2039
+  local lxd_backend
+  lxd_backend=$(storage_backend "$LXD_DIR")
+
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
 
@@ -113,7 +121,7 @@ test_snap_restore() {
 
   ##########################################################
 
-  if [ "${LXD_BACKEND}" != "zfs" ]; then
+  if [ "${lxd_backend}" != "zfs" ]; then
     # The problem here is that you can't `zfs rollback` to a snapshot with a
     # parent, which snap0 has (snap1).
     restore_and_compare_fs snap0
@@ -143,7 +151,7 @@ test_snap_restore() {
   # Start container and then restore snapshot to verify the running state after restore.
   lxc start bar
 
-  if [ "${LXD_BACKEND}" != "zfs" ]; then
+  if [ "${lxd_backend}" != "zfs" ]; then
     # see comment above about snap0
     restore_and_compare_fs snap0
 
@@ -163,7 +171,7 @@ restore_and_compare_fs() {
   lxc restore bar "${snap}"
 
   # FIXME: make this backend agnostic
-  if [ "${LXD_BACKEND}" = "dir" ]; then
+  if [ "$(storage_backend "$LXD_DIR")" = "dir" ]; then
     # Recursive diff of container FS
     diff -r "${LXD_DIR}/containers/bar/rootfs" "${LXD_DIR}/snapshots/bar/${snap}/rootfs"
   fi

From 0b7ac41e2ffe4193149a9606b9ddcc486ea22fa1 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 27 Apr 2017 20:38:30 +0200
Subject: [PATCH 0885/1193] Explicitly pass shell type to shellcheck.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 test/backends/btrfs.sh         | 2 --
 test/backends/dir.sh           | 1 -
 test/backends/lvm.sh           | 2 --
 test/backends/zfs.sh           | 2 --
 test/suites/basic.sh           | 2 --
 test/suites/concurrent.sh      | 2 --
 test/suites/config.sh          | 1 -
 test/suites/database_update.sh | 2 --
 test/suites/deps.sh            | 2 --
 test/suites/devlxd.sh          | 2 --
 test/suites/exec.sh            | 2 --
 test/suites/fdleak.sh          | 2 --
 test/suites/filemanip.sh       | 2 --
 test/suites/fuidshift.sh       | 2 --
 test/suites/idmap.sh           | 2 --
 test/suites/image.sh           | 2 --
 test/suites/init.sh            | 2 --
 test/suites/migration.sh       | 2 --
 test/suites/profiling.sh       | 2 --
 test/suites/remote.sh          | 2 --
 test/suites/security.sh        | 2 --
 test/suites/serverconfig.sh    | 2 --
 test/suites/snapshots.sh       | 2 --
 test/suites/static_analysis.sh | 4 +---
 test/suites/template.sh        | 2 --
 25 files changed, 1 insertion(+), 49 deletions(-)

diff --git a/test/backends/btrfs.sh b/test/backends/btrfs.sh
index ee42bb933..e26e8bb1a 100644
--- a/test/backends/btrfs.sh
+++ b/test/backends/btrfs.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 btrfs_setup() {
   # shellcheck disable=2039
   local LXD_DIR
diff --git a/test/backends/dir.sh b/test/backends/dir.sh
index 7a14be57e..64d11df05 100644
--- a/test/backends/dir.sh
+++ b/test/backends/dir.sh
@@ -1,4 +1,3 @@
-#!/bin/sh
 # Nothing need be done for the dir backed, but we still need some functions.
 # This file can also serve as a skel file for what needs to be done to
 # implement a new backend.
diff --git a/test/backends/lvm.sh b/test/backends/lvm.sh
index 01ed29ebe..90ad9ef74 100644
--- a/test/backends/lvm.sh
+++ b/test/backends/lvm.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 lvm_setup() {
   # shellcheck disable=2039
   local LXD_DIR
diff --git a/test/backends/zfs.sh b/test/backends/zfs.sh
index 8aa303490..1b92dbdda 100644
--- a/test/backends/zfs.sh
+++ b/test/backends/zfs.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 zfs_setup() {
   # shellcheck disable=2039
   local LXD_DIR
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 3eb09f465..1b0bc75b4 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 gen_third_cert() {
   [ -f "${LXD_CONF}/client3.crt" ] && return
   mv "${LXD_CONF}/client.crt" "${LXD_CONF}/client.crt.bak"
diff --git a/test/suites/concurrent.sh b/test/suites/concurrent.sh
index 33362bcd7..107f4f08c 100644
--- a/test/suites/concurrent.sh
+++ b/test/suites/concurrent.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_concurrent() {
   if [ -z "${LXD_CONCURRENT:-}" ]; then
     echo "==> SKIP: LXD_CONCURRENT isn't set"
diff --git a/test/suites/config.sh b/test/suites/config.sh
index 3e4870914..300fbc053 100644
--- a/test/suites/config.sh
+++ b/test/suites/config.sh
@@ -1,4 +1,3 @@
-#!/bin/sh
 ensure_removed() {
   bad=0
   lxc exec foo -- stat /dev/ttyS0 && bad=1
diff --git a/test/suites/database_update.sh b/test/suites/database_update.sh
index aff744166..ecd87977a 100644
--- a/test/suites/database_update.sh
+++ b/test/suites/database_update.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_database_update(){
   LXD_MIGRATE_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
   MIGRATE_DB=${LXD_MIGRATE_DIR}/lxd.db
diff --git a/test/suites/deps.sh b/test/suites/deps.sh
index 04a58f6fd..2ec30a983 100644
--- a/test/suites/deps.sh
+++ b/test/suites/deps.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_check_deps() {
   ! ldd "$(which lxc)" | grep -q liblxc
 }
diff --git a/test/suites/devlxd.sh b/test/suites/devlxd.sh
index 722d247ec..ea1a6e880 100644
--- a/test/suites/devlxd.sh
+++ b/test/suites/devlxd.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_devlxd() {
   ensure_import_testimage
 
diff --git a/test/suites/exec.sh b/test/suites/exec.sh
index 8adb7f321..db06ce6ea 100644
--- a/test/suites/exec.sh
+++ b/test/suites/exec.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_concurrent_exec() {
   if [ -z "${LXD_CONCURRENT:-}" ]; then
     echo "==> SKIP: LXD_CONCURRENT isn't set"
diff --git a/test/suites/fdleak.sh b/test/suites/fdleak.sh
index ea4c5e26e..d31af828d 100644
--- a/test/suites/fdleak.sh
+++ b/test/suites/fdleak.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_fdleak() {
   LXD_FDLEAK_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
   chmod +x "${LXD_FDLEAK_DIR}"
diff --git a/test/suites/filemanip.sh b/test/suites/filemanip.sh
index 51a046b2b..3269a9337 100644
--- a/test/suites/filemanip.sh
+++ b/test/suites/filemanip.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_filemanip() {
   # Workaround for shellcheck getting confused by "cd"
   set -e
diff --git a/test/suites/fuidshift.sh b/test/suites/fuidshift.sh
index c4a58a84a..8b3a1f1a5 100644
--- a/test/suites/fuidshift.sh
+++ b/test/suites/fuidshift.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_common_fuidshift() {
   # test some bad arguments
   fail=0
diff --git a/test/suites/idmap.sh b/test/suites/idmap.sh
index 1cf6dfb16..88b3f2232 100644
--- a/test/suites/idmap.sh
+++ b/test/suites/idmap.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_idmap() {
   # Check that we have a big enough range for this test
   if [ ! -e /etc/subuid ] && [ ! -e /etc/subgid ]; then
diff --git a/test/suites/image.sh b/test/suites/image.sh
index 0155d0bfa..e3b9f9b79 100644
--- a/test/suites/image.sh
+++ b/test/suites/image.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_image_expiry() {
   # shellcheck disable=2039
   local LXD2_DIR LXD2_ADDR
diff --git a/test/suites/init.sh b/test/suites/init.sh
index e999a9d4f..336c524cb 100644
--- a/test/suites/init.sh
+++ b/test/suites/init.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_lxd_autoinit() {
   # - lxd init --auto --storage-backend zfs
   # and
diff --git a/test/suites/migration.sh b/test/suites/migration.sh
index fd5533438..7d2e65db0 100644
--- a/test/suites/migration.sh
+++ b/test/suites/migration.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_migration() {
   # setup a second LXD
   # shellcheck disable=2039
diff --git a/test/suites/profiling.sh b/test/suites/profiling.sh
index 193a22fa1..746590d3c 100644
--- a/test/suites/profiling.sh
+++ b/test/suites/profiling.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_cpu_profiling() {
   LXD3_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
   chmod +x "${LXD3_DIR}"
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index 71472ac1c..474a03e0a 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 gen_second_cert() {
   [ -f "${LXD_CONF}/client2.crt" ] && return
   mv "${LXD_CONF}/client.crt" "${LXD_CONF}/client.crt.bak"
diff --git a/test/suites/security.sh b/test/suites/security.sh
index f2a96e40a..633c72b92 100644
--- a/test/suites/security.sh
+++ b/test/suites/security.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_security() {
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
diff --git a/test/suites/serverconfig.sh b/test/suites/serverconfig.sh
index d9cd0c5b2..0c5941c73 100644
--- a/test/suites/serverconfig.sh
+++ b/test/suites/serverconfig.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_server_config() {
   LXD_SERVERCONFIG_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
   spawn_lxd "${LXD_SERVERCONFIG_DIR}"
diff --git a/test/suites/snapshots.sh b/test/suites/snapshots.sh
index 5ce7e5296..142b8e710 100644
--- a/test/suites/snapshots.sh
+++ b/test/suites/snapshots.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_snapshots() {
   ensure_import_testimage
   ensure_has_localhost_remote "${LXD_ADDR}"
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 2721058b5..5845a3938 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 safe_pot_hash() {
   sed -e "/Project-Id-Version/,/Content-Transfer-Encoding/d" -e "/^#/d" "po/lxd.pot" | md5sum | cut -f1 -d" "
 }
@@ -18,7 +16,7 @@ test_static_analysis() {
 
     # Shell static analysis
     if which shellcheck >/dev/null 2>&1; then
-      shellcheck test/main.sh test/suites/* test/backends/*
+      shellcheck --shell sh test/main.sh test/suites/* test/backends/*
     else
       echo "shellcheck not found, shell static analysis disabled"
     fi
diff --git a/test/suites/template.sh b/test/suites/template.sh
index 398b79b34..da6b1d5c2 100644
--- a/test/suites/template.sh
+++ b/test/suites/template.sh
@@ -1,5 +1,3 @@
-#!/bin/sh
-
 test_template() {
   # Import a template which only triggers on create
   deps/import-busybox --alias template-test --template create

From e091c85e30e41e3e291bfe0892c5db3635f12cc3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Apr 2017 18:03:05 -0400
Subject: [PATCH 0886/1193] client: Implement image upload
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go           |  20 ++++-
 client/lxd_images.go           | 161 ++++++++++++++++++++++++++++++++++++++++-
 client/simplestreams_images.go |   2 +-
 3 files changed, 177 insertions(+), 6 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index 3ecbee28a..a7194ca3b 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -78,7 +78,7 @@ type ContainerServer interface {
 
 	// Image functions
 	ImageServer
-	CreateImage(image api.ImagesPost) (op *Operation, err error)
+	CreateImage(image api.ImagesPost, args *ImageCreateArgs) (op *Operation, err error)
 	UpdateImage(fingerprint string, image api.ImagePut, ETag string) (err error)
 	DeleteImage(fingerprint string) (op *Operation, err error)
 	CreateImageSecret(fingerprint string) (op *Operation, err error)
@@ -126,6 +126,24 @@ type ProgressData struct {
 	TotalBytes int64
 }
 
+// The ImageCreateArgs struct is used for direct image upload
+type ImageCreateArgs struct {
+	// Reader for the meta file
+	MetaFile io.Reader
+
+	// Filename for the meta file
+	MetaName string
+
+	// Reader for the rootfs file
+	RootfsFile io.Reader
+
+	// Filename for the rootfs file
+	RootfsName string
+
+	// Progress handler (called with upload progress)
+	ProgressHandler func(progress ProgressData)
+}
+
 // The ImageFileRequest struct is used for an image download request
 type ImageFileRequest struct {
 	// Writer for the metadata file
diff --git a/client/lxd_images.go b/client/lxd_images.go
index 1d296886f..36d43ca9f 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -2,11 +2,15 @@ package lxd
 
 import (
 	"crypto/sha256"
+	"encoding/json"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"mime"
 	"mime/multipart"
 	"net/http"
+	"net/url"
+	"os"
 	"strings"
 
 	"github.com/lxc/lxd/shared"
@@ -262,14 +266,163 @@ func (r *ProtocolLXD) GetImageAlias(name string) (*api.ImageAliasesEntry, string
 }
 
 // CreateImage requests that LXD creates, copies or import a new image
-func (r *ProtocolLXD) CreateImage(image api.ImagesPost) (*Operation, error) {
+func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (*Operation, error) {
+	// Send the JSON based request
+	if args == nil {
+		op, _, err := r.queryOperation("POST", "/images", image, "")
+		if err != nil {
+			return nil, err
+		}
+
+		return op, nil
+	}
+
+	// Prepare an image upload
+	if args.MetaFile == nil {
+		return nil, fmt.Errorf("Metadata file is required")
+	}
+
+	// Prepare the body
+	var body io.Reader
+	var contentType string
+	if args.RootfsFile == nil {
+		// If unified image, just pass it through
+		body = args.MetaFile
+
+		contentType = "application/octet-stream"
+	} else {
+		// If split image, we need mime encoding
+		tmpfile, err := ioutil.TempFile("", "lxc_image_")
+		if err != nil {
+			return nil, err
+		}
+		defer os.Remove(tmpfile.Name())
+
+		// Setup the multipart writer
+		w := multipart.NewWriter(tmpfile)
+
+		// Metadata file
+		fw, err := w.CreateFormFile("metadata", args.MetaName)
+		if err != nil {
+			return nil, err
+		}
+
+		_, err = io.Copy(fw, args.MetaFile)
+		if err != nil {
+			return nil, err
+		}
+
+		// Rootfs file
+		fw, err = w.CreateFormFile("rootfs", args.RootfsName)
+		if err != nil {
+			return nil, err
+		}
+
+		_, err = io.Copy(fw, args.RootfsFile)
+		if err != nil {
+			return nil, err
+		}
+
+		// Done writing to multipart
+		w.Close()
+
+		// Figure out the size of the whole thing
+		size, err := tmpfile.Seek(0, 2)
+		if err != nil {
+			return nil, err
+		}
+
+		_, err = tmpfile.Seek(0, 0)
+		if err != nil {
+			return nil, err
+		}
+
+		// Setup progress handler
+		body = &ioprogress.ProgressReader{
+			ReadCloser: tmpfile,
+			Tracker: &ioprogress.ProgressTracker{
+				Length: size,
+				Handler: func(percent int64, speed int64) {
+					args.ProgressHandler(ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed, 2))})
+				},
+			},
+		}
+
+		contentType = w.FormDataContentType()
+	}
+
+	// Prepare the HTTP request
+	reqURL := fmt.Sprintf("%s/1.0/images", r.httpHost)
+	req, err := http.NewRequest("POST", reqURL, body)
+	if err != nil {
+		return nil, err
+	}
+
+	// Setup the headers
+	req.Header.Set("Content-Type", contentType)
+	if image.Public {
+		req.Header.Set("X-LXD-public", "true")
+	}
+
+	if image.Filename != "" {
+		req.Header.Set("X-LXD-filename", image.Filename)
+	}
+
+	if len(image.Properties) > 0 {
+		imgProps := url.Values{}
+
+		for k, v := range image.Properties {
+			imgProps.Set(k, v)
+		}
+
+		req.Header.Set("X-LXD-properties", imgProps.Encode())
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
 	// Send the request
-	op, _, err := r.queryOperation("POST", "/images", image, "")
+	resp, err := r.http.Do(req)
 	if err != nil {
 		return nil, err
 	}
+	defer resp.Body.Close()
 
-	return op, nil
+	// Decode the response
+	decoder := json.NewDecoder(resp.Body)
+	response := api.Response{}
+
+	err = decoder.Decode(&response)
+	if err != nil {
+		// Check the return value for a cleaner error
+		if resp.StatusCode != http.StatusOK {
+			return nil, fmt.Errorf("Failed to fetch %s: %s", reqURL, resp.Status)
+		}
+
+		return nil, err
+	}
+
+	// Handle errors
+	if response.Type == api.ErrorResponse {
+		return nil, fmt.Errorf(response.Error)
+	}
+
+	// Get to the operation
+	respOperation, err := response.MetadataAsOperation()
+	if err != nil {
+		return nil, err
+	}
+
+	// Setup an Operation wrapper
+	op := Operation{
+		Operation: *respOperation,
+		r:         r,
+		chActive:  make(chan bool),
+	}
+
+	return &op, nil
 }
 
 // CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
@@ -304,7 +457,7 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 		req.Public = args.Public
 	}
 
-	return target.CreateImage(req)
+	return target.CreateImage(req, nil)
 }
 
 // UpdateImage updates the image definition
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index 1ff5fefbe..e067c279b 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -165,5 +165,5 @@ func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServe
 		req.Public = args.Public
 	}
 
-	return target.CreateImage(req)
+	return target.CreateImage(req, nil)
 }

From e0b18dcce28a48bed0323a85e24c1e02047c1ec0 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 28 Apr 2017 11:23:13 +0200
Subject: [PATCH 0887/1193] lxc {copy,move}: improve error handling

Closes #3243.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxc/copy.go | 27 ++++++++++++++++++---------
 lxc/move.go |  3 ++-
 2 files changed, 20 insertions(+), 10 deletions(-)

diff --git a/lxc/copy.go b/lxc/copy.go
index a02a99181..e14d5ef18 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -172,17 +172,20 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 		if err != nil {
 			msg[senderid] = err
 			ch <- msg
+			return
 		}
 
 		msg[senderid] = nil
 		ch <- msg
 	}
+
+	var migrationErrFromClient error
 	for _, addr := range addresses {
 		var migration *api.Response
 
 		sourceWSUrl := "https://" + addr + sourceWSResponse.Operation
-		migration, err = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1)
-		if err != nil {
+		migration, migrationErrFromClient = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1)
+		if migrationErrFromClient != nil {
 			continue
 		}
 
@@ -191,18 +194,24 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 		sourceOpId := 1
 		go wait(source, sourceWSResponse.Operation, waitchan, sourceOpId)
 
-		opStatus := make([]map[int]error, 2)
+		var sourceOpErr error
+		var destOpErr error
 		for i := 0; i < cap(waitchan); i++ {
-			opStatus[i] = <-waitchan
+			tmp := <-waitchan
+			err, ok := tmp[sourceOpId]
+			if ok {
+				sourceOpErr = err
+			} else {
+				destOpErr = err
+			}
 		}
 
-		if opStatus[0][destOpId] != nil {
+		if destOpErr != nil {
 			continue
 		}
 
-		err = opStatus[1][sourceOpId]
-		if err != nil {
-			return err
+		if sourceOpErr != nil {
+			return sourceOpErr
 		}
 
 		return nil
@@ -215,7 +224,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 	}
 
 	// Return the error from destination
-	return fmt.Errorf(i18n.G("Migration failed on target host: %s"), err)
+	return fmt.Errorf(i18n.G("Migration failed on target host: %s"), migrationErrFromClient)
 }
 
 func (c *copyCmd) run(config *lxd.Config, args []string) error {
diff --git a/lxc/move.go b/lxc/move.go
index 112d2a7da..12b43b296 100644
--- a/lxc/move.go
+++ b/lxc/move.go
@@ -61,7 +61,8 @@ func (c *moveCmd) run(config *lxd.Config, args []string) error {
 
 	// A move is just a copy followed by a delete; however, we want to
 	// keep the volatile entries around since we are moving the container.
-	if err := cpy.copyContainer(config, args[0], args[1], true, -1); err != nil {
+	err := cpy.copyContainer(config, args[0], args[1], true, -1)
+	if err != nil {
 		return err
 	}
 

From 23129feabe537ea25a8440c437c7e7441910428a Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free at ekanayaka.io>
Date: Fri, 28 Apr 2017 10:22:52 +0200
Subject: [PATCH 0888/1193] Complete cmd.Context support for various AskXXX
 methods

This branch is a mechanical follow-up of the first one introducing
cmd.Context. It adds the rest of AskXXX methods as defined in cmdInit,
and should be a step towards making cmdInit itself testable.

The logic is exactly the same as the inline functions currently
defined in main_init.go, although some boilerplate could be avoided by
factoring common logic together.

Signed-off-by: Free Ekanayaka <free at ekanayaka.io>
---
 shared/cmd/context.go      |  92 +++++++++++++++++++++++++++++--
 shared/cmd/context_test.go | 131 +++++++++++++++++++++++++++++++++++++++------
 shared/cmd/testing.go      |  45 ++++++++++++++++
 3 files changed, 250 insertions(+), 18 deletions(-)
 create mode 100644 shared/cmd/testing.go

diff --git a/shared/cmd/context.go b/shared/cmd/context.go
index c265f8d62..346657ca9 100644
--- a/shared/cmd/context.go
+++ b/shared/cmd/context.go
@@ -4,6 +4,7 @@ import (
 	"bufio"
 	"fmt"
 	"io"
+	"strconv"
 	"strings"
 
 	"github.com/lxc/lxd/shared"
@@ -29,8 +30,7 @@ func NewContext(stdin io.Reader, stdout, stderr io.Writer) *Context {
 // AskBool asks a question an expect a yes/no answer.
 func (c *Context) AskBool(question string, defaultAnswer string) bool {
 	for {
-		fmt.Fprintf(c.stdout, question)
-		answer := c.readAnswer(defaultAnswer)
+		answer := c.askQuestion(question, defaultAnswer)
 
 		if shared.StringInSlice(strings.ToLower(answer), []string{"yes", "y"}) {
 			return true
@@ -38,10 +38,96 @@ func (c *Context) AskBool(question string, defaultAnswer string) bool {
 			return false
 		}
 
-		fmt.Fprintf(c.stderr, "Invalid input, try again.\n\n")
+		c.invalidInput()
+	}
+}
+
+// AskChoice asks the user to select between a set of choices
+func (c *Context) AskChoice(question string, choices []string, defaultAnswer string) string {
+	for {
+		answer := c.askQuestion(question, defaultAnswer)
+
+		if shared.StringInSlice(answer, choices) {
+			return answer
+		}
+
+		c.invalidInput()
+	}
+}
+
+// AskInt asks the user to enter an integer between a min and max value
+func (c *Context) AskInt(question string, min int64, max int64, defaultAnswer string) int64 {
+	for {
+		answer := c.askQuestion(question, defaultAnswer)
+
+		result, err := strconv.ParseInt(answer, 10, 64)
+
+		if err == nil && (min == -1 || result >= min) && (max == -1 || result <= max) {
+			return result
+		}
+
+		c.invalidInput()
+	}
+}
+
+// AskString asks the user to enter a string, which optionally
+// conforms to a validation function.
+func (c *Context) AskString(question string, defaultAnswer string, validate func(string) error) string {
+	for {
+		answer := c.askQuestion(question, defaultAnswer)
+
+		if validate != nil {
+			error := validate(answer)
+			if error != nil {
+				fmt.Fprintf(c.stderr, "Invalid input: %s\n\n", error)
+				continue
+			}
+		}
+		if len(answer) != 0 {
+			return answer
+		}
+
+		c.invalidInput()
 	}
 }
 
+// AskPassword asks the user to enter a password. The reader function used to
+// read the password without echoing characters must be passed (usually
+// terminal.ReadPassword from golang.org/x/crypto/ssh/terminal).
+func (c *Context) AskPassword(question string, reader func(int) ([]byte, error)) string {
+	for {
+		fmt.Fprintf(c.stdout, question)
+
+		pwd, _ := reader(0)
+		fmt.Fprintf(c.stdout, "\n")
+		inFirst := string(pwd)
+		inFirst = strings.TrimSuffix(inFirst, "\n")
+
+		fmt.Fprintf(c.stdout, "Again: ")
+		pwd, _ = reader(0)
+		fmt.Fprintf(c.stdout, "\n")
+		inSecond := string(pwd)
+		inSecond = strings.TrimSuffix(inSecond, "\n")
+
+		if inFirst == inSecond {
+			return inFirst
+		}
+
+		c.invalidInput()
+	}
+}
+
+// Ask a question on the output stream and read the answer from the input stream
+func (c *Context) askQuestion(question, defaultAnswer string) string {
+	fmt.Fprintf(c.stdout, question)
+	return c.readAnswer(defaultAnswer)
+}
+
+// Print an invalid input message on the error stream
+func (c *Context) invalidInput() {
+	fmt.Fprintf(c.stderr, "Invalid input, try again.\n\n")
+}
+
 // Read the user's answer from the input stream, trimming newline and providing a default.
 func (c *Context) readAnswer(defaultAnswer string) string {
 	answer, _ := c.stdin.ReadString('\n')
diff --git a/shared/cmd/context_test.go b/shared/cmd/context_test.go
index b57743fff..24006b086 100644
--- a/shared/cmd/context_test.go
+++ b/shared/cmd/context_test.go
@@ -1,11 +1,11 @@
 package cmd_test
 
 import (
-	"bytes"
-	"strings"
+	"fmt"
 	"testing"
 
 	"github.com/lxc/lxd/shared/cmd"
+	"github.com/stretchr/testify/assert"
 )
 
 // AskBool returns a boolean result depending on the user input.
@@ -26,22 +26,123 @@ func TestAskBool(t *testing.T) {
 		{"Do you code?", "yes", "Do you code?Do you code?", "Invalid input, try again.\n\n", "foo\nyes\n", true},
 	}
 	for _, c := range cases {
-		stdin := strings.NewReader(c.input)
-		stdout := new(bytes.Buffer)
-		stderr := new(bytes.Buffer)
-		context := cmd.NewContext(stdin, stdout, stderr)
+		streams := cmd.NewMemoryStreams(c.input)
+		context := cmd.NewMemoryContext(streams)
 		result := context.AskBool(c.question, c.defaultAnswer)
 
-		if result != c.result {
-			t.Errorf("Expected '%v' result got '%v'", c.result, result)
-		}
+		assert.Equal(t, c.result, result, "Unexpected answer result")
+		streams.AssertOutEqual(t, c.output)
+		streams.AssertErrEqual(t, c.error)
+	}
+}
+
+// AskChoice returns one of the given choices
+func TestAskChoice(t *testing.T) {
+	cases := []struct {
+		question      string
+		choices       []string
+		defaultAnswer string
+		output        string
+		error         string
+		input         string
+		result        string
+	}{
+		{"Best food?", []string{"pizza", "rice"}, "rice", "Best food?", "", "\n", "rice"},
+		{"Best food?", []string{"pizza", "rice"}, "rice", "Best food?", "", "pizza\n", "pizza"},
+		{"Best food?", []string{"pizza", "rice"}, "rice", "Best food?Best food?", "Invalid input, try again.\n\n", "foo\npizza\n", "pizza"},
+	}
+	for _, c := range cases {
+		streams := cmd.NewMemoryStreams(c.input)
+		context := cmd.NewMemoryContext(streams)
+		result := context.AskChoice(c.question, c.choices, c.defaultAnswer)
 
-		if output := stdout.String(); output != c.output {
-			t.Errorf("Expected '%s' output got '%s'", c.output, output)
-		}
+		assert.Equal(t, c.result, result, "Unexpected answer result")
+		streams.AssertOutEqual(t, c.output)
+		streams.AssertErrEqual(t, c.error)
+	}
+}
+
+// AskInt returns an integer within the given bounds
+func TestAskInt(t *testing.T) {
+	cases := []struct {
+		question      string
+		min           int64
+		max           int64
+		defaultAnswer string
+		output        string
+		error         string
+		input         string
+		result        int64
+	}{
+		{"Age?", 0, 100, "30", "Age?", "", "\n", 30},
+		{"Age?", 0, 100, "30", "Age?", "", "40\n", 40},
+		{"Age?", 0, 100, "30", "Age?Age?", "Invalid input, try again.\n\n", "foo\n40\n", 40},
+		{"Age?", 18, 65, "30", "Age?Age?", "Invalid input, try again.\n\n", "10\n30\n", 30},
+		{"Age?", 18, 65, "30", "Age?Age?", "Invalid input, try again.\n\n", "70\n30\n", 30},
+		{"Age?", 0, -1, "30", "Age?", "", "120\n", 120},
+	}
+	for _, c := range cases {
+		streams := cmd.NewMemoryStreams(c.input)
+		context := cmd.NewMemoryContext(streams)
+		result := context.AskInt(c.question, c.min, c.max, c.defaultAnswer)
+
+		assert.Equal(t, c.result, result, "Unexpected answer result")
+		streams.AssertOutEqual(t, c.output)
+		streams.AssertErrEqual(t, c.error)
+	}
+}
+
+// AskString returns a string conforming the validation function.
+func TestAskString(t *testing.T) {
+	cases := []struct {
+		question      string
+		defaultAnswer string
+		validate      func(string) error
+		output        string
+		error         string
+		input         string
+		result        string
+	}{
+		{"Name?", "Joe", nil, "Name?", "", "\n", "Joe"},
+		{"Name?", "Joe", nil, "Name?", "", "John\n", "John"},
+		{"Name?", "Joe", func(s string) error {
+			if s[0] != 'J' {
+				return fmt.Errorf("ugly name")
+			}
+			return nil
+		}, "Name?Name?", "Invalid input: ugly name\n\n", "Ted\nJohn", "John"},
+	}
+	for _, c := range cases {
+		streams := cmd.NewMemoryStreams(c.input)
+		context := cmd.NewMemoryContext(streams)
+		result := context.AskString(c.question, c.defaultAnswer, c.validate)
+
+		assert.Equal(t, c.result, result, "Unexpected answer result")
+		streams.AssertOutEqual(t, c.output)
+		streams.AssertErrEqual(t, c.error)
+	}
+}
+
+// AskPassword returns the password entered twice by the user.
+func TestAskPassword(t *testing.T) {
+	cases := []struct {
+		question string
+		reader   func(int) ([]byte, error)
+		output   string
+		error    string
+		result   string
+	}{
+		{"Pass?", func(int) ([]byte, error) {
+			return []byte("pwd"), nil
+		}, "Pass?\nAgain: \n", "", "pwd"},
+	}
+	for _, c := range cases {
+		streams := cmd.NewMemoryStreams("")
+		context := cmd.NewMemoryContext(streams)
+		result := context.AskPassword(c.question, c.reader)
 
-		if error := stderr.String(); error != c.error {
-			t.Errorf("Expected '%s' error got '%s'", c.error, error)
-		}
+		assert.Equal(t, c.result, result, "Unexpected answer result")
+		streams.AssertOutEqual(t, c.output)
+		streams.AssertErrEqual(t, c.error)
 	}
 }
diff --git a/shared/cmd/testing.go b/shared/cmd/testing.go
new file mode 100644
index 000000000..faefb48da
--- /dev/null
+++ b/shared/cmd/testing.go
@@ -0,0 +1,45 @@
+// Utilities for testing cmd-related code.
+
+package cmd
+
+import (
+	"bytes"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+// MemoryStreams provide an in-memory version of the system
+// stdin/stdout/stderr streams.
+type MemoryStreams struct {
+	in  *strings.Reader
+	out *bytes.Buffer
+	err *bytes.Buffer
+}
+
+// NewMemoryStreams creates a new set of in-memory streams with the given
+// user input.
+func NewMemoryStreams(input string) *MemoryStreams {
+	return &MemoryStreams{
+		in:  strings.NewReader(input),
+		out: new(bytes.Buffer),
+		err: new(bytes.Buffer),
+	}
+}
+
+// AssertOutEqual checks that the given text matches the the out stream.
+func (s *MemoryStreams) AssertOutEqual(t *testing.T, expected string) {
+	assert.Equal(t, expected, s.out.String(), "Unexpected output stream")
+}
+
+// AssertErrEqual checks that the given text matches the the err stream.
+func (s *MemoryStreams) AssertErrEqual(t *testing.T, expected string) {
+	assert.Equal(t, expected, s.err.String(), "Unexpected error stream")
+}
+
+// NewMemoryContext creates a new command Context using the given in-memory
+// streams.
+func NewMemoryContext(streams *MemoryStreams) *Context {
+	return NewContext(streams.in, streams.out, streams.err)
+}

From 87ec5daf3dbdd4bf5b82ad7895d98bbe938808f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 12:48:18 -0400
Subject: [PATCH 0889/1193] Fix typos
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/operations.go  | 2 +-
 lxd/container_exec.go | 2 +-
 test/README.md        | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/client/operations.go b/client/operations.go
index cd958fbe9..7f381a011 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -147,7 +147,7 @@ func (op *Operation) setupListener() error {
 			return
 		}
 
-		// We don't want concurency while processing events
+		// We don't want concurrency while processing events
 		op.listenerLock.Lock()
 		defer op.listenerLock.Unlock()
 
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 987246f4a..2dc81bf00 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -169,7 +169,7 @@ func (s *execWs) Do(op *operation) error {
 						break
 					}
 
-					// If an abnormal closure occured, kill the attached process.
+					// If an abnormal closure occurred, kill the attached process.
 					err := syscall.Kill(attachedChildPid, syscall.SIGKILL)
 					if err != nil {
 						logger.Debugf("Failed to send SIGKILL to pid %d.", attachedChildPid)
diff --git a/test/README.md b/test/README.md
index 3c543b601..5d0e2856e 100644
--- a/test/README.md
+++ b/test/README.md
@@ -13,7 +13,7 @@ To run only the integration tests, run from the test directory:
 Name                            | Default                   | Description
 :--                             | :---                      | :----------
 LXD\_BACKEND                    | dir                       | What backend to test against (btrfs, dir, lvm, zfs, or random)
-LXD\_CONCURRENT                 | 0                         | Run concurency tests, very CPU intensive
+LXD\_CONCURRENT                 | 0                         | Run concurrency tests, very CPU intensive
 LXD\_DEBUG                      | 0                         | Run lxd, lxc and the shell in debug mode (very verbose)
 LXD\_INSPECT                    | 0                         | Don't teardown the test environment on failure
 LXD\_LOGS                       | ""                        | Path to a directory to copy all the LXD logs to

From b229d76af9cc25b7e4b5305dd79dd7ffe5c6b551 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 12:58:03 -0400
Subject: [PATCH 0890/1193] shared/logging: Make golint clean
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/logging/format.go       | 2 +-
 shared/logging/log.go          | 3 ++-
 shared/logging/log_posix.go    | 4 ++--
 test/suites/static_analysis.sh | 1 +
 4 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/shared/logging/format.go b/shared/logging/format.go
index 083d40ea4..93ddda62a 100644
--- a/shared/logging/format.go
+++ b/shared/logging/format.go
@@ -67,7 +67,7 @@ func TerminalFormat() log.Format {
 	})
 }
 
-func LogfmtFormat() log.Format {
+func logfmtFormat() log.Format {
 	return log.FormatFunc(func(r *log.Record) []byte {
 		common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg}
 		buf := &bytes.Buffer{}
diff --git a/shared/logging/log.go b/shared/logging/log.go
index 15b8ac9cd..8d6bf2a1a 100644
--- a/shared/logging/log.go
+++ b/shared/logging/log.go
@@ -19,7 +19,7 @@ func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHa
 	var syshandler log.Handler
 
 	// Format handler
-	format := LogfmtFormat()
+	format := logfmtFormat()
 	if term.IsTty(os.Stderr.Fd()) {
 		format = TerminalFormat()
 	}
@@ -81,6 +81,7 @@ func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHa
 	return Log, nil
 }
 
+// AddContext will return a copy of the logger with extra context added
 func AddContext(logger logger.Logger, ctx log.Ctx) logger.Logger {
 	log15logger, ok := logger.(log.Logger)
 	if !ok {
diff --git a/shared/logging/log_posix.go b/shared/logging/log_posix.go
index 7cdbfd922..ffc55da5d 100644
--- a/shared/logging/log_posix.go
+++ b/shared/logging/log_posix.go
@@ -15,9 +15,9 @@ func getSystemHandler(syslog string, debug bool, format log.Format) log.Handler
 				log.LvlInfo,
 				log.Must.SyslogHandler(syslog, format),
 			)
-		} else {
-			return log.Must.SyslogHandler(syslog, format)
 		}
+
+		return log.Must.SyslogHandler(syslog, format)
 	}
 
 	return nil
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 5845a3938..f1ff86713 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -48,6 +48,7 @@ test_static_analysis() {
       golint -set_exit_status shared/gnuflag/
       golint -set_exit_status shared/i18n/
       golint -set_exit_status shared/ioprogress/
+      golint -set_exit_status shared/logging/
       golint -set_exit_status shared/version/
     fi
 

From 90a485b33153a0fece6c6ccbbaa2e3f43af630da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 13:04:49 -0400
Subject: [PATCH 0891/1193] shared/logger: Make golint clean
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/logger/format.go        |  3 ++-
 shared/logger/log.go           | 16 +++++++++++++---
 test/suites/static_analysis.sh |  1 +
 3 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/shared/logger/format.go b/shared/logger/format.go
index 16f2e83c5..df6d86ff2 100644
--- a/shared/logger/format.go
+++ b/shared/logger/format.go
@@ -5,10 +5,11 @@ import (
 	"fmt"
 )
 
+// Pretty will attempt to convert any Go structure into a string suitable for logging
 func Pretty(input interface{}) string {
 	pretty, err := json.MarshalIndent(input, "\t", "\t")
 	if err != nil {
-		return fmt.Sprintf("%s", input)
+		return fmt.Sprintf("%v", input)
 	}
 
 	return fmt.Sprintf("\n\t%s", pretty)
diff --git a/shared/logger/log.go b/shared/logger/log.go
index 60148d497..8e737a3f5 100644
--- a/shared/logger/log.go
+++ b/shared/logger/log.go
@@ -7,6 +7,7 @@ import (
 	"runtime"
 )
 
+// Logger is the main logging interface
 type Logger interface {
 	Debug(msg string, ctx ...interface{})
 	Info(msg string, ctx ...interface{})
@@ -15,6 +16,7 @@ type Logger interface {
 	Crit(msg string, ctx ...interface{})
 }
 
+// Log contains the logger used by all the logging functions
 var Log Logger
 
 type nullLogger struct{}
@@ -29,69 +31,77 @@ func init() {
 	Log = nullLogger{}
 }
 
-// General wrappers around Logger interface functions.
+// Debug logs a message (with optional context) at the DEBUG log level
 func Debug(msg string, ctx ...interface{}) {
 	if Log != nil {
 		Log.Debug(msg, ctx...)
 	}
 }
 
+// Info logs a message (with optional context) at the INFO log level
 func Info(msg string, ctx ...interface{}) {
 	if Log != nil {
 		Log.Info(msg, ctx...)
 	}
 }
 
+// Warn logs a message (with optional context) at the WARNING log level
 func Warn(msg string, ctx ...interface{}) {
 	if Log != nil {
 		Log.Warn(msg, ctx...)
 	}
 }
 
+// Error logs a message (with optional context) at the ERROR log level
 func Error(msg string, ctx ...interface{}) {
 	if Log != nil {
 		Log.Error(msg, ctx...)
 	}
 }
 
+// Crit logs a message (with optional context) at the CRITICAL log level
 func Crit(msg string, ctx ...interface{}) {
 	if Log != nil {
 		Log.Crit(msg, ctx...)
 	}
 }
 
-// Wrappers around Logger interface functions that send a string to the Logger
-// by running it through fmt.Sprintf().
+// Infof logs at the INFO log level using a standard printf format string
 func Infof(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Info(fmt.Sprintf(format, args...))
 	}
 }
 
+// Debugf logs at the DEBUG log level using a standard printf format string
 func Debugf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Debug(fmt.Sprintf(format, args...))
 	}
 }
 
+// Warnf logs at the WARNING log level using a standard printf format string
 func Warnf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Warn(fmt.Sprintf(format, args...))
 	}
 }
 
+// Errorf logs at the ERROR log level using a standard printf format string
 func Errorf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Error(fmt.Sprintf(format, args...))
 	}
 }
 
+// Critf logs at the CRITICAL log level using a standard printf format string
 func Critf(format string, args ...interface{}) {
 	if Log != nil {
 		Log.Crit(fmt.Sprintf(format, args...))
 	}
 }
 
+// PrintStack logs the current Go stack at the ERROR log level
 func PrintStack() {
 	buf := make([]byte, 1<<16)
 	runtime.Stack(buf, true)
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index f1ff86713..3bebf59ef 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -48,6 +48,7 @@ test_static_analysis() {
       golint -set_exit_status shared/gnuflag/
       golint -set_exit_status shared/i18n/
       golint -set_exit_status shared/ioprogress/
+      golint -set_exit_status shared/logger/
       golint -set_exit_status shared/logging/
       golint -set_exit_status shared/version/
     fi

From c1e660226f7b3f589bc100144666c821b9addf38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 13:08:59 -0400
Subject: [PATCH 0892/1193] shared/logger: Replace PrintStack with GetStack
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/containers_get.go   |  2 +-
 lxd/db.go               | 12 ++++++------
 lxd/main_daemon.go      |  2 +-
 shared/logger/format.go |  9 +++++++++
 shared/logger/log.go    |  8 --------
 5 files changed, 17 insertions(+), 16 deletions(-)

diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 87a45f5da..62e2682dd 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -26,7 +26,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 	}
 
 	logger.Debugf("DBERR: containersGet, db is locked")
-	logger.PrintStack()
+	logger.Debugf(logger.GetStack())
 	return InternalError(fmt.Errorf("DB is locked"))
 }
 
diff --git a/lxd/db.go b/lxd/db.go
index 9c4a5f15f..8a41a1d35 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -301,7 +301,7 @@ func dbBegin(db *sql.DB) (*sql.Tx, error) {
 	}
 
 	logger.Debugf("DbBegin: DB still locked")
-	logger.PrintStack()
+	logger.Debugf(logger.GetStack())
 	return nil, fmt.Errorf("DB is locked")
 }
 
@@ -319,7 +319,7 @@ func txCommit(tx *sql.Tx) error {
 	}
 
 	logger.Debugf("Txcommit: db still locked")
-	logger.PrintStack()
+	logger.Debugf(logger.GetStack())
 	return fmt.Errorf("DB is locked")
 }
 
@@ -339,7 +339,7 @@ func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interfac
 	}
 
 	logger.Debugf("DbQueryRowScan: query %q args %q, DB still locked", q, args)
-	logger.PrintStack()
+	logger.Debugf(logger.GetStack())
 	return fmt.Errorf("DB is locked")
 }
 
@@ -357,7 +357,7 @@ func dbQuery(db *sql.DB, q string, args ...interface{}) (*sql.Rows, error) {
 	}
 
 	logger.Debugf("DbQuery: query %q args %q, DB still locked", q, args)
-	logger.PrintStack()
+	logger.Debugf(logger.GetStack())
 	return nil, fmt.Errorf("DB is locked")
 }
 
@@ -437,7 +437,7 @@ func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{
 	}
 
 	logger.Debugf("DbQueryscan: query %q inargs %q, DB still locked", q, inargs)
-	logger.PrintStack()
+	logger.Debugf(logger.GetStack())
 	return nil, fmt.Errorf("DB is locked")
 }
 
@@ -455,6 +455,6 @@ func dbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) {
 	}
 
 	logger.Debugf("DbExec: query %q args %q, DB still locked", q, args)
-	logger.PrintStack()
+	logger.Debugf(logger.GetStack())
 	return nil, fmt.Errorf("DB is locked")
 }
diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
index 9153fb587..bbf3e8001 100644
--- a/lxd/main_daemon.go
+++ b/lxd/main_daemon.go
@@ -46,7 +46,7 @@ func cmdDaemon() error {
 		go func() {
 			for {
 				time.Sleep(time.Duration(*argPrintGoroutinesEvery) * time.Second)
-				logger.PrintStack()
+				logger.Debugf(logger.GetStack())
 			}
 		}()
 	}
diff --git a/shared/logger/format.go b/shared/logger/format.go
index df6d86ff2..ddb03217b 100644
--- a/shared/logger/format.go
+++ b/shared/logger/format.go
@@ -3,6 +3,7 @@ package logger
 import (
 	"encoding/json"
 	"fmt"
+	"runtime"
 )
 
 // Pretty will attempt to convert any Go structure into a string suitable for logging
@@ -14,3 +15,11 @@ func Pretty(input interface{}) string {
 
 	return fmt.Sprintf("\n\t%s", pretty)
 }
+
+// GetStack will convert the Go stack into a string suitable for logging
+func GetStack() string {
+	buf := make([]byte, 1<<16)
+	runtime.Stack(buf, true)
+
+	return fmt.Sprintf("\n\t%s", buf)
+}
diff --git a/shared/logger/log.go b/shared/logger/log.go
index 8e737a3f5..a031d8c14 100644
--- a/shared/logger/log.go
+++ b/shared/logger/log.go
@@ -4,7 +4,6 @@ package logger
 
 import (
 	"fmt"
-	"runtime"
 )
 
 // Logger is the main logging interface
@@ -100,10 +99,3 @@ func Critf(format string, args ...interface{}) {
 		Log.Crit(fmt.Sprintf(format, args...))
 	}
 }
-
-// PrintStack logs the current Go stack at the ERROR log level
-func PrintStack() {
-	buf := make([]byte, 1<<16)
-	runtime.Stack(buf, true)
-	Errorf("%s", buf)
-}

From 4dfa55751a6af49ccd909b56e74794e8b0191be3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 13:11:44 -0400
Subject: [PATCH 0893/1193] test/deps: Make golint clean
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/deps/devlxd-client.go     | 13 +++++++------
 test/suites/static_analysis.sh |  1 +
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/test/deps/devlxd-client.go b/test/deps/devlxd-client.go
index 83399bd52..f88f4a77e 100644
--- a/test/deps/devlxd-client.go
+++ b/test/deps/devlxd-client.go
@@ -1,8 +1,9 @@
+package main
+
 /*
  * An example of how to use lxd's golang /dev/lxd client. This is intended to
  * be run from inside a container.
  */
-package main
 
 import (
 	"encoding/json"
@@ -13,11 +14,11 @@ import (
 	"os"
 )
 
-type DevLxdDialer struct {
+type devLxdDialer struct {
 	Path string
 }
 
-func (d DevLxdDialer) DevLxdDial(network, path string) (net.Conn, error) {
+func (d devLxdDialer) devLxdDial(network, path string) (net.Conn, error) {
 	addr, err := net.ResolveUnixAddr("unix", d.Path)
 	if err != nil {
 		return nil, err
@@ -31,12 +32,12 @@ func (d DevLxdDialer) DevLxdDial(network, path string) (net.Conn, error) {
 	return conn, err
 }
 
-var DevLxdTransport = &http.Transport{
-	Dial: DevLxdDialer{"/dev/lxd/sock"}.DevLxdDial,
+var devLxdTransport = &http.Transport{
+	Dial: devLxdDialer{"/dev/lxd/sock"}.devLxdDial,
 }
 
 func main() {
-	c := http.Client{Transport: DevLxdTransport}
+	c := http.Client{Transport: devLxdTransport}
 	raw, err := c.Get("http://meshuggah-rocks/")
 	if err != nil {
 		fmt.Println(err)
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 3bebf59ef..8add36791 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -51,6 +51,7 @@ test_static_analysis() {
       golint -set_exit_status shared/logger/
       golint -set_exit_status shared/logging/
       golint -set_exit_status shared/version/
+      golint -set_exit_status test/deps/
     fi
 
     ## deadcode

From 50129b7c2e517900705ebe18a1f4ca22c8089b97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 28 Apr 2017 13:18:15 -0400
Subject: [PATCH 0894/1193] shared/termios: Make golint clean
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/termios/termios.go         | 6 ++++++
 shared/termios/termios_windows.go | 6 ++++++
 test/suites/static_analysis.sh    | 1 +
 3 files changed, 13 insertions(+)

diff --git a/shared/termios/termios.go b/shared/termios/termios.go
index 4004bffe3..1b841c722 100644
--- a/shared/termios/termios.go
+++ b/shared/termios/termios.go
@@ -12,15 +12,18 @@ import (
 // #include <termios.h>
 import "C"
 
+// State contains the state of a terminal.
 type State struct {
 	Termios syscall.Termios
 }
 
+// IsTerminal returns true if the given file descriptor is a terminal.
 func IsTerminal(fd int) bool {
 	_, err := GetState(fd)
 	return err == nil
 }
 
+// GetState returns the current state of a terminal which may be useful to restore the terminal after a signal.
 func GetState(fd int) (*State, error) {
 	termios := syscall.Termios{}
 
@@ -35,6 +38,7 @@ func GetState(fd int) (*State, error) {
 	return &state, nil
 }
 
+// GetSize returns the dimensions of the given terminal.
 func GetSize(fd int) (int, int, error) {
 	var dimensions [4]uint16
 
@@ -45,6 +49,7 @@ func GetSize(fd int) (int, int, error) {
 	return int(dimensions[1]), int(dimensions[0]), nil
 }
 
+// MakeRaw put the terminal connected to the given file descriptor into raw mode and returns the previous state of the terminal so that it can be restored.
 func MakeRaw(fd int) (*State, error) {
 	var err error
 	var oldState, newState *State
@@ -69,6 +74,7 @@ func MakeRaw(fd int) (*State, error) {
 	return oldState, nil
 }
 
+// Restore restores the terminal connected to the given file descriptor to a previous state.
 func Restore(fd int, state *State) error {
 	ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(&state.Termios)))
 	if ret != 0 {
diff --git a/shared/termios/termios_windows.go b/shared/termios/termios_windows.go
index 9d0b576c2..64c8b1a53 100644
--- a/shared/termios/termios_windows.go
+++ b/shared/termios/termios_windows.go
@@ -6,12 +6,15 @@ import (
 	"golang.org/x/crypto/ssh/terminal"
 )
 
+// State contains the state of a terminal.
 type State terminal.State
 
+// IsTerminal returns true if the given file descriptor is a terminal.
 func IsTerminal(fd int) bool {
 	return terminal.IsTerminal(fd)
 }
 
+// GetState returns the current state of a terminal which may be useful to restore the terminal after a signal.
 func GetState(fd int) (*State, error) {
 	state, err := terminal.GetState(fd)
 	if err != nil {
@@ -22,10 +25,12 @@ func GetState(fd int) (*State, error) {
 	return &currentState, nil
 }
 
+// GetSize returns the dimensions of the given terminal.
 func GetSize(fd int) (int, int, error) {
 	return terminal.GetSize(fd)
 }
 
+// MakeRaw put the terminal connected to the given file descriptor into raw mode and returns the previous state of the terminal so that it can be restored.
 func MakeRaw(fd int) (*State, error) {
 	state, err := terminal.MakeRaw(fd)
 	if err != nil {
@@ -36,6 +41,7 @@ func MakeRaw(fd int) (*State, error) {
 	return &oldState, nil
 }
 
+// Restore restores the terminal connected to the given file descriptor to a previous state.
 func Restore(fd int, state *State) error {
 	newState := terminal.State(*state)
 
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 8add36791..55567b229 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -50,6 +50,7 @@ test_static_analysis() {
       golint -set_exit_status shared/ioprogress/
       golint -set_exit_status shared/logger/
       golint -set_exit_status shared/logging/
+      golint -set_exit_status shared/termios/
       golint -set_exit_status shared/version/
       golint -set_exit_status test/deps/
     fi

From caa14f797cd5268e957714d36cdae86e2d09ff7b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sun, 30 Apr 2017 00:16:16 +0200
Subject: [PATCH 0895/1193] lxc copy: simplify

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxc/copy.go | 11 +----------
 1 file changed, 1 insertion(+), 10 deletions(-)

diff --git a/lxc/copy.go b/lxc/copy.go
index e14d5ef18..4112d8ee8 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -167,16 +167,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 	 */
 	waitchan := make(chan map[int]error, 2)
 	wait := func(cli *lxd.Client, op string, ch chan map[int]error, senderid int) {
-		msg := make(map[int]error, 1)
-		err := cli.WaitForSuccess(op)
-		if err != nil {
-			msg[senderid] = err
-			ch <- msg
-			return
-		}
-
-		msg[senderid] = nil
-		ch <- msg
+		ch <- map[int]error{senderid: cli.WaitForSuccess(op)}
 	}
 
 	var migrationErrFromClient error

From 4151835db1ad67fd64fcb00a7611940829e75250 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 1 May 2017 21:57:44 -0400
Subject: [PATCH 0896/1193] shared/logging: Export LogfmtFormat
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

I'd like to use it in another project of mine :)

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/logging/format.go | 3 ++-
 shared/logging/log.go    | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/shared/logging/format.go b/shared/logging/format.go
index 93ddda62a..d60b7e481 100644
--- a/shared/logging/format.go
+++ b/shared/logging/format.go
@@ -67,7 +67,8 @@ func TerminalFormat() log.Format {
 	})
 }
 
-func logfmtFormat() log.Format {
+// LogfmtFormat return a formatter for a text log file
+func LogfmtFormat() log.Format {
 	return log.FormatFunc(func(r *log.Record) []byte {
 		common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg}
 		buf := &bytes.Buffer{}
diff --git a/shared/logging/log.go b/shared/logging/log.go
index 8d6bf2a1a..86023412e 100644
--- a/shared/logging/log.go
+++ b/shared/logging/log.go
@@ -19,7 +19,7 @@ func GetLogger(syslog string, logfile string, verbose bool, debug bool, customHa
 	var syshandler log.Handler
 
 	// Format handler
-	format := logfmtFormat()
+	format := LogfmtFormat()
 	if term.IsTty(os.Stderr.Fd()) {
 		format = TerminalFormat()
 	}

From 02b78f6f3458ca66537530f23c3fc800b9cc10a3 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 3 May 2017 14:09:24 +0200
Subject: [PATCH 0897/1193] test: make sure storage volume is mounted

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/migration.sh | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/test/suites/migration.sh b/test/suites/migration.sh
index 7d2e65db0..58d805625 100644
--- a/test/suites/migration.sh
+++ b/test/suites/migration.sh
@@ -32,10 +32,15 @@ test_migration() {
   lxc_remote move l1:nonlive l2:
   lxc_remote config show l2:nonlive/snap0 | grep user.tester | grep foo
 
+  # This line exists so that the container's storage volume is mounted when we
+  # perform existence check for various files.
+  lxc_remote start l2:nonlive
   # FIXME: make this backend agnostic
   if [ "${lxd2_backend}" != "lvm" ]; then
     [ -d "${LXD2_DIR}/containers/nonlive/rootfs" ]
   fi
+  lxc_remote stop l2:nonlive
+
   [ ! -d "${LXD_DIR}/containers/nonlive" ]
   # FIXME: make this backend agnostic
   if [ "${lxd2_backend}" = "dir" ]; then

From 896e61721ef78fb91fba63764a114d24aa6bdf4b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 3 May 2017 15:12:02 +0200
Subject: [PATCH 0898/1193] tree-wide: replace file Chmod() with os.Chmod()

The file chmod that go uses calls out to syscall.Fchmod() which is not
implemented for Windows. The os.Chmod() method call out to syscall.Chmod()
which seems to be implemented on all platforms if I read the go sources
correctly.

Closes #3275.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/shared/util.go b/shared/util.go
index 53f7003e0..22f122c67 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -544,7 +544,8 @@ func TextEditor(inPath string, inContent []byte) ([]byte, error) {
 			return []byte{}, err
 		}
 
-		if err = f.Chmod(0600); err != nil {
+		err = os.Chmod(f.Name(), 0600)
+		if err != nil {
 			f.Close()
 			os.Remove(f.Name())
 			return []byte{}, err

From d76dacfc996df8a9b34fd39612ca93d95ae40854 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 3 May 2017 16:36:12 +0200
Subject: [PATCH 0899/1193] Check if the image already exists.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/db_images.go | 38 ++++++++++++++++++++------------------
 lxd/db_test.go   | 35 +++++++++++++++++++++++++++++++++++
 lxd/images.go    |  8 ++++++++
 3 files changed, 63 insertions(+), 18 deletions(-)

diff --git a/lxd/db_images.go b/lxd/db_images.go
index 6f2466e11..3a7a01feb 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -105,8 +105,19 @@ func dbImageSourceGet(db *sql.DB, imageId int) (int, api.ImageSource, error) {
 
 }
 
-// dbImageGet gets an ImageBaseInfo object from the database.
-// The argument fingerprint will be queried with a LIKE query, means you can
+// Whether an image with the given fingerprint exists.
+func dbImageExists(db *sql.DB, fingerprint string) (bool, error) {
+	var exists bool
+	var err error
+	query := "SELECT COUNT(*) > 0 FROM images WHERE fingerprint=?"
+	inargs := []interface{}{fingerprint}
+	outargs := []interface{}{&exists}
+	err = dbQueryRowScan(db, query, inargs, outargs)
+	return exists, err
+}
+
+// dbImageGet gets an Image object from the database.
+// If strictMatching is false, The fingerprint argument will be queried with a LIKE query, means you can
 // pass a shortform and will get the full fingerprint.
 // There can never be more than one image with a given fingerprint, as it is
 // enforced by a UNIQUE constraint in the schema.
@@ -124,31 +135,22 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 		&image.Size, &image.Cached, &image.Public, &image.AutoUpdate, &arch,
 		&create, &expire, &used, &upload}
 
-	var query string
-
 	var inargs []interface{}
-	if strictMatching {
-		inargs = []interface{}{fingerprint}
-		query = `
+	query := `
         SELECT
             id, fingerprint, filename, size, cached, public, auto_update, architecture,
             creation_date, expiry_date, last_use_date, upload_date
-        FROM
-            images
-        WHERE fingerprint = ?`
+        FROM images`
+	if strictMatching {
+		inargs = []interface{}{fingerprint}
+		query += " WHERE fingerprint = ?"
 	} else {
 		inargs = []interface{}{fingerprint + "%"}
-		query = `
-        SELECT
-            id, fingerprint, filename, size, cached, public, auto_update, architecture,
-            creation_date, expiry_date, last_use_date, upload_date
-        FROM
-            images
-        WHERE fingerprint LIKE ?`
+		query += " WHERE fingerprint LIKE ?"
 	}
 
 	if public {
-		query = query + " AND public=1"
+		query += " AND public=1"
 	}
 
 	err = dbQueryRowScan(db, query, inargs, outfmt)
diff --git a/lxd/db_test.go b/lxd/db_test.go
index 513d7f08a..c34e2bd91 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -463,6 +463,41 @@ func Test_dbImageGet_for_missing_fingerprint(t *testing.T) {
 	}
 }
 
+func Test_dbImageExists_true(t *testing.T) {
+	var db *sql.DB
+	var err error
+
+	db = createTestDb(t)
+	defer db.Close()
+
+	exists, err := dbImageExists(db, "fingerprint")
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !exists {
+		t.Fatal("Image not found by fingerprint")
+	}
+
+}
+
+func Test_dbImageExists_false(t *testing.T) {
+	var db *sql.DB
+	var err error
+
+	db = createTestDb(t)
+	defer db.Close()
+
+	exists, err := dbImageExists(db, "foobar")
+
+	if err != nil {
+		t.Fatal(err)
+	}
+	if exists {
+		t.Fatal("Image should not have been found")
+	}
+}
+
 func Test_dbImageAliasGet_alias_exists(t *testing.T) {
 	var db *sql.DB
 	var err error
diff --git a/lxd/images.go b/lxd/images.go
index a0f4cbf0d..1238a499a 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -625,6 +625,14 @@ func getImgPostInfo(d *Daemon, r *http.Request, builddir string, post *os.File)
 		return nil, err
 	}
 
+	// Check if the image already exists
+	exists, err := dbImageExists(d.db, info.Fingerprint)
+	if err != nil {
+		return nil, err
+	}
+	if exists {
+		return nil, fmt.Errorf("Image with same fingerprint already exists")
+	}
 	// Create the database entry
 	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {

From abbb750925c355b4481ac8bc01c3848a8fcced21 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 5 May 2017 01:22:17 +0200
Subject: [PATCH 0900/1193] storage: move mount helpers to storage utils

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage.go       | 40 ----------------------------------------
 lxd/storage_utils.go | 45 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 40 deletions(-)
 create mode 100644 lxd/storage_utils.go

diff --git a/lxd/storage.go b/lxd/storage.go
index 8b41c7729..3629bdf4f 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -7,7 +7,6 @@ import (
 	"path/filepath"
 	"reflect"
 	"syscall"
-	"time"
 
 	"github.com/gorilla/websocket"
 
@@ -762,42 +761,3 @@ func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, c
 
 	return nil
 }
-
-// Useful functions for unreliable backends
-func tryMount(src string, dst string, fs string, flags uintptr, options string) error {
-	var err error
-
-	for i := 0; i < 20; i++ {
-		err = syscall.Mount(src, dst, fs, flags, options)
-		if err == nil {
-			break
-		}
-
-		time.Sleep(500 * time.Millisecond)
-	}
-
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func tryUnmount(path string, flags int) error {
-	var err error
-
-	for i := 0; i < 20; i++ {
-		err = syscall.Unmount(path, flags)
-		if err == nil {
-			break
-		}
-
-		time.Sleep(500 * time.Millisecond)
-	}
-
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
diff --git a/lxd/storage_utils.go b/lxd/storage_utils.go
new file mode 100644
index 000000000..212e9a365
--- /dev/null
+++ b/lxd/storage_utils.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+	"syscall"
+	"time"
+)
+
+// Useful functions for unreliable backends
+func tryMount(src string, dst string, fs string, flags uintptr, options string) error {
+	var err error
+
+	for i := 0; i < 20; i++ {
+		err = syscall.Mount(src, dst, fs, flags, options)
+		if err == nil {
+			break
+		}
+
+		time.Sleep(500 * time.Millisecond)
+	}
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func tryUnmount(path string, flags int) error {
+	var err error
+
+	for i := 0; i < 20; i++ {
+		err = syscall.Unmount(path, flags)
+		if err == nil {
+			break
+		}
+
+		time.Sleep(500 * time.Millisecond)
+	}
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}

From 959375203e2416addf854a06ed385034f2b7edaf Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 4 May 2017 09:27:04 +0200
Subject: [PATCH 0901/1193] Add a testify test suite for db tests, rework
 existing tests.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/db_test.go | 482 +++++++++++++++++----------------------------------------
 1 file changed, 145 insertions(+), 337 deletions(-)

diff --git a/lxd/db_test.go b/lxd/db_test.go
index c34e2bd91..701d9c6a4 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -6,6 +6,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/suite"
+
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -27,181 +29,139 @@ const DB_FIXTURES string = `
     INSERT INTO profiles_devices_config (profile_device_id, key, value) VALUES (3, 'devicekey', 'devicevalue');
     `
 
-//  This Helper will initialize a test in-memory DB.
-func createTestDb(t *testing.T) (db *sql.DB) {
+type dbTestSuite struct {
+	suite.Suite
+
+	db *sql.DB
+}
+
+func (s *dbTestSuite) SetupTest() {
+	s.db = s.CreateTestDb()
+}
+
+func (s *dbTestSuite) TearDownTest() {
+	s.db.Close()
+}
+
+// Initialize a test in-memory DB.
+func (s *dbTestSuite) CreateTestDb() (db *sql.DB) {
 	// Setup logging if main() hasn't been called/when testing
 	if logger.Log == nil {
 		var err error
 		logger.Log, err = logging.GetLogger("", "", true, true, nil)
-		if err != nil {
-			t.Fatal(err)
-		}
+		s.Nil(err)
 	}
 
 	var err error
 	d := &Daemon{MockMode: true}
 	err = initializeDbObject(d, ":memory:")
+	s.Nil(err)
 	db = d.db
 
-	if err != nil {
-		t.Fatal(err)
-	}
-
 	_, err = db.Exec(DB_FIXTURES)
-	if err != nil {
-		t.Fatal(err)
-	}
+	s.Nil(err)
 	return // db is a named output param
 }
 
-func Test_deleting_a_container_cascades_on_related_tables(t *testing.T) {
-	var db *sql.DB
+func TestDBTestSuite(t *testing.T) {
+	suite.Run(t, new(dbTestSuite))
+}
+
+func (s *dbTestSuite) Test_deleting_a_container_cascades_on_related_tables() {
 	var err error
 	var count int
 	var statements string
 
-	// Insert a container and a related profile.
-	db = createTestDb(t)
-	defer db.Close()
-
 	// Drop the container we just created.
 	statements = `DELETE FROM containers WHERE name = 'thename';`
 
-	_, err = db.Exec(statements)
-	if err != nil {
-		t.Errorf("Error deleting container! %s", err)
-	}
+	_, err = s.db.Exec(statements)
+	s.Nil(err, "Error deleting container!")
 
 	// Make sure there are 0 container_profiles entries left.
 	statements = `SELECT count(*) FROM containers_profiles;`
-	err = db.QueryRow(statements).Scan(&count)
-
-	if count != 0 {
-		t.Errorf("Deleting a container didn't delete the profile association! There are %d left", count)
-	}
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting a container didn't delete the profile association!")
 
 	// Make sure there are 0 containers_config entries left.
 	statements = `SELECT count(*) FROM containers_config;`
-	err = db.QueryRow(statements).Scan(&count)
-
-	if count != 0 {
-		t.Errorf("Deleting a container didn't delete the associated container_config! There are %d left", count)
-	}
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting a container didn't delete the associated container_config!")
 
 	// Make sure there are 0 containers_devices entries left.
 	statements = `SELECT count(*) FROM containers_devices;`
-	err = db.QueryRow(statements).Scan(&count)
-
-	if count != 0 {
-		t.Errorf("Deleting a container didn't delete the associated container_devices! There are %d left", count)
-	}
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting a container didn't delete the associated container_devices!")
 
 	// Make sure there are 0 containers_devices_config entries left.
 	statements = `SELECT count(*) FROM containers_devices_config;`
-	err = db.QueryRow(statements).Scan(&count)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if count != 0 {
-		t.Errorf("Deleting a container didn't delete the associated container_devices_config! There are %d left", count)
-	}
-
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting a container didn't delete the associated container_devices_config!")
 }
 
-func Test_deleting_a_profile_cascades_on_related_tables(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_deleting_a_profile_cascades_on_related_tables() {
 	var err error
 	var count int
 	var statements string
 
-	// Insert a container and a related profile.
-	db = createTestDb(t)
-	defer db.Close()
-
 	// Drop the profile we just created.
 	statements = `DELETE FROM profiles WHERE name = 'theprofile';`
 
-	_, err = db.Exec(statements)
-	if err != nil {
-		t.Errorf("Error deleting profile! %s", err)
-	}
+	_, err = s.db.Exec(statements)
+	s.Nil(err)
 
 	// Make sure there are 0 container_profiles entries left.
 	statements = `SELECT count(*) FROM containers_profiles WHERE profile_id = 3;`
-	err = db.QueryRow(statements).Scan(&count)
-
-	if count != 0 {
-		t.Errorf("Deleting a profile didn't delete the container association! There are %d left", count)
-	}
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Equal(count, 0, "Deleting a profile didn't delete the container association!")
 
 	// Make sure there are 0 profiles_devices entries left.
 	statements = `SELECT count(*) FROM profiles_devices WHERE profile_id == 3;`
-	err = db.QueryRow(statements).Scan(&count)
-
-	if count != 0 {
-		t.Errorf("Deleting a profile didn't delete the related profiles_devices! There are %d left", count)
-	}
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting a profile didn't delete the related profiles_devices!")
 
 	// Make sure there are 0 profiles_config entries left.
 	statements = `SELECT count(*) FROM profiles_config WHERE profile_id == 3;`
-	err = db.QueryRow(statements).Scan(&count)
-
-	if count != 0 {
-		t.Errorf("Deleting a profile didn't delete the related profiles_config! There are %d left", count)
-	}
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting a profile didn't delete the related profiles_config! There are %d left")
 
 	// Make sure there are 0 profiles_devices_config entries left.
 	statements = `SELECT count(*) FROM profiles_devices_config WHERE profile_device_id == 4;`
-	err = db.QueryRow(statements).Scan(&count)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if count != 0 {
-		t.Errorf("Deleting a profile didn't delete the related profiles_devices_config! There are %d left", count)
-	}
-
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting a profile didn't delete the related profiles_devices_config!")
 }
 
-func Test_deleting_an_image_cascades_on_related_tables(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_deleting_an_image_cascades_on_related_tables() {
 	var err error
 	var count int
 	var statements string
 
-	db = createTestDb(t)
-	defer db.Close()
-
 	// Drop the image we just created.
 	statements = `DELETE FROM images;`
 
-	_, err = db.Exec(statements)
-	if err != nil {
-		t.Errorf("Error deleting image! %s", err)
-	}
-
+	_, err = s.db.Exec(statements)
+	s.Nil(err)
 	// Make sure there are 0 images_aliases entries left.
 	statements = `SELECT count(*) FROM images_aliases;`
-	err = db.QueryRow(statements).Scan(&count)
-
-	if count != 0 {
-		t.Errorf("Deleting an image didn't delete the image alias association! There are %d left", count)
-	}
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting an image didn't delete the image alias association!")
 
 	// Make sure there are 0 images_properties entries left.
 	statements = `SELECT count(*) FROM images_properties;`
-	err = db.QueryRow(statements).Scan(&count)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if count != 0 {
-		t.Errorf("Deleting an image didn't delete the related images_properties! There are %d left", count)
-	}
+	err = s.db.QueryRow(statements).Scan(&count)
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting an image didn't delete the related images_properties!")
 }
 
-func Test_initializing_db_is_indempotent(t *testing.T) {
+func (s *dbTestSuite) Test_initializing_db_is_idempotent() {
 	var db *sql.DB
 	var err error
 
@@ -209,32 +169,24 @@ func Test_initializing_db_is_indempotent(t *testing.T) {
 	d := &Daemon{MockMode: true}
 	err = initializeDbObject(d, ":memory:")
 	db = d.db
-
 	defer db.Close()
 
 	// Let's call it a second time.
 	err = createDb(db)
-	if err != nil {
-		t.Errorf("The database schema is not indempotent, err='%s'", err)
-	}
+	s.Nil(err)
 }
 
-func Test_get_schema_returns_0_on_uninitialized_db(t *testing.T) {
+func (s *dbTestSuite) Test_get_schema_returns_0_on_uninitialized_db() {
 	var db *sql.DB
 	var err error
 
 	db, err = sql.Open("sqlite3", ":memory:")
-	if err != nil {
-		t.Error(err)
-	}
+	s.Nil(err)
 	result := dbGetSchema(db)
-
-	if result != 0 {
-		t.Error("getSchema should return 0 on uninitialized db!")
-	}
+	s.Equal(0, result, "getSchema should return 0 on uninitialized db!")
 }
 
-func Test_running_dbUpdateFromV6_adds_on_delete_cascade(t *testing.T) {
+func (s *dbTestSuite) Test_running_dbUpdateFromV6_adds_on_delete_cascade() {
 	// Upgrading the database schema with updateFromV6 adds ON DELETE CASCADE
 	// to sqlite tables that require it, and conserve the data.
 
@@ -268,42 +220,32 @@ INSERT INTO containers (name, architecture, type) VALUES ('thename', 1, 1);
 INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 'thevalue');`
 
 	_, err = d.db.Exec(statements)
-	if err != nil {
-		t.Error(err)
-	}
+	s.Nil(err)
 
 	// Run the upgrade from V6 code
 	err = dbUpdateFromV6(5, 6, d)
+	s.Nil(err)
 
 	// Make sure the inserted data is still there.
 	statements = `SELECT count(*) FROM containers_config;`
 	err = d.db.QueryRow(statements).Scan(&count)
-
-	if count != 1 {
-		t.Fatalf("There should be exactly one entry in containers_config! There are %d.", count)
-	}
+	s.Nil(err)
+	s.Equal(count, 1, "There should be exactly one entry in containers_config!")
 
 	// Drop the container.
 	statements = `DELETE FROM containers WHERE name = 'thename';`
 
 	_, err = d.db.Exec(statements)
-	if err != nil {
-		t.Errorf("Error deleting container! %s", err)
-	}
+	s.Nil(err)
 
 	// Make sure there are 0 container_profiles entries left.
 	statements = `SELECT count(*) FROM containers_profiles;`
 	err = d.db.QueryRow(statements).Scan(&count)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if count != 0 {
-		t.Errorf("Deleting a container didn't delete the profile association! There are %d left", count)
-	}
+	s.Nil(err)
+	s.Equal(count, 0, "Deleting a container didn't delete the profile association!")
 }
 
-func Test_run_database_upgrades_with_some_foreign_keys_inconsistencies(t *testing.T) {
+func (s *dbTestSuite) Test_run_database_upgrades_with_some_foreign_keys_inconsistencies() {
 	var db *sql.DB
 	var err error
 	var count int
@@ -311,10 +253,7 @@ func Test_run_database_upgrades_with_some_foreign_keys_inconsistencies(t *testin
 
 	db, err = sql.Open("sqlite3", ":memory:")
 	defer db.Close()
-
-	if err != nil {
-		t.Fatal(err)
-	}
+	s.Nil(err)
 
 	// This schema is a part of schema rev 1.
 	statements = `
@@ -372,17 +311,13 @@ INSERT INTO containers (name, architecture, type) VALUES ('thename', 1, 1);
 INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 'thevalue');`
 
 	_, err = db.Exec(statements)
-	if err != nil {
-		t.Fatal("Error creating schema!")
-	}
+	s.Nil(err)
 
 	// Now that we have a consistent schema, let's remove the container entry
 	// *without* the ON DELETE CASCADE in place.
 	statements = `DELETE FROM containers;`
 	_, err = db.Exec(statements)
-	if err != nil {
-		t.Fatal("Error truncating the container table!")
-	}
+	s.Nil(err)
 
 	// The "foreign key" on containers_config now points to nothing.
 	// Let's run the schema upgrades.
@@ -391,292 +326,165 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	daemonConfigInit(db)
 
 	err = dbUpdatesApplyAll(d)
-	if err != nil {
-		t.Error("Error upgrading database schema!")
-		t.Fatal(err)
-	}
+	s.Nil(err)
 
 	result := dbGetSchema(db)
-	if result != dbGetLatestSchema() {
-		t.Fatal(fmt.Sprintf("The schema is not at the latest version after update! Found: %d, should be: %d", result, dbGetLatestSchema()))
-	}
+	s.Equal(result, dbGetLatestSchema(), "The schema is not at the latest version after update!")
 
 	// Make sure there are 0 containers_config entries left.
 	statements = `SELECT count(*) FROM containers_config;`
 	err = db.QueryRow(statements).Scan(&count)
-	if err != nil {
-		t.Error(err)
-	}
-
-	if count != 0 {
-		t.Fatal("updateDb did not delete orphaned child entries after adding ON DELETE CASCADE!")
-	}
-
+	s.Nil(err)
+	s.Equal(count, 0, "updateDb did not delete orphaned child entries after adding ON DELETE CASCADE!")
 }
 
-func Test_dbImageGet_finds_image_for_fingerprint(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbImageGet_finds_image_for_fingerprint() {
 	var err error
 	var result *api.Image
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	_, result, err = dbImageGet(db, "fingerprint", false, false)
-
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if result == nil {
-		t.Fatal("No image returned!")
-	}
-
-	if result.Filename != "filename" {
-		t.Fatal("Filename should be set.")
-	}
-
-	if result.CreatedAt.UTC() != time.Unix(1431547174, 0).UTC() {
-		t.Fatal(fmt.Sprintf("%s != %s", result.CreatedAt, time.Unix(1431547174, 0)))
-	}
-
-	if result.ExpiresAt.UTC() != time.Unix(1431547175, 0).UTC() { // It was short lived
-		t.Fatal(fmt.Sprintf("%s != %s", result.ExpiresAt, time.Unix(1431547175, 0)))
-	}
-
-	if result.UploadedAt.UTC() != time.Unix(1431547176, 0).UTC() {
-		t.Fatal(fmt.Sprintf("%s != %s", result.UploadedAt, time.Unix(1431547176, 0)))
-	}
+	_, result, err = dbImageGet(s.db, "fingerprint", false, false)
+	s.Nil(err)
+	s.NotNil(result)
+	s.Equal(result.Filename, "filename")
+	s.Equal(result.CreatedAt.UTC(), time.Unix(1431547174, 0).UTC())
+	s.Equal(result.ExpiresAt.UTC(), time.Unix(1431547175, 0).UTC())
+	s.Equal(result.UploadedAt.UTC(), time.Unix(1431547176, 0).UTC())
 }
 
-func Test_dbImageGet_for_missing_fingerprint(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbImageGet_for_missing_fingerprint() {
 	var err error
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	_, _, err = dbImageGet(db, "unknown", false, false)
-
-	if err != sql.ErrNoRows {
-		t.Fatal("Wrong err type returned")
-	}
+	_, _, err = dbImageGet(s.db, "unknown", false, false)
+	s.Equal(err, sql.ErrNoRows)
 }
 
-func Test_dbImageExists_true(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbImageExists_true() {
 	var err error
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	exists, err := dbImageExists(db, "fingerprint")
-
-	if err != nil {
-		t.Fatal(err)
-	}
-	if !exists {
-		t.Fatal("Image not found by fingerprint")
-	}
-
+	exists, err := dbImageExists(s.db, "fingerprint")
+	s.Nil(err)
+	s.True(exists)
 }
 
-func Test_dbImageExists_false(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbImageExists_false() {
 	var err error
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	exists, err := dbImageExists(db, "foobar")
-
-	if err != nil {
-		t.Fatal(err)
-	}
-	if exists {
-		t.Fatal("Image should not have been found")
-	}
+	exists, err := dbImageExists(s.db, "foobar")
+	s.Nil(err)
+	s.False(exists)
 }
 
-func Test_dbImageAliasGet_alias_exists(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbImageAliasGet_alias_exists() {
 	var err error
-	var result string
-
-	db = createTestDb(t)
-	defer db.Close()
-
-	_, alias, err := dbImageAliasGet(db, "somealias", true)
-	result = alias.Target
-
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	if result != "fingerprint" {
-		t.Fatal("Fingerprint is not the expected fingerprint!")
-	}
 
+	_, alias, err := dbImageAliasGet(s.db, "somealias", true)
+	s.Nil(err)
+	s.Equal(alias.Target, "fingerprint")
 }
 
-func Test_dbImageAliasGet_alias_does_not_exists(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbImageAliasGet_alias_does_not_exists() {
 	var err error
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	_, _, err = dbImageAliasGet(db, "whatever", true)
-
-	if err != NoSuchObjectError {
-		t.Fatal("Error should be NoSuchObjectError")
-	}
+	_, _, err = dbImageAliasGet(s.db, "whatever", true)
+	s.Equal(err, NoSuchObjectError)
 }
 
-func Test_dbImageAliasAdd(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbImageAliasAdd() {
 	var err error
-	var result string
-
-	db = createTestDb(t)
-	defer db.Close()
-
-	err = dbImageAliasAdd(db, "Chaosphere", 1, "Someone will like the name")
-	if err != nil {
-		t.Fatal("Error inserting Image alias.")
-	}
 
-	_, alias, err := dbImageAliasGet(db, "Chaosphere", true)
-	if err != nil {
-		t.Fatal(err)
-	}
-	result = alias.Target
+	err = dbImageAliasAdd(s.db, "Chaosphere", 1, "Someone will like the name")
+	s.Nil(err)
 
-	if result != "fingerprint" {
-		t.Fatal("Couldn't retrieve newly created alias.")
-	}
+	_, alias, err := dbImageAliasGet(s.db, "Chaosphere", true)
+	s.Nil(err)
+	s.Equal(alias.Target, "fingerprint")
 }
 
-func Test_dbContainerConfig(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbContainerConfig() {
 	var err error
 	var result map[string]string
 	var expected map[string]string
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	_, err = db.Exec("INSERT INTO containers_config (container_id, key, value) VALUES (1, 'something', 'something else');")
+	_, err = s.db.Exec("INSERT INTO containers_config (container_id, key, value) VALUES (1, 'something', 'something else');")
+	s.Nil(err)
 
-	result, err = dbContainerConfig(db, 1)
-	if err != nil {
-		t.Fatal(err)
-	}
+	result, err = dbContainerConfig(s.db, 1)
+	s.Nil(err)
 
 	expected = map[string]string{"thekey": "thevalue", "something": "something else"}
 
 	for key, value := range expected {
-		if result[key] != value {
-			t.Errorf("Mismatching value for key %s: %s != %s", key, result[key], value)
-		}
+		s.Equal(result[key], value,
+			fmt.Sprintf("Mismatching value for key %s: %s != %s", key, result[key], value))
 	}
 }
 
-func Test_dbProfileConfig(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbProfileConfig() {
 	var err error
 	var result map[string]string
 	var expected map[string]string
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	_, err = db.Exec("INSERT INTO profiles_config (profile_id, key, value) VALUES (3, 'something', 'something else');")
+	_, err = s.db.Exec("INSERT INTO profiles_config (profile_id, key, value) VALUES (3, 'something', 'something else');")
+	s.Nil(err)
 
-	result, err = dbProfileConfig(db, "theprofile")
-	if err != nil {
-		t.Fatal(err)
-	}
+	result, err = dbProfileConfig(s.db, "theprofile")
+	s.Nil(err)
 
 	expected = map[string]string{"thekey": "thevalue", "something": "something else"}
 
 	for key, value := range expected {
-		if result[key] != value {
-			t.Errorf("Mismatching value for key %s: %s != %s", key, result[key], value)
-		}
+		s.Equal(result[key], value,
+			fmt.Sprintf("Mismatching value for key %s: %s != %s", key, result[key], value))
 	}
 }
 
-func Test_dbContainerProfiles(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbContainerProfiles() {
 	var err error
 	var result []string
 	var expected []string
 
-	db = createTestDb(t)
-	defer db.Close()
-
 	expected = []string{"theprofile"}
-	result, err = dbContainerProfiles(db, 1)
-	if err != nil {
-		t.Fatal(err)
-	}
+	result, err = dbContainerProfiles(s.db, 1)
+	s.Nil(err)
 
 	for i := range expected {
-		if expected[i] != result[i] {
-			t.Fatal(fmt.Sprintf("Mismatching contents for profile list: %s != %s", result[i], expected[i]))
-		}
+		s.Equal(expected[i], result[i],
+			fmt.Sprintf("Mismatching contents for profile list: %s != %s", result[i], expected[i]))
 	}
 }
 
-func Test_dbDevices_profiles(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbDevices_profiles() {
 	var err error
 	var result types.Devices
 	var subresult types.Device
 	var expected types.Device
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	result, err = dbDevices(db, "theprofile", true)
-	if err != nil {
-		t.Fatal(err)
-	}
+	result, err = dbDevices(s.db, "theprofile", true)
+	s.Nil(err)
 
 	expected = types.Device{"type": "nic", "devicekey": "devicevalue"}
 	subresult = result["devicename"]
 
 	for key, value := range expected {
-		if subresult[key] != value {
-			t.Errorf("Mismatching value for key %s: %v != %v", key, subresult[key], value)
-		}
+		s.Equal(subresult[key], value,
+			fmt.Sprintf("Mismatching value for key %s: %v != %v", key, subresult[key], value))
 	}
-
 }
 
-func Test_dbDevices_containers(t *testing.T) {
-	var db *sql.DB
+func (s *dbTestSuite) Test_dbDevices_containers() {
 	var err error
 	var result types.Devices
 	var subresult types.Device
 	var expected types.Device
 
-	db = createTestDb(t)
-	defer db.Close()
-
-	result, err = dbDevices(db, "thename", false)
-	if err != nil {
-		t.Fatal(err)
-	}
+	result, err = dbDevices(s.db, "thename", false)
+	s.Nil(err)
 
 	expected = types.Device{"type": "nic", "configkey": "configvalue"}
 	subresult = result["somename"]
 
 	for key, value := range expected {
-		if subresult[key] != value {
-			t.Errorf("Mismatching value for key %s: %s != %s", key, subresult[key], value)
-		}
+		s.Equal(subresult[key], value,
+			fmt.Sprintf("Mismatching value for key %s: %s != %s", key, subresult[key], value))
 	}
-
 }

From ed3e9f67fd2c06e1a0005a73947ac7e4185b58a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 8 May 2017 13:53:46 -0400
Subject: [PATCH 0902/1193] client: Fill the server fingerprint if missing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_server.go | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/client/lxd_server.go b/client/lxd_server.go
index b358e22de..2e42f3944 100644
--- a/client/lxd_server.go
+++ b/client/lxd_server.go
@@ -1,6 +1,7 @@
 package lxd
 
 import (
+	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
 
@@ -16,6 +17,15 @@ func (r *ProtocolLXD) GetServer() (*api.Server, string, error) {
 		return nil, "", err
 	}
 
+	// Fill in certificate fingerprint if not provided
+	if server.Environment.CertificateFingerprint == "" && server.Environment.Certificate != "" {
+		var err error
+		server.Environment.CertificateFingerprint, err = shared.CertFingerprintStr(server.Environment.Certificate)
+		if err != nil {
+			return nil, "", err
+		}
+	}
+
 	// Add the value to the cache
 	r.server = &server
 

From 50db8ac2d222ee0534ecdf2dadf5bd80f31742b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 8 May 2017 13:54:00 -0400
Subject: [PATCH 0903/1193] lxc/remote: Show the fingerprint as string not hex
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3293

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/remote.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index d7ceb8311..01f518f7c 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -206,7 +206,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 		if !acceptCert {
 			digest := shared.CertFingerprint(certificate)
 
-			fmt.Printf(i18n.G("Certificate fingerprint: %x")+"\n", digest)
+			fmt.Printf(i18n.G("Certificate fingerprint: %s")+"\n", digest)
 			fmt.Printf(i18n.G("ok (y/n)?") + " ")
 			line, err := shared.ReadStdin()
 			if err != nil {

From c91f9d039cba6affd639cea4d8c37ff4cf2f854d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 8 May 2017 14:00:50 -0400
Subject: [PATCH 0904/1193] daemon: Set ServerFingerprint
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We somehow forgot to do that a long time ago. Our client has always been
computing it itself instead, lets just set it to save some hashing time
on the client side.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/api_1.0.go | 32 +++++++++++++++++++-------------
 1 file changed, 19 insertions(+), 13 deletions(-)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 4f26acda4..3a3b568c4 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -115,8 +115,13 @@ func api10Get(d *Daemon, r *http.Request) Response {
 	}
 
 	var certificate string
+	var certificateFingerprint string
 	if len(d.tlsConfig.Certificates) != 0 {
 		certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: d.tlsConfig.Certificates[0].Certificate[0]}))
+		certificateFingerprint, err = shared.CertFingerprintStr(certificate)
+		if err != nil {
+			return InternalError(err)
+		}
 	}
 
 	architectures := []string{}
@@ -130,19 +135,20 @@ func api10Get(d *Daemon, r *http.Request) Response {
 	}
 
 	env := api.ServerEnvironment{
-		Addresses:          addresses,
-		Architectures:      architectures,
-		Certificate:        certificate,
-		Driver:             "lxc",
-		DriverVersion:      lxc.Version(),
-		Kernel:             kernel,
-		KernelArchitecture: kernelArchitecture,
-		KernelVersion:      kernelVersion,
-		Storage:            d.Storage.GetStorageTypeName(),
-		StorageVersion:     d.Storage.GetStorageTypeVersion(),
-		Server:             "lxd",
-		ServerPid:          os.Getpid(),
-		ServerVersion:      version.Version}
+		Addresses:              addresses,
+		Architectures:          architectures,
+		Certificate:            certificate,
+		CertificateFingerprint: certificateFingerprint,
+		Driver:                 "lxc",
+		DriverVersion:          lxc.Version(),
+		Kernel:                 kernel,
+		KernelArchitecture:     kernelArchitecture,
+		KernelVersion:          kernelVersion,
+		Storage:                d.Storage.GetStorageTypeName(),
+		StorageVersion:         d.Storage.GetStorageTypeVersion(),
+		Server:                 "lxd",
+		ServerPid:              os.Getpid(),
+		ServerVersion:          version.Version}
 
 	fullSrv := api.Server{ServerUntrusted: srv}
 	fullSrv.Environment = env

From 93c970e0ca74b23bd1f76c5ded920765fedefb64 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 9 May 2017 18:38:25 +0200
Subject: [PATCH 0905/1193] Save image source certificate and pass it to the
 download.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/daemon_images.go | 2 +-
 lxd/images.go        | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index d9a909e4a..1394819ec 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -454,7 +454,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			return nil, err
 		}
 
-		err = dbImageSourceInsert(d.db, id, server, protocol, "", alias)
+		err = dbImageSourceInsert(d.db, id, server, protocol, certificate, alias)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/images.go b/lxd/images.go
index 1238a499a..c24f942d6 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -860,7 +860,7 @@ func autoUpdateImages(d *Daemon) {
 
 		logger.Debug("Processing image", log.Ctx{"fp": fp, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias})
 
-		newInfo, err := d.ImageDownload(nil, source.Server, source.Protocol, "", "", source.Alias, false, true)
+		newInfo, err := d.ImageDownload(nil, source.Server, source.Protocol, source.Certificate, "", source.Alias, false, true)
 		if err != nil {
 			logger.Error("Failed to update the image", log.Ctx{"err": err, "fp": fp})
 			continue

From 60f102bffab875de6a35a66cf8171d1fc055ee2b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 10 May 2017 00:57:11 +0200
Subject: [PATCH 0906/1193] Makefile: add update-po to i18n target

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 05e5f0ba1..4bc77ce90 100644
--- a/Makefile
+++ b/Makefile
@@ -80,7 +80,7 @@ dist:
 	rm -Rf $(TMP)
 
 .PHONY: i18n update-po update-pot build-mo static-analysis
-i18n: update-pot
+i18n: update-po update-pot
 
 po/%.mo: po/%.po
 	msgfmt --statistics -o $@ $<

From fa994d5fff109ea97b2b7d26214a5f4f88eaa0a4 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Fri, 5 May 2017 12:49:38 +0200
Subject: [PATCH 0907/1193] Split autoUpdateImage function.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/images.go | 95 ++++++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 58 insertions(+), 37 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index c24f942d6..dcda5560a 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -842,10 +842,10 @@ func autoUpdateImages(d *Daemon) {
 		return
 	}
 
-	for _, fp := range images {
-		id, info, err := dbImageGet(d.db, fp, false, true)
+	for _, fingerprint := range images {
+		id, info, err := dbImageGet(d.db, fingerprint, false, true)
 		if err != nil {
-			logger.Error("Error loading image", log.Ctx{"err": err, "fp": fp})
+			logger.Error("Error loading image", log.Ctx{"err": err, "fp": fingerprint})
 			continue
 		}
 
@@ -853,50 +853,71 @@ func autoUpdateImages(d *Daemon) {
 			continue
 		}
 
-		_, source, err := dbImageSourceGet(d.db, id)
-		if err != nil {
-			continue
-		}
+		autoUpdateImage(d, nil, fingerprint, id, info)
+	}
 
-		logger.Debug("Processing image", log.Ctx{"fp": fp, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias})
+	logger.Infof("Done updating images")
+}
 
-		newInfo, err := d.ImageDownload(nil, source.Server, source.Protocol, source.Certificate, "", source.Alias, false, true)
-		if err != nil {
-			logger.Error("Failed to update the image", log.Ctx{"err": err, "fp": fp})
-			continue
-		}
+// Update a single image.  The operation can be nil, if no progress tracking is needed.
+// Returns whether the image has been updated.
+func autoUpdateImage(d *Daemon, op *operation, fingerprint string, id int, info *api.Image) error {
+	_, source, err := dbImageSourceGet(d.db, id)
+	if err != nil {
+		logger.Error("Error getting source image", log.Ctx{"err": err, "fp": fingerprint})
+		return err
+	}
 
-		hash := newInfo.Fingerprint
-		if hash == fp {
-			logger.Debug("Already up to date", log.Ctx{"fp": fp})
-			continue
-		}
+	logger.Debug("Processing image", log.Ctx{"fp": fingerprint, "server": source.Server, "protocol": source.Protocol, "alias": source.Alias})
 
-		newId, _, err := dbImageGet(d.db, hash, false, true)
-		if err != nil {
-			logger.Error("Error loading image", log.Ctx{"err": err, "fp": hash})
-			continue
+	// Set operation metadata to indicate whether a refresh happened
+	setRefreshResult := func(result bool) {
+		if op == nil {
+			return
 		}
 
-		err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedAt)
-		if err != nil {
-			logger.Error("Error setting last use date", log.Ctx{"err": err, "fp": hash})
-			continue
-		}
+		metadata := map[string]interface{}{"refreshed": result}
+		op.UpdateMetadata(metadata)
+	}
 
-		err = dbImageAliasesMove(d.db, id, newId)
-		if err != nil {
-			logger.Error("Error moving aliases", log.Ctx{"err": err, "fp": hash})
-			continue
-		}
+	newInfo, err := d.ImageDownload(op, source.Server, source.Protocol, source.Certificate, "", source.Alias, false, true)
+	if err != nil {
+		logger.Error("Failed to update the image", log.Ctx{"err": err, "fp": fingerprint})
+		return err
+	}
 
-		err = doDeleteImage(d, fp)
-		if err != nil {
-			logger.Error("Error deleting image", log.Ctx{"err": err, "fp": fp})
-		}
+	// Image didn't change, nothing to do.
+	hash := newInfo.Fingerprint
+	if hash == fingerprint {
+		setRefreshResult(false)
+		return nil
 	}
 
-	logger.Infof("Done updating images")
+	newId, _, err := dbImageGet(d.db, hash, false, true)
+	if err != nil {
+		logger.Error("Error loading image", log.Ctx{"err": err, "fp": hash})
+		return err
+	}
+
+	err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedAt)
+	if err != nil {
+		logger.Error("Error setting last use date", log.Ctx{"err": err, "fp": hash})
+		return err
+	}
+
+	err = dbImageAliasesMove(d.db, id, newId)
+	if err != nil {
+		logger.Error("Error moving aliases", log.Ctx{"err": err, "fp": hash})
+		return err
+	}
+
+	err = doDeleteImage(d, fingerprint)
+	if err != nil {
+		logger.Error("Error deleting image", log.Ctx{"err": err, "fp": fingerprint})
+	}
+
+	setRefreshResult(true)
+	return nil
 }
 
 func pruneExpiredImages(d *Daemon) {

From 936b510271d75b3e76c7e19f11022dadd99e48db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Apr 2017 17:10:36 -0400
Subject: [PATCH 0908/1193] i18n: Update translations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 112 +++++++++++++++++++++++++++++------------------------------
 po/fr.po   | 112 +++++++++++++++++++++++++++++------------------------------
 po/ja.po   | 114 ++++++++++++++++++++++++++++++-------------------------------
 po/lxd.pot | 112 +++++++++++++++++++++++++++++------------------------------
 4 files changed, 225 insertions(+), 225 deletions(-)

diff --git a/po/de.po b/po/de.po
index 254810567..c9d061caf 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-18 01:16-0400\n"
+"POT-Creation-Date: 2017-05-10 00:17-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -133,15 +133,15 @@ msgstr ""
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:405
+#: lxc/list.go:409
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:65
+#: lxc/remote.go:66
 msgid "Accept certificate"
 msgstr "Akzeptiere Zertifikat"
 
-#: lxc/remote.go:258
+#: lxc/remote.go:259
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
@@ -173,7 +173,7 @@ msgstr ""
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:406
+#: lxc/list.go:410
 msgid "CREATED AT"
 msgstr ""
 
@@ -191,12 +191,12 @@ msgstr ""
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:208
+#: lxc/remote.go:209
 #, fuzzy, c-format
-msgid "Certificate fingerprint: %x"
+msgid "Certificate fingerprint: %s"
 msgstr "Fingerabdruck des Zertifikats: % x\n"
 
-#: lxc/remote.go:281
+#: lxc/remote.go:282
 msgid "Client certificate stored at server: "
 msgstr "Gespeichertes Nutzerzertifikat auf dem Server: "
 
@@ -218,7 +218,7 @@ msgstr "kann nicht zum selben Container Namen kopieren"
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
 
-#: lxc/main.go:32
+#: lxc/main.go:33
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
@@ -245,7 +245,7 @@ msgstr "Kopiere Aliasse von der Quelle"
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:223
+#: lxc/remote.go:224
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
@@ -282,7 +282,7 @@ msgstr "Gerät %s wurde von %s entfernt\n"
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:491
+#: lxc/list.go:495
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -290,12 +290,12 @@ msgstr ""
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:44
+#: lxc/main.go:45
 #, fuzzy
 msgid "Enable debug mode"
 msgstr "Aktiviert Debug Modus"
 
-#: lxc/main.go:43
+#: lxc/main.go:44
 #, fuzzy
 msgid "Enable verbose mode"
 msgstr "Aktiviert ausführliche Ausgabe"
@@ -357,7 +357,7 @@ msgstr "Herunterfahren des Containers erzwingen."
 msgid "Force the removal of stopped containers"
 msgstr ""
 
-#: lxc/main.go:45
+#: lxc/main.go:46
 msgid "Force using the local unix socket"
 msgstr ""
 
@@ -370,11 +370,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Generiere Nutzerzertifikat. Dies kann wenige Minuten dauern...\n"
 
-#: lxc/list.go:403
+#: lxc/list.go:407
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:404
+#: lxc/list.go:408
 msgid "IPV6"
 msgstr ""
 
@@ -387,7 +387,7 @@ msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:46
+#: lxc/main.go:47
 msgid "Ignore aliases when determining what command to run"
 msgstr ""
 
@@ -410,7 +410,7 @@ msgstr "Abbild mit Fingerabdruck %s importiert\n"
 msgid "Importing the image: %s"
 msgstr ""
 
-#: lxc/remote.go:134
+#: lxc/remote.go:135
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
@@ -442,7 +442,7 @@ msgstr ""
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:30
+#: lxc/main.go:31
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
@@ -480,17 +480,17 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/copy.go:191
+#: lxc/copy.go:214
 #, c-format
 msgid "Migration failed on source host: %s"
 msgstr ""
 
-#: lxc/copy.go:195
+#: lxc/copy.go:218
 #, c-format
 msgid "Migration failed on target host: %s"
 msgstr ""
 
-#: lxc/utils.go:173
+#: lxc/utils.go:193
 msgid "Missing summary."
 msgstr "Fehlende Zusammenfassung."
 
@@ -504,11 +504,11 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
-#: lxc/list.go:407 lxc/remote.go:365
+#: lxc/list.go:411 lxc/remote.go:366
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:339 lxc/remote.go:344
+#: lxc/remote.go:340 lxc/remote.go:345
 msgid "NO"
 msgstr ""
 
@@ -534,7 +534,7 @@ msgstr "Kein Zertifikat zum hinzufügen bereitgestellt"
 msgid "No fingerprint specified."
 msgstr "Kein Fingerabdruck angegeben."
 
-#: lxc/remote.go:119
+#: lxc/remote.go:120
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
@@ -555,23 +555,23 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:493
+#: lxc/list.go:497
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:408
+#: lxc/list.go:412
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:409
+#: lxc/list.go:413
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:368
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:610 lxc/remote.go:368
+#: lxc/image.go:610 lxc/remote.go:369
 msgid "PUBLIC"
 msgstr ""
 
@@ -598,7 +598,7 @@ msgstr "Alternatives config Verzeichnis."
 msgid "Pause containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/main.go:34
+#: lxc/main.go:35
 msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
@@ -662,7 +662,7 @@ msgstr "Profil %s erstellt\n"
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
 
-#: lxc/remote.go:68
+#: lxc/remote.go:69
 msgid "Public image server"
 msgstr ""
 
@@ -671,7 +671,7 @@ msgstr ""
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
 
-#: lxc/remote.go:66
+#: lxc/remote.go:67
 msgid "Remote admin password"
 msgstr "Entferntes Administrator Passwort"
 
@@ -698,7 +698,7 @@ msgstr ""
 msgid "Restart containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/init.go:218
+#: lxc/init.go:217
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
@@ -707,28 +707,28 @@ msgstr ""
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:410
+#: lxc/list.go:414
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:411
+#: lxc/list.go:415
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:369
+#: lxc/remote.go:370
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:216
+#: lxc/remote.go:217
 msgid "Server certificate NACKed by user"
 msgstr "Server Zertifikat vom Benutzer nicht akzeptiert"
 
-#: lxc/remote.go:278
+#: lxc/remote.go:279
 msgid "Server doesn't trust us after adding our cert"
 msgstr ""
 "Der Server vertraut uns nicht nachdem er unser Zertifikat hinzugefügt hat"
 
-#: lxc/remote.go:67
+#: lxc/remote.go:68
 msgid "Server protocol (lxd or simplestreams)"
 msgstr ""
 
@@ -819,7 +819,7 @@ msgstr ""
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:412
+#: lxc/list.go:416
 msgid "TYPE"
 msgstr ""
 
@@ -839,7 +839,7 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/init.go:275
+#: lxc/init.go:274
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -896,7 +896,7 @@ msgstr ""
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:366
+#: lxc/remote.go:367
 msgid "URL"
 msgstr ""
 
@@ -904,7 +904,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/remote.go:94
+#: lxc/remote.go:95
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
@@ -1127,7 +1127,7 @@ msgid ""
 "    split images) as found in the database will be used for the exported\n"
 "    image.  If the target is a file (not a directory and not stdout), then\n"
 "    the appropriate extension will be appended to the provided file name\n"
-"    based on the algorithm used to compress the image. \n"
+"    based on the algorithm used to compress the image.\n"
 "\n"
 "lxc image info [<remote>:]<image>\n"
 "    Print everything LXD knows about a given image.\n"
@@ -1465,7 +1465,7 @@ msgid ""
 "Publish containers as images."
 msgstr ""
 
-#: lxc/remote.go:37
+#: lxc/remote.go:38
 #, fuzzy
 msgid ""
 "Usage: lxc remote <subcommand> [options]\n"
@@ -1568,11 +1568,11 @@ msgstr ""
 msgid "Whether or not to snapshot the container's running state"
 msgstr "Zustand des laufenden Containers sichern oder nicht"
 
-#: lxc/remote.go:341 lxc/remote.go:346
+#: lxc/remote.go:342 lxc/remote.go:347
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:55
+#: lxc/main.go:56
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
@@ -1589,11 +1589,11 @@ msgstr ""
 msgid "can't copy to the same container name"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/remote.go:329
+#: lxc/remote.go:330
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:355
+#: lxc/remote.go:356
 msgid "default"
 msgstr ""
 
@@ -1609,7 +1609,7 @@ msgstr ""
 msgid "enabled"
 msgstr ""
 
-#: lxc/action.go:126 lxc/main.go:25 lxc/main.go:160
+#: lxc/action.go:126 lxc/main.go:26 lxc/main.go:160
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "Fehler: %v\n"
@@ -1631,7 +1631,7 @@ msgstr ""
 msgid "not all the profiles from the source exist on the target"
 msgstr "nicht alle Profile der Quelle sind am Ziel vorhanden."
 
-#: lxc/remote.go:209
+#: lxc/remote.go:210
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "OK (y/n)? "
@@ -1641,22 +1641,22 @@ msgstr "OK (y/n)? "
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:391
+#: lxc/remote.go:392
 #, c-format
 msgid "remote %s already exists"
 msgstr "entfernte Instanz %s existiert bereits"
 
-#: lxc/remote.go:321 lxc/remote.go:383 lxc/remote.go:418 lxc/remote.go:434
+#: lxc/remote.go:322 lxc/remote.go:384 lxc/remote.go:419 lxc/remote.go:435
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/remote.go:304
+#: lxc/remote.go:305
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "entfernte Instanz %s existiert als <%s>"
 
-#: lxc/remote.go:325 lxc/remote.go:387 lxc/remote.go:422
+#: lxc/remote.go:326 lxc/remote.go:388 lxc/remote.go:423
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
diff --git a/po/fr.po b/po/fr.po
index bd4574336..4ed6a2c68 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-18 01:16-0400\n"
+"POT-Creation-Date: 2017-05-10 00:17-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -90,15 +90,15 @@ msgstr ""
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:405
+#: lxc/list.go:409
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:65
+#: lxc/remote.go:66
 msgid "Accept certificate"
 msgstr ""
 
-#: lxc/remote.go:258
+#: lxc/remote.go:259
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
@@ -129,7 +129,7 @@ msgstr ""
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:406
+#: lxc/list.go:410
 msgid "CREATED AT"
 msgstr ""
 
@@ -147,12 +147,12 @@ msgstr ""
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:208
+#: lxc/remote.go:209
 #, fuzzy, c-format
-msgid "Certificate fingerprint: %x"
+msgid "Certificate fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/remote.go:281
+#: lxc/remote.go:282
 msgid "Client certificate stored at server: "
 msgstr "Certificat client enregistré avec le serveur: "
 
@@ -173,7 +173,7 @@ msgstr ""
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
 
-#: lxc/main.go:32
+#: lxc/main.go:33
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
@@ -200,7 +200,7 @@ msgstr ""
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:223
+#: lxc/remote.go:224
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
@@ -236,7 +236,7 @@ msgstr ""
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:491
+#: lxc/list.go:495
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -244,12 +244,12 @@ msgstr ""
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:44
+#: lxc/main.go:45
 #, fuzzy
 msgid "Enable debug mode"
 msgstr "Active le mode de déboguage."
 
-#: lxc/main.go:43
+#: lxc/main.go:44
 #, fuzzy
 msgid "Enable verbose mode"
 msgstr "Active le mode verbeux."
@@ -311,7 +311,7 @@ msgstr "Force l'arrêt du conteneur."
 msgid "Force the removal of stopped containers"
 msgstr ""
 
-#: lxc/main.go:45
+#: lxc/main.go:46
 msgid "Force using the local unix socket"
 msgstr ""
 
@@ -324,11 +324,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Géneration d'un certificat client. Ceci peut prendre une minute...\n"
 
-#: lxc/list.go:403
+#: lxc/list.go:407
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:404
+#: lxc/list.go:408
 msgid "IPV6"
 msgstr ""
 
@@ -341,7 +341,7 @@ msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:46
+#: lxc/main.go:47
 msgid "Ignore aliases when determining what command to run"
 msgstr ""
 
@@ -364,7 +364,7 @@ msgstr "Empreinte du certificat: % x\n"
 msgid "Importing the image: %s"
 msgstr ""
 
-#: lxc/remote.go:134
+#: lxc/remote.go:135
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
@@ -397,7 +397,7 @@ msgstr ""
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:30
+#: lxc/main.go:31
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
@@ -434,17 +434,17 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/copy.go:191
+#: lxc/copy.go:214
 #, c-format
 msgid "Migration failed on source host: %s"
 msgstr ""
 
-#: lxc/copy.go:195
+#: lxc/copy.go:218
 #, c-format
 msgid "Migration failed on target host: %s"
 msgstr ""
 
-#: lxc/utils.go:173
+#: lxc/utils.go:193
 msgid "Missing summary."
 msgstr "Sommaire manquant."
 
@@ -457,11 +457,11 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr ""
 
-#: lxc/list.go:407 lxc/remote.go:365
+#: lxc/list.go:411 lxc/remote.go:366
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:339 lxc/remote.go:344
+#: lxc/remote.go:340 lxc/remote.go:345
 msgid "NO"
 msgstr ""
 
@@ -487,7 +487,7 @@ msgstr "Un certificat n'a pas été fournis"
 msgid "No fingerprint specified."
 msgstr "Aucune empreinte n'a été spécifié."
 
-#: lxc/remote.go:119
+#: lxc/remote.go:120
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
@@ -509,23 +509,23 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:493
+#: lxc/list.go:497
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:408
+#: lxc/list.go:412
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:409
+#: lxc/list.go:413
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:368
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:610 lxc/remote.go:368
+#: lxc/image.go:610 lxc/remote.go:369
 msgid "PUBLIC"
 msgstr ""
 
@@ -552,7 +552,7 @@ msgstr "Dossier de configuration alternatif."
 msgid "Pause containers."
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/main.go:34
+#: lxc/main.go:35
 msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
@@ -614,7 +614,7 @@ msgstr "Mauvaise URL pour le conteneur %s"
 msgid "Properties:"
 msgstr ""
 
-#: lxc/remote.go:68
+#: lxc/remote.go:69
 msgid "Public image server"
 msgstr ""
 
@@ -623,7 +623,7 @@ msgstr ""
 msgid "Public: %s"
 msgstr ""
 
-#: lxc/remote.go:66
+#: lxc/remote.go:67
 msgid "Remote admin password"
 msgstr ""
 
@@ -650,7 +650,7 @@ msgstr ""
 msgid "Restart containers."
 msgstr "Liste de l'information sur les conteneurs.\n"
 
-#: lxc/init.go:218
+#: lxc/init.go:217
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
@@ -659,27 +659,27 @@ msgstr ""
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:410
+#: lxc/list.go:414
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:411
+#: lxc/list.go:415
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:369
+#: lxc/remote.go:370
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:216
+#: lxc/remote.go:217
 msgid "Server certificate NACKed by user"
 msgstr "Le certificat serveur a été rejeté par l'utilisateur"
 
-#: lxc/remote.go:278
+#: lxc/remote.go:279
 msgid "Server doesn't trust us after adding our cert"
 msgstr "Identification refuse après l'ajout du certificat client"
 
-#: lxc/remote.go:67
+#: lxc/remote.go:68
 msgid "Server protocol (lxd or simplestreams)"
 msgstr ""
 
@@ -770,7 +770,7 @@ msgstr ""
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:412
+#: lxc/list.go:416
 msgid "TYPE"
 msgstr ""
 
@@ -790,7 +790,7 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/init.go:275
+#: lxc/init.go:274
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -846,7 +846,7 @@ msgstr ""
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:366
+#: lxc/remote.go:367
 msgid "URL"
 msgstr ""
 
@@ -854,7 +854,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/remote.go:94
+#: lxc/remote.go:95
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
@@ -1069,7 +1069,7 @@ msgid ""
 "    split images) as found in the database will be used for the exported\n"
 "    image.  If the target is a file (not a directory and not stdout), then\n"
 "    the appropriate extension will be appended to the provided file name\n"
-"    based on the algorithm used to compress the image. \n"
+"    based on the algorithm used to compress the image.\n"
 "\n"
 "lxc image info [<remote>:]<image>\n"
 "    Print everything LXD knows about a given image.\n"
@@ -1332,7 +1332,7 @@ msgid ""
 "Publish containers as images."
 msgstr ""
 
-#: lxc/remote.go:37
+#: lxc/remote.go:38
 msgid ""
 "Usage: lxc remote <subcommand> [options]\n"
 "\n"
@@ -1418,11 +1418,11 @@ msgstr ""
 "Est-ce que l'état de fonctionement du conteneur doit être inclus dans "
 "l'instantané (snapshot)"
 
-#: lxc/remote.go:341 lxc/remote.go:346
+#: lxc/remote.go:342 lxc/remote.go:347
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:55
+#: lxc/main.go:56
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
@@ -1439,11 +1439,11 @@ msgstr "mauvais type de réponse pour l'action!"
 msgid "can't copy to the same container name"
 msgstr ""
 
-#: lxc/remote.go:329
+#: lxc/remote.go:330
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:355
+#: lxc/remote.go:356
 msgid "default"
 msgstr ""
 
@@ -1460,7 +1460,7 @@ msgstr ""
 msgid "enabled"
 msgstr ""
 
-#: lxc/action.go:126 lxc/main.go:25 lxc/main.go:160
+#: lxc/action.go:126 lxc/main.go:26 lxc/main.go:160
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "erreur: %v\n"
@@ -1482,7 +1482,7 @@ msgstr ""
 msgid "not all the profiles from the source exist on the target"
 msgstr ""
 
-#: lxc/remote.go:209
+#: lxc/remote.go:210
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
@@ -1492,22 +1492,22 @@ msgstr "ok (y/n)?"
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:391
+#: lxc/remote.go:392
 #, c-format
 msgid "remote %s already exists"
 msgstr "le serveur distant %s existe déjà"
 
-#: lxc/remote.go:321 lxc/remote.go:383 lxc/remote.go:418 lxc/remote.go:434
+#: lxc/remote.go:322 lxc/remote.go:384 lxc/remote.go:419 lxc/remote.go:435
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/remote.go:304
+#: lxc/remote.go:305
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "le serveur distant %s existe en tant que <%s>"
 
-#: lxc/remote.go:325 lxc/remote.go:387 lxc/remote.go:422
+#: lxc/remote.go:326 lxc/remote.go:388 lxc/remote.go:423
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
diff --git a/po/ja.po b/po/ja.po
index 4565b3c61..d493b13ad 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-04-18 01:16-0400\n"
+"POT-Creation-Date: 2017-05-10 00:17-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -89,15 +89,15 @@ msgstr ""
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:405
+#: lxc/list.go:409
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:65
+#: lxc/remote.go:66
 msgid "Accept certificate"
 msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
 
-#: lxc/remote.go:258
+#: lxc/remote.go:259
 #, c-format
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
@@ -128,7 +128,7 @@ msgstr "送信バイト数"
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:406
+#: lxc/list.go:410
 msgid "CREATED AT"
 msgstr ""
 
@@ -146,12 +146,12 @@ msgstr "キー '%s' が指定されていないので削除できません。"
 msgid "Cannot provide container name to list"
 msgstr "コンテナ名を取得できません"
 
-#: lxc/remote.go:208
-#, c-format
-msgid "Certificate fingerprint: %x"
+#: lxc/remote.go:209
+#, fuzzy, c-format
+msgid "Certificate fingerprint: %s"
 msgstr "証明書のフィンガープリント: %x"
 
-#: lxc/remote.go:281
+#: lxc/remote.go:282
 msgid "Client certificate stored at server: "
 msgstr "クライアント証明書がサーバに格納されました: "
 
@@ -172,7 +172,7 @@ msgstr "新しいコンテナに適用するキー/値の設定"
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
 
-#: lxc/main.go:32
+#: lxc/main.go:33
 msgid "Connection refused; is LXD running?"
 msgstr "接続が拒否されました。LXDが実行されていますか?"
 
@@ -199,7 +199,7 @@ msgstr "ソースからエイリアスをコピーしました"
 msgid "Copying the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/remote.go:223
+#: lxc/remote.go:224
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
@@ -236,7 +236,7 @@ msgstr "デバイス %s が %s から削除されました"
 msgid "Disk usage:"
 msgstr "  ディスク使用量:"
 
-#: lxc/list.go:491
+#: lxc/list.go:495
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -244,12 +244,12 @@ msgstr ""
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:44
+#: lxc/main.go:45
 #, fuzzy
 msgid "Enable debug mode"
 msgstr "デバッグモードを有効にします。"
 
-#: lxc/main.go:43
+#: lxc/main.go:44
 #, fuzzy
 msgid "Enable verbose mode"
 msgstr "詳細モードを有効にします。"
@@ -314,7 +314,7 @@ msgstr "コンテナを強制シャットダウンします。"
 msgid "Force the removal of stopped containers"
 msgstr "停止したコンテナを強制的に削除します。"
 
-#: lxc/main.go:45
+#: lxc/main.go:46
 #, fuzzy
 msgid "Force using the local unix socket"
 msgstr "強制的にローカルのUNIXソケットを使います。"
@@ -327,11 +327,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "クライアント証明書を生成します。1分ぐらいかかります..."
 
-#: lxc/list.go:403
+#: lxc/list.go:407
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:404
+#: lxc/list.go:408
 msgid "IPV6"
 msgstr ""
 
@@ -344,7 +344,7 @@ msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要があります"
 
-#: lxc/main.go:46
+#: lxc/main.go:47
 #, fuzzy
 msgid "Ignore aliases when determining what command to run"
 msgstr "どのコマンドを実行するか決める際にエイリアスを無視します。"
@@ -368,7 +368,7 @@ msgstr "イメージは以下のフィンガープリントでインポートさ
 msgid "Importing the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/remote.go:134
+#: lxc/remote.go:135
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr "不正な URL スキーム \"%s\" (\"%s\" 内)"
@@ -400,7 +400,7 @@ msgstr "IPアドレス:"
 msgid "Keep the image up to date after initial copy"
 msgstr "最初にコピーした後も常にイメージを最新の状態に保つ"
 
-#: lxc/main.go:30
+#: lxc/main.go:31
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
@@ -438,17 +438,17 @@ msgstr "メモリ (ピーク)"
 msgid "Memory usage:"
 msgstr "  メモリ消費量:"
 
-#: lxc/copy.go:191
+#: lxc/copy.go:214
 #, c-format
 msgid "Migration failed on source host: %s"
 msgstr ""
 
-#: lxc/copy.go:195
+#: lxc/copy.go:218
 #, c-format
 msgid "Migration failed on target host: %s"
 msgstr ""
 
-#: lxc/utils.go:173
+#: lxc/utils.go:193
 msgid "Missing summary."
 msgstr "サマリーはありません。"
 
@@ -462,11 +462,11 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "コンテナ名を指定する必要があります: "
 
-#: lxc/list.go:407 lxc/remote.go:365
+#: lxc/list.go:411 lxc/remote.go:366
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:339 lxc/remote.go:344
+#: lxc/remote.go:340 lxc/remote.go:345
 msgid "NO"
 msgstr ""
 
@@ -492,7 +492,7 @@ msgstr "追加すべき証明書が提供されていません"
 msgid "No fingerprint specified."
 msgstr "フィンガープリントが指定されていません。"
 
-#: lxc/remote.go:119
+#: lxc/remote.go:120
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
@@ -513,23 +513,23 @@ msgstr "%s に出力されます"
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)"
 
-#: lxc/list.go:493
+#: lxc/list.go:497
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:408
+#: lxc/list.go:412
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:409
+#: lxc/list.go:413
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:368
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:610 lxc/remote.go:368
+#: lxc/image.go:610 lxc/remote.go:369
 msgid "PUBLIC"
 msgstr ""
 
@@ -556,7 +556,7 @@ msgstr "別のサーバ用設定ディレクトリ"
 msgid "Pause containers."
 msgstr "コンテナの不正なURL %s"
 
-#: lxc/main.go:34
+#: lxc/main.go:35
 #, fuzzy
 msgid "Permission denied, are you in the lxd group?"
 msgstr "アクセスが拒否されました。lxd グループに所属していますか?"
@@ -622,7 +622,7 @@ msgstr "プロファイル: %s"
 msgid "Properties:"
 msgstr "プロパティ:"
 
-#: lxc/remote.go:68
+#: lxc/remote.go:69
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
@@ -631,7 +631,7 @@ msgstr "Public なイメージサーバとして設定します"
 msgid "Public: %s"
 msgstr ""
 
-#: lxc/remote.go:66
+#: lxc/remote.go:67
 msgid "Remote admin password"
 msgstr "リモートの管理者パスワード"
 
@@ -659,7 +659,7 @@ msgstr "リソース:"
 msgid "Restart containers."
 msgstr "コンテナを作成中"
 
-#: lxc/init.go:218
+#: lxc/init.go:217
 #, c-format
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
@@ -668,27 +668,27 @@ msgstr "イメージの取得中: %s"
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:410
+#: lxc/list.go:414
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:411
+#: lxc/list.go:415
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:369
+#: lxc/remote.go:370
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:216
+#: lxc/remote.go:217
 msgid "Server certificate NACKed by user"
 msgstr "ユーザによりサーバ証明書が拒否されました"
 
-#: lxc/remote.go:278
+#: lxc/remote.go:279
 msgid "Server doesn't trust us after adding our cert"
 msgstr "サーバが我々の証明書を追加した後我々を信頼していません"
 
-#: lxc/remote.go:67
+#: lxc/remote.go:68
 msgid "Server protocol (lxd or simplestreams)"
 msgstr "サーバのプロトコル (lxd or simplestreams)"
 
@@ -780,7 +780,7 @@ msgstr "Swap (現在値)"
 msgid "Swap (peak)"
 msgstr "Swap (ピーク)"
 
-#: lxc/list.go:412
+#: lxc/list.go:416
 msgid "TYPE"
 msgstr ""
 
@@ -801,7 +801,7 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "デバイスが存在しません"
 
-#: lxc/init.go:275
+#: lxc/init.go:274
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -864,7 +864,7 @@ msgstr "タイプ: persistent"
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:366
+#: lxc/remote.go:367
 msgid "URL"
 msgstr ""
 
@@ -872,7 +872,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/remote.go:94
+#: lxc/remote.go:95
 msgid "Unable to read remote TLS certificate"
 msgstr "リモートの TLS 証明書を読めません"
 
@@ -1101,7 +1101,7 @@ msgid ""
 "    split images) as found in the database will be used for the exported\n"
 "    image.  If the target is a file (not a directory and not stdout), then\n"
 "    the appropriate extension will be appended to the provided file name\n"
-"    based on the algorithm used to compress the image. \n"
+"    based on the algorithm used to compress the image.\n"
 "\n"
 "lxc image info [<remote>:]<image>\n"
 "    Print everything LXD knows about a given image.\n"
@@ -1564,7 +1564,7 @@ msgstr ""
 "lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
 "value]..."
 
-#: lxc/remote.go:37
+#: lxc/remote.go:38
 #, fuzzy
 msgid ""
 "Usage: lxc remote <subcommand> [options]\n"
@@ -1692,11 +1692,11 @@ msgstr ""
 msgid "Whether or not to snapshot the container's running state"
 msgstr "コンテナの稼動状態のスナップショットを取得するかどうか"
 
-#: lxc/remote.go:341 lxc/remote.go:346
+#: lxc/remote.go:342 lxc/remote.go:347
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:55
+#: lxc/main.go:56
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
@@ -1713,11 +1713,11 @@ msgstr "アクションからの結果タイプが不正です"
 msgid "can't copy to the same container name"
 msgstr "同じコンテナ名へはコピーできません"
 
-#: lxc/remote.go:329
+#: lxc/remote.go:330
 msgid "can't remove the default remote"
 msgstr "デフォルトのリモートは削除できません"
 
-#: lxc/remote.go:355
+#: lxc/remote.go:356
 msgid "default"
 msgstr ""
 
@@ -1735,7 +1735,7 @@ msgstr "無効"
 msgid "enabled"
 msgstr "有効"
 
-#: lxc/action.go:126 lxc/main.go:25 lxc/main.go:160
+#: lxc/action.go:126 lxc/main.go:26 lxc/main.go:160
 #, c-format
 msgid "error: %v"
 msgstr "エラー: %v"
@@ -1757,7 +1757,7 @@ msgstr ""
 msgid "not all the profiles from the source exist on the target"
 msgstr "コピー元の全てのプロファイルがターゲットに存在しません"
 
-#: lxc/remote.go:209
+#: lxc/remote.go:210
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
@@ -1766,22 +1766,22 @@ msgstr "ok (y/n)?"
 msgid "processing aliases failed %s\n"
 msgstr "エイリアスの処理が失敗しました %s\n"
 
-#: lxc/remote.go:391
+#: lxc/remote.go:392
 #, c-format
 msgid "remote %s already exists"
 msgstr "リモート %s は既に存在します"
 
-#: lxc/remote.go:321 lxc/remote.go:383 lxc/remote.go:418 lxc/remote.go:434
+#: lxc/remote.go:322 lxc/remote.go:384 lxc/remote.go:419 lxc/remote.go:435
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "リモート %s は存在しません"
 
-#: lxc/remote.go:304
+#: lxc/remote.go:305
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "リモート %s は <%s> として存在します"
 
-#: lxc/remote.go:325 lxc/remote.go:387 lxc/remote.go:422
+#: lxc/remote.go:326 lxc/remote.go:388 lxc/remote.go:423
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr "リモート %s は static ですので変更できません"
diff --git a/po/lxd.pot b/po/lxd.pot
index 34d597054..68efec47a 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-04-18 01:16-0400\n"
+        "POT-Creation-Date: 2017-05-10 00:17-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -86,15 +86,15 @@ msgstr  ""
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:405
+#: lxc/list.go:409
 msgid   "ARCHITECTURE"
 msgstr  ""
 
-#: lxc/remote.go:65
+#: lxc/remote.go:66
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:258
+#: lxc/remote.go:259
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
@@ -125,7 +125,7 @@ msgstr  ""
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:406
+#: lxc/list.go:410
 msgid   "CREATED AT"
 msgstr  ""
 
@@ -143,12 +143,12 @@ msgstr  ""
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:208
+#: lxc/remote.go:209
 #, c-format
-msgid   "Certificate fingerprint: %x"
+msgid   "Certificate fingerprint: %s"
 msgstr  ""
 
-#: lxc/remote.go:281
+#: lxc/remote.go:282
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
@@ -169,7 +169,7 @@ msgstr  ""
 msgid   "Config parsing error: %s"
 msgstr  ""
 
-#: lxc/main.go:32
+#: lxc/main.go:33
 msgid   "Connection refused; is LXD running?"
 msgstr  ""
 
@@ -196,7 +196,7 @@ msgstr  ""
 msgid   "Copying the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:223
+#: lxc/remote.go:224
 msgid   "Could not create server cert dir"
 msgstr  ""
 
@@ -232,7 +232,7 @@ msgstr  ""
 msgid   "Disk usage:"
 msgstr  ""
 
-#: lxc/list.go:491
+#: lxc/list.go:495
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -240,11 +240,11 @@ msgstr  ""
 msgid   "EXPIRY DATE"
 msgstr  ""
 
-#: lxc/main.go:44
+#: lxc/main.go:45
 msgid   "Enable debug mode"
 msgstr  ""
 
-#: lxc/main.go:43
+#: lxc/main.go:44
 msgid   "Enable verbose mode"
 msgstr  ""
 
@@ -304,7 +304,7 @@ msgstr  ""
 msgid   "Force the removal of stopped containers"
 msgstr  ""
 
-#: lxc/main.go:45
+#: lxc/main.go:46
 msgid   "Force using the local unix socket"
 msgstr  ""
 
@@ -316,11 +316,11 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:403
+#: lxc/list.go:407
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:404
+#: lxc/list.go:408
 msgid   "IPV6"
 msgstr  ""
 
@@ -332,7 +332,7 @@ msgstr  ""
 msgid   "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr  ""
 
-#: lxc/main.go:46
+#: lxc/main.go:47
 msgid   "Ignore aliases when determining what command to run"
 msgstr  ""
 
@@ -354,7 +354,7 @@ msgstr  ""
 msgid   "Importing the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:134
+#: lxc/remote.go:135
 #, c-format
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
@@ -385,7 +385,7 @@ msgstr  ""
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
-#: lxc/main.go:30
+#: lxc/main.go:31
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
@@ -422,17 +422,17 @@ msgstr  ""
 msgid   "Memory usage:"
 msgstr  ""
 
-#: lxc/copy.go:191
+#: lxc/copy.go:214
 #, c-format
 msgid   "Migration failed on source host: %s"
 msgstr  ""
 
-#: lxc/copy.go:195
+#: lxc/copy.go:218
 #, c-format
 msgid   "Migration failed on target host: %s"
 msgstr  ""
 
-#: lxc/utils.go:173
+#: lxc/utils.go:193
 msgid   "Missing summary."
 msgstr  ""
 
@@ -444,11 +444,11 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:407 lxc/remote.go:365
+#: lxc/list.go:411 lxc/remote.go:366
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/remote.go:339 lxc/remote.go:344
+#: lxc/remote.go:340 lxc/remote.go:345
 msgid   "NO"
 msgstr  ""
 
@@ -473,7 +473,7 @@ msgstr  ""
 msgid   "No fingerprint specified."
 msgstr  ""
 
-#: lxc/remote.go:119
+#: lxc/remote.go:120
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
@@ -494,23 +494,23 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:493
+#: lxc/list.go:497
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:408
+#: lxc/list.go:412
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:409
+#: lxc/list.go:413
 msgid   "PROFILES"
 msgstr  ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:368
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:610 lxc/remote.go:368
+#: lxc/image.go:610 lxc/remote.go:369
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -534,7 +534,7 @@ msgstr  ""
 msgid   "Pause containers."
 msgstr  ""
 
-#: lxc/main.go:34
+#: lxc/main.go:35
 msgid   "Permission denied, are you in the lxd group?"
 msgstr  ""
 
@@ -596,7 +596,7 @@ msgstr  ""
 msgid   "Properties:"
 msgstr  ""
 
-#: lxc/remote.go:68
+#: lxc/remote.go:69
 msgid   "Public image server"
 msgstr  ""
 
@@ -605,7 +605,7 @@ msgstr  ""
 msgid   "Public: %s"
 msgstr  ""
 
-#: lxc/remote.go:66
+#: lxc/remote.go:67
 msgid   "Remote admin password"
 msgstr  ""
 
@@ -631,7 +631,7 @@ msgstr  ""
 msgid   "Restart containers."
 msgstr  ""
 
-#: lxc/init.go:218
+#: lxc/init.go:217
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
@@ -640,27 +640,27 @@ msgstr  ""
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:410
+#: lxc/list.go:414
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:411
+#: lxc/list.go:415
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:369
+#: lxc/remote.go:370
 msgid   "STATIC"
 msgstr  ""
 
-#: lxc/remote.go:216
+#: lxc/remote.go:217
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:278
+#: lxc/remote.go:279
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
-#: lxc/remote.go:67
+#: lxc/remote.go:68
 msgid   "Server protocol (lxd or simplestreams)"
 msgstr  ""
 
@@ -748,7 +748,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:412
+#: lxc/list.go:416
 msgid   "TYPE"
 msgstr  ""
 
@@ -764,7 +764,7 @@ msgstr  ""
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:275
+#: lxc/init.go:274
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -818,7 +818,7 @@ msgstr  ""
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:366
+#: lxc/remote.go:367
 msgid   "URL"
 msgstr  ""
 
@@ -826,7 +826,7 @@ msgstr  ""
 msgid   "Unable to find help2man."
 msgstr  ""
 
-#: lxc/remote.go:94
+#: lxc/remote.go:95
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
@@ -1016,7 +1016,7 @@ msgid   "Usage: lxc image <subcommand> [options]\n"
         "    split images) as found in the database will be used for the exported\n"
         "    image.  If the target is a file (not a directory and not stdout), then\n"
         "    the appropriate extension will be appended to the provided file name\n"
-        "    based on the algorithm used to compress the image. \n"
+        "    based on the algorithm used to compress the image.\n"
         "\n"
         "lxc image info [<remote>:]<image>\n"
         "    Print everything LXD knows about a given image.\n"
@@ -1256,7 +1256,7 @@ msgid   "Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--a
         "Publish containers as images."
 msgstr  ""
 
-#: lxc/remote.go:37
+#: lxc/remote.go:38
 msgid   "Usage: lxc remote <subcommand> [options]\n"
         "\n"
         "Manage the list of remote LXD servers.\n"
@@ -1329,11 +1329,11 @@ msgstr  ""
 msgid   "Whether or not to snapshot the container's running state"
 msgstr  ""
 
-#: lxc/remote.go:341 lxc/remote.go:346
+#: lxc/remote.go:342 lxc/remote.go:347
 msgid   "YES"
 msgstr  ""
 
-#: lxc/main.go:55
+#: lxc/main.go:56
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
@@ -1349,11 +1349,11 @@ msgstr  ""
 msgid   "can't copy to the same container name"
 msgstr  ""
 
-#: lxc/remote.go:329
+#: lxc/remote.go:330
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:355
+#: lxc/remote.go:356
 msgid   "default"
 msgstr  ""
 
@@ -1369,7 +1369,7 @@ msgstr  ""
 msgid   "enabled"
 msgstr  ""
 
-#: lxc/action.go:126 lxc/main.go:25 lxc/main.go:160
+#: lxc/action.go:126 lxc/main.go:26 lxc/main.go:160
 #, c-format
 msgid   "error: %v"
 msgstr  ""
@@ -1391,7 +1391,7 @@ msgstr  ""
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
-#: lxc/remote.go:209
+#: lxc/remote.go:210
 msgid   "ok (y/n)?"
 msgstr  ""
 
@@ -1400,22 +1400,22 @@ msgstr  ""
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:391
+#: lxc/remote.go:392
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:321 lxc/remote.go:383 lxc/remote.go:418 lxc/remote.go:434
+#: lxc/remote.go:322 lxc/remote.go:384 lxc/remote.go:419 lxc/remote.go:435
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:304
+#: lxc/remote.go:305
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
-#: lxc/remote.go:325 lxc/remote.go:387 lxc/remote.go:422
+#: lxc/remote.go:326 lxc/remote.go:388 lxc/remote.go:423
 #, c-format
 msgid   "remote %s is static and cannot be modified"
 msgstr  ""

From 2ba9de34cbf3c591bc5bea70f429be5038bd7eb8 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 10 May 2017 19:09:55 +0200
Subject: [PATCH 0909/1193] storage btrfs: cleanup empty migration dirs

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_btrfs.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 5b3bf21c5..1c915e0bd 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -806,6 +806,7 @@ func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn) erro
 		if err != nil {
 			return err
 		}
+		defer os.RemoveAll(tmpPath)
 
 		btrfsPath := fmt.Sprintf("%s/.root", tmpPath)
 		if err := s.btrfs.subvolsSnapshot(s.container.Path(), btrfsPath, true); err != nil {
@@ -836,11 +837,13 @@ func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn) erro
 	if err != nil {
 		return err
 	}
+	defer os.RemoveAll(tmpPath)
 
 	s.runningSnapName = fmt.Sprintf("%s/.root", tmpPath)
 	if err := s.btrfs.subvolsSnapshot(s.container.Path(), s.runningSnapName, true); err != nil {
 		return err
 	}
+	defer s.btrfs.subvolsDelete(s.runningSnapName)
 
 	btrfsParent := ""
 	if len(s.btrfsSnapshotNames) > 0 {
@@ -856,6 +859,7 @@ func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) e
 	if err != nil {
 		return err
 	}
+	defer os.RemoveAll(tmpPath)
 
 	s.stoppedSnapName = fmt.Sprintf("%s/.root", tmpPath)
 	err = s.btrfs.subvolsSnapshot(s.container.Path(), s.stoppedSnapName, true)

From 4726d102650080babe3ac2a01a13cca5a4148008 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 11 May 2017 13:32:05 -0400
Subject: [PATCH 0910/1193] Release LXD 2.0.10
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/version/flex.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/version/flex.go b/shared/version/flex.go
index cf650cc63..9c4234ef1 100644
--- a/shared/version/flex.go
+++ b/shared/version/flex.go
@@ -1,7 +1,7 @@
 package version
 
 // Version contains the LXD version number
-var Version = "2.0.9"
+var Version = "2.0.10"
 
 // UserAgent contains a string suitable as a user-agent
 var UserAgent = "LXD " + Version

From 4d0969a981da6aac83657d490e50db1a5979da54 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 12 May 2017 12:57:13 +0200
Subject: [PATCH 0911/1193] Support running individual testify test suites

Since the standard "go test" tool is not aware of testify and since
unit tests using testify are all direct methods of lxdTestSuite, it's
not possible to easily run a particular set of tests in isolation (say
all tests in container_test.go).

This commit changes the test files to declare their own test suite,
based on lxdTestSuite, so now running:

```
go test -v -run TestContainerTestSuite ./lxd
```

or

```
go test -v -run TestDaemonTestSuite ./lxd
```

will only execute the the tests in those suites, as opposed to the
entire suite.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/container_test.go | 38 ++++++++++++++++++++++++--------------
 lxd/daemon_test.go    | 16 +++++++++++++++-
 lxd/main_test.go      |  5 -----
 3 files changed, 39 insertions(+), 20 deletions(-)

diff --git a/lxd/container_test.go b/lxd/container_test.go
index 7eac9ecde..687a8de4e 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -2,13 +2,19 @@ package main
 
 import (
 	"fmt"
+	"testing"
 
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/stretchr/testify/suite"
 )
 
-func (suite *lxdTestSuite) TestContainer_ProfilesDefault() {
+type containerTestSuite struct {
+	lxdTestSuite
+}
+
+func (suite *containerTestSuite) TestContainer_ProfilesDefault() {
 	args := containerArgs{
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
@@ -31,7 +37,7 @@ func (suite *lxdTestSuite) TestContainer_ProfilesDefault() {
 		"First profile should be the default profile.")
 }
 
-func (suite *lxdTestSuite) TestContainer_ProfilesMulti() {
+func (suite *containerTestSuite) TestContainer_ProfilesMulti() {
 	// Create an unprivileged profile
 	_, err := dbProfileCreate(
 		suite.d.db,
@@ -67,7 +73,7 @@ func (suite *lxdTestSuite) TestContainer_ProfilesMulti() {
 		"The container is not privileged (didn't apply the unprivileged profile?).")
 }
 
-func (suite *lxdTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
+func (suite *containerTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 	args := containerArgs{
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
@@ -97,7 +103,7 @@ func (suite *lxdTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 		"Container config doesn't overwrite profile config.")
 }
 
-func (suite *lxdTestSuite) TestContainer_LoadFromDB() {
+func (suite *containerTestSuite) TestContainer_LoadFromDB() {
 	args := containerArgs{
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
@@ -126,7 +132,7 @@ func (suite *lxdTestSuite) TestContainer_LoadFromDB() {
 		"The loaded container isn't excactly the same as the created one.")
 }
 
-func (suite *lxdTestSuite) TestContainer_Path_Regular() {
+func (suite *containerTestSuite) TestContainer_Path_Regular() {
 	// Regular
 	args := containerArgs{
 		Ctype:     cTypeRegular,
@@ -143,7 +149,7 @@ func (suite *lxdTestSuite) TestContainer_Path_Regular() {
 	suite.Req.Equal(shared.VarPath("containers", "testFoo2"), containerPath("testFoo2", false))
 }
 
-func (suite *lxdTestSuite) TestContainer_Path_Snapshot() {
+func (suite *containerTestSuite) TestContainer_Path_Snapshot() {
 	// Snapshot
 	args := containerArgs{
 		Ctype:     cTypeSnapshot,
@@ -164,7 +170,7 @@ func (suite *lxdTestSuite) TestContainer_Path_Snapshot() {
 		containerPath("test/snap1", true))
 }
 
-func (suite *lxdTestSuite) TestContainer_LogPath() {
+func (suite *containerTestSuite) TestContainer_LogPath() {
 	args := containerArgs{
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
@@ -178,7 +184,7 @@ func (suite *lxdTestSuite) TestContainer_LogPath() {
 	suite.Req.Equal(shared.VarPath("logs", "testFoo"), c.LogPath())
 }
 
-func (suite *lxdTestSuite) TestContainer_IsPrivileged_Privileged() {
+func (suite *containerTestSuite) TestContainer_IsPrivileged_Privileged() {
 	args := containerArgs{
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
@@ -194,7 +200,7 @@ func (suite *lxdTestSuite) TestContainer_IsPrivileged_Privileged() {
 	suite.Req.Nil(c.Delete(), "Failed to delete the container.")
 }
 
-func (suite *lxdTestSuite) TestContainer_IsPrivileged_Unprivileged() {
+func (suite *containerTestSuite) TestContainer_IsPrivileged_Unprivileged() {
 	args := containerArgs{
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
@@ -210,7 +216,7 @@ func (suite *lxdTestSuite) TestContainer_IsPrivileged_Unprivileged() {
 	suite.Req.Nil(c.Delete(), "Failed to delete the container.")
 }
 
-func (suite *lxdTestSuite) TestContainer_Rename() {
+func (suite *containerTestSuite) TestContainer_Rename() {
 	args := containerArgs{
 		Ctype:     cTypeRegular,
 		Ephemeral: false,
@@ -225,7 +231,7 @@ func (suite *lxdTestSuite) TestContainer_Rename() {
 	suite.Req.Equal(shared.VarPath("containers", "testFoo2"), c.Path())
 }
 
-func (suite *lxdTestSuite) TestContainer_findIdmap_isolated() {
+func (suite *containerTestSuite) TestContainer_findIdmap_isolated() {
 	c1, err := containerCreateInternal(suite.d, containerArgs{
 		Ctype: cTypeRegular,
 		Name:  "isol-1",
@@ -266,7 +272,7 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_isolated() {
 	}
 }
 
-func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
+func (suite *containerTestSuite) TestContainer_findIdmap_mixed() {
 	c1, err := containerCreateInternal(suite.d, containerArgs{
 		Ctype: cTypeRegular,
 		Name:  "isol-1",
@@ -307,7 +313,7 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_mixed() {
 	}
 }
 
-func (suite *lxdTestSuite) TestContainer_findIdmap_raw() {
+func (suite *containerTestSuite) TestContainer_findIdmap_raw() {
 	c1, err := containerCreateInternal(suite.d, containerArgs{
 		Ctype: cTypeRegular,
 		Name:  "isol-1",
@@ -343,7 +349,7 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_raw() {
 	}
 }
 
-func (suite *lxdTestSuite) TestContainer_findIdmap_maxed() {
+func (suite *containerTestSuite) TestContainer_findIdmap_maxed() {
 	maps := []*shared.IdmapSet{}
 
 	for i := 0; i < 7; i++ {
@@ -383,3 +389,7 @@ func (suite *lxdTestSuite) TestContainer_findIdmap_maxed() {
 		}
 	}
 }
+
+func TestContainerTestSuite(t *testing.T) {
+	suite.Run(t, new(containerTestSuite))
+}
diff --git a/lxd/daemon_test.go b/lxd/daemon_test.go
index 948f58a94..fa98ec393 100644
--- a/lxd/daemon_test.go
+++ b/lxd/daemon_test.go
@@ -1,6 +1,16 @@
 package main
 
-func (suite *lxdTestSuite) Test_config_value_set_empty_removes_val() {
+import (
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+)
+
+type daemonTestSuite struct {
+	lxdTestSuite
+}
+
+func (suite *daemonTestSuite) Test_config_value_set_empty_removes_val() {
 	var err error
 	d := suite.d
 
@@ -25,3 +35,7 @@ func (suite *lxdTestSuite) Test_config_value_set_empty_removes_val() {
 	_, present = valMap["core.trust_password"]
 	suite.Req.False(present)
 }
+
+func TestDaemonTestSuite(t *testing.T) {
+	suite.Run(t, new(daemonTestSuite))
+}
diff --git a/lxd/main_test.go b/lxd/main_test.go
index 40a639eee..67ec48c56 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -3,7 +3,6 @@ package main
 import (
 	"io/ioutil"
 	"os"
-	"testing"
 
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
@@ -71,7 +70,3 @@ func (suite *lxdTestSuite) TearDownSuite() {
 func (suite *lxdTestSuite) SetupTest() {
 	suite.Req = require.New(suite.T())
 }
-
-func TestLxdTestSuite(t *testing.T) {
-	suite.Run(t, new(lxdTestSuite))
-}

From 7aa6d26da909184c3a41a80ea119432f7c7a53b3 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 16 May 2017 10:12:27 +0200
Subject: [PATCH 0912/1193] Don't special-case mock mode unnecessarily in db
 patches

A few db patches are currently checking if the daemon is being run in
mock mode (i.e. unit tests), and no-op the patch in that.

It turns out that this is not really necessary, becasue the underlying
logic will work fine in mock mode too (and also, more generally it
might open the doors to creating tests that specicically exercise
certain patches).

This change removes all current occurrences of the special casing, the
main goal would be to reduce coupling between daemon
code (higher-level) and db code (lower-level), so eventually we can
have dependencies only in one direction (deamon depends on db but not
viceversa).

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db_update.go | 20 --------------------
 1 file changed, 20 deletions(-)

diff --git a/lxd/db_update.go b/lxd/db_update.go
index b10530216..35202cd36 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -135,10 +135,6 @@ CREATE TABLE IF NOT EXISTS patches (
 }
 
 func dbUpdateFromV30(currentVersion int, version int, d *Daemon) error {
-	if d.MockMode {
-		return nil
-	}
-
 	entries, err := ioutil.ReadDir(shared.VarPath("containers"))
 	if err != nil {
 		/* If the directory didn't exist before, the user had never
@@ -178,10 +174,6 @@ func dbUpdateFromV30(currentVersion int, version int, d *Daemon) error {
 }
 
 func dbUpdateFromV29(currentVersion int, version int, d *Daemon) error {
-	if d.MockMode {
-		return nil
-	}
-
 	if shared.PathExists(shared.VarPath("zfs.img")) {
 		err := os.Chmod(shared.VarPath("zfs.img"), 0600)
 		if err != nil {
@@ -484,12 +476,6 @@ ALTER TABLE images ADD COLUMN last_use_date DATETIME;`
 }
 
 func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
-	if d.MockMode {
-		// No need to move snapshots no mock runs,
-		// dbUpdateFromV12 will then set the db version to 13
-		return nil
-	}
-
 	cNames, err := dbContainersList(d.db, cTypeSnapshot)
 	if err != nil {
 		return err
@@ -558,12 +544,6 @@ func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
 }
 
 func dbUpdateFromV10(currentVersion int, version int, d *Daemon) error {
-	if d.MockMode {
-		// No need to move lxc to containers in mock runs,
-		// dbUpdateFromV12 will then set the db version to 13
-		return nil
-	}
-
 	if shared.PathExists(shared.VarPath("lxc")) {
 		err := os.Rename(shared.VarPath("lxc"), shared.VarPath("containers"))
 		if err != nil {

From f8b5a05929c76299c198a68d87cce8aa7e5ba6fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 24 May 2017 00:13:22 -0400
Subject: [PATCH 0913/1193] doc: Add section on macvlan vs bridge
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3273

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/containers.md | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/doc/containers.md b/doc/containers.md
index 3f1b9e438..0ad45d0e6 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -160,6 +160,23 @@ hwaddr          | string    | randomly assigned | no        | all
 mtu             | integer   | parent MTU        | no        | all                           | The MTU of the new interface
 parent          | string    | -                 | yes       | physical, bridged, macvlan    | The name of the host device or bridge
 
+#### bridged or macvlan for connection to physical network
+The "bridged" and "macvlan" interface types can both be used to connect
+to an existing physical network.
+
+macvlan effectively lets you fork your physical NIC, getting a second
+interface that's then used by the container. This saves you from
+creating a bridge device and veth pairs and usually offers better
+performance than a bridge.
+
+The downside to this is that macvlan devices while able to communicate
+between themselves and to the outside, aren't able to talk to their
+parent device. This means that you can't use macvlan if you ever need
+your containers to talk to the host itself.
+
+In such case, a bridge is preferable. A bridge will also let you use mac
+filtering and I/O limits which cannot be applied to a macvlan device.
+
 ### Type: disk
 Disk entries are essentially mountpoints inside the container. They can
 either be a bind-mount of an existing file or directory on the host, or

From 5a84aa8968bfe8c01d0c4ffe4a9b49f0f7d509a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 24 May 2017 14:48:05 -0400
Subject: [PATCH 0914/1193] shared/cmd: Update to match master
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/cmd/context.go      | 17 +++++++++++++++++
 shared/cmd/context_test.go | 22 ++++++++++++++++++++++
 shared/cmd/testing.go      | 35 +++++++++++++++++++++++++++++++++++
 3 files changed, 74 insertions(+)

diff --git a/shared/cmd/context.go b/shared/cmd/context.go
index 346657ca9..ef192ce31 100644
--- a/shared/cmd/context.go
+++ b/shared/cmd/context.go
@@ -3,7 +3,9 @@ package cmd
 import (
 	"bufio"
 	"fmt"
+	"gopkg.in/yaml.v2"
 	"io"
+	"io/ioutil"
 	"strconv"
 	"strings"
 
@@ -27,6 +29,11 @@ func NewContext(stdin io.Reader, stdout, stderr io.Writer) *Context {
 	}
 }
 
+// Output prints a message on standard output.
+func (c *Context) Output(format string, a ...interface{}) {
+	fmt.Fprintf(c.stdout, format, a...)
+}
+
 // AskBool asks a question an expect a yes/no answer.
 func (c *Context) AskBool(question string, defaultAnswer string) bool {
 	for {
@@ -117,6 +124,16 @@ func (c *Context) AskPassword(question string, reader func(int) ([]byte, error))
 	}
 }
 
+// InputYAML treats stdin as YAML content and returns the unmarshalled
+// structure
+func (c *Context) InputYAML(out interface{}) error {
+	bytes, err := ioutil.ReadAll(c.stdin)
+	if err != nil {
+		return err
+	}
+	return yaml.Unmarshal(bytes, out)
+}
+
 // Ask a question on the output stream and read the answer from the input stream
 func (c *Context) askQuestion(question, defaultAnswer string) string {
 	fmt.Fprintf(c.stdout, question)
diff --git a/shared/cmd/context_test.go b/shared/cmd/context_test.go
index 24006b086..443541e46 100644
--- a/shared/cmd/context_test.go
+++ b/shared/cmd/context_test.go
@@ -8,6 +8,14 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+// Output prints the given message on standard output
+func TestOutput(t *testing.T) {
+	streams := cmd.NewMemoryStreams("")
+	context := cmd.NewMemoryContext(streams)
+	context.Output("Hello %s", "world")
+	streams.AssertOutEqual(t, "Hello world")
+}
+
 // AskBool returns a boolean result depending on the user input.
 func TestAskBool(t *testing.T) {
 	cases := []struct {
@@ -146,3 +154,17 @@ func TestAskPassword(t *testing.T) {
 		streams.AssertErrEqual(t, c.error)
 	}
 }
+
+// InputYAML parses the YAML content passed via stdin.
+func TestInputYAML(t *testing.T) {
+	streams := cmd.NewMemoryStreams("field: foo")
+	context := cmd.NewMemoryContext(streams)
+
+	type Schema struct {
+		Field string
+	}
+	schema := Schema{}
+
+	assert.Nil(t, context.InputYAML(&schema))
+	assert.Equal(t, "foo", schema.Field, "Unexpected field value")
+}
diff --git a/shared/cmd/testing.go b/shared/cmd/testing.go
index faefb48da..a51813e4a 100644
--- a/shared/cmd/testing.go
+++ b/shared/cmd/testing.go
@@ -4,6 +4,7 @@ package cmd
 
 import (
 	"bytes"
+	"io/ioutil"
 	"strings"
 	"testing"
 
@@ -28,6 +29,40 @@ func NewMemoryStreams(input string) *MemoryStreams {
 	}
 }
 
+// InputRead returns the current input string.
+func (s *MemoryStreams) InputRead() string {
+	bytes, _ := ioutil.ReadAll(s.in)
+	return string(bytes)
+}
+
+// InputReset replaces the data in the input stream.
+func (s *MemoryStreams) InputReset(input string) {
+	// XXX This is what the stdlib strings.Reader.Reset() does, however
+	//     this method is not available in Go 1.6.
+	*s.in = *strings.NewReader(input)
+}
+
+// InputAppend adds the given text to the current input.
+func (s *MemoryStreams) InputAppend(text string) {
+	s.InputReset(s.InputRead() + text)
+}
+
+// InputAppendLine adds a single line to the input stream.
+func (s *MemoryStreams) InputAppendLine(line string) {
+	s.InputAppend(line + "\n")
+}
+
+// InputAppendBoolAnswer adds a new "yes" or "no" line depending on the answer.
+func (s *MemoryStreams) InputAppendBoolAnswer(answer bool) {
+	var line string
+	if answer {
+		line = "yes"
+	} else {
+		line = "no"
+	}
+	s.InputAppendLine(line)
+}
+
 // AssertOutEqual checks that the given text matches the the out stream.
 func (s *MemoryStreams) AssertOutEqual(t *testing.T, expected string) {
 	assert.Equal(t, expected, s.out.String(), "Unexpected output stream")

From 07ca3029109036756256cef10599636a9d02fe66 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 24 May 2017 16:33:03 +0200
Subject: [PATCH 0915/1193] Don't depend on testify in the cmd package

Move out the assertions to the test package instead.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 shared/cmd/context_test.go | 32 +++++++++++++++++++++-----------
 shared/cmd/testing.go      | 25 +++++++++++--------------
 2 files changed, 32 insertions(+), 25 deletions(-)

diff --git a/shared/cmd/context_test.go b/shared/cmd/context_test.go
index 443541e46..1e4b0c6bc 100644
--- a/shared/cmd/context_test.go
+++ b/shared/cmd/context_test.go
@@ -8,12 +8,22 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+// AssertOutEqual checks that the given text matches the the out stream.
+func AssertOutEqual(t *testing.T, stream *cmd.MemoryStreams, expected string) {
+	assert.Equal(t, expected, stream.Out(), "Unexpected output stream")
+}
+
+// AssertErrEqual checks that the given text matches the the err stream.
+func AssertErrEqual(t *testing.T, stream *cmd.MemoryStreams, expected string) {
+	assert.Equal(t, expected, stream.Err(), "Unexpected error stream")
+}
+
 // Output prints the given message on standard output
 func TestOutput(t *testing.T) {
 	streams := cmd.NewMemoryStreams("")
 	context := cmd.NewMemoryContext(streams)
 	context.Output("Hello %s", "world")
-	streams.AssertOutEqual(t, "Hello world")
+	AssertOutEqual(t, streams, "Hello world")
 }
 
 // AskBool returns a boolean result depending on the user input.
@@ -39,8 +49,8 @@ func TestAskBool(t *testing.T) {
 		result := context.AskBool(c.question, c.defaultAnswer)
 
 		assert.Equal(t, c.result, result, "Unexpected answer result")
-		streams.AssertOutEqual(t, c.output)
-		streams.AssertErrEqual(t, c.error)
+		AssertOutEqual(t, streams, c.output)
+		AssertErrEqual(t, streams, c.error)
 	}
 }
 
@@ -65,8 +75,8 @@ func TestAskChoice(t *testing.T) {
 		result := context.AskChoice(c.question, c.choices, c.defaultAnswer)
 
 		assert.Equal(t, c.result, result, "Unexpected answer result")
-		streams.AssertOutEqual(t, c.output)
-		streams.AssertErrEqual(t, c.error)
+		AssertOutEqual(t, streams, c.output)
+		AssertErrEqual(t, streams, c.error)
 	}
 }
 
@@ -95,8 +105,8 @@ func TestAskInt(t *testing.T) {
 		result := context.AskInt(c.question, c.min, c.max, c.defaultAnswer)
 
 		assert.Equal(t, c.result, result, "Unexpected answer result")
-		streams.AssertOutEqual(t, c.output)
-		streams.AssertErrEqual(t, c.error)
+		AssertOutEqual(t, streams, c.output)
+		AssertErrEqual(t, streams, c.error)
 	}
 }
 
@@ -126,8 +136,8 @@ func TestAskString(t *testing.T) {
 		result := context.AskString(c.question, c.defaultAnswer, c.validate)
 
 		assert.Equal(t, c.result, result, "Unexpected answer result")
-		streams.AssertOutEqual(t, c.output)
-		streams.AssertErrEqual(t, c.error)
+		AssertOutEqual(t, streams, c.output)
+		AssertErrEqual(t, streams, c.error)
 	}
 }
 
@@ -150,8 +160,8 @@ func TestAskPassword(t *testing.T) {
 		result := context.AskPassword(c.question, c.reader)
 
 		assert.Equal(t, c.result, result, "Unexpected answer result")
-		streams.AssertOutEqual(t, c.output)
-		streams.AssertErrEqual(t, c.error)
+		AssertOutEqual(t, streams, c.output)
+		AssertErrEqual(t, streams, c.error)
 	}
 }
 
diff --git a/shared/cmd/testing.go b/shared/cmd/testing.go
index a51813e4a..bb5eca24f 100644
--- a/shared/cmd/testing.go
+++ b/shared/cmd/testing.go
@@ -1,4 +1,4 @@
-// Utilities for testing cmd-related code.
+// In-memory streams, useful for testing cmd-related code.
 
 package cmd
 
@@ -6,9 +6,6 @@ import (
 	"bytes"
 	"io/ioutil"
 	"strings"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
 )
 
 // MemoryStreams provide an in-memory version of the system
@@ -35,6 +32,16 @@ func (s *MemoryStreams) InputRead() string {
 	return string(bytes)
 }
 
+// Out returns the current content of the out stream.
+func (s *MemoryStreams) Out() string {
+	return s.out.String()
+}
+
+// Err returns the current content of the err stream.
+func (s *MemoryStreams) Err() string {
+	return s.err.String()
+}
+
 // InputReset replaces the data in the input stream.
 func (s *MemoryStreams) InputReset(input string) {
 	// XXX This is what the stdlib strings.Reader.Reset() does, however
@@ -63,16 +70,6 @@ func (s *MemoryStreams) InputAppendBoolAnswer(answer bool) {
 	s.InputAppendLine(line)
 }
 
-// AssertOutEqual checks that the given text matches the the out stream.
-func (s *MemoryStreams) AssertOutEqual(t *testing.T, expected string) {
-	assert.Equal(t, expected, s.out.String(), "Unexpected output stream")
-}
-
-// AssertErrEqual checks that the given text matches the the err stream.
-func (s *MemoryStreams) AssertErrEqual(t *testing.T, expected string) {
-	assert.Equal(t, expected, s.err.String(), "Unexpected error stream")
-}
-
 // NewMemoryContext creates a new command Context using the given in-memory
 // streams.
 func NewMemoryContext(streams *MemoryStreams) *Context {

From 7776e87e1660d40254ad956d10f16b2789caef1b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 17:59:39 -0400
Subject: [PATCH 0916/1193] client: Keep track of protocol
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go | 7 +++++--
 client/lxd.go        | 3 ++-
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/client/connection.go b/client/connection.go
index a9597613d..514a1fd7a 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -43,9 +43,10 @@ func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 
 	// Initialize the client struct
 	server := ProtocolLXD{
+		httpCertificate: args.TLSServerCert,
 		httpHost:        url,
+		httpProtocol:    "https",
 		httpUserAgent:   args.UserAgent,
-		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client
@@ -79,6 +80,7 @@ func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error)
 	// Initialize the client struct
 	server := ProtocolLXD{
 		httpHost:      "http://unix.socket",
+		httpProtocol:  "unix",
 		httpUserAgent: args.UserAgent,
 	}
 
@@ -124,9 +126,10 @@ func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 
 	// Initialize the client struct
 	server := ProtocolLXD{
+		httpCertificate: args.TLSServerCert,
 		httpHost:        url,
+		httpProtocol:    "https",
 		httpUserAgent:   args.UserAgent,
-		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client
diff --git a/client/lxd.go b/client/lxd.go
index 3f8663b38..90b11e6dc 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -22,9 +22,10 @@ type ProtocolLXD struct {
 	eventListenersLock sync.Mutex
 
 	http            *http.Client
+	httpCertificate string
 	httpHost        string
+	httpProtocol    string
 	httpUserAgent   string
-	httpCertificate string
 }
 
 // RawQuery allows directly querying the LXD API

From 551a44119717db8fad04e32888fa53cfbb46146b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 18:01:23 -0400
Subject: [PATCH 0917/1193] client: Implement remote operations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/operations.go | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/client/operations.go b/client/operations.go
index 7f381a011..6520556cb 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -224,3 +224,44 @@ func (op *Operation) extractOperation(data interface{}) *api.Operation {
 
 	return &newOp
 }
+
+// The RemoteOperation type represents an ongoing LXD operation between two servers
+type RemoteOperation struct {
+	targetOp *Operation
+
+	handlers []func(api.Operation)
+
+	chDone chan bool
+	err    error
+}
+
+// Wait lets you wait until the operation reaches a final state
+func (op *RemoteOperation) Wait() error {
+	<-op.chDone
+	return op.err
+}
+
+// AddHandler adds a function to be called whenever an event is received
+func (op *RemoteOperation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
+	var err error
+	var target *EventTarget
+
+	// Attach to the existing target operation
+	if op.targetOp != nil {
+		target, err = op.targetOp.AddHandler(function)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		// Generate a mock EventTarget
+		target = &EventTarget{
+			function: func(interface{}) { function(api.Operation{}) },
+			types:    []string{"operation"},
+		}
+	}
+
+	// Add the handler to our list
+	op.handlers = append(op.handlers, function)
+
+	return target, nil
+}

From 35331c6c87a8f6bf28d5ba45a8ce2019a98b7e24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 18:30:45 -0400
Subject: [PATCH 0918/1193] client: Use RemoteOperation for CopyImage
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go           |  2 +-
 client/lxd.go                  | 22 +++++++++++++++
 client/lxd_images.go           | 61 +++++++++++++++++++++++++++++++++++++++---
 client/simplestreams_images.go | 20 ++++++++++++--
 4 files changed, 99 insertions(+), 6 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index a7194ca3b..6aac7b483 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -25,7 +25,7 @@ type ImageServer interface {
 
 	GetImageAlias(name string) (alias *api.ImageAliasesEntry, ETag string, err error)
 
-	CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (op *Operation, err error)
+	CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (op *RemoteOperation, err error)
 }
 
 // The ContainerServer type represents a full featured LXD server.
diff --git a/client/lxd.go b/client/lxd.go
index 90b11e6dc..69bd4e8e9 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -222,3 +223,24 @@ func (r *ProtocolLXD) websocket(path string) (*websocket.Conn, error) {
 
 	return r.rawWebsocket(url)
 }
+
+// getServerUrls returns a prioritized list of potential URLs for the server
+func (r *ProtocolLXD) getServerUrls() ([]string, error) {
+	if len(r.server.Environment.Addresses) == 0 {
+		return nil, fmt.Errorf("Source server isn't reachable over the network")
+	}
+
+	urls := []string{}
+	if r.httpProtocol == "https" {
+		urls = append(urls, r.httpHost)
+	}
+
+	for _, addr := range r.server.Environment.Addresses {
+		url := fmt.Sprintf("https://%s", addr)
+		if !shared.StringInSlice(url, urls) {
+			urls = append(urls, url)
+		}
+	}
+
+	return urls, nil
+}
diff --git a/client/lxd_images.go b/client/lxd_images.go
index 36d43ca9f..e6c58a692 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -425,15 +425,70 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
 	return &op, nil
 }
 
+// tryCopyImage iterates through the source server URLs until one lets it download the image
+func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, urls []string) (*RemoteOperation, error) {
+	rop := RemoteOperation{
+		chDone: make(chan bool),
+	}
+
+	// Forward targetOp to remote op
+	go func() {
+		success := false
+		errors := []string{}
+		for _, serverURL := range urls {
+			req.Source.Server = serverURL
+
+			op, err := target.CreateImage(req, nil)
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			rop.targetOp = op
+
+			for _, handler := range rop.handlers {
+				rop.targetOp.AddHandler(handler)
+			}
+
+			err = rop.targetOp.Wait()
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			success = true
+			break
+		}
+
+		if !success {
+			rop.err = fmt.Errorf("%s", strings.Join(errors, "\n"))
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
 // CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
-func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*Operation, error) {
+func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*RemoteOperation, error) {
+	// Sanity checks
+	if r == target {
+		return nil, fmt.Errorf("The source and target servers must be different")
+	}
+
+	// Get a list of addresses for the source server
+	urls, err := r.getServerUrls()
+	if err != nil {
+		return nil, err
+	}
+
 	// Prepare the copy request
 	req := api.ImagesPost{
 		Source: &api.ImagesPostSource{
 			ImageSource: api.ImageSource{
 				Certificate: r.httpCertificate,
 				Protocol:    "lxd",
-				Server:      r.httpHost,
 			},
 			Fingerprint: image.Fingerprint,
 			Mode:        "pull",
@@ -457,7 +512,7 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 		req.Public = args.Public
 	}
 
-	return target.CreateImage(req, nil)
+	return r.tryCopyImage(target, req, urls)
 }
 
 // UpdateImage updates the image definition
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index e067c279b..fe1eca0c9 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -144,7 +144,7 @@ func (r *ProtocolSimpleStreams) GetImageAlias(name string) (*api.ImageAliasesEnt
 }
 
 // CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
-func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*Operation, error) {
+func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*RemoteOperation, error) {
 	// Prepare the copy request
 	req := api.ImagesPost{
 		Source: &api.ImagesPostSource{
@@ -165,5 +165,21 @@ func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServe
 		req.Public = args.Public
 	}
 
-	return target.CreateImage(req, nil)
+	op, err := target.CreateImage(req, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	rop := RemoteOperation{
+		targetOp: op,
+		chDone:   make(chan bool),
+	}
+
+	// Forward targetOp to remote op
+	go func() {
+		rop.err = rop.targetOp.Wait()
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
 }

From 9c63c5cb6b583aef8ad1cccb2fade1f54e227591 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 23:11:42 -0400
Subject: [PATCH 0919/1193] client: Improve error on image copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_images.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index e6c58a692..fc84a8e72 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -461,7 +461,7 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 		}
 
 		if !success {
-			rop.err = fmt.Errorf("%s", strings.Join(errors, "\n"))
+			rop.err = fmt.Errorf("Failed remote image download:\n - %s", strings.Join(errors, "\n - "))
 		}
 
 		close(rop.chDone)

From 041f6a7d0af594e2b390bf573599ef20d940ec8c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 29 May 2017 16:39:07 -0400
Subject: [PATCH 0920/1193] client: Add image_create_aliases backward compat
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_images.go           | 33 +++++++++++++++++++++++++++++++++
 client/operations.go           | 27 +++++++++++++++++++++------
 client/simplestreams_images.go | 36 +++++++++++++++++++++++++++++++++++-
 3 files changed, 89 insertions(+), 7 deletions(-)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index fc84a8e72..f8486cfe7 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -431,6 +431,39 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 		chDone: make(chan bool),
 	}
 
+	// For older servers, apply the aliases after copy
+	if !target.HasExtension("image_create_aliases") && req.Aliases != nil {
+		rop.chPost = make(chan bool)
+
+		go func() {
+			defer close(rop.chPost)
+
+			// Wait for the main operation to finish
+			<-rop.chDone
+			if rop.err != nil {
+				return
+			}
+
+			// Get the operation data
+			op, err := rop.GetTarget()
+			if err != nil {
+				return
+			}
+
+			// Extract the fingerprint
+			fingerprint := op.Metadata["fingerprint"].(string)
+
+			// Add the aliases
+			for _, entry := range req.Aliases {
+				alias := api.ImageAliasesPost{}
+				alias.Name = entry.Name
+				alias.Target = fingerprint
+
+				target.CreateImageAlias(alias)
+			}
+		}()
+	}
+
 	// Forward targetOp to remote op
 	go func() {
 		success := false
diff --git a/client/operations.go b/client/operations.go
index 6520556cb..ec092644c 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -232,15 +232,10 @@ type RemoteOperation struct {
 	handlers []func(api.Operation)
 
 	chDone chan bool
+	chPost chan bool
 	err    error
 }
 
-// Wait lets you wait until the operation reaches a final state
-func (op *RemoteOperation) Wait() error {
-	<-op.chDone
-	return op.err
-}
-
 // AddHandler adds a function to be called whenever an event is received
 func (op *RemoteOperation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
 	var err error
@@ -265,3 +260,23 @@ func (op *RemoteOperation) AddHandler(function func(api.Operation)) (*EventTarge
 
 	return target, nil
 }
+
+// GetTarget returns the target operation
+func (op *RemoteOperation) GetTarget() (*api.Operation, error) {
+	if op.targetOp == nil {
+		return nil, fmt.Errorf("No associated target operation")
+	}
+
+	return &op.targetOp.Operation, nil
+}
+
+// Wait lets you wait until the operation reaches a final state
+func (op *RemoteOperation) Wait() error {
+	<-op.chDone
+
+	if op.chPost != nil {
+		<-op.chPost
+	}
+
+	return op.err
+}
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index fe1eca0c9..220c4924e 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -2,8 +2,9 @@ package lxd
 
 import (
 	"fmt"
-	"github.com/lxc/lxd/shared/api"
 	"strings"
+
+	"github.com/lxc/lxd/shared/api"
 )
 
 // Image handling functions
@@ -175,6 +176,39 @@ func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServe
 		chDone:   make(chan bool),
 	}
 
+	// For older servers, apply the aliases after copy
+	if !target.HasExtension("image_create_aliases") && req.Aliases != nil {
+		rop.chPost = make(chan bool)
+
+		go func() {
+			defer close(rop.chPost)
+
+			// Wait for the main operation to finish
+			<-rop.chDone
+			if rop.err != nil {
+				return
+			}
+
+			// Get the operation data
+			op, err := rop.GetTarget()
+			if err != nil {
+				return
+			}
+
+			// Extract the fingerprint
+			fingerprint := op.Metadata["fingerprint"].(string)
+
+			// Add the aliases
+			for _, entry := range req.Aliases {
+				alias := api.ImageAliasesPost{}
+				alias.Name = entry.Name
+				alias.Target = fingerprint
+
+				target.CreateImageAlias(alias)
+			}
+		}()
+	}
+
 	// Forward targetOp to remote op
 	go func() {
 		rop.err = rop.targetOp.Wait()

From 77fe220aa30244b1ac71e43fd1b97dc8b60dfe22 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 29 May 2017 18:56:26 -0400
Subject: [PATCH 0921/1193] client: Move CopyImage to the target server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This avoids a bunch of duplication and better aligns with what we'll do
for CreateFromImage and CopyContainer.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go           | 21 +++++++++--
 client/lxd.go                  | 45 +++++++++++++-----------
 client/lxd_images.go           | 36 ++++++++++++-------
 client/simplestreams.go        | 10 ++++++
 client/simplestreams_images.go | 79 +++---------------------------------------
 test/lxd-benchmark/main.go     |  2 +-
 6 files changed, 81 insertions(+), 112 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index 6aac7b483..8f504d4bc 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -8,14 +8,22 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
+// The Server type represents a generic read-only server.
+type Server interface {
+	GetConnectionInfo() (info *ConnectionInfo, err error)
+}
+
 // The ImageServer type represents a read-only image server.
 type ImageServer interface {
+	Server
+
 	// Image handling functions
 	GetImages() (images []api.Image, err error)
 	GetImageFingerprints() (fingerprints []string, err error)
 
 	GetImage(fingerprint string) (image *api.Image, ETag string, err error)
 	GetImageFile(fingerprint string, req ImageFileRequest) (resp *ImageFileResponse, err error)
+	GetImageSecret(fingerprint string) (secret string, err error)
 
 	GetPrivateImage(fingerprint string, secret string) (image *api.Image, ETag string, err error)
 	GetPrivateImageFile(fingerprint string, secret string, req ImageFileRequest) (resp *ImageFileResponse, err error)
@@ -24,12 +32,12 @@ type ImageServer interface {
 	GetImageAliasNames() (names []string, err error)
 
 	GetImageAlias(name string) (alias *api.ImageAliasesEntry, ETag string, err error)
-
-	CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (op *RemoteOperation, err error)
 }
 
 // The ContainerServer type represents a full featured LXD server.
 type ContainerServer interface {
+	ImageServer
+
 	// Server functions
 	GetServer() (server *api.Server, ETag string, err error)
 	UpdateServer(server api.ServerPut, ETag string) (err error)
@@ -77,8 +85,8 @@ type ContainerServer interface {
 	GetEvents() (listener *EventListener, err error)
 
 	// Image functions
-	ImageServer
 	CreateImage(image api.ImagesPost, args *ImageCreateArgs) (op *Operation, err error)
+	CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (op *RemoteOperation, err error)
 	UpdateImage(fingerprint string, image api.ImagePut, ETag string) (err error)
 	DeleteImage(fingerprint string) (op *Operation, err error)
 	CreateImageSecret(fingerprint string) (op *Operation, err error)
@@ -111,6 +119,13 @@ type ContainerServer interface {
 	RawWebsocket(path string) (conn *websocket.Conn, err error)
 }
 
+// The ConnectionInfo struct represents general information for a connection
+type ConnectionInfo struct {
+	Addresses   []string
+	Certificate string
+	Protocol    string
+}
+
 // The ProgressData struct represents new progress information on an operation
 type ProgressData struct {
 	// Preferred string repreentation of progress (always set)
diff --git a/client/lxd.go b/client/lxd.go
index 69bd4e8e9..4c02eabe4 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -29,6 +29,30 @@ type ProtocolLXD struct {
 	httpUserAgent   string
 }
 
+// GetConnectionInfo returns the basic connection information used to interact with the server
+func (r *ProtocolLXD) GetConnectionInfo() (*ConnectionInfo, error) {
+	info := ConnectionInfo{}
+	info.Certificate = r.httpCertificate
+	info.Protocol = "lxd"
+
+	urls := []string{}
+	if len(r.server.Environment.Addresses) > 0 {
+		if r.httpProtocol == "https" {
+			urls = append(urls, r.httpHost)
+		}
+
+		for _, addr := range r.server.Environment.Addresses {
+			url := fmt.Sprintf("https://%s", addr)
+			if !shared.StringInSlice(url, urls) {
+				urls = append(urls, url)
+			}
+		}
+	}
+	info.Addresses = urls
+
+	return &info, nil
+}
+
 // RawQuery allows directly querying the LXD API
 //
 // This should only be used by internal LXD tools.
@@ -223,24 +247,3 @@ func (r *ProtocolLXD) websocket(path string) (*websocket.Conn, error) {
 
 	return r.rawWebsocket(url)
 }
-
-// getServerUrls returns a prioritized list of potential URLs for the server
-func (r *ProtocolLXD) getServerUrls() ([]string, error) {
-	if len(r.server.Environment.Addresses) == 0 {
-		return nil, fmt.Errorf("Source server isn't reachable over the network")
-	}
-
-	urls := []string{}
-	if r.httpProtocol == "https" {
-		urls = append(urls, r.httpHost)
-	}
-
-	for _, addr := range r.server.Environment.Addresses {
-		url := fmt.Sprintf("https://%s", addr)
-		if !shared.StringInSlice(url, urls) {
-			urls = append(urls, url)
-		}
-	}
-
-	return urls, nil
-}
diff --git a/client/lxd_images.go b/client/lxd_images.go
index f8486cfe7..7f6095da8 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -62,6 +62,16 @@ func (r *ProtocolLXD) GetImageFile(fingerprint string, req ImageFileRequest) (*I
 	return r.GetPrivateImageFile(fingerprint, "", req)
 }
 
+// GetImageSecret is a helper around CreateImageSecret that returns a secret for the image
+func (r *ProtocolLXD) GetImageSecret(fingerprint string) (string, error) {
+	op, err := r.CreateImageSecret(fingerprint)
+	if err != nil {
+		return "", err
+	}
+
+	return op.Metadata["secret"].(string), nil
+}
+
 // GetPrivateImage is similar to GetImage but allows passing a secret download token
 func (r *ProtocolLXD) GetPrivateImage(fingerprint string, secret string) (*api.Image, string, error) {
 	image := api.Image{}
@@ -426,13 +436,13 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
 }
 
 // tryCopyImage iterates through the source server URLs until one lets it download the image
-func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, urls []string) (*RemoteOperation, error) {
+func (r *ProtocolLXD) tryCopyImage(req api.ImagesPost, urls []string) (*RemoteOperation, error) {
 	rop := RemoteOperation{
 		chDone: make(chan bool),
 	}
 
 	// For older servers, apply the aliases after copy
-	if !target.HasExtension("image_create_aliases") && req.Aliases != nil {
+	if !r.HasExtension("image_create_aliases") && req.Aliases != nil {
 		rop.chPost = make(chan bool)
 
 		go func() {
@@ -459,7 +469,7 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 				alias.Name = entry.Name
 				alias.Target = fingerprint
 
-				target.CreateImageAlias(alias)
+				r.CreateImageAlias(alias)
 			}
 		}()
 	}
@@ -471,7 +481,7 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 		for _, serverURL := range urls {
 			req.Source.Server = serverURL
 
-			op, err := target.CreateImage(req, nil)
+			op, err := r.CreateImage(req, nil)
 			if err != nil {
 				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
 				continue
@@ -503,15 +513,15 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 	return &rop, nil
 }
 
-// CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
-func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*RemoteOperation, error) {
+// CopyImage copies an image from a remote server. Additional options can be passed using ImageCopyArgs
+func (r *ProtocolLXD) CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (*RemoteOperation, error) {
 	// Sanity checks
-	if r == target {
+	if r == source {
 		return nil, fmt.Errorf("The source and target servers must be different")
 	}
 
 	// Get a list of addresses for the source server
-	urls, err := r.getServerUrls()
+	info, err := source.GetConnectionInfo()
 	if err != nil {
 		return nil, err
 	}
@@ -520,8 +530,8 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 	req := api.ImagesPost{
 		Source: &api.ImagesPostSource{
 			ImageSource: api.ImageSource{
-				Certificate: r.httpCertificate,
-				Protocol:    "lxd",
+				Certificate: info.Certificate,
+				Protocol:    info.Protocol,
 			},
 			Fingerprint: image.Fingerprint,
 			Mode:        "pull",
@@ -531,12 +541,12 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 
 	// Generate secret token if needed
 	if !image.Public {
-		op, err := r.CreateImageSecret(image.Fingerprint)
+		secret, err := source.GetImageSecret(image.Fingerprint)
 		if err != nil {
 			return nil, err
 		}
 
-		req.Source.Secret = op.Metadata["secret"].(string)
+		req.Source.Secret = secret
 	}
 
 	// Process the arguments
@@ -545,7 +555,7 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 		req.Public = args.Public
 	}
 
-	return r.tryCopyImage(target, req, urls)
+	return r.tryCopyImage(req, info.Addresses)
 }
 
 // UpdateImage updates the image definition
diff --git a/client/simplestreams.go b/client/simplestreams.go
index a69a2ea8d..79fef42ae 100644
--- a/client/simplestreams.go
+++ b/client/simplestreams.go
@@ -15,3 +15,13 @@ type ProtocolSimpleStreams struct {
 	httpUserAgent   string
 	httpCertificate string
 }
+
+// GetConnectionInfo returns the basic connection information used to interact with the server
+func (r *ProtocolSimpleStreams) GetConnectionInfo() (*ConnectionInfo, error) {
+	info := ConnectionInfo{}
+	info.Addresses = []string{r.httpHost}
+	info.Certificate = r.httpCertificate
+	info.Protocol = "simplestreams"
+
+	return &info, nil
+}
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index 220c4924e..d84f8ed09 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -102,6 +102,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 	return &resp, nil
 }
 
+// GetImageSecret isn't relevant for the simplestreams protocol
+func (r *ProtocolSimpleStreams) GetImageSecret(fingerprint string) (string, error) {
+	return "", fmt.Errorf("Private images aren't supported by the simplestreams protocol")
+}
+
 // GetPrivateImage isn't relevant for the simplestreams protocol
 func (r *ProtocolSimpleStreams) GetPrivateImage(fingerprint string, secret string) (*api.Image, string, error) {
 	return nil, "", fmt.Errorf("Private images aren't supported by the simplestreams protocol")
@@ -143,77 +148,3 @@ func (r *ProtocolSimpleStreams) GetImageAlias(name string) (*api.ImageAliasesEnt
 
 	return alias, "", err
 }
-
-// CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
-func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*RemoteOperation, error) {
-	// Prepare the copy request
-	req := api.ImagesPost{
-		Source: &api.ImagesPostSource{
-			ImageSource: api.ImageSource{
-				Certificate: r.httpCertificate,
-				Protocol:    "simplestreams",
-				Server:      r.httpHost,
-			},
-			Fingerprint: image.Fingerprint,
-			Mode:        "pull",
-			Type:        "image",
-		},
-	}
-
-	// Process the arguments
-	if args != nil {
-		req.AutoUpdate = args.AutoUpdate
-		req.Public = args.Public
-	}
-
-	op, err := target.CreateImage(req, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	rop := RemoteOperation{
-		targetOp: op,
-		chDone:   make(chan bool),
-	}
-
-	// For older servers, apply the aliases after copy
-	if !target.HasExtension("image_create_aliases") && req.Aliases != nil {
-		rop.chPost = make(chan bool)
-
-		go func() {
-			defer close(rop.chPost)
-
-			// Wait for the main operation to finish
-			<-rop.chDone
-			if rop.err != nil {
-				return
-			}
-
-			// Get the operation data
-			op, err := rop.GetTarget()
-			if err != nil {
-				return
-			}
-
-			// Extract the fingerprint
-			fingerprint := op.Metadata["fingerprint"].(string)
-
-			// Add the aliases
-			for _, entry := range req.Aliases {
-				alias := api.ImageAliasesPost{}
-				alias.Name = entry.Name
-				alias.Target = fingerprint
-
-				target.CreateImageAlias(alias)
-			}
-		}()
-	}
-
-	// Forward targetOp to remote op
-	go func() {
-		rop.err = rop.targetOp.Wait()
-		close(rop.chDone)
-	}()
-
-	return &rop, nil
-}
diff --git a/test/lxd-benchmark/main.go b/test/lxd-benchmark/main.go
index 686146249..9c8eb3c11 100644
--- a/test/lxd-benchmark/main.go
+++ b/test/lxd-benchmark/main.go
@@ -168,7 +168,7 @@ func spawnContainers(c lxd.ContainerServer, count int, image string, privileged
 				return err
 			}
 
-			op, err := d.CopyImage(*image, c, nil)
+			op, err := c.CopyImage(d, *image, nil)
 			if err != nil {
 				logf(fmt.Sprintf("Failed to import image: %s", err))
 				return err

From 1566f4016706a8e03c23ebef18d4930e8ed7abc3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 29 May 2017 23:58:57 -0400
Subject: [PATCH 0922/1193] client: Add CreateContainerFromImage function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go     |   1 +
 client/lxd_containers.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++
 client/lxd_images.go     |   2 +-
 3 files changed, 109 insertions(+), 1 deletion(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index 8f504d4bc..172887321 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -55,6 +55,7 @@ type ContainerServer interface {
 	GetContainers() (containers []api.Container, err error)
 	GetContainer(name string) (container *api.Container, ETag string, err error)
 	CreateContainer(container api.ContainersPost) (op *Operation, err error)
+	CreateContainerFromImage(source ImageServer, image api.Image, imgcontainer api.ContainersPost) (op *RemoteOperation, err error)
 	UpdateContainer(name string, container api.ContainerPut, ETag string) (op *Operation, err error)
 	RenameContainer(name string, container api.ContainerPost) (op *Operation, err error)
 	MigrateContainer(name string, container api.ContainerPost) (op *Operation, err error)
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index de9fd5f65..17b10b11c 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -71,6 +71,113 @@ func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (*Operation,
 	return op, nil
 }
 
+func (r *ProtocolLXD) tryCreateContainerFromImage(req api.ContainersPost, urls []string) (*RemoteOperation, error) {
+	rop := RemoteOperation{
+		chDone: make(chan bool),
+	}
+
+	// Forward targetOp to remote op
+	go func() {
+		success := false
+		errors := []string{}
+		for _, serverURL := range urls {
+			req.Source.Server = serverURL
+
+			op, err := r.CreateContainer(req)
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			rop.targetOp = op
+
+			for _, handler := range rop.handlers {
+				rop.targetOp.AddHandler(handler)
+			}
+
+			err = rop.targetOp.Wait()
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			success = true
+			break
+		}
+
+		if !success {
+			rop.err = fmt.Errorf("Failed container creation:\n - %s", strings.Join(errors, "\n - "))
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
+// CreateContainerFromImage is a convenience function to make it easier to create a container from an existing image
+func (r *ProtocolLXD) CreateContainerFromImage(source ImageServer, image api.Image, req api.ContainersPost) (*RemoteOperation, error) {
+	// Set the minimal source fields
+	req.Source.Type = "image"
+
+	// Optimization for the local image case
+	if r == source {
+		// Always use fingerprints for local case
+		req.Source.Fingerprint = image.Fingerprint
+		req.Source.Alias = ""
+
+		op, err := r.CreateContainer(req)
+		if err != nil {
+			return nil, err
+		}
+
+		rop := RemoteOperation{
+			targetOp: op,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Minimal source fields for remote image
+	req.Source.Mode = "pull"
+
+	// If we have an alias and the image is public, use that
+	if req.Source.Alias != "" && image.Public {
+		req.Source.Fingerprint = ""
+	} else {
+		req.Source.Fingerprint = image.Fingerprint
+		req.Source.Alias = ""
+	}
+
+	// Get source server connection information
+	info, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, err
+	}
+
+	req.Source.Protocol = info.Protocol
+	req.Source.Certificate = info.Certificate
+
+	// Generate secret token if needed
+	if !image.Public {
+		secret, err := source.GetImageSecret(image.Fingerprint)
+		if err != nil {
+			return nil, err
+		}
+
+		req.Source.Secret = secret
+	}
+
+	return r.tryCreateContainerFromImage(req, info.Addresses)
+}
+
 // UpdateContainer updates the container definition
 func (r *ProtocolLXD) UpdateContainer(name string, container api.ContainerPut, ETag string) (*Operation, error) {
 	// Send the request
diff --git a/client/lxd_images.go b/client/lxd_images.go
index 7f6095da8..f427458d3 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -520,7 +520,7 @@ func (r *ProtocolLXD) CopyImage(source ImageServer, image api.Image, args *Image
 		return nil, fmt.Errorf("The source and target servers must be different")
 	}
 
-	// Get a list of addresses for the source server
+	// Get source server connection information
 	info, err := source.GetConnectionInfo()
 	if err != nil {
 		return nil, err

From 9c1dffbcc7fc866905c7c8bd5fe897eae0703a38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 30 May 2017 01:07:44 -0400
Subject: [PATCH 0923/1193] images: Fix potential double unlock
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 1394819ec..2f9e918f5 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -251,9 +251,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			// Other download succeeded, we're done
 			return imgInfo, nil
 		}
+	} else {
+		imagesDownloadingLock.Unlock()
 	}
 
 	// Add the download to the queue
+	imagesDownloadingLock.Lock()
 	imagesDownloading[fp] = make(chan bool)
 	imagesDownloadingLock.Unlock()
 

From 6afc3177709245e02d6805d7c3824e164c35c139 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 30 May 2017 10:47:11 +0200
Subject: [PATCH 0924/1193] Fix static-analysis target.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 Makefile | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Makefile b/Makefile
index 4bc77ce90..ab0a295fa 100644
--- a/Makefile
+++ b/Makefile
@@ -57,25 +57,25 @@ gccgo:
 dist:
 	# Cleanup
 	rm -Rf $(ARCHIVE).gz
-	
+
 	# Create build dir
 	$(eval TMP := $(shell mktemp -d))
 	git archive --prefix=lxd-$(VERSION)/ HEAD | tar -x -C $(TMP)
 	mkdir -p $(TMP)/dist/src/github.com/lxc
 	ln -s ../../../../lxd-$(VERSION) $(TMP)/dist/src/github.com/lxc/lxd
-	
+
 	# Download dependencies
 	cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -t -v -d ./...
-	
+
 	# Workaround for gorilla/mux on Go < 1.7
 	cd $(TMP)/lxd-$(VERSION) && GOPATH=$(TMP)/dist go get -v -d github.com/gorilla/context
-	
+
 	# Assemble tarball
 	rm $(TMP)/dist/src/github.com/lxc/lxd
 	ln -s ../../../../ $(TMP)/dist/src/github.com/lxc/lxd
 	mv $(TMP)/dist $(TMP)/lxd-$(VERSION)/
 	tar --exclude-vcs -C $(TMP) -zcf $(ARCHIVE).gz lxd-$(VERSION)/
-	
+
 	# Cleanup
 	rm -Rf $(TMP)
 
@@ -101,7 +101,7 @@ update-pot:
 build-mo: $(MOFILES)
 
 static-analysis:
-	/bin/bash -x -c ". test/static_analysis.sh; static_analysis"
+	(cd test;  /bin/sh -x -c ". suites/static_analysis.sh; test_static_analysis")
 
 tags: *.go lxd/*.go shared/*.go lxc/*.go
 	find . | grep \.go | grep -v git | grep -v .swp | grep -v vagrant | xargs gotags > tags

From 66be78d2a4ef6db44927659de9c489f66548412c Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Wed, 31 May 2017 08:57:30 +0300
Subject: [PATCH 0925/1193] Read file perms from Windows FS (fixes #3363)

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 shared/util_windows.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/util_windows.go b/shared/util_windows.go
index 7f91ba0fd..7a480f5bc 100644
--- a/shared/util_windows.go
+++ b/shared/util_windows.go
@@ -7,5 +7,5 @@ import (
 )
 
 func GetOwnerMode(fInfo os.FileInfo) (os.FileMode, int, int) {
-	return os.FileMode(0), -1, -1
+	return fInfo.Mode(), -1, -1
 }

From bd7c37466e46c243535975355162b3f23e886b39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 2 Jun 2017 17:20:25 -0400
Subject: [PATCH 0926/1193] Sync shared/api with master branch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This means that shared/api will now contain everything which was added
through API extensions after the release of LXD 2.0.

The LXD daemon in the 2.0 LTS branch will not be using any of those
added features, but having the API defintions in sync will allow for
keeping the client library in sync too (which is extension aware) and
overall make maintenance much simpler.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/certificate.go     | 21 +++++++++---
 shared/api/container.go       | 25 +++++++++++++--
 shared/api/container_exec.go  |  3 ++
 shared/api/container_state.go | 10 ++++++
 shared/api/image.go           |  6 ++++
 shared/api/network.go         | 38 ++++++++++++++++++++++
 shared/api/profile.go         |  3 ++
 shared/api/storage.go         | 74 +++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 174 insertions(+), 6 deletions(-)
 create mode 100644 shared/api/storage.go

diff --git a/shared/api/certificate.go b/shared/api/certificate.go
index f5b4c4d88..56664fa6c 100644
--- a/shared/api/certificate.go
+++ b/shared/api/certificate.go
@@ -2,16 +2,29 @@ package api
 
 // CertificatesPost represents the fields of a new LXD certificate
 type CertificatesPost struct {
-	Name        string `json:"name" yaml:"name"`
-	Type        string `json:"type" yaml:"type"`
+	CertificatePut `yaml:",inline"`
+
 	Certificate string `json:"certificate" yaml:"certificate"`
 	Password    string `json:"password" yaml:"password"`
 }
 
+// CertificatePut represents the modifiable fields of a LXD certificate
+//
+// API extension: certificate_update
+type CertificatePut struct {
+	Name string `json:"name" yaml:"name"`
+	Type string `json:"type" yaml:"type"`
+}
+
 // Certificate represents a LXD certificate
 type Certificate struct {
-	Name        string `json:"name" yaml:"name"`
-	Type        string `json:"type" yaml:"type"`
+	CertificatePut `yaml:",inline"`
+
 	Certificate string `json:"certificate" yaml:"certificate"`
 	Fingerprint string `json:"fingerprint" yaml:"fingerprint"`
 }
+
+// Writable converts a full Certificate struct into a CertificatePut struct (filters read-only fields)
+func (cert *Certificate) Writable() CertificatePut {
+	return cert.CertificatePut
+}
diff --git a/shared/api/container.go b/shared/api/container.go
index 5fd43dd7f..a9d916164 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -14,12 +14,24 @@ type ContainersPost struct {
 
 // ContainerPost represents the fields required to rename/move a LXD container
 type ContainerPost struct {
-	Migration bool   `json:"migration" yaml:"migration"`
-	Name      string `json:"name" yaml:"name"`
+	// Used for renames
+	Name string `json:"name" yaml:"name"`
+
+	// Used for migration
+	Migration bool `json:"migration" yaml:"migration"`
+
+	// API extension: container_stateless_copy
+	Live bool `json:"live" yaml:"live"`
+
+	// API extension: container_only_migration
+	ContainerOnly bool `json:"container_only" yaml:"container_only"`
 }
 
 // ContainerPut represents the modifiable fields of a LXD container
 type ContainerPut struct {
+	// API extension: entity_description
+	Description string `json:"description" yaml:"description"`
+
 	Architecture string                       `json:"architecture" yaml:"architecture"`
 	Config       map[string]string            `json:"config" yaml:"config"`
 	Devices      map[string]map[string]string `json:"devices" yaml:"devices"`
@@ -41,6 +53,9 @@ type Container struct {
 	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"`
 }
 
 // Writable converts a full Container struct into a ContainerPut struct (filters read-only fields)
@@ -81,6 +96,12 @@ type ContainerSource struct {
 	Operation  string            `json:"operation,omitempty" yaml:"operation,omitempty"`
 	Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
 
+	// API extension: container_push
+	Live bool `json:"live,omitempty" yaml:"live,omitempty"`
+
 	// For "copy" type
 	Source string `json:"source,omitempty" yaml:"source,omitempty"`
+
+	// API extension: container_only_migration
+	ContainerOnly bool `json:"container_only,omitempty" yaml:"container_only,omitempty"`
 }
diff --git a/shared/api/container_exec.go b/shared/api/container_exec.go
index d1548e603..e243749b4 100644
--- a/shared/api/container_exec.go
+++ b/shared/api/container_exec.go
@@ -15,4 +15,7 @@ type ContainerExecPost struct {
 	Environment map[string]string `json:"environment" yaml:"environment"`
 	Width       int               `json:"width" yaml:"width"`
 	Height      int               `json:"height" yaml:"height"`
+
+	// API extension: container_exec_recording
+	RecordOutput bool `json:"record-output" yaml:"record-output"`
 }
diff --git a/shared/api/container_state.go b/shared/api/container_state.go
index 4606c2c4c..f9e1cb9b5 100644
--- a/shared/api/container_state.go
+++ b/shared/api/container_state.go
@@ -17,6 +17,9 @@ type ContainerState struct {
 	Network    map[string]ContainerStateNetwork `json:"network" yaml:"network"`
 	Pid        int64                            `json:"pid" yaml:"pid"`
 	Processes  int64                            `json:"processes" yaml:"processes"`
+
+	// API extension: container_cpu_time
+	CPU ContainerStateCPU `json:"cpu" yaml:"cpu"`
 }
 
 // ContainerStateDisk represents the disk information section of a LXD container's state
@@ -24,6 +27,13 @@ type ContainerStateDisk struct {
 	Usage int64 `json:"usage" yaml:"usage"`
 }
 
+// ContainerStateCPU represents the cpu information section of a LXD container's state
+//
+// API extension: container_cpu_time
+type ContainerStateCPU struct {
+	Usage int64 `json:"usage" yaml:"usage"`
+}
+
 // ContainerStateMemory represents the memory information section of a LXD container's state
 type ContainerStateMemory struct {
 	Usage         int64 `json:"usage" yaml:"usage"`
diff --git a/shared/api/image.go b/shared/api/image.go
index f88751a37..814c0981d 100644
--- a/shared/api/image.go
+++ b/shared/api/image.go
@@ -10,6 +10,12 @@ type ImagesPost struct {
 
 	Filename string            `json:"filename" yaml:"filename"`
 	Source   *ImagesPostSource `json:"source" yaml:"source"`
+
+	// API extension: image_compression_algorithm
+	CompressionAlgorithm string `json:"compression_algorithm" yaml:"compression_algorithm"`
+
+	// API extension: image_create_aliases
+	Aliases []ImageAlias `json:"aliases" yaml:"aliases"`
 }
 
 // ImagesPostSource represents the source of a new LXD image
diff --git a/shared/api/network.go b/shared/api/network.go
index 28a8206e4..2b390ff23 100644
--- a/shared/api/network.go
+++ b/shared/api/network.go
@@ -1,8 +1,46 @@
 package api
 
+// NetworksPost represents the fields of a new LXD network
+//
+// API extension: network
+type NetworksPost struct {
+	NetworkPut `yaml:",inline"`
+
+	Managed bool   `json:"managed" yaml:"managed"`
+	Name    string `json:"name" yaml:"name"`
+	Type    string `json:"type" yaml:"type"`
+}
+
+// NetworkPost represents the fields required to rename a LXD network
+//
+// API extension: network
+type NetworkPost struct {
+	Name string `json:"name" yaml:"name"`
+}
+
+// NetworkPut represents the modifiable fields of a LXD network
+//
+// API extension: network
+type NetworkPut struct {
+	// API extension: entity_description
+	Description string `json:"description" yaml:"description"`
+
+	Config map[string]string `json:"config" yaml:"config"`
+}
+
 // Network represents a LXD network
 type Network struct {
+	NetworkPut `yaml:",inline"`
+
 	Name   string   `json:"name" yaml:"name"`
 	Type   string   `json:"type" yaml:"type"`
 	UsedBy []string `json:"used_by" yaml:"used_by"`
+
+	// API extension: network
+	Managed bool `json:"managed" yaml:"managed"`
+}
+
+// Writable converts a full Network struct into a NetworkPut struct (filters read-only fields)
+func (network *Network) Writable() NetworkPut {
+	return network.NetworkPut
 }
diff --git a/shared/api/profile.go b/shared/api/profile.go
index 30bdbba3d..c7d7f4e18 100644
--- a/shared/api/profile.go
+++ b/shared/api/profile.go
@@ -24,6 +24,9 @@ type Profile struct {
 	ProfilePut `yaml:",inline"`
 
 	Name string `json:"name" yaml:"name"`
+
+	// API extension: profile_usedby
+	UsedBy []string `json:"used_by" yaml:"used_by"`
 }
 
 // Writable converts a full Profile struct into a ProfilePut struct (filters read-only fields)
diff --git a/shared/api/storage.go b/shared/api/storage.go
new file mode 100644
index 000000000..7a1be284b
--- /dev/null
+++ b/shared/api/storage.go
@@ -0,0 +1,74 @@
+package api
+
+// StoragePoolsPost represents the fields of a new LXD storage pool
+//
+// API extension: storage
+type StoragePoolsPost struct {
+	StoragePoolPut `yaml:",inline"`
+
+	Name   string `json:"name" yaml:"name"`
+	Driver string `json:"driver" yaml:"driver"`
+}
+
+// StoragePool represents the fields of a LXD storage pool.
+//
+// API extension: storage
+type StoragePool struct {
+	StoragePoolPut `yaml:",inline"`
+
+	Name   string   `json:"name" yaml:"name"`
+	Driver string   `json:"driver" yaml:"driver"`
+	UsedBy []string `json:"used_by" yaml:"used_by"`
+}
+
+// StoragePoolPut represents the modifiable fields of a LXD storage pool.
+//
+// API extension: storage
+type StoragePoolPut struct {
+	// API extension: entity_description
+	Description string `json:"description" yaml:"description"`
+
+	Config map[string]string `json:"config" yaml:"config"`
+}
+
+// StorageVolumesPost represents the fields of a new LXD storage pool volume
+//
+// API extension: storage
+type StorageVolumesPost struct {
+	StorageVolumePut `yaml:",inline"`
+
+	Name string `json:"name" yaml:"name"`
+	Type string `json:"type" yaml:"type"`
+}
+
+// StorageVolume represents the fields of a LXD storage volume.
+//
+// API extension: storage
+type StorageVolume struct {
+	StorageVolumePut `yaml:",inline"`
+	Name             string   `json:"name" yaml:"name"`
+	Type             string   `json:"type" yaml:"type"`
+	UsedBy           []string `json:"used_by" yaml:"used_by"`
+}
+
+// StorageVolumePut represents the modifiable fields of a LXD storage volume.
+//
+// API extension: storage
+type StorageVolumePut struct {
+	// API extension: entity_description
+	Description string `json:"description" yaml:"description"`
+
+	Config map[string]string `json:"config" yaml:"config"`
+}
+
+// Writable converts a full StoragePool struct into a StoragePoolPut struct
+// (filters read-only fields).
+func (storagePool *StoragePool) Writable() StoragePoolPut {
+	return storagePool.StoragePoolPut
+}
+
+// Writable converts a full StorageVolume struct into a StorageVolumePut struct
+// (filters read-only fields).
+func (storageVolume *StorageVolume) Writable() StorageVolumePut {
+	return storageVolume.StorageVolumePut
+}

From 40120da5717b680f0b6e84bec515a3bedcd2ec37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 2 Jun 2017 17:22:31 -0400
Subject: [PATCH 0927/1193] Sync client with master branch
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This syncs the new client library with the LXD master branch.

As the new client is aware of API extensions, it can safely be used from
older LXD releases as it will only ever use new APIs if they're
supported by all affected servers.

This will make maintenance of the stable branch much easier.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go          | 11 +++--
 client/interfaces.go          | 37 +++++++++++++++++
 client/lxd_certificates.go    | 15 +++++++
 client/lxd_containers.go      | 68 +++++++++++++++++++++++++++++-
 client/lxd_images.go          | 29 +++++++++++++
 client/lxd_networks.go        | 48 +++++++++++++++++++++
 client/lxd_storage_pools.go   | 97 +++++++++++++++++++++++++++++++++++++++++++
 client/lxd_storage_volumes.go | 93 +++++++++++++++++++++++++++++++++++++++++
 client/util.go                |  4 +-
 9 files changed, 396 insertions(+), 6 deletions(-)
 create mode 100644 client/lxd_storage_pools.go
 create mode 100644 client/lxd_storage_volumes.go

diff --git a/client/connection.go b/client/connection.go
index 514a1fd7a..02e0e6aa6 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -21,6 +21,9 @@ type ConnectionArgs struct {
 	// TLS key to use for client authentication.
 	TLSClientKey string
 
+	// TLS CA to validate against when in PKI mode.
+	TLSCA string
+
 	// User agent string
 	UserAgent string
 
@@ -32,6 +35,8 @@ type ConnectionArgs struct {
 //
 // A client certificate (TLSClientCert) and key (TLSClientKey) must be provided.
 //
+// If connecting to a LXD daemon running in PKI mode, the PKI CA (TLSCA) must also be provided.
+//
 // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
 func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 	logger.Infof("Connecting to a remote LXD over HTTPs")
@@ -50,7 +55,7 @@ func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 	}
 
 	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSServerCert, args.Proxy)
+	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
 	if err != nil {
 		return nil, err
 	}
@@ -133,7 +138,7 @@ func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 	}
 
 	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSServerCert, args.Proxy)
+	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
 	if err != nil {
 		return nil, err
 	}
@@ -167,7 +172,7 @@ func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error)
 	}
 
 	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSServerCert, args.Proxy)
+	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
 	if err != nil {
 		return nil, err
 	}
diff --git a/client/interfaces.go b/client/interfaces.go
index 172887321..e5541564d 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -48,6 +48,7 @@ type ContainerServer interface {
 	GetCertificates() (certificates []api.Certificate, err error)
 	GetCertificate(fingerprint string) (certificate *api.Certificate, ETag string, err error)
 	CreateCertificate(certificate api.CertificatesPost) (err error)
+	UpdateCertificate(fingerprint string, certificate api.CertificatePut, ETag string) (err error)
 	DeleteCertificate(fingerprint string) (err error)
 
 	// Container functions
@@ -90,6 +91,7 @@ type ContainerServer interface {
 	CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (op *RemoteOperation, err error)
 	UpdateImage(fingerprint string, image api.ImagePut, ETag string) (err error)
 	DeleteImage(fingerprint string) (op *Operation, err error)
+	RefreshImage(fingerprint string) (op *Operation, err error)
 	CreateImageSecret(fingerprint string) (op *Operation, err error)
 	CreateImageAlias(alias api.ImageAliasesPost) (err error)
 	UpdateImageAlias(name string, alias api.ImageAliasesEntryPut, ETag string) (err error)
@@ -100,6 +102,10 @@ type ContainerServer interface {
 	GetNetworkNames() (names []string, err error)
 	GetNetworks() (networks []api.Network, err error)
 	GetNetwork(name string) (network *api.Network, ETag string, err error)
+	CreateNetwork(network api.NetworksPost) (err error)
+	UpdateNetwork(name string, network api.NetworkPut, ETag string) (err error)
+	RenameNetwork(name string, network api.NetworkPost) (err error)
+	DeleteNetwork(name string) (err error)
 
 	// Operation functions
 	GetOperation(uuid string) (op *api.Operation, ETag string, err error)
@@ -115,6 +121,22 @@ type ContainerServer interface {
 	RenameProfile(name string, profile api.ProfilePost) (err error)
 	DeleteProfile(name string) (err error)
 
+	// Storage pool functions ("storage" API extension)
+	GetStoragePoolNames() (names []string, err error)
+	GetStoragePools() (pools []api.StoragePool, err error)
+	GetStoragePool(name string) (pool *api.StoragePool, ETag string, err error)
+	CreateStoragePool(pool api.StoragePoolsPost) (err error)
+	UpdateStoragePool(name string, pool api.StoragePoolPut, ETag string) (err error)
+	DeleteStoragePool(name string) (err error)
+
+	// Storage volume functions ("storage" API extension)
+	GetStoragePoolVolumeNames(pool string) (names []string, err error)
+	GetStoragePoolVolumes(pool string) (volumes []api.StorageVolume, err error)
+	GetStoragePoolVolume(pool string, volType string, name string) (volume *api.StorageVolume, ETag string, err error)
+	CreateStoragePoolVolume(pool string, volume api.StorageVolumesPost) (err error)
+	UpdateStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePut, ETag string) (err error)
+	DeleteStoragePoolVolume(pool string, volType string, name string) (err error)
+
 	// Internal functions (for internal use)
 	RawQuery(method string, path string, data interface{}, queryETag string) (resp *api.Response, ETag string, err error)
 	RawWebsocket(path string) (conn *websocket.Conn, err error)
@@ -195,6 +217,9 @@ type ImageCopyArgs struct {
 	// Whether to have LXD keep this image up to date
 	AutoUpdate bool
 
+	// Whether to copy the source image aliases to the target
+	CopyAliases bool
+
 	// Whether this image is to be made available to unauthenticated users
 	Public bool
 }
@@ -227,6 +252,12 @@ type ContainerFileArgs struct {
 
 	// File permissions
 	Mode int
+
+	// File type (file or directory)
+	Type string
+
+	// File write mode (overwrite or append)
+	WriteMode string
 }
 
 // The ContainerFileResponse struct is used as part of the response for a container file download
@@ -239,4 +270,10 @@ type ContainerFileResponse struct {
 
 	// File permissions
 	Mode int
+
+	// File type (file or directory)
+	Type string
+
+	// If a directory, the list of files inside it
+	Entries []string
 }
diff --git a/client/lxd_certificates.go b/client/lxd_certificates.go
index 4eb275bcd..bf5349599 100644
--- a/client/lxd_certificates.go
+++ b/client/lxd_certificates.go
@@ -66,6 +66,21 @@ func (r *ProtocolLXD) CreateCertificate(certificate api.CertificatesPost) error
 	return nil
 }
 
+// UpdateCertificate updates the certificate definition
+func (r *ProtocolLXD) UpdateCertificate(fingerprint string, certificate api.CertificatePut, ETag string) error {
+	if !r.HasExtension("certificate_update") {
+		return fmt.Errorf("The server is missing the required \"certificate_update\" API extension")
+	}
+
+	// Send the request
+	_, _, err := r.query("PUT", fmt.Sprintf("/certificates/%s", fingerprint), certificate, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // DeleteCertificate removes a certificate from the LXD trust store
 func (r *ProtocolLXD) DeleteCertificate(fingerprint string) error {
 	// Send the request
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 17b10b11c..ead43a04a 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -1,6 +1,7 @@
 package lxd
 
 import (
+	"encoding/json"
 	"fmt"
 	"io"
 	"net/http"
@@ -62,6 +63,12 @@ func (r *ProtocolLXD) GetContainer(name string) (*api.Container, string, error)
 
 // CreateContainer requests that LXD creates a new container
 func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (*Operation, error) {
+	if container.Source.ContainerOnly {
+		if !r.HasExtension("container_only_migration") {
+			return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+		}
+	}
+
 	// Send the request
 	op, _, err := r.queryOperation("POST", "/containers", container, "")
 	if err != nil {
@@ -207,6 +214,12 @@ func (r *ProtocolLXD) RenameContainer(name string, container api.ContainerPost)
 
 // MigrateContainer requests that LXD prepares for a container migration
 func (r *ProtocolLXD) MigrateContainer(name string, container api.ContainerPost) (*Operation, error) {
+	if container.ContainerOnly {
+		if !r.HasExtension("container_only_migration") {
+			return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+		}
+	}
+
 	// Sanity check
 	if !container.Migration {
 		return nil, fmt.Errorf("Can't ask for a rename through MigrateContainer")
@@ -234,6 +247,12 @@ func (r *ProtocolLXD) DeleteContainer(name string) (*Operation, error) {
 
 // ExecContainer requests that LXD spawns a command inside the container
 func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (*Operation, error) {
+	if exec.RecordOutput {
+		if !r.HasExtension("container_exec_recording") {
+			return nil, fmt.Errorf("The server is missing the required \"container_exec_recording\" API extension")
+		}
+	}
+
 	// Send the request
 	op, _, err := r.queryOperation("POST", fmt.Sprintf("/containers/%s/exec", containerName), exec, "")
 	if err != nil {
@@ -363,11 +382,34 @@ func (r *ProtocolLXD) GetContainerFile(containerName string, path string) (io.Re
 	}
 
 	// Parse the headers
-	uid, gid, mode := shared.ParseLXDFileHeaders(resp.Header)
+	uid, gid, mode, fileType, _ := shared.ParseLXDFileHeaders(resp.Header)
 	fileResp := ContainerFileResponse{
 		UID:  uid,
 		GID:  gid,
 		Mode: mode,
+		Type: fileType,
+	}
+
+	if fileResp.Type == "directory" {
+		// Decode the response
+		response := api.Response{}
+		decoder := json.NewDecoder(resp.Body)
+
+		err = decoder.Decode(&response)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		// Get the file list
+		entries := []string{}
+		err = response.MetadataAsStruct(&entries)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		fileResp.Entries = entries
+
+		return nil, &fileResp, err
 	}
 
 	return resp.Body, &fileResp, err
@@ -375,6 +417,18 @@ func (r *ProtocolLXD) GetContainerFile(containerName string, path string) (io.Re
 
 // CreateContainerFile tells LXD to create a file in the container
 func (r *ProtocolLXD) CreateContainerFile(containerName string, path string, args ContainerFileArgs) error {
+	if args.Type == "directory" {
+		if !r.HasExtension("directory_manipulation") {
+			return fmt.Errorf("The server is missing the required \"directory_manipulation\" API extension")
+		}
+	}
+
+	if args.WriteMode == "append" {
+		if !r.HasExtension("file_append") {
+			return fmt.Errorf("The server is missing the required \"file_append\" API extension")
+		}
+	}
+
 	// Prepare the HTTP request
 	url := fmt.Sprintf("%s/1.0/containers/%s/files?path=%s", r.httpHost, containerName, path)
 	req, err := http.NewRequest("POST", url, args.Content)
@@ -392,6 +446,14 @@ func (r *ProtocolLXD) CreateContainerFile(containerName string, path string, arg
 	req.Header.Set("X-LXD-gid", fmt.Sprintf("%d", args.GID))
 	req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", args.Mode))
 
+	if args.Type != "" {
+		req.Header.Set("X-LXD-type", args.Type)
+	}
+
+	if args.WriteMode != "" {
+		req.Header.Set("X-LXD-write", args.WriteMode)
+	}
+
 	// Send the request
 	resp, err := r.http.Do(req)
 	if err != nil {
@@ -408,6 +470,10 @@ func (r *ProtocolLXD) CreateContainerFile(containerName string, path string, arg
 
 // DeleteContainerFile deletes a file in the container
 func (r *ProtocolLXD) DeleteContainerFile(containerName string, path string) error {
+	if !r.HasExtension("file_delete") {
+		return fmt.Errorf("The server is missing the required \"file_delete\" API extension")
+	}
+
 	// Send the request
 	_, _, err := r.query("DELETE", fmt.Sprintf("/containers/%s/files?path=%s", containerName, path), nil, "")
 	if err != nil {
diff --git a/client/lxd_images.go b/client/lxd_images.go
index f427458d3..f9065e3e5 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -277,6 +277,12 @@ func (r *ProtocolLXD) GetImageAlias(name string) (*api.ImageAliasesEntry, string
 
 // CreateImage requests that LXD creates, copies or import a new image
 func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (*Operation, error) {
+	if image.CompressionAlgorithm != "" {
+		if !r.HasExtension("image_compression_algorithm") {
+			return nil, fmt.Errorf("The server is missing the required \"image_compression_algorithm\" API extension")
+		}
+	}
+
 	// Send the JSON based request
 	if args == nil {
 		op, _, err := r.queryOperation("POST", "/images", image, "")
@@ -551,8 +557,16 @@ func (r *ProtocolLXD) CopyImage(source ImageServer, image api.Image, args *Image
 
 	// Process the arguments
 	if args != nil {
+		req.Aliases = args.Aliases
 		req.AutoUpdate = args.AutoUpdate
 		req.Public = args.Public
+
+		if args.CopyAliases {
+			req.Aliases = image.Aliases
+			if args.Aliases != nil {
+				req.Aliases = append(req.Aliases, args.Aliases...)
+			}
+		}
 	}
 
 	return r.tryCopyImage(req, info.Addresses)
@@ -580,6 +594,21 @@ func (r *ProtocolLXD) DeleteImage(fingerprint string) (*Operation, error) {
 	return op, nil
 }
 
+// RefreshImage requests that LXD issues an image refresh
+func (r *ProtocolLXD) RefreshImage(fingerprint string) (*Operation, error) {
+	if !r.HasExtension("image_force_refresh") {
+		return nil, fmt.Errorf("The server is missing the required \"image_force_refresh\" API extension")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/refresh", fingerprint), nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
 // CreateImageSecret requests that LXD issues a temporary image secret
 func (r *ProtocolLXD) CreateImageSecret(fingerprint string) (*Operation, error) {
 	// Send the request
diff --git a/client/lxd_networks.go b/client/lxd_networks.go
index 755bb2b84..cbc3fd30a 100644
--- a/client/lxd_networks.go
+++ b/client/lxd_networks.go
@@ -52,3 +52,51 @@ func (r *ProtocolLXD) GetNetwork(name string) (*api.Network, string, error) {
 
 	return &network, etag, nil
 }
+
+// CreateNetwork defines a new network using the provided Network struct
+func (r *ProtocolLXD) CreateNetwork(network api.NetworksPost) error {
+	if !r.HasExtension("network") {
+		return fmt.Errorf("The server is missing the required \"network\" API extension")
+	}
+
+	// Send the request
+	_, _, err := r.query("POST", "/networks", network, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UpdateNetwork updates the network to match the provided Network struct
+func (r *ProtocolLXD) UpdateNetwork(name string, network api.NetworkPut, ETag string) error {
+	// Send the request
+	_, _, err := r.query("PUT", fmt.Sprintf("/networks/%s", name), network, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// RenameNetwork renames an existing network entry
+func (r *ProtocolLXD) RenameNetwork(name string, network api.NetworkPost) error {
+	// Send the request
+	_, _, err := r.query("POST", fmt.Sprintf("/networks/%s", name), network, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteNetwork deletes an existing network
+func (r *ProtocolLXD) DeleteNetwork(name string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/networks/%s", name), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/client/lxd_storage_pools.go b/client/lxd_storage_pools.go
new file mode 100644
index 000000000..fb49058f5
--- /dev/null
+++ b/client/lxd_storage_pools.go
@@ -0,0 +1,97 @@
+package lxd
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// Storage pool handling functions
+
+// GetStoragePoolNames returns the names of all storage pools
+func (r *ProtocolLXD) GetStoragePoolNames() ([]string, error) {
+	if !r.HasExtension("storage") {
+		return nil, fmt.Errorf("The server is missing the required \"storage\" API extension")
+	}
+
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/storage-pools", nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, url := range urls {
+		fields := strings.Split(url, "/storage-pools/")
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetStoragePools returns a list of StoragePool entries
+func (r *ProtocolLXD) GetStoragePools() ([]api.StoragePool, error) {
+	pools := []api.StoragePool{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", "/storage-pools?recursion=1", nil, "", &pools)
+	if err != nil {
+		return nil, err
+	}
+
+	return pools, nil
+}
+
+// GetStoragePool returns a StoragePool entry for the provided pool name
+func (r *ProtocolLXD) GetStoragePool(name string) (*api.StoragePool, string, error) {
+	pool := api.StoragePool{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/storage-pools/%s", name), nil, "", &pool)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &pool, etag, nil
+}
+
+// CreateStoragePool defines a new storage pool using the provided StoragePool struct
+func (r *ProtocolLXD) CreateStoragePool(pool api.StoragePoolsPost) error {
+	if !r.HasExtension("storage") {
+		return fmt.Errorf("The server is missing the required \"storage\" API extension")
+	}
+
+	// Send the request
+	_, _, err := r.query("POST", "/storage-pools", pool, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UpdateStoragePool updates the pool to match the provided StoragePool struct
+func (r *ProtocolLXD) UpdateStoragePool(name string, pool api.StoragePoolPut, ETag string) error {
+	// Send the request
+	_, _, err := r.query("PUT", fmt.Sprintf("/storage-pools/%s", name), pool, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteStoragePool deletes a storage pool
+func (r *ProtocolLXD) DeleteStoragePool(name string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/storage-pools/%s", name), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
new file mode 100644
index 000000000..47ae4e6e9
--- /dev/null
+++ b/client/lxd_storage_volumes.go
@@ -0,0 +1,93 @@
+package lxd
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+// Storage volumes handling function
+
+// GetStoragePoolVolumeNames returns the names of all volumes in a pool
+func (r *ProtocolLXD) GetStoragePoolVolumeNames(pool string) ([]string, error) {
+	if !r.HasExtension("storage") {
+		return nil, fmt.Errorf("The server is missing the required \"storage\" API extension")
+	}
+
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", fmt.Sprintf("/storage-pools/%s/volumes", pool), nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, url := range urls {
+		fields := strings.Split(url, fmt.Sprintf("/storage-pools/%s/volumes/", pool))
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetStoragePoolVolumes returns a list of StorageVolume entries for the provided pool
+func (r *ProtocolLXD) GetStoragePoolVolumes(pool string) ([]api.StorageVolume, error) {
+	volumes := []api.StorageVolume{}
+
+	// Fetch the raw value
+	_, err := r.queryStruct("GET", fmt.Sprintf("/storage-pools/%s/volumes?recursion=1", pool), nil, "", &volumes)
+	if err != nil {
+		return nil, err
+	}
+
+	return volumes, nil
+}
+
+// GetStoragePoolVolume returns a StorageVolume entry for the provided pool and volume name
+func (r *ProtocolLXD) GetStoragePoolVolume(pool string, volType string, name string) (*api.StorageVolume, string, error) {
+	volume := api.StorageVolume{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("/storage-pools/%s/volumes/%s/%s", pool, volType, name), nil, "", &volume)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &volume, etag, nil
+}
+
+// CreateStoragePoolVolume defines a new storage volume
+func (r *ProtocolLXD) CreateStoragePoolVolume(pool string, volume api.StorageVolumesPost) error {
+	// Send the request
+	_, _, err := r.query("POST", fmt.Sprintf("/storage-pools/%s/volumes/%s", pool, volume.Type), volume, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// UpdateStoragePoolVolume updates the volume to match the provided StoragePoolVolume struct
+func (r *ProtocolLXD) UpdateStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePut, ETag string) error {
+	// Send the request
+	_, _, err := r.query("PUT", fmt.Sprintf("/storage-pools/%s/volumes/%s/%s", pool, volType, name), volume, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteStoragePoolVolume deletes a storage pool
+func (r *ProtocolLXD) DeleteStoragePoolVolume(pool string, volType string, name string) error {
+	// Send the request
+	_, _, err := r.query("DELETE", fmt.Sprintf("/storage-pools/%s/volumes/%s/%s", pool, volType, name), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/client/util.go b/client/util.go
index a2f635c89..973f1d562 100644
--- a/client/util.go
+++ b/client/util.go
@@ -12,9 +12,9 @@ import (
 	"github.com/lxc/lxd/shared/ioprogress"
 )
 
-func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
+func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsCA string, tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
 	// Get the TLS configuration
-	tlsConfig, err := shared.GetTLSConfigMem(tlsClientCert, tlsClientKey, tlsServerCert)
+	tlsConfig, err := shared.GetTLSConfigMem(tlsClientCert, tlsClientKey, tlsCA, tlsServerCert)
 	if err != nil {
 		return nil, err
 	}

From fee41455a373711b33e338e3b8b0cd073e39d783 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 4 Jun 2017 19:19:31 -0400
Subject: [PATCH 0928/1193] Sync shared/network.go TLS handling with master
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is needed to keep client/ in sync.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go              |  4 ++--
 lxc/remote.go          |  2 +-
 lxd/containers_post.go |  2 +-
 lxd/daemon.go          |  2 +-
 shared/network.go      | 29 ++++++++++++++++++++++++++---
 5 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/client.go b/client.go
index f087d4277..8ca9e1cd2 100644
--- a/client.go
+++ b/client.go
@@ -227,7 +227,7 @@ func connectViaUnix(c *Client, remote *RemoteConfig) error {
 }
 
 func connectViaHttp(c *Client, remote *RemoteConfig, clientCert, clientKey, serverCert string) error {
-	tlsconfig, err := shared.GetTLSConfigMem(clientCert, clientKey, serverCert)
+	tlsconfig, err := shared.GetTLSConfigMem(clientCert, clientKey, "", serverCert)
 	if err != nil {
 		return err
 	}
@@ -286,7 +286,7 @@ func NewClientFromInfo(info ConnectInfo) (*Client, error) {
 	}
 
 	if info.RemoteConfig.Protocol == "simplestreams" {
-		tlsconfig, err := shared.GetTLSConfig("", "", nil)
+		tlsconfig, err := shared.GetTLSConfig("", "", "", nil)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxc/remote.go b/lxc/remote.go
index 01f518f7c..5eff2ab2d 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -71,7 +71,7 @@ func (c *remoteCmd) flags() {
 
 func (c *remoteCmd) getRemoteCertificate(address string) (*x509.Certificate, error) {
 	// Setup a permissive TLS config
-	tlsConfig, err := shared.GetTLSConfig("", "", nil)
+	tlsConfig, err := shared.GetTLSConfig("", "", "", nil)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 4efea4001..76edaa366 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -229,7 +229,7 @@ func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
 		}
 	}
 
-	config, err := shared.GetTLSConfig("", "", cert)
+	config, err := shared.GetTLSConfig("", "", "", cert)
 	if err != nil {
 		c.Delete()
 		return InternalError(err)
diff --git a/lxd/daemon.go b/lxd/daemon.go
index b0d35b26a..5f1c9a45b 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -122,7 +122,7 @@ func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
 		}
 	}
 
-	tlsConfig, err := shared.GetTLSConfig("", "", cert)
+	tlsConfig, err := shared.GetTLSConfig("", "", "", cert)
 	if err != nil {
 		return nil, err
 	}
diff --git a/shared/network.go b/shared/network.go
index c979e3b55..e9857e547 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -52,7 +52,10 @@ func initTLSConfig() *tls.Config {
 func finalizeTLSConfig(tlsConfig *tls.Config, tlsRemoteCert *x509.Certificate) {
 	// Trusted certificates
 	if tlsRemoteCert != nil {
-		caCertPool := x509.NewCertPool()
+		caCertPool := tlsConfig.RootCAs
+		if caCertPool == nil {
+			caCertPool = x509.NewCertPool()
+		}
 
 		// Make it a valid RootCA
 		tlsRemoteCert.IsCA = true
@@ -71,7 +74,7 @@ func finalizeTLSConfig(tlsConfig *tls.Config, tlsRemoteCert *x509.Certificate) {
 	tlsConfig.BuildNameToCertificate()
 }
 
-func GetTLSConfig(tlsClientCertFile string, tlsClientKeyFile string, tlsRemoteCert *x509.Certificate) (*tls.Config, error) {
+func GetTLSConfig(tlsClientCertFile string, tlsClientKeyFile string, tlsClientCAFile string, tlsRemoteCert *x509.Certificate) (*tls.Config, error) {
 	tlsConfig := initTLSConfig()
 
 	// Client authentication
@@ -84,11 +87,23 @@ func GetTLSConfig(tlsClientCertFile string, tlsClientKeyFile string, tlsRemoteCe
 		tlsConfig.Certificates = []tls.Certificate{cert}
 	}
 
+	if tlsClientCAFile != "" {
+		caCertificates, err := ioutil.ReadFile(tlsClientCAFile)
+		if err != nil {
+			return nil, err
+		}
+
+		caPool := x509.NewCertPool()
+		caPool.AppendCertsFromPEM(caCertificates)
+
+		tlsConfig.RootCAs = caPool
+	}
+
 	finalizeTLSConfig(tlsConfig, tlsRemoteCert)
 	return tlsConfig, nil
 }
 
-func GetTLSConfigMem(tlsClientCert string, tlsClientKey string, tlsRemoteCertPEM string) (*tls.Config, error) {
+func GetTLSConfigMem(tlsClientCert string, tlsClientKey string, tlsClientCA string, tlsRemoteCertPEM string) (*tls.Config, error) {
 	tlsConfig := initTLSConfig()
 
 	// Client authentication
@@ -115,6 +130,14 @@ func GetTLSConfigMem(tlsClientCert string, tlsClientKey string, tlsRemoteCertPEM
 			return nil, err
 		}
 	}
+
+	if tlsClientCA != "" {
+		caPool := x509.NewCertPool()
+		caPool.AppendCertsFromPEM([]byte(tlsClientCA))
+
+		tlsConfig.RootCAs = caPool
+	}
+
 	finalizeTLSConfig(tlsConfig, tlsRemoteCert)
 
 	return tlsConfig, nil

From c4b7c8df29cb53ac8a82c524c2de0e06fea63107 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 4 Jun 2017 19:20:04 -0400
Subject: [PATCH 0929/1193] Sync ParseLXDFileHeaders with master
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is needed to keep client/ in sync.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go             |  2 +-
 lxd/container_file.go |  2 +-
 shared/util.go        | 20 ++++++++++++++++++--
 3 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/client.go b/client.go
index 8ca9e1cd2..645e547ef 100644
--- a/client.go
+++ b/client.go
@@ -1786,7 +1786,7 @@ func (c *Client) PullFile(container string, p string) (int64, int64, int, io.Rea
 		return 0, 0, 0, nil, err
 	}
 
-	uid, gid, mode := shared.ParseLXDFileHeaders(r.Header)
+	uid, gid, mode, _, _ := shared.ParseLXDFileHeaders(r.Header)
 
 	return uid, gid, mode, r.Body, nil
 }
diff --git a/lxd/container_file.go b/lxd/container_file.go
index 7ac2085c6..b8866d2b0 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -72,7 +72,7 @@ func containerFileGet(c container, path string, r *http.Request) Response {
 
 func containerFilePut(c container, path string, r *http.Request) Response {
 	// Extract file ownership and mode from headers
-	uid, gid, mode := shared.ParseLXDFileHeaders(r.Header)
+	uid, gid, mode, _, _ := shared.ParseLXDFileHeaders(r.Header)
 
 	// Write file content to a tempfile
 	temp, err := ioutil.TempFile("", "lxd_forkputfile_")
diff --git a/shared/util.go b/shared/util.go
index 22f122c67..eb19caf91 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -122,7 +122,7 @@ func LogPath(path ...string) string {
 	return filepath.Join(items...)
 }
 
-func ParseLXDFileHeaders(headers http.Header) (uid int64, gid int64, mode int) {
+func ParseLXDFileHeaders(headers http.Header) (uid int64, gid int64, mode int, type_ string, write string) {
 	uid, err := strconv.ParseInt(headers.Get("X-LXD-uid"), 10, 64)
 	if err != nil {
 		uid = -1
@@ -143,7 +143,23 @@ func ParseLXDFileHeaders(headers http.Header) (uid int64, gid int64, mode int) {
 		}
 	}
 
-	return uid, gid, mode
+	type_ = headers.Get("X-LXD-type")
+	/* backwards compat: before "type" was introduced, we could only
+	 * manipulate files
+	 */
+	if type_ == "" {
+		type_ = "file"
+	}
+
+	write = headers.Get("X-LXD-write")
+	/* backwards compat: before "write" was introduced, we could only
+	 * overwrite files
+	 */
+	if write == "" {
+		write = "overwrite"
+	}
+
+	return uid, gid, mode, type_, write
 }
 
 func ReadToJSON(r io.Reader, req interface{}) error {

From 8c1cb0d44bf8bca349aae5d89b2b2eaafd3f9f7c Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Mon, 15 May 2017 08:37:03 +0200
Subject: [PATCH 0930/1193] Fix regression in image auto-update logic

From what I understand, it seems that this commit:

https://github.com/lxc/lxd/commit/7d4869527f1cef579015a06258c93a33e76cc865

introduced a regression in the auto-update logic. The code that was
setting the AutoUpdate flag in the *api.Image structure in line 541:

https://github.com/lxc/lxd/commit/7d4869527f1cef579015a06258c93a33e76cc865#diff-6182827642cd00e061c6ffe52891acc5L541

was moved down in the function:

https://github.com/lxc/lxd/commit/7d4869527f1cef579015a06258c93a33e76cc865#diff-6182827642cd00e061c6ffe52891acc5R482

however the new location is after the call to dbImageInsert:

```
	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
```

that actually makes use of such setting. This results in the
auto_update column in the database always being 0, no matter what, and
the server logic to auto-update images will never kick in.

This commit should also fix another (non fatal) regression introduced
by:

https://github.com/lxc/lxd/commit/806d55c88562ab82b32d2d8563c20a8ec9bdac98

where appending the empty string "" to the poolNames to be processed
ends up triggering a call to ```doDeleteImageFromPool()``` from
``autoUpdateImage``:

https://github.com/lxc/lxd/blob/471604191b9793ffe479036dab8e527cb743cee1/lxd/images.go#L975

which as far as I understand will always end up with an error in case
poolName is the empty string, so a spurious error will be logged.

Note that auto-update regression affects the latest 2.13 release, but
not 2.12.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon_images.go             |  9 +++--
 test/main.sh                     |  1 +
 test/suites/image_auto_update.sh | 72 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 80 insertions(+), 2 deletions(-)
 create mode 100644 test/suites/image_auto_update.sh

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 2f9e918f5..fa2f6d848 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -442,6 +442,13 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		return nil, err
 	}
 
+	// We want to enable auto-update only if we were passed an
+	// alias name, so we can figure when the associated
+	// fingerprint changes in the remote.
+	if alias != fp {
+		info.AutoUpdate = autoUpdate
+	}
+
 	// Create the database entry
 	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
@@ -461,8 +468,6 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		if err != nil {
 			return nil, err
 		}
-
-		info.AutoUpdate = autoUpdate
 	}
 
 	// Mark the image as "cached" if downloading for a container
diff --git a/test/main.sh b/test/main.sh
index e4b6265ac..5a87e36e0 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -506,6 +506,7 @@ run_test test_remote_usage "remote usage"
 run_test test_basic_usage "basic usage"
 run_test test_security "security features"
 run_test test_image_expiry "image expiry"
+run_test test_image_auto_update "image auto-update"
 run_test test_concurrent_exec "concurrent exec"
 run_test test_concurrent "concurrent startup"
 run_test test_snapshots "container snapshots"
diff --git a/test/suites/image_auto_update.sh b/test/suites/image_auto_update.sh
new file mode 100644
index 000000000..6cfda3650
--- /dev/null
+++ b/test/suites/image_auto_update.sh
@@ -0,0 +1,72 @@
+test_image_auto_update() {
+  # XXX this test appears to be flaky when running on Jenkins
+  # against the LVM backend. Needs further investigation.
+  # shellcheck disable=2153
+  backend=$(storage_backend "$LXD_DIR")
+  if [ "${backend}" = "lvm" ]; then
+      return 0
+  fi
+
+  if lxc image alias list | grep -q "^| testimage\s*|.*$"; then
+      lxc image delete testimage
+  fi
+
+  # shellcheck disable=2039
+  local LXD2_DIR LXD2_ADDR
+  LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD2_DIR}"
+  spawn_lxd "${LXD2_DIR}" true
+  LXD2_ADDR=$(cat "${LXD2_DIR}/lxd.addr")
+
+  (LXD_DIR=${LXD2_DIR} deps/import-busybox --alias testimage --public)
+  fp1=$(LXD_DIR=${LXD2_DIR} lxc image info testimage | awk -F: '/^Fingerprint/ { print $2 }' | awk '{ print $1 }')
+
+  lxc remote add l2 "${LXD2_ADDR}" --accept-certificate --password foo
+  lxc init l2:testimage c1
+
+  # Now the first image image is in the local store, since it was
+  # downloaded to create c1.
+  alias=$(lxc image info "${fp1}" | awk -F: '/^    Alias/ { print $2 }' | awk '{ print $1 }')
+  [ "${alias}" = "testimage" ]
+
+  # Delete the first image from the remote store and replace it with a
+  # new one with a different fingerprint (passing "--template create"
+  # will do that).
+  (LXD_DIR=${LXD2_DIR} lxc image delete testimage)
+  (LXD_DIR=${LXD2_DIR} deps/import-busybox --alias testimage --public --template create)
+  fp2=$(LXD_DIR=${LXD2_DIR} lxc image info testimage | awk -F: '/^Fingerprint/ { print $2 }' | awk '{ print $1 }')
+  [ "${fp1}" != "${fp2}" ]
+
+  # Restart the server to force an image refresh immediately
+  # shellcheck disable=2153
+  shutdown_lxd "${LXD_DIR}"
+  respawn_lxd "${LXD_DIR}"
+
+  # Check that the first image got deleted from the local storage
+  #
+  # XXX: Since the auto-update logic runs asynchronously we need to wait
+  #      a little bit before it actually completes.
+  retries=10
+  while [ "${retries}" != "0" ]; do
+    if lxc image info "${fp1}" > /dev/null 2>&1; then
+	sleep 2
+	retries=$((retries-1))
+	continue
+    fi
+    break
+  done
+
+  if [ "${retries}" -eq 0 ]; then
+      echo "First image ${fp1} not deleted from local store"
+      return 1
+  fi
+
+  # The second image replaced the first one in the local storage.
+  alias=$(lxc image info "${fp2}" | awk -F: '/^    Alias/ { print $2 }' | awk '{ print $1 }')
+  [ "${alias}" = "testimage" ]
+
+  lxc delete c1
+  lxc remote remove l2
+  lxc image delete "${fp2}"
+  kill_lxd "$LXD2_DIR"
+}

From cf8ebff4f3a097430caf3294fb98a2aba625af9e Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 16 May 2017 09:55:41 +0200
Subject: [PATCH 0931/1193] Clear database state in the mock daemon after each
 lxdSuiteTest

Currently unit tests based on lxdSuiteTest all share the same
database, meaning that state left by certain tests can influence other
tests (for instance it's not possible to create a pool with the same
name in two different tests).

This change resets the db state after each individual test run,
increasing isolation between tests.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/main_test.go | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/lxd/main_test.go b/lxd/main_test.go
index 67ec48c56..748f8f8b9 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -56,6 +56,7 @@ func (suite *lxdTestSuite) SetupSuite() {
 	if err != nil {
 		os.Exit(1)
 	}
+	suite.Req = require.New(suite.T())
 }
 
 func (suite *lxdTestSuite) TearDownSuite() {
@@ -68,5 +69,14 @@ func (suite *lxdTestSuite) TearDownSuite() {
 }
 
 func (suite *lxdTestSuite) SetupTest() {
-	suite.Req = require.New(suite.T())
+	initializeDbObject(suite.d, shared.VarPath("lxd.db"))
+	daemonConfigInit(suite.d.db)
+}
+
+func (suite *lxdTestSuite) TearDownTest() {
+	suite.d.db.Close()
+	err := os.Remove(shared.VarPath("lxd.db"))
+	if err != nil {
+		os.Exit(1)
+	}
 }

From 283077e1df268ac1618b1cd60dfee189321c54e8 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Tue, 16 May 2017 12:38:11 +0200
Subject: [PATCH 0932/1193] Separate db-level update logic from daemon-level
 one

Our DB patches are currently performing a mix of pure SQL changes and
some legacy higher-level upgrade logic (such as dealing with container
paths and LVM volumes).

Such non-db logic was written before the lxd/patches.go mechanism was
put in place, and prevents removing the coupling between the daemon
layer and the db layer, making it harder to test SQL upgrade logic
in isolation, and perhaps to eventually turn the db code into its own
self-contained package that doesn't depend on deamon code.

This change extracts all daemon-specific upgrade logic from
db_updates.go to patches.go, and adds a 'postApply' hook parameter to
dbUpdatesApplyAll in order to preserve their exact current
semantics (i.e. these patches are applied during the database
upgrade).

There are now no references to Deamon left in db_update.go, and this
should be a purely refactoring branch as there are no logical changes.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon.go    |  48 ++++++++
 lxd/db.go        |  36 ------
 lxd/db_test.go   |   4 +-
 lxd/db_update.go | 345 ++++++++++++++++---------------------------------------
 lxd/patches.go   | 213 ++++++++++++++++++++++++++++++++++
 5 files changed, 361 insertions(+), 285 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 5f1c9a45b..b66f6ca5c 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1305,3 +1305,51 @@ func (s *lxdHttpServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 	// Call the original server
 	s.r.ServeHTTP(rw, req)
 }
+
+// Create a database connection and perform any updates needed.
+func initializeDbObject(d *Daemon, path string) error {
+	var openPath string
+	var err error
+
+	timeout := 5 // TODO - make this command-line configurable?
+
+	// These are used to tune the transaction BEGIN behavior instead of using the
+	// similar "locking_mode" pragma (locking for the whole database connection).
+	openPath = fmt.Sprintf("%s?_busy_timeout=%d&_txlock=exclusive", path, timeout*1000)
+
+	// Open the database. If the file doesn't exist it is created.
+	d.db, err = sql.Open("sqlite3_with_fk", openPath)
+	if err != nil {
+		return err
+	}
+
+	// Create the DB if it doesn't exist.
+	err = createDb(d.db)
+	if err != nil {
+		return fmt.Errorf("Error creating database: %s", err)
+	}
+
+	// Detect LXD downgrades
+	if dbGetSchema(d.db) > dbGetLatestSchema() {
+		return fmt.Errorf("The database schema is more recent than LXD's schema.")
+	}
+
+	// Apply any database update.
+	//
+	// NOTE: we use the postApply parameter to run a couple of
+	// legacy non-db updates that were introduced before the
+	// patches mechanism was introduced in lxd/patches.go. The
+	// rest of non-db patches will be applied separately via
+	// patchesApplyAll. See PR #3322 for more details.
+	err = dbUpdatesApplyAll(d.db, true, func(version int) error {
+		if legacyPatch, ok := legacyPatches[version]; ok {
+			return legacyPatch(d)
+		}
+		return nil
+	})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/lxd/db.go b/lxd/db.go
index 8a41a1d35..1c6d9d64f 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -228,42 +228,6 @@ func dbGetLatestSchema() int {
 	return dbUpdates[len(dbUpdates)-1].version
 }
 
-// Create a database connection object and return it.
-func initializeDbObject(d *Daemon, path string) (err error) {
-	var openPath string
-
-	timeout := 5 // TODO - make this command-line configurable?
-
-	// These are used to tune the transaction BEGIN behavior instead of using the
-	// similar "locking_mode" pragma (locking for the whole database connection).
-	openPath = fmt.Sprintf("%s?_busy_timeout=%d&_txlock=exclusive", path, timeout*1000)
-
-	// Open the database. If the file doesn't exist it is created.
-	d.db, err = sql.Open("sqlite3_with_fk", openPath)
-	if err != nil {
-		return err
-	}
-
-	// Create the DB if it doesn't exist.
-	err = createDb(d.db)
-	if err != nil {
-		return fmt.Errorf("Error creating database: %s", err)
-	}
-
-	// Detect LXD downgrades
-	if dbGetSchema(d.db) > dbGetLatestSchema() {
-		return fmt.Errorf("The database schema is more recent than LXD's schema.")
-	}
-
-	// Apply any update
-	err = dbUpdatesApplyAll(d)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
 func isDbLockedError(err error) bool {
 	if err == nil {
 		return false
diff --git a/lxd/db_test.go b/lxd/db_test.go
index 701d9c6a4..bdfa428ac 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -223,7 +223,7 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	s.Nil(err)
 
 	// Run the upgrade from V6 code
-	err = dbUpdateFromV6(5, 6, d)
+	err = dbUpdateFromV6(5, 6, d.db)
 	s.Nil(err)
 
 	// Make sure the inserted data is still there.
@@ -325,7 +325,7 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	d.db = db
 	daemonConfigInit(db)
 
-	err = dbUpdatesApplyAll(d)
+	err = dbUpdatesApplyAll(d.db, false, nil)
 	s.Nil(err)
 
 	result := dbGetSchema(db)
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 35202cd36..70c5a913f 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -1,19 +1,15 @@
 package main
 
 import (
+	"database/sql"
 	"encoding/hex"
 	"fmt"
-	"io/ioutil"
 	"os"
-	"path/filepath"
 	"strconv"
 	"strings"
-	"syscall"
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
-
-	log "gopkg.in/inconshreveable/log15.v2"
 )
 
 /* Database updates are one-time actions that are needed to move an
@@ -28,7 +24,7 @@ import (
    version of LXD.
 
    DO NOT USE this mechanism for one-time actions which do not involve
-   changes to the database schema. Use patches instead.
+   changes to the database schema. Use patches instead (see lxd/patches.go).
 
    Only append to the updates list, never remove entries and never re-order them.
 */
@@ -70,20 +66,20 @@ var dbUpdates = []dbUpdate{
 
 type dbUpdate struct {
 	version int
-	run     func(previousVersion int, version int, d *Daemon) error
+	run     func(previousVersion int, version int, db *sql.DB) error
 }
 
-func (u *dbUpdate) apply(currentVersion int, d *Daemon) error {
+func (u *dbUpdate) apply(currentVersion int, db *sql.DB) error {
 	// Get the current schema version
 
 	logger.Debugf("Updating DB schema from %d to %d", currentVersion, u.version)
 
-	err := u.run(currentVersion, u.version, d)
+	err := u.run(currentVersion, u.version, db)
 	if err != nil {
 		return err
 	}
 
-	_, err = d.db.Exec("INSERT INTO schema (version, updated_at) VALUES (?, strftime(\"%s\"));", u.version)
+	_, err = db.Exec("INSERT INTO schema (version, updated_at) VALUES (?, strftime(\"%s\"));", u.version)
 	if err != nil {
 		return err
 	}
@@ -91,8 +87,15 @@ func (u *dbUpdate) apply(currentVersion int, d *Daemon) error {
 	return nil
 }
 
-func dbUpdatesApplyAll(d *Daemon) error {
-	currentVersion := dbGetSchema(d.db)
+// Apply all possible database patches. If "doBackup" is true, the
+// sqlite file will be backed up before any update is applied. If
+// "postApply" it's passed, it will be called after each database
+// update gets successfully applied, and be passed the its version (as
+// of now "postApply" is only used by the daemon as a mean to apply
+// the legacy V10 and V15 non-db updates during the database upgrade
+// sequence to, avoid changing semantics see PR #3322).
+func dbUpdatesApplyAll(db *sql.DB, doBackup bool, postApply func(int) error) error {
+	currentVersion := dbGetSchema(db)
 
 	backup := false
 	for _, update := range dbUpdates {
@@ -100,7 +103,7 @@ func dbUpdatesApplyAll(d *Daemon) error {
 			continue
 		}
 
-		if !d.MockMode && !backup {
+		if doBackup && !backup {
 			logger.Infof("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
 			err := shared.FileCopy(shared.VarPath("lxd.db"), shared.VarPath("lxd.db.bak"))
 			if err != nil {
@@ -110,10 +113,16 @@ func dbUpdatesApplyAll(d *Daemon) error {
 			backup = true
 		}
 
-		err := update.apply(currentVersion, d)
+		err := update.apply(currentVersion, db)
 		if err != nil {
 			return err
 		}
+		if postApply != nil {
+			err = postApply(update.version)
+			if err != nil {
+				return err
+			}
+		}
 
 		currentVersion = update.version
 	}
@@ -122,7 +131,7 @@ func dbUpdatesApplyAll(d *Daemon) error {
 }
 
 // Schema updates begin here
-func dbUpdateFromV31(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV31(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS patches (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@@ -130,50 +139,17 @@ CREATE TABLE IF NOT EXISTS patches (
     applied_at DATETIME NOT NULL,
     UNIQUE (name)
 );`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV30(currentVersion int, version int, d *Daemon) error {
-	entries, err := ioutil.ReadDir(shared.VarPath("containers"))
-	if err != nil {
-		/* If the directory didn't exist before, the user had never
-		 * started containers, so we don't need to fix up permissions
-		 * on anything.
-		 */
-		if os.IsNotExist(err) {
-			return nil
-		}
-		return err
-	}
-
-	for _, entry := range entries {
-		if !shared.IsDir(shared.VarPath("containers", entry.Name(), "rootfs")) {
-			continue
-		}
-
-		info, err := os.Stat(shared.VarPath("containers", entry.Name(), "rootfs"))
-		if err != nil {
-			return err
-		}
-
-		if int(info.Sys().(*syscall.Stat_t).Uid) == 0 {
-			err := os.Chmod(shared.VarPath("containers", entry.Name()), 0700)
-			if err != nil {
-				return err
-			}
-
-			err = os.Chown(shared.VarPath("containers", entry.Name()), 0, 0)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
+func dbUpdateFromV30(currentVersion int, version int, db *sql.DB) error {
+	// NOTE: this database update contained daemon-level logic which
+	//       was been moved to patchUpdateFromV15 in patches.go.
 	return nil
 }
 
-func dbUpdateFromV29(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV29(currentVersion int, version int, db *sql.DB) error {
 	if shared.PathExists(shared.VarPath("zfs.img")) {
 		err := os.Chmod(shared.VarPath("zfs.img"), 0600)
 		if err != nil {
@@ -184,22 +160,22 @@ func dbUpdateFromV29(currentVersion int, version int, d *Daemon) error {
 	return nil
 }
 
-func dbUpdateFromV28(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV28(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "aadisable", 2 FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "source", "/dev/null" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker" AND profiles_devices.name = "aadisable";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "path", "/sys/module/apparmor/parameters/enabled" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker" AND profiles_devices.name = "aadisable";`
-	d.db.Exec(stmt)
+	db.Exec(stmt)
 
 	return nil
 }
 
-func dbUpdateFromV27(currentVersion int, version int, d *Daemon) error {
-	_, err := d.db.Exec("UPDATE profiles_devices SET type=3 WHERE type='unix-char';")
+func dbUpdateFromV27(currentVersion int, version int, db *sql.DB) error {
+	_, err := db.Exec("UPDATE profiles_devices SET type=3 WHERE type='unix-char';")
 	return err
 }
 
-func dbUpdateFromV26(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV26(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 ALTER TABLE images ADD COLUMN auto_update INTEGER NOT NULL DEFAULT 0;
 CREATE TABLE IF NOT EXISTS images_source (
@@ -211,58 +187,58 @@ CREATE TABLE IF NOT EXISTS images_source (
     alias VARCHAR(255) NOT NULL,
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
 );`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV25(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV25(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 INSERT INTO profiles (name, description) VALUES ("docker", "Profile supporting docker in containers");
 INSERT INTO profiles_config (profile_id, key, value) SELECT id, "security.nesting", "true" FROM profiles WHERE name="docker";
 INSERT INTO profiles_config (profile_id, key, value) SELECT id, "linux.kernel_modules", "overlay, nf_nat" FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "fuse", "unix-char" FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "path", "/dev/fuse" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker";`
-	d.db.Exec(stmt)
+	db.Exec(stmt)
 
 	return nil
 }
 
-func dbUpdateFromV24(currentVersion int, version int, d *Daemon) error {
-	_, err := d.db.Exec("ALTER TABLE containers ADD COLUMN stateful INTEGER NOT NULL DEFAULT 0;")
+func dbUpdateFromV24(currentVersion int, version int, db *sql.DB) error {
+	_, err := db.Exec("ALTER TABLE containers ADD COLUMN stateful INTEGER NOT NULL DEFAULT 0;")
 	return err
 }
 
-func dbUpdateFromV23(currentVersion int, version int, d *Daemon) error {
-	_, err := d.db.Exec("ALTER TABLE profiles ADD COLUMN description TEXT;")
+func dbUpdateFromV23(currentVersion int, version int, db *sql.DB) error {
+	_, err := db.Exec("ALTER TABLE profiles ADD COLUMN description TEXT;")
 	return err
 }
 
-func dbUpdateFromV22(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV22(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 DELETE FROM containers_devices_config WHERE key='type';
 DELETE FROM profiles_devices_config WHERE key='type';`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV21(currentVersion int, version int, d *Daemon) error {
-	_, err := d.db.Exec("ALTER TABLE containers ADD COLUMN creation_date DATETIME NOT NULL DEFAULT 0;")
+func dbUpdateFromV21(currentVersion int, version int, db *sql.DB) error {
+	_, err := db.Exec("ALTER TABLE containers ADD COLUMN creation_date DATETIME NOT NULL DEFAULT 0;")
 	return err
 }
 
-func dbUpdateFromV20(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV20(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 UPDATE containers_devices SET name='__lxd_upgrade_root' WHERE name='root';
 UPDATE profiles_devices SET name='__lxd_upgrade_root' WHERE name='root';
 
 INSERT INTO containers_devices (container_id, name, type) SELECT id, "root", 2 FROM containers;
 INSERT INTO containers_devices_config (container_device_id, key, value) SELECT id, "path", "/" FROM containers_devices WHERE name='root';`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 
 	return err
 }
 
-func dbUpdateFromV19(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV19(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 DELETE FROM containers_config WHERE container_id NOT IN (SELECT id FROM containers);
 DELETE FROM containers_devices_config WHERE container_device_id NOT IN (SELECT id FROM containers_devices WHERE container_id IN (SELECT id FROM containers));
@@ -270,16 +246,16 @@ DELETE FROM containers_devices WHERE container_id NOT IN (SELECT id FROM contain
 DELETE FROM containers_profiles WHERE container_id NOT IN (SELECT id FROM containers);
 DELETE FROM images_aliases WHERE image_id NOT IN (SELECT id FROM images);
 DELETE FROM images_properties WHERE image_id NOT IN (SELECT id FROM images);`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV18(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV18(currentVersion int, version int, db *sql.DB) error {
 	var id int
 	var value string
 
 	// Update container config
-	rows, err := dbQueryScan(d.db, "SELECT id, value FROM containers_config WHERE key='limits.memory'", nil, []interface{}{id, value})
+	rows, err := dbQueryScan(db, "SELECT id, value FROM containers_config WHERE key='limits.memory'", nil, []interface{}{id, value})
 	if err != nil {
 		return err
 	}
@@ -302,21 +278,21 @@ func dbUpdateFromV18(currentVersion int, version int, d *Daemon) error {
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
 			logger.Debugf("Invalid container memory limit, id=%d value=%s, removing.", id, value)
-			_, err = d.db.Exec("DELETE FROM containers_config WHERE id=?;", id)
+			_, err = db.Exec("DELETE FROM containers_config WHERE id=?;", id)
 			if err != nil {
 				return err
 			}
 		}
 
 		// Set the new value
-		_, err = d.db.Exec("UPDATE containers_config SET value=? WHERE id=?", value, id)
+		_, err = db.Exec("UPDATE containers_config SET value=? WHERE id=?", value, id)
 		if err != nil {
 			return err
 		}
 	}
 
 	// Update profiles config
-	rows, err = dbQueryScan(d.db, "SELECT id, value FROM profiles_config WHERE key='limits.memory'", nil, []interface{}{id, value})
+	rows, err = dbQueryScan(db, "SELECT id, value FROM profiles_config WHERE key='limits.memory'", nil, []interface{}{id, value})
 	if err != nil {
 		return err
 	}
@@ -339,14 +315,14 @@ func dbUpdateFromV18(currentVersion int, version int, d *Daemon) error {
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
 			logger.Debugf("Invalid profile memory limit, id=%d value=%s, removing.", id, value)
-			_, err = d.db.Exec("DELETE FROM profiles_config WHERE id=?;", id)
+			_, err = db.Exec("DELETE FROM profiles_config WHERE id=?;", id)
 			if err != nil {
 				return err
 			}
 		}
 
 		// Set the new value
-		_, err = d.db.Exec("UPDATE profiles_config SET value=? WHERE id=?", value, id)
+		_, err = db.Exec("UPDATE profiles_config SET value=? WHERE id=?", value, id)
 		if err != nil {
 			return err
 		}
@@ -355,79 +331,30 @@ func dbUpdateFromV18(currentVersion int, version int, d *Daemon) error {
 	return nil
 }
 
-func dbUpdateFromV17(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV17(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 DELETE FROM profiles_config WHERE key LIKE 'volatile.%';
 UPDATE containers_config SET key='limits.cpu' WHERE key='limits.cpus';
 UPDATE profiles_config SET key='limits.cpu' WHERE key='limits.cpus';`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV16(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV16(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 UPDATE config SET key='storage.lvm_vg_name' WHERE key = 'core.lvm_vg_name';
 UPDATE config SET key='storage.lvm_thinpool_name' WHERE key = 'core.lvm_thinpool_name';`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV15(currentVersion int, version int, d *Daemon) error {
-	// munge all LVM-backed containers' LV names to match what is
-	// required for snapshot support
-
-	cNames, err := dbContainersList(d.db, cTypeRegular)
-	if err != nil {
-		return err
-	}
-
-	err = daemonConfigInit(d.db)
-	if err != nil {
-		return err
-	}
-
-	vgName := daemonConfig["storage.lvm_vg_name"].Get()
-
-	for _, cName := range cNames {
-		var lvLinkPath string
-		if strings.Contains(cName, shared.SnapshotDelimiter) {
-			lvLinkPath = shared.VarPath("snapshots", fmt.Sprintf("%s.lv", cName))
-		} else {
-			lvLinkPath = shared.VarPath("containers", fmt.Sprintf("%s.lv", cName))
-		}
-
-		if !shared.PathExists(lvLinkPath) {
-			continue
-		}
-
-		newLVName := strings.Replace(cName, "-", "--", -1)
-		newLVName = strings.Replace(newLVName, shared.SnapshotDelimiter, "-", -1)
-
-		if cName == newLVName {
-			logger.Debug("No need to rename, skipping", log.Ctx{"cName": cName, "newLVName": newLVName})
-			continue
-		}
-
-		logger.Debug("About to rename cName in lv upgrade", log.Ctx{"lvLinkPath": lvLinkPath, "cName": cName, "newLVName": newLVName})
-
-		output, err := shared.RunCommand("lvrename", vgName, cName, newLVName)
-		if err != nil {
-			return fmt.Errorf("Could not rename LV '%s' to '%s': %v\noutput:%s", cName, newLVName, err, output)
-		}
-
-		if err := os.Remove(lvLinkPath); err != nil {
-			return fmt.Errorf("Couldn't remove lvLinkPath '%s'", lvLinkPath)
-		}
-		newLinkDest := fmt.Sprintf("/dev/%s/%s", vgName, newLVName)
-		if err := os.Symlink(newLinkDest, lvLinkPath); err != nil {
-			return fmt.Errorf("Couldn't recreate symlink '%s'->'%s'", lvLinkPath, newLinkDest)
-		}
-	}
-
+func dbUpdateFromV15(currentVersion int, version int, db *sql.DB) error {
+	// NOTE: this database update contained daemon-level logic which
+	//       was been moved to patchUpdateFromV15 in patches.go.
 	return nil
 }
 
-func dbUpdateFromV14(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV14(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 PRAGMA foreign_keys=OFF; -- So that integrity doesn't get in the way for now
 
@@ -456,109 +383,38 @@ DROP TABLE containers;
 ALTER TABLE tmp RENAME TO containers;
 
 PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV13(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV13(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 UPDATE containers_config SET key='volatile.base_image' WHERE key = 'volatile.baseImage';`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV12(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV12(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 ALTER TABLE images ADD COLUMN cached INTEGER NOT NULL DEFAULT 0;
 ALTER TABLE images ADD COLUMN last_use_date DATETIME;`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV11(currentVersion int, version int, d *Daemon) error {
-	cNames, err := dbContainersList(d.db, cTypeSnapshot)
-	if err != nil {
-		return err
-	}
-
-	errors := 0
-
-	for _, cName := range cNames {
-		snapParentName, snapOnlyName, _ := containerGetParentAndSnapshotName(cName)
-		oldPath := shared.VarPath("containers", snapParentName, "snapshots", snapOnlyName)
-		newPath := shared.VarPath("snapshots", snapParentName, snapOnlyName)
-		if shared.PathExists(oldPath) && !shared.PathExists(newPath) {
-			logger.Info(
-				"Moving snapshot",
-				log.Ctx{
-					"snapshot": cName,
-					"oldPath":  oldPath,
-					"newPath":  newPath})
-
-			// Rsync
-			// containers/<container>/snapshots/<snap0>
-			//   to
-			// snapshots/<container>/<snap0>
-			output, err := storageRsyncCopy(oldPath, newPath)
-			if err != nil {
-				logger.Error(
-					"Failed rsync snapshot",
-					log.Ctx{
-						"snapshot": cName,
-						"output":   string(output),
-						"err":      err})
-				errors++
-				continue
-			}
-
-			// Remove containers/<container>/snapshots/<snap0>
-			if err := os.RemoveAll(oldPath); err != nil {
-				logger.Error(
-					"Failed to remove the old snapshot path",
-					log.Ctx{
-						"snapshot": cName,
-						"oldPath":  oldPath,
-						"err":      err})
-
-				// Ignore this error.
-				// errors++
-				// continue
-			}
-
-			// Remove /var/lib/lxd/containers/<container>/snapshots
-			// if its empty.
-			cPathParent := filepath.Dir(oldPath)
-			if ok, _ := shared.PathIsEmpty(cPathParent); ok {
-				os.Remove(cPathParent)
-			}
-
-		} // if shared.PathExists(oldPath) && !shared.PathExists(newPath) {
-	} // for _, cName := range cNames {
-
-	// Refuse to start lxd if a rsync failed.
-	if errors > 0 {
-		return fmt.Errorf("Got errors while moving snapshots, see the log output.")
-	}
-
+func dbUpdateFromV11(currentVersion int, version int, db *sql.DB) error {
+	// NOTE: this database update contained daemon-level logic which
+	//       was been moved to patchUpdateFromV15 in patches.go.
 	return nil
 }
 
-func dbUpdateFromV10(currentVersion int, version int, d *Daemon) error {
-	if shared.PathExists(shared.VarPath("lxc")) {
-		err := os.Rename(shared.VarPath("lxc"), shared.VarPath("containers"))
-		if err != nil {
-			return err
-		}
-
-		logger.Debugf("Restarting all the containers following directory rename")
-		containersShutdown(d)
-		containersRestart(d)
-	}
-
+func dbUpdateFromV10(currentVersion int, version int, db *sql.DB) error {
+	// NOTE: this database update contained daemon-level logic which
+	//       was been moved to patchUpdateFromV10 in patches.go.
 	return nil
 }
 
-func dbUpdateFromV9(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV9(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 CREATE TABLE tmp (
     id INTEGER primary key AUTOINCREMENT NOT NULL,
@@ -597,26 +453,26 @@ UPDATE profiles_devices SET type=3 WHERE id IN (SELECT id FROM tmp WHERE type="u
 UPDATE profiles_devices SET type=4 WHERE id IN (SELECT id FROM tmp WHERE type="unix-block");
 
 DROP TABLE tmp;`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV8(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV8(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 UPDATE certificates SET fingerprint = replace(fingerprint, " ", "");`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV7(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV7(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 UPDATE config SET key='core.trust_password' WHERE key IN ('password', 'trust_password', 'trust-password', 'core.trust-password');
 DELETE FROM config WHERE key != 'core.trust_password';`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV6(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV6(currentVersion int, version int, db *sql.DB) error {
 	// This update recreates the schemas that need an ON DELETE CASCADE foreign
 	// key.
 	stmt := `
@@ -741,13 +597,13 @@ INSERT INTO profiles_devices_config SELECT * FROM tmp;
 DROP TABLE tmp;
 
 PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	if err != nil {
 		return err
 	}
 
 	// Get the rows with broken foreign keys an nuke them
-	rows, err := d.db.Query("PRAGMA foreign_key_check;")
+	rows, err := db.Query("PRAGMA foreign_key_check;")
 	if err != nil {
 		return err
 	}
@@ -769,7 +625,7 @@ PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
 	rows.Close()
 
 	for i := range tablestodelete {
-		_, err = d.db.Exec(fmt.Sprintf("DELETE FROM %s WHERE rowid = %d;", tablestodelete[i], rowidtodelete[i]))
+		_, err = db.Exec(fmt.Sprintf("DELETE FROM %s WHERE rowid = %d;", tablestodelete[i], rowidtodelete[i]))
 		if err != nil {
 			return err
 		}
@@ -778,15 +634,15 @@ PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
 	return err
 }
 
-func dbUpdateFromV5(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV5(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 ALTER TABLE containers ADD COLUMN power_state INTEGER NOT NULL DEFAULT 0;
 ALTER TABLE containers ADD COLUMN ephemeral INTEGER NOT NULL DEFAULT 0;`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV4(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV4(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS config (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@@ -795,7 +651,7 @@ CREATE TABLE IF NOT EXISTS config (
     UNIQUE (key)
 );`
 
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	if err != nil {
 		return err
 	}
@@ -814,7 +670,7 @@ CREATE TABLE IF NOT EXISTS config (
 		oldPassword = hex.EncodeToString(buff)
 		stmt := `INSERT INTO config (key, value) VALUES ("core.trust_password", ?);`
 
-		_, err := d.db.Exec(stmt, oldPassword)
+		_, err := db.Exec(stmt, oldPassword)
 		if err != nil {
 			return err
 		}
@@ -825,19 +681,14 @@ CREATE TABLE IF NOT EXISTS config (
 	return nil
 }
 
-func dbUpdateFromV3(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV3(currentVersion int, version int, db *sql.DB) error {
 	// Attempt to create a default profile (but don't fail if already there)
-	stmt := `INSERT INTO profiles (name) VALUES ("default");
-INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "eth0", "nic" FROM profiles WHERE profiles.name="default";
-INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "nictype", "bridged" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";
-INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, 'name', "eth0" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";
-INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "parent", "lxdbr0" FROM profiles_devices LEFT JOIN profiles ON profiles.id=profiles_devices.profile_id WHERE profiles.name == "default";`
-	d.db.Exec(stmt)
+	db.Exec("INSERT INTO profiles (name) VALUES (\"default\");")
 
 	return nil
 }
 
-func dbUpdateFromV2(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV2(currentVersion int, version int, db *sql.DB) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS containers_devices (
     id INTEGER primary key AUTOINCREMENT NOT NULL,
@@ -893,11 +744,11 @@ CREATE TABLE IF NOT EXISTS profiles_devices_config (
     UNIQUE (profile_device_id, key),
     FOREIGN KEY (profile_device_id) REFERENCES profiles_devices (id)
 );`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV1(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV1(currentVersion int, version int, db *sql.DB) error {
 	// v1..v2 adds images aliases
 	stmt := `
 CREATE TABLE IF NOT EXISTS images_aliases (
@@ -908,11 +759,11 @@ CREATE TABLE IF NOT EXISTS images_aliases (
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
     UNIQUE (name)
 );`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV0(currentVersion int, version int, d *Daemon) error {
+func dbUpdateFromV0(currentVersion int, version int, db *sql.DB) error {
 	// v0..v1 adds schema table
 	stmt := `
 CREATE TABLE IF NOT EXISTS schema (
@@ -921,6 +772,6 @@ CREATE TABLE IF NOT EXISTS schema (
     updated_at DATETIME NOT NULL,
     UNIQUE (version)
 );`
-	_, err := d.db.Exec(stmt)
+	_, err := db.Exec(stmt)
 	return err
 }
diff --git a/lxd/patches.go b/lxd/patches.go
index 94f9655e0..f6c899dcb 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -1,7 +1,12 @@
 package main
 
 import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
 	"strings"
+	"syscall"
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
@@ -106,3 +111,211 @@ func patchInvalidProfileNames(name string, d *Daemon) error {
 
 	return nil
 }
+
+// Patches end here
+
+// Here are a couple of legacy patches that were originally in
+// db_updates.go and were written before the new patch mechanism
+// above. To preserve exactly their semantics we treat them
+// differently and still apply them during the database upgrade. In
+// principle they could be converted to regular patches like the ones
+// above, however that seems an unnecessary risk at this moment. See
+// also PR #3322.
+//
+// NOTE: don't add any legacy patch here, instead use the patches
+// mechanism above.
+var legacyPatches = map[int](func(d *Daemon) error){
+	11: patchUpdateFromV10,
+	12: patchUpdateFromV11,
+	16: patchUpdateFromV15,
+	31: patchUpdateFromV30,
+	30: patchUpdateFromV29,
+}
+
+func patchUpdateFromV10(d *Daemon) error {
+	if shared.PathExists(shared.VarPath("lxc")) {
+		err := os.Rename(shared.VarPath("lxc"), shared.VarPath("containers"))
+		if err != nil {
+			return err
+		}
+
+		logger.Debugf("Restarting all the containers following directory rename")
+		containersShutdown(d)
+		containersRestart(d)
+	}
+
+	return nil
+}
+
+func patchUpdateFromV11(d *Daemon) error {
+	cNames, err := dbContainersList(d.db, cTypeSnapshot)
+	if err != nil {
+		return err
+	}
+
+	errors := 0
+
+	for _, cName := range cNames {
+		snapParentName, snapOnlyName, _ := containerGetParentAndSnapshotName(cName)
+		oldPath := shared.VarPath("containers", snapParentName, "snapshots", snapOnlyName)
+		newPath := shared.VarPath("snapshots", snapParentName, snapOnlyName)
+		if shared.PathExists(oldPath) && !shared.PathExists(newPath) {
+			logger.Info(
+				"Moving snapshot",
+				log.Ctx{
+					"snapshot": cName,
+					"oldPath":  oldPath,
+					"newPath":  newPath})
+
+			// Rsync
+			// containers/<container>/snapshots/<snap0>
+			//   to
+			// snapshots/<container>/<snap0>
+			output, err := storageRsyncCopy(oldPath, newPath)
+			if err != nil {
+				logger.Error(
+					"Failed rsync snapshot",
+					log.Ctx{
+						"snapshot": cName,
+						"output":   string(output),
+						"err":      err})
+				errors++
+				continue
+			}
+
+			// Remove containers/<container>/snapshots/<snap0>
+			if err := os.RemoveAll(oldPath); err != nil {
+				logger.Error(
+					"Failed to remove the old snapshot path",
+					log.Ctx{
+						"snapshot": cName,
+						"oldPath":  oldPath,
+						"err":      err})
+
+				// Ignore this error.
+				// errors++
+				// continue
+			}
+
+			// Remove /var/lib/lxd/containers/<container>/snapshots
+			// if its empty.
+			cPathParent := filepath.Dir(oldPath)
+			if ok, _ := shared.PathIsEmpty(cPathParent); ok {
+				os.Remove(cPathParent)
+			}
+
+		} // if shared.PathExists(oldPath) && !shared.PathExists(newPath) {
+	} // for _, cName := range cNames {
+
+	// Refuse to start lxd if a rsync failed.
+	if errors > 0 {
+		return fmt.Errorf("Got errors while moving snapshots, see the log output.")
+	}
+
+	return nil
+}
+
+func patchUpdateFromV15(d *Daemon) error {
+	// munge all LVM-backed containers' LV names to match what is
+	// required for snapshot support
+
+	cNames, err := dbContainersList(d.db, cTypeRegular)
+	if err != nil {
+		return err
+	}
+
+	err = daemonConfigInit(d.db)
+	if err != nil {
+		return err
+	}
+
+	vgName := daemonConfig["storage.lvm_vg_name"].Get()
+
+	for _, cName := range cNames {
+		var lvLinkPath string
+		if strings.Contains(cName, shared.SnapshotDelimiter) {
+			lvLinkPath = shared.VarPath("snapshots", fmt.Sprintf("%s.lv", cName))
+		} else {
+			lvLinkPath = shared.VarPath("containers", fmt.Sprintf("%s.lv", cName))
+		}
+
+		if !shared.PathExists(lvLinkPath) {
+			continue
+		}
+
+		newLVName := strings.Replace(cName, "-", "--", -1)
+		newLVName = strings.Replace(newLVName, shared.SnapshotDelimiter, "-", -1)
+
+		if cName == newLVName {
+			logger.Debug("No need to rename, skipping", log.Ctx{"cName": cName, "newLVName": newLVName})
+			continue
+		}
+
+		logger.Debug("About to rename cName in lv upgrade", log.Ctx{"lvLinkPath": lvLinkPath, "cName": cName, "newLVName": newLVName})
+
+		output, err := shared.RunCommand("lvrename", vgName, cName, newLVName)
+		if err != nil {
+			return fmt.Errorf("Could not rename LV '%s' to '%s': %v\noutput:%s", cName, newLVName, err, output)
+		}
+
+		if err := os.Remove(lvLinkPath); err != nil {
+			return fmt.Errorf("Couldn't remove lvLinkPath '%s'", lvLinkPath)
+		}
+		newLinkDest := fmt.Sprintf("/dev/%s/%s", vgName, newLVName)
+		if err := os.Symlink(newLinkDest, lvLinkPath); err != nil {
+			return fmt.Errorf("Couldn't recreate symlink '%s'->'%s'", lvLinkPath, newLinkDest)
+		}
+	}
+
+	return nil
+}
+
+func patchUpdateFromV29(d *Daemon) error {
+	if shared.PathExists(shared.VarPath("zfs.img")) {
+		err := os.Chmod(shared.VarPath("zfs.img"), 0600)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func patchUpdateFromV30(d *Daemon) error {
+	entries, err := ioutil.ReadDir(shared.VarPath("containers"))
+	if err != nil {
+		/* If the directory didn't exist before, the user had never
+		 * started containers, so we don't need to fix up permissions
+		 * on anything.
+		 */
+		if os.IsNotExist(err) {
+			return nil
+		}
+		return err
+	}
+
+	for _, entry := range entries {
+		if !shared.IsDir(shared.VarPath("containers", entry.Name(), "rootfs")) {
+			continue
+		}
+
+		info, err := os.Stat(shared.VarPath("containers", entry.Name(), "rootfs"))
+		if err != nil {
+			return err
+		}
+
+		if int(info.Sys().(*syscall.Stat_t).Uid) == 0 {
+			err := os.Chmod(shared.VarPath("containers", entry.Name()), 0700)
+			if err != nil {
+				return err
+			}
+
+			err = os.Chown(shared.VarPath("containers", entry.Name()), 0, 0)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}

From 2d293a7405178a922035cf56b5bce43df1438fbe Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sat, 20 May 2017 11:48:57 +0200
Subject: [PATCH 0933/1193] storage utils: add permission helpers

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/storage_utils.go | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/lxd/storage_utils.go b/lxd/storage_utils.go
index 212e9a365..ec3fbf0bb 100644
--- a/lxd/storage_utils.go
+++ b/lxd/storage_utils.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"os"
 	"syscall"
 	"time"
 )
@@ -43,3 +44,13 @@ func tryUnmount(path string, flags int) error {
 
 	return nil
 }
+
+// Default permissions for folders in ${LXD_DIR}
+const containersDirMode os.FileMode = 0755
+const customDirMode os.FileMode = 0755
+const imagesDirMode os.FileMode = 0700
+const snapshotsDirMode os.FileMode = 0700
+
+// Driver permissions for driver specific folders in ${LXD_DIR}
+// zfs
+const deletedDirMode os.FileMode = 0700

From 60e4bc3fec45eac5b15b2bfd9721bc40f3a4b6f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 23 May 2017 23:59:39 -0400
Subject: [PATCH 0934/1193] lxd/containers: Cleanup volatile keys on update
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3231

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 90 ++++++++++++++++++++++++----------------------------
 1 file changed, 42 insertions(+), 48 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 6be4121cd..fdd52984b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1589,51 +1589,6 @@ func (c *containerLXC) startCommon() (string, error) {
 		return "", err
 	}
 
-	// Cleanup any leftover volatile entries
-	netNames := []string{}
-	for _, k := range c.expandedDevices.DeviceNames() {
-		v := c.expandedDevices[k]
-		if v["type"] == "nic" {
-			netNames = append(netNames, k)
-		}
-	}
-
-	for k := range c.localConfig {
-		// We only care about volatile
-		if !strings.HasPrefix(k, "volatile.") {
-			continue
-		}
-
-		// Confirm it's a key of format volatile.<device>.<key>
-		fields := strings.SplitN(k, ".", 3)
-		if len(fields) != 3 {
-			continue
-		}
-
-		// The only device keys we care about are name and hwaddr
-		if !shared.StringInSlice(fields[2], []string{"name", "hwaddr"}) {
-			continue
-		}
-
-		// Check if the device still exists
-		if shared.StringInSlice(fields[1], netNames) {
-			// Don't remove the volatile entry if the device doesn't have the matching field set
-			if c.expandedDevices[fields[1]][fields[2]] == "" {
-				continue
-			}
-		}
-
-		// Remove the volatile key from the DB
-		err := dbContainerConfigRemove(c.daemon.db, c.id, k)
-		if err != nil {
-			return "", err
-		}
-
-		// Remove the volatile key from the in-memory configs
-		delete(c.localConfig, k)
-		delete(c.expandedConfig, k)
-	}
-
 	// Rotate the log file
 	logfile := c.LogFilePath()
 	if shared.PathExists(logfile) {
@@ -3196,6 +3151,45 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 	}
 
+	// Cleanup any leftover volatile entries
+	netNames := []string{}
+	for _, k := range c.expandedDevices.DeviceNames() {
+		v := c.expandedDevices[k]
+		if v["type"] == "nic" {
+			netNames = append(netNames, k)
+		}
+	}
+
+	for k := range c.localConfig {
+		// We only care about volatile
+		if !strings.HasPrefix(k, "volatile.") {
+			continue
+		}
+
+		// Confirm it's a key of format volatile.<device>.<key>
+		fields := strings.SplitN(k, ".", 3)
+		if len(fields) != 3 {
+			continue
+		}
+
+		// The only device keys we care about are name and hwaddr
+		if !shared.StringInSlice(fields[2], []string{"name", "hwaddr", "host_name"}) {
+			continue
+		}
+
+		// Check if the device still exists
+		if shared.StringInSlice(fields[1], netNames) {
+			// Don't remove the volatile entry if the device doesn't have the matching field set
+			if c.expandedDevices[fields[1]][fields[2]] == "" {
+				continue
+			}
+		}
+
+		// Remove the volatile key from the in-memory configs
+		delete(c.localConfig, k)
+		delete(c.expandedConfig, k)
+	}
+
 	// Finally, apply the changes to the database
 	tx, err := dbBegin(c.daemon.db)
 	if err != nil {
@@ -3208,19 +3202,19 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		return err
 	}
 
-	err = dbContainerConfigInsert(tx, c.id, args.Config)
+	err = dbContainerConfigInsert(tx, c.id, c.localConfig)
 	if err != nil {
 		tx.Rollback()
 		return err
 	}
 
-	err = dbContainerProfilesInsert(tx, c.id, args.Profiles)
+	err = dbContainerProfilesInsert(tx, c.id, c.profiles)
 	if err != nil {
 		tx.Rollback()
 		return err
 	}
 
-	err = dbDevicesAdd(tx, "container", int64(c.id), args.Devices)
+	err = dbDevicesAdd(tx, "container", int64(c.id), c.localDevices)
 	if err != nil {
 		tx.Rollback()
 		return err

From b8d94d175f6a108719461d274e6e1a0976cdf44a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 24 May 2017 00:49:55 -0400
Subject: [PATCH 0935/1193] lxd/main_init: Properly set default port
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3341

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_init.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/main_init.go b/lxd/main_init.go
index d89afae0a..b06706426 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -436,6 +436,10 @@ they otherwise would.
 	}
 
 	if networkAddress != "" {
+		if networkPort == -1 {
+			networkPort = 8443
+		}
+
 		err = setServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
 		if err != nil {
 			return err

From 2f887974fe2cf4242646481a0792b456f8728d97 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 29 Aug 2017 21:35:16 -0400
Subject: [PATCH 0936/1193] Revert "storage utils: add permission helpers"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This reverts commit 2d293a7405178a922035cf56b5bce43df1438fbe.

This commit introduces code which isn't used right now.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/storage_utils.go | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/lxd/storage_utils.go b/lxd/storage_utils.go
index ec3fbf0bb..212e9a365 100644
--- a/lxd/storage_utils.go
+++ b/lxd/storage_utils.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"os"
 	"syscall"
 	"time"
 )
@@ -44,13 +43,3 @@ func tryUnmount(path string, flags int) error {
 
 	return nil
 }
-
-// Default permissions for folders in ${LXD_DIR}
-const containersDirMode os.FileMode = 0755
-const customDirMode os.FileMode = 0755
-const imagesDirMode os.FileMode = 0700
-const snapshotsDirMode os.FileMode = 0700
-
-// Driver permissions for driver specific folders in ${LXD_DIR}
-// zfs
-const deletedDirMode os.FileMode = 0700

From 2f43b47c72fa8996044a3be71f093bcc743404fc Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 22 Mar 2017 20:04:02 +0100
Subject: [PATCH 0937/1193] test: add helpers

- shutdown_lxd()
- respawn_lxd()

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/main.sh | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/test/main.sh b/test/main.sh
index 5a87e36e0..13b768c0d 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -155,6 +155,28 @@ spawn_lxd() {
   "$lxd_backend"_configure "${lxddir}"
 }
 
+respawn_lxd() {
+  set +x
+  # LXD_DIR is local here because since $(lxc) is actually a function, it
+  # overwrites the environment and we would lose LXD_DIR's value otherwise.
+
+  # shellcheck disable=2039
+  local LXD_DIR
+
+  lxddir=${1}
+  shift
+
+  echo "==> Spawning lxd in ${lxddir}"
+  # shellcheck disable=SC2086
+  LXD_DIR="${lxddir}" lxd --logfile "${lxddir}/lxd.log" ${DEBUG-} "$@" 2>&1 &
+  LXD_PID=$!
+  echo "${LXD_PID}" > "${lxddir}/lxd.pid"
+  echo "==> Spawned LXD (PID is ${LXD_PID})"
+
+  echo "==> Confirming lxd is responsive"
+  LXD_DIR="${lxddir}" lxd waitready --timeout=300
+}
+
 lxc() {
   LXC_LOCAL=1
   lxc_remote "$@"
@@ -331,6 +353,22 @@ kill_lxd() {
   sed "\|^${daemon_dir}|d" -i "${TEST_DIR}/daemons"
 }
 
+shutdown_lxd() {
+  # LXD_DIR is local here because since $(lxc) is actually a function, it
+  # overwrites the environment and we would lose LXD_DIR's value otherwise.
+
+  # shellcheck disable=2039
+  local LXD_DIR
+
+  daemon_dir=${1}
+  LXD_DIR=${daemon_dir}
+  daemon_pid=$(cat "${daemon_dir}/lxd.pid")
+  echo "==> Killing LXD at ${daemon_dir}"
+
+  # Kill the daemon
+  lxd shutdown || kill -9 "${daemon_pid}" 2>/dev/null || true
+}
+
 cleanup() {
   # Allow for failures and stop tracing everything
   set +ex

From 704d09d750b0d3e0a00084e93cd8ce49f38e165b Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 22 Mar 2017 20:11:51 +0100
Subject: [PATCH 0938/1193] test: switch to new helpers

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/basic.sh | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 1b0bc75b4..17db957ad 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -211,17 +211,13 @@ test_basic_usage() {
 
     lxc start autostart --force-local
     PID=$(lxc info autostart --force-local | grep ^Pid | awk '{print $2}')
-    lxd shutdown
+    shutdown_lxd "${LXD_DIR}"
     [ -d "/proc/${PID}" ] && false
 
     lxd activateifneeded --debug 2>&1 | grep -q "Daemon has auto-started containers, activating..."
 
-    # shellcheck disable=SC2086
-    lxd --logfile "${LXD_DIR}/lxd.log" ${DEBUG-} "$@" 2>&1 &
-    LXD_PID=$!
-    echo "${LXD_PID}" > "${LXD_DIR}/lxd.pid"
-    echo "${LXD_DIR}" >> "${TEST_DIR}/daemons"
-    lxd waitready --timeout=300
+    # shellcheck disable=SC2031
+    respawn_lxd "${LXD_DIR}"
 
     lxc list --force-local autostart | grep -q RUNNING
 

From 887134481c79b815b1eac8ca4cdc801c1fef51b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 29 Aug 2017 23:08:24 -0400
Subject: [PATCH 0939/1193] tests: Fix image_auto_update
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/image_auto_update.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/image_auto_update.sh b/test/suites/image_auto_update.sh
index 6cfda3650..efcf20396 100644
--- a/test/suites/image_auto_update.sh
+++ b/test/suites/image_auto_update.sh
@@ -15,7 +15,7 @@ test_image_auto_update() {
   local LXD2_DIR LXD2_ADDR
   LXD2_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
   chmod +x "${LXD2_DIR}"
-  spawn_lxd "${LXD2_DIR}" true
+  spawn_lxd "${LXD2_DIR}"
   LXD2_ADDR=$(cat "${LXD2_DIR}/lxd.addr")
 
   (LXD_DIR=${LXD2_DIR} deps/import-busybox --alias testimage --public)

From ea7445fb10a38a6f3125948d0c8dfb450eaa4788 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free at ekanayaka.io>
Date: Fri, 28 Apr 2017 14:48:02 +0200
Subject: [PATCH 0940/1193] Make the log cmdInit unit-testable

This branch essentially wires into cmdInit the logic previously
introduced in the shared/cmd package.

In order to be able to run the underlying logic in isolation a few
globals (such as command line arguments) have been replaced with
explicit parameters, that get passed to the CmdInit structure.

An initial unit test has been added, mostly for exemplification, and I
plan to keep working on the logic and on the tests in order to
implement #2834.

Signed-off-by: Free Ekanayaka <free at ekanayaka.io>
---
 lxd/main_init.go      | 232 ++++++++++++++++++--------------------------------
 lxd/main_init_test.go |  42 +++++++++
 lxd/main_test.go      |   5 +-
 3 files changed, 131 insertions(+), 148 deletions(-)
 create mode 100644 lxd/main_init_test.go

diff --git a/lxd/main_init.go b/lxd/main_init.go
index b06706426..4044d2fde 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -1,22 +1,42 @@
 package main
 
 import (
-	"bufio"
 	"fmt"
 	"net"
 	"os"
 	"os/exec"
-	"strconv"
-	"strings"
 	"syscall"
 
 	"golang.org/x/crypto/ssh/terminal"
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/cmd"
 )
 
-func cmdInit() error {
+// CmdInitArgs holds command line arguments for the "lxd init" command.
+type CmdInitArgs struct {
+	Auto                bool
+	StorageBackend      string
+	StorageCreateDevice string
+	StorageCreateLoop   int64
+	StoragePool         string
+	NetworkPort         int64
+	NetworkAddress      string
+	TrustPassword       string
+}
+
+// CmdInit implements the "lxd init" command line.
+type CmdInit struct {
+	Context         *cmd.Context
+	Args            *CmdInitArgs
+	RunningInUserns bool
+	SocketPath      string
+	PasswordReader  func(int) ([]byte, error)
+}
+
+// Run triggers the execution of the init command.
+func (cmd *CmdInit) Run() error {
 	var defaultPrivileged int // controls whether we set security.privileged=true
 	var storageBackend string // dir or zfs
 	var storageMode string    // existing, loop or device
@@ -29,12 +49,7 @@ func cmdInit() error {
 
 	// Detect userns
 	defaultPrivileged = -1
-	runningInUserns = shared.RunningInUserNS()
-
-	// Only root should run this
-	if os.Geteuid() != 0 {
-		return fmt.Errorf("This must be run as root")
-	}
+	runningInUserns = cmd.RunningInUserns
 
 	backendsAvailable := []string{"dir"}
 	backendsSupported := []string{"dir", "zfs"}
@@ -50,107 +65,8 @@ func cmdInit() error {
 		}
 	}
 
-	reader := bufio.NewReader(os.Stdin)
-
-	askBool := func(question string, default_ string) bool {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if shared.StringInSlice(strings.ToLower(input), []string{"yes", "y"}) {
-				return true
-			} else if shared.StringInSlice(strings.ToLower(input), []string{"no", "n"}) {
-				return false
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askChoice := func(question string, choices []string, default_ string) string {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if shared.StringInSlice(input, choices) {
-				return input
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askInt := func(question string, min int64, max int64, default_ string) int64 {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			intInput, err := strconv.ParseInt(input, 10, 64)
-
-			if err == nil && (min == -1 || intInput >= min) && (max == -1 || intInput <= max) {
-				return intInput
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askString := func(question string, default_ string, validate func(string) error) string {
-		for {
-			fmt.Printf(question)
-			input, _ := reader.ReadString('\n')
-			input = strings.TrimSuffix(input, "\n")
-			if input == "" {
-				input = default_
-			}
-			if validate != nil {
-				result := validate(input)
-				if result != nil {
-					fmt.Printf("Invalid input: %s\n\n", result)
-					continue
-				}
-			}
-			if len(input) != 0 {
-				return input
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
-	askPassword := func(question string) string {
-		for {
-			fmt.Printf(question)
-			pwd, _ := terminal.ReadPassword(0)
-			fmt.Printf("\n")
-			inFirst := string(pwd)
-			inFirst = strings.TrimSuffix(inFirst, "\n")
-
-			fmt.Printf("Again: ")
-			pwd, _ = terminal.ReadPassword(0)
-			fmt.Printf("\n")
-			inSecond := string(pwd)
-			inSecond = strings.TrimSuffix(inSecond, "\n")
-
-			if inFirst == inSecond {
-				return inFirst
-			}
-
-			fmt.Printf("Invalid input, try again.\n\n")
-		}
-	}
-
 	// Connect to LXD
-	c, err := lxd.ConnectLXDUnix("", nil)
+	c, err := lxd.ConnectLXDUnix(cmd.SocketPath, nil)
 	if err != nil {
 		return fmt.Errorf("Unable to talk to LXD: %s", err)
 	}
@@ -210,63 +126,63 @@ func cmdInit() error {
 		return fmt.Errorf("You have existing containers or images. lxd init requires an empty LXD.")
 	}
 
-	if *argAuto {
-		if *argStorageBackend == "" {
-			*argStorageBackend = "dir"
+	if cmd.Args.Auto {
+		if cmd.Args.StorageBackend == "" {
+			cmd.Args.StorageBackend = "dir"
 		}
 
 		// Do a bunch of sanity checks
-		if !shared.StringInSlice(*argStorageBackend, backendsSupported) {
-			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", *argStorageBackend)
+		if !shared.StringInSlice(cmd.Args.StorageBackend, backendsSupported) {
+			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", cmd.Args.StorageBackend)
 		}
 
-		if !shared.StringInSlice(*argStorageBackend, backendsAvailable) {
-			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", *argStorageBackend)
+		if !shared.StringInSlice(cmd.Args.StorageBackend, backendsAvailable) {
+			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", cmd.Args.StorageBackend)
 		}
 
-		if *argStorageBackend == "dir" {
-			if *argStorageCreateLoop != -1 || *argStorageCreateDevice != "" || *argStoragePool != "" {
+		if cmd.Args.StorageBackend == "dir" {
+			if cmd.Args.StorageCreateLoop != -1 || cmd.Args.StorageCreateDevice != "" || cmd.Args.StoragePool != "" {
 				return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
 			}
 		}
 
-		if *argStorageBackend == "zfs" {
-			if *argStorageCreateLoop != -1 && *argStorageCreateDevice != "" {
+		if cmd.Args.StorageBackend == "zfs" {
+			if cmd.Args.StorageCreateLoop != -1 && cmd.Args.StorageCreateDevice != "" {
 				return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified with the 'zfs' backend.")
 			}
 
-			if *argStoragePool == "" {
+			if cmd.Args.StoragePool == "" {
 				return fmt.Errorf("--storage-pool must be specified with the 'zfs' backend.")
 			}
 		}
 
-		if *argNetworkAddress == "" {
-			if *argNetworkPort != -1 {
+		if cmd.Args.NetworkAddress == "" {
+			if cmd.Args.NetworkPort != -1 {
 				return fmt.Errorf("--network-port cannot be used without --network-address.")
 			}
-			if *argTrustPassword != "" {
+			if cmd.Args.TrustPassword != "" {
 				return fmt.Errorf("--trust-password cannot be used without --network-address.")
 			}
 		}
 
 		// Set the local variables
-		if *argStorageCreateDevice != "" {
+		if cmd.Args.StorageCreateDevice != "" {
 			storageMode = "device"
-		} else if *argStorageCreateLoop != -1 {
+		} else if cmd.Args.StorageCreateLoop != -1 {
 			storageMode = "loop"
 		} else {
 			storageMode = "existing"
 		}
 
-		storageBackend = *argStorageBackend
-		storageLoopSize = *argStorageCreateLoop
-		storageDevice = *argStorageCreateDevice
-		storagePool = *argStoragePool
-		networkAddress = *argNetworkAddress
-		networkPort = *argNetworkPort
-		trustPassword = *argTrustPassword
+		storageBackend = cmd.Args.StorageBackend
+		storageLoopSize = cmd.Args.StorageCreateLoop
+		storageDevice = cmd.Args.StorageCreateDevice
+		storagePool = cmd.Args.StoragePool
+		networkAddress = cmd.Args.NetworkAddress
+		networkPort = cmd.Args.NetworkPort
+		trustPassword = cmd.Args.TrustPassword
 	} else {
-		if *argStorageBackend != "" || *argStorageCreateDevice != "" || *argStorageCreateLoop != -1 || *argStoragePool != "" || *argNetworkAddress != "" || *argNetworkPort != -1 || *argTrustPassword != "" {
+		if cmd.Args.StorageBackend != "" || cmd.Args.StorageCreateDevice != "" || cmd.Args.StorageCreateLoop != -1 || cmd.Args.StoragePool != "" || cmd.Args.NetworkAddress != "" || cmd.Args.NetworkPort != -1 || cmd.Args.TrustPassword != "" {
 			return fmt.Errorf("Init configuration is only valid with --auto")
 		}
 
@@ -275,7 +191,7 @@ func cmdInit() error {
 			defaultStorage = "zfs"
 		}
 
-		storageBackend = askChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), backendsSupported, defaultStorage)
+		storageBackend = cmd.Context.AskChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), backendsSupported, defaultStorage)
 
 		if !shared.StringInSlice(storageBackend, backendsSupported) {
 			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)
@@ -286,16 +202,16 @@ func cmdInit() error {
 		}
 
 		if storageBackend == "zfs" {
-			if askBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
-				storagePool = askString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
-				if askBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
+			if cmd.Context.AskBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
+				storagePool = cmd.Context.AskString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
+				if cmd.Context.AskBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
 					deviceExists := func(path string) error {
 						if !shared.IsBlockdevPath(path) {
 							return fmt.Errorf("'%s' is not a block device", path)
 						}
 						return nil
 					}
-					storageDevice = askString("Path to the existing block device: ", "", deviceExists)
+					storageDevice = cmd.Context.AskString("Path to the existing block device: ", "", deviceExists)
 					storageMode = "device"
 				} else {
 					st := syscall.Statfs_t{}
@@ -314,11 +230,11 @@ func cmdInit() error {
 					}
 
 					q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%d]: ", def)
-					storageLoopSize = askInt(q, 1, -1, fmt.Sprintf("%d", def))
+					storageLoopSize = cmd.Context.AskInt(q, 1, -1, fmt.Sprintf("%d", def))
 					storageMode = "loop"
 				}
 			} else {
-				storagePool = askString("Name of the existing ZFS pool or dataset: ", "", nil)
+				storagePool = cmd.Context.AskString("Name of the existing ZFS pool or dataset: ", "", nil)
 				storageMode = "existing"
 			}
 		}
@@ -342,14 +258,14 @@ in theory attack their parent container and gain more privileges than
 they otherwise would.
 
 `)
-			if askBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
+			if cmd.Context.AskBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
 				defaultPrivileged = 1
 			} else {
 				defaultPrivileged = 0
 			}
 		}
 
-		if askBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
+		if cmd.Context.AskBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
 			isIPAddress := func(s string) error {
 				if s != "all" && net.ParseIP(s) == nil {
 					return fmt.Errorf("'%s' is not an IP address", s)
@@ -357,7 +273,7 @@ they otherwise would.
 				return nil
 			}
 
-			networkAddress = askString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
+			networkAddress = cmd.Context.AskString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
 			if networkAddress == "all" {
 				networkAddress = "::"
 			}
@@ -365,8 +281,8 @@ they otherwise would.
 			if net.ParseIP(networkAddress).To4() == nil {
 				networkAddress = fmt.Sprintf("[%s]", networkAddress)
 			}
-			networkPort = askInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
-			trustPassword = askPassword("Trust password for new clients: ")
+			networkPort = cmd.Context.AskInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
+			trustPassword = cmd.Context.AskPassword("Trust password for new clients: ", cmd.PasswordReader)
 		}
 	}
 
@@ -456,3 +372,25 @@ they otherwise would.
 	fmt.Printf("LXD has been successfully configured.\n")
 	return nil
 }
+
+func cmdInit() error {
+	context := cmd.NewContext(os.Stdin, os.Stdout, os.Stderr)
+	args := &CmdInitArgs{
+		Auto:                *argAuto,
+		StorageBackend:      *argStorageBackend,
+		StorageCreateDevice: *argStorageCreateDevice,
+		StorageCreateLoop:   *argStorageCreateLoop,
+		StoragePool:         *argStoragePool,
+		NetworkPort:         *argNetworkPort,
+		NetworkAddress:      *argNetworkAddress,
+		TrustPassword:       *argTrustPassword,
+	}
+	command := &CmdInit{
+		Context:         context,
+		Args:            args,
+		RunningInUserns: shared.RunningInUserNS(),
+		SocketPath:      "",
+		PasswordReader:  terminal.ReadPassword,
+	}
+	return command.Run()
+}
diff --git a/lxd/main_init_test.go b/lxd/main_init_test.go
new file mode 100644
index 000000000..0c4b0bdc8
--- /dev/null
+++ b/lxd/main_init_test.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/lxc/lxd/shared/cmd"
+	"github.com/stretchr/testify/suite"
+)
+
+type cmdInitTestSuite struct {
+	lxdTestSuite
+	context *cmd.Context
+	args    *CmdInitArgs
+	command *CmdInit
+}
+
+func (suite *cmdInitTestSuite) SetupSuite() {
+	suite.lxdTestSuite.SetupSuite()
+	suite.context = cmd.NewMemoryContext(cmd.NewMemoryStreams(""))
+	suite.args = &CmdInitArgs{
+		NetworkPort:       -1,
+		StorageCreateLoop: -1,
+	}
+	suite.command = &CmdInit{
+		Context:         suite.context,
+		Args:            suite.args,
+		RunningInUserns: false,
+		SocketPath:      suite.d.UnixSocket.Socket.Addr().String(),
+	}
+}
+
+// If any argument intended for --auto is passed in interactive mode, an
+// error is returned.
+func (suite *cmdInitTestSuite) TestCmdInit_InteractiveWithAutoArgs() {
+	suite.args.NetworkPort = 9999
+	err := suite.command.Run()
+	suite.Req.Equal(err.Error(), "Init configuration is only valid with --auto")
+}
+
+func TestCmdInitTestSuite(t *testing.T) {
+	suite.Run(t, new(cmdInitTestSuite))
+}
diff --git a/lxd/main_test.go b/lxd/main_test.go
index 748f8f8b9..efd5e292d 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"crypto/tls"
 	"io/ioutil"
 	"os"
 
@@ -12,7 +13,8 @@ import (
 
 func mockStartDaemon() (*Daemon, error) {
 	d := &Daemon{
-		MockMode: true,
+		MockMode:  true,
+		tlsConfig: &tls.Config{},
 	}
 
 	if err := d.Init(); err != nil {
@@ -71,6 +73,7 @@ func (suite *lxdTestSuite) TearDownSuite() {
 func (suite *lxdTestSuite) SetupTest() {
 	initializeDbObject(suite.d, shared.VarPath("lxd.db"))
 	daemonConfigInit(suite.d.db)
+	suite.Req = require.New(suite.T())
 }
 
 func (suite *lxdTestSuite) TearDownTest() {

From 53a7072f143522a1f18fca8d062d86c180dd7cf3 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free at ekanayaka.io>
Date: Tue, 2 May 2017 07:40:32 +0200
Subject: [PATCH 0941/1193] Move state-changing inline functions to own methods

This is a mechanical commit just moving the various inline helpers
functions of CmdInit.Run to separate methods, in order to make Run()
itself a bit slimmer and easier to read with a brief eye look.

There's no logic change.

Signed-off-by: Free Ekanayaka <free at ekanayaka.io>
---
 lxd/main_init.go | 117 +++++++++++++++++++++++++++++++++----------------------
 1 file changed, 71 insertions(+), 46 deletions(-)

diff --git a/lxd/main_init.go b/lxd/main_init.go
index 4044d2fde..e673f9924 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -71,46 +71,6 @@ func (cmd *CmdInit) Run() error {
 		return fmt.Errorf("Unable to talk to LXD: %s", err)
 	}
 
-	setServerConfig := func(key string, value string) error {
-		server, etag, err := c.GetServer()
-		if err != nil {
-			return err
-		}
-
-		if server.Config == nil {
-			server.Config = map[string]interface{}{}
-		}
-
-		server.Config[key] = value
-
-		err = c.UpdateServer(server.Writable(), etag)
-		if err != nil {
-			return err
-		}
-
-		return nil
-	}
-
-	setProfileConfigItem := func(profileName string, key string, value string) error {
-		profile, etag, err := c.GetProfile(profileName)
-		if err != nil {
-			return err
-		}
-
-		if profile.Config == nil {
-			profile.Config = map[string]string{}
-		}
-
-		profile.Config[key] = value
-
-		err = c.UpdateProfile(profileName, profile.Writable(), etag)
-		if err != nil {
-			return err
-		}
-
-		return nil
-	}
-
 	// Check that we have no containers or images in the store
 	containers, err := c.GetContainerNames()
 	if err != nil {
@@ -288,7 +248,7 @@ they otherwise would.
 
 	// Unset all storage keys, core.https_address and core.trust_password
 	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
-		err = setServerConfig(key, "")
+		err = cmd.setServerConfig(c, key, "")
 		if err != nil {
 			return err
 		}
@@ -334,19 +294,19 @@ they otherwise would.
 		}
 
 		// Configure LXD to use the pool
-		err = setServerConfig("storage.zfs_pool_name", storagePool)
+		err = cmd.setServerConfig(c, "storage.zfs_pool_name", storagePool)
 		if err != nil {
 			return err
 		}
 	}
 
 	if defaultPrivileged == 0 {
-		err = setProfileConfigItem("default", "security.privileged", "")
+		err = cmd.setProfileConfigItem(c, "default", "security.privileged", "")
 		if err != nil {
 			return err
 		}
 	} else if defaultPrivileged == 1 {
-		err = setProfileConfigItem("default", "security.privileged", "true")
+		err = cmd.setProfileConfigItem(c, "default", "security.privileged", "true")
 		if err != nil {
 		}
 	}
@@ -356,13 +316,13 @@ they otherwise would.
 			networkPort = 8443
 		}
 
-		err = setServerConfig("core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
+		err = cmd.setServerConfig(c, "core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
 		if err != nil {
 			return err
 		}
 
 		if trustPassword != "" {
-			err = setServerConfig("core.trust_password", trustPassword)
+			err = cmd.setServerConfig(c, "core.trust_password", trustPassword)
 			if err != nil {
 				return err
 			}
@@ -373,6 +333,71 @@ they otherwise would.
 	return nil
 }
 
+func (cmd *CmdInit) setServerConfig(c lxd.ContainerServer, key string, value string) error {
+	server, etag, err := c.GetServer()
+	if err != nil {
+		return err
+	}
+
+	if server.Config == nil {
+		server.Config = map[string]interface{}{}
+	}
+
+	server.Config[key] = value
+
+	err = c.UpdateServer(server.Writable(), etag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (cmd *CmdInit) profileDeviceAdd(c lxd.ContainerServer, profileName string, deviceName string, deviceConfig map[string]string) error {
+	profile, etag, err := c.GetProfile(profileName)
+	if err != nil {
+		return err
+	}
+
+	if profile.Devices == nil {
+		profile.Devices = map[string]map[string]string{}
+	}
+
+	_, ok := profile.Devices[deviceName]
+	if ok {
+		return fmt.Errorf("Device already exists: %s", deviceName)
+	}
+
+	profile.Devices[deviceName] = deviceConfig
+
+	err = c.UpdateProfile(profileName, profile.Writable(), etag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (cmd *CmdInit) setProfileConfigItem(c lxd.ContainerServer, profileName string, key string, value string) error {
+	profile, etag, err := c.GetProfile(profileName)
+	if err != nil {
+		return err
+	}
+
+	if profile.Config == nil {
+		profile.Config = map[string]string{}
+	}
+
+	profile.Config[key] = value
+
+	err = c.UpdateProfile(profileName, profile.Writable(), etag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func cmdInit() error {
 	context := cmd.NewContext(os.Stdin, os.Stdout, os.Stderr)
 	args := &CmdInitArgs{

From 0151b3baee2b7f49371c5a34fe892a0446b27add Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Thu, 4 May 2017 14:42:35 +0200
Subject: [PATCH 0942/1193] Extract validation of --auto args into a separate
 method

This just mechanically moves into a standalone method the code that
validates the various arguments that go with --auto. It makes the rest
of the code a bit shorter and easier to refactor in the follow-up
commit.

For good measure, I've also added a few unit tests covering some of
the invalid arguments combinations or values.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/main_init.go      | 160 +++++++++++++++++++++++++++++++-------------------
 lxd/main_init_test.go |  72 +++++++++++++++++++++--
 2 files changed, 169 insertions(+), 63 deletions(-)

diff --git a/lxd/main_init.go b/lxd/main_init.go
index e673f9924..19d1cd72a 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -37,6 +37,39 @@ type CmdInit struct {
 
 // Run triggers the execution of the init command.
 func (cmd *CmdInit) Run() error {
+	// Figure what storage drivers among the supported ones are actually
+	// available on this system.
+	availableStoragePoolsDrivers := cmd.availableStoragePoolsDrivers()
+
+	// Check that command line arguments don't conflict with each other
+	err := cmd.validateArgs()
+	if err != nil {
+		return err
+	}
+
+	// Connect to LXD
+	client, err := lxd.ConnectLXDUnix(cmd.SocketPath, nil)
+	if err != nil {
+		return fmt.Errorf("Unable to talk to LXD: %s", err)
+	}
+
+	err = cmd.runAutoOrInteractive(client, availableStoragePoolsDrivers)
+
+	if err == nil {
+		cmd.Context.Output("LXD has been successfully configured.\n")
+	}
+
+	return err
+}
+
+// Run the logic for auto or interactive mode.
+//
+// XXX: this logic is going to be refactored into two separate runAuto
+// and runInteractive methods, sharing relevant logic with
+// runPreseed. The idea being that both runAuto and runInteractive
+// will end up populating the same low-level cmdInitData structure
+// passed to the common run() method.
+func (cmd *CmdInit) runAutoOrInteractive(c lxd.ContainerServer, backendsAvailable []string) error {
 	var defaultPrivileged int // controls whether we set security.privileged=true
 	var storageBackend string // dir or zfs
 	var storageMode string    // existing, loop or device
@@ -51,26 +84,6 @@ func (cmd *CmdInit) Run() error {
 	defaultPrivileged = -1
 	runningInUserns = cmd.RunningInUserns
 
-	backendsAvailable := []string{"dir"}
-	backendsSupported := []string{"dir", "zfs"}
-
-	// Detect zfs
-	out, err := exec.LookPath("zfs")
-	if err == nil && len(out) != 0 && !runningInUserns {
-		_ = loadModule("zfs")
-
-		_, err := shared.RunCommand("zpool", "list")
-		if err == nil {
-			backendsAvailable = append(backendsAvailable, "zfs")
-		}
-	}
-
-	// Connect to LXD
-	c, err := lxd.ConnectLXDUnix(cmd.SocketPath, nil)
-	if err != nil {
-		return fmt.Errorf("Unable to talk to LXD: %s", err)
-	}
-
 	// Check that we have no containers or images in the store
 	containers, err := c.GetContainerNames()
 	if err != nil {
@@ -91,38 +104,9 @@ func (cmd *CmdInit) Run() error {
 			cmd.Args.StorageBackend = "dir"
 		}
 
-		// Do a bunch of sanity checks
-		if !shared.StringInSlice(cmd.Args.StorageBackend, backendsSupported) {
-			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", cmd.Args.StorageBackend)
-		}
-
-		if !shared.StringInSlice(cmd.Args.StorageBackend, backendsAvailable) {
-			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", cmd.Args.StorageBackend)
-		}
-
-		if cmd.Args.StorageBackend == "dir" {
-			if cmd.Args.StorageCreateLoop != -1 || cmd.Args.StorageCreateDevice != "" || cmd.Args.StoragePool != "" {
-				return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
-			}
-		}
-
-		if cmd.Args.StorageBackend == "zfs" {
-			if cmd.Args.StorageCreateLoop != -1 && cmd.Args.StorageCreateDevice != "" {
-				return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified with the 'zfs' backend.")
-			}
-
-			if cmd.Args.StoragePool == "" {
-				return fmt.Errorf("--storage-pool must be specified with the 'zfs' backend.")
-			}
-		}
-
-		if cmd.Args.NetworkAddress == "" {
-			if cmd.Args.NetworkPort != -1 {
-				return fmt.Errorf("--network-port cannot be used without --network-address.")
-			}
-			if cmd.Args.TrustPassword != "" {
-				return fmt.Errorf("--trust-password cannot be used without --network-address.")
-			}
+		err = cmd.validateArgsAuto(backendsAvailable)
+		if err != nil {
+			return err
 		}
 
 		// Set the local variables
@@ -142,18 +126,14 @@ func (cmd *CmdInit) Run() error {
 		networkPort = cmd.Args.NetworkPort
 		trustPassword = cmd.Args.TrustPassword
 	} else {
-		if cmd.Args.StorageBackend != "" || cmd.Args.StorageCreateDevice != "" || cmd.Args.StorageCreateLoop != -1 || cmd.Args.StoragePool != "" || cmd.Args.NetworkAddress != "" || cmd.Args.NetworkPort != -1 || cmd.Args.TrustPassword != "" {
-			return fmt.Errorf("Init configuration is only valid with --auto")
-		}
-
 		defaultStorage := "dir"
 		if shared.StringInSlice("zfs", backendsAvailable) {
 			defaultStorage = "zfs"
 		}
 
-		storageBackend = cmd.Context.AskChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), backendsSupported, defaultStorage)
+		storageBackend = cmd.Context.AskChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), supportedStoragePoolDrivers, defaultStorage)
 
-		if !shared.StringInSlice(storageBackend, backendsSupported) {
+		if !shared.StringInSlice(storageBackend, supportedStoragePoolDrivers) {
 			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)
 		}
 
@@ -329,10 +309,70 @@ they otherwise would.
 		}
 	}
 
-	fmt.Printf("LXD has been successfully configured.\n")
+	cmd.Context.Output("LXD has been successfully configured.\n")
+	return nil
+}
+
+// Check that the arguments passed via command line are consistent,
+// and no invalid combination is provided.
+func (cmd *CmdInit) validateArgs() error {
+	if !cmd.Args.Auto {
+		if cmd.Args.StorageBackend != "" || cmd.Args.StorageCreateDevice != "" || cmd.Args.StorageCreateLoop != -1 || cmd.Args.StoragePool != "" || cmd.Args.NetworkAddress != "" || cmd.Args.NetworkPort != -1 || cmd.Args.TrustPassword != "" {
+			return fmt.Errorf("Init configuration is only valid with --auto")
+		}
+	}
+	return nil
+}
+
+// Check that the arguments passed along with --auto are valid and consistent.
+// and no invalid combination is provided.
+func (cmd *CmdInit) validateArgsAuto(availableStoragePoolsDrivers []string) error {
+	if !shared.StringInSlice(cmd.Args.StorageBackend, supportedStoragePoolDrivers) {
+		return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", cmd.Args.StorageBackend)
+	}
+	if !shared.StringInSlice(cmd.Args.StorageBackend, availableStoragePoolsDrivers) {
+		return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", cmd.Args.StorageBackend)
+	}
+
+	if cmd.Args.StorageBackend == "dir" {
+		if cmd.Args.StorageCreateLoop != -1 || cmd.Args.StorageCreateDevice != "" || cmd.Args.StoragePool != "" {
+			return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
+		}
+	} else {
+		if cmd.Args.StorageCreateLoop != -1 && cmd.Args.StorageCreateDevice != "" {
+			return fmt.Errorf("Only one of --storage-create-device or --storage-create-loop can be specified.")
+		}
+	}
+
+	if cmd.Args.NetworkAddress == "" {
+		if cmd.Args.NetworkPort != -1 {
+			return fmt.Errorf("--network-port cannot be used without --network-address.")
+		}
+		if cmd.Args.TrustPassword != "" {
+			return fmt.Errorf("--trust-password cannot be used without --network-address.")
+		}
+	}
+
 	return nil
 }
 
+// Return the available storage pools drivers (depending on installed tools).
+func (cmd *CmdInit) availableStoragePoolsDrivers() []string {
+	drivers := []string{"dir"}
+
+	// Detect zfs
+	out, err := exec.LookPath("zfs")
+	if err == nil && len(out) != 0 && !cmd.RunningInUserns {
+		_ = loadModule("zfs")
+
+		_, err := shared.RunCommand("zpool", "list")
+		if err == nil {
+			drivers = append(drivers, "zfs")
+		}
+	}
+	return drivers
+}
+
 func (cmd *CmdInit) setServerConfig(c lxd.ContainerServer, key string, value string) error {
 	server, etag, err := c.GetServer()
 	if err != nil {
@@ -419,3 +459,5 @@ func cmdInit() error {
 	}
 	return command.Run()
 }
+
+var supportedStoragePoolDrivers = []string{"dir", "zfs"}
diff --git a/lxd/main_init_test.go b/lxd/main_init_test.go
index 0c4b0bdc8..b02313c07 100644
--- a/lxd/main_init_test.go
+++ b/lxd/main_init_test.go
@@ -3,20 +3,24 @@ package main
 import (
 	"testing"
 
+	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared/cmd"
 	"github.com/stretchr/testify/suite"
 )
 
 type cmdInitTestSuite struct {
 	lxdTestSuite
+	streams *cmd.MemoryStreams
 	context *cmd.Context
 	args    *CmdInitArgs
 	command *CmdInit
+	client  lxd.ContainerServer
 }
 
-func (suite *cmdInitTestSuite) SetupSuite() {
-	suite.lxdTestSuite.SetupSuite()
-	suite.context = cmd.NewMemoryContext(cmd.NewMemoryStreams(""))
+func (suite *cmdInitTestSuite) SetupTest() {
+	suite.lxdTestSuite.SetupTest()
+	suite.streams = cmd.NewMemoryStreams("")
+	suite.context = cmd.NewMemoryContext(suite.streams)
 	suite.args = &CmdInitArgs{
 		NetworkPort:       -1,
 		StorageCreateLoop: -1,
@@ -27,6 +31,9 @@ func (suite *cmdInitTestSuite) SetupSuite() {
 		RunningInUserns: false,
 		SocketPath:      suite.d.UnixSocket.Socket.Addr().String(),
 	}
+	client, err := lxd.ConnectLXDUnix(suite.command.SocketPath, nil)
+	suite.Req.Nil(err)
+	suite.client = client
 }
 
 // If any argument intended for --auto is passed in interactive mode, an
@@ -34,7 +41,64 @@ func (suite *cmdInitTestSuite) SetupSuite() {
 func (suite *cmdInitTestSuite) TestCmdInit_InteractiveWithAutoArgs() {
 	suite.args.NetworkPort = 9999
 	err := suite.command.Run()
-	suite.Req.Equal(err.Error(), "Init configuration is only valid with --auto")
+	suite.Req.Equal("Init configuration is only valid with --auto", err.Error())
+}
+
+// Some arguments can only be passed together with --auto.
+func (suite *cmdInitTestSuite) TestCmdInit_AutoSpecificArgs() {
+	suite.args.StorageBackend = "dir"
+	err := suite.command.Run()
+	suite.Req.Equal("Init configuration is only valid with --auto", err.Error())
+}
+
+// If an invalid backend type is passed with --storage-backend, an
+// error is returned.
+func (suite *cmdInitTestSuite) TestCmdInit_AutoWithInvalidBackendType() {
+	suite.args.Auto = true
+	suite.args.StorageBackend = "foo"
+
+	err := suite.command.Run()
+	suite.Req.Equal("The requested backend 'foo' isn't supported by lxd init.", err.Error())
+}
+
+// If an backend type that is not available on the system is passed
+// with --storage-backend, an error is returned.
+func (suite *cmdInitTestSuite) TestCmdInit_AutoWithUnavailableBackendType() {
+	suite.args.Auto = true
+	suite.args.StorageBackend = "zfs"
+	suite.command.RunningInUserns = true // This makes zfs unavailable
+
+	err := suite.command.Run()
+	suite.Req.Equal("The requested backend 'zfs' isn't available on your system (missing tools).", err.Error())
+}
+
+// If --storage-backend is set to "dir", --storage-create-device can't be passed.
+func (suite *cmdInitTestSuite) TestCmdInit_AutoWithDirStorageBackendAndCreateDevice() {
+	suite.args.Auto = true
+	suite.args.StorageBackend = "dir"
+	suite.args.StorageCreateDevice = "/dev/sda4"
+
+	err := suite.command.Run()
+	suite.Req.Equal("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.", err.Error())
+}
+
+// Convenience for building the input text a user would enter for a certain
+// sequence of answers.
+type cmdInitAnswers struct {
+	StoragePoolDriver        string
+	WantAvailableOverNetwork bool
+	BindToAddress            string
+	BindToPort               string
+}
+
+// Render the input text the user would type for the desired answers, populating
+// the stdin of the given streams.
+func (answers *cmdInitAnswers) Render(streams *cmd.MemoryStreams) {
+	streams.InputAppendLine(answers.StoragePoolDriver)
+	if answers.WantAvailableOverNetwork {
+		streams.InputAppendLine(answers.BindToAddress)
+		streams.InputAppendLine(answers.BindToPort)
+	}
 }
 
 func TestCmdInitTestSuite(t *testing.T) {

From c5aa4d3edbc3dfc4e7784bbf1007cc4e4e1a05a3 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Thu, 4 May 2017 17:54:19 +0200
Subject: [PATCH 0943/1193] Consolidate interactive/auto init logic with the
 preseed one

Change initAutoOrInteractive to use the same code path that
initPreseeds uses to create or update the entities using the API.

This is done by making initAutoOrInteractive fill a cmdInitData
structure that has the same schema as the preseed YAML.

The resulting code is slightly shorter and less redundant.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/main_init.go                      | 253 ++++++++++++++++++++++------------
 test/main.sh                          |   3 +-
 test/suites/{init.sh => init_auto.sh} |   2 +-
 test/suites/init_interactive.sh       |  23 ++++
 4 files changed, 194 insertions(+), 87 deletions(-)
 rename test/suites/{init.sh => init_auto.sh} (99%)
 create mode 100644 test/suites/init_interactive.sh

diff --git a/lxd/main_init.go b/lxd/main_init.go
index 19d1cd72a..5a0f26a95 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -5,13 +5,16 @@ import (
 	"net"
 	"os"
 	"os/exec"
+	"strconv"
 	"syscall"
 
 	"golang.org/x/crypto/ssh/terminal"
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/cmd"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 // CmdInitArgs holds command line arguments for the "lxd init" command.
@@ -226,19 +229,24 @@ they otherwise would.
 		}
 	}
 
-	// Unset all storage keys, core.https_address and core.trust_password
-	for _, key := range []string{"storage.zfs_pool_name", "core.https_address", "core.trust_password"} {
-		err = cmd.setServerConfig(c, key, "")
-		if err != nil {
-			return err
-		}
-	}
-
 	// Destroy any existing loop device
 	for _, file := range []string{"zfs.img"} {
 		os.Remove(shared.VarPath(file))
 	}
 
+	server, _, err := c.GetServer()
+	if err != nil {
+		return err
+	}
+
+	data := &cmdInitData{}
+	data.ServerPut = server.Writable()
+
+	// If there's a default profile, and certain conditions are
+	// met we'll update its root disk device and/or eth0 network
+	// device, as well as its privileged mode (see below).
+	defaultProfile, _, getDefaultProfileErr := c.GetProfile("default")
+
 	if storageBackend == "zfs" {
 		if storageMode == "loop" {
 			storageDevice = shared.VarPath("zfs.img")
@@ -273,22 +281,16 @@ they otherwise would.
 			}
 		}
 
-		// Configure LXD to use the pool
-		err = cmd.setServerConfig(c, "storage.zfs_pool_name", storagePool)
-		if err != nil {
-			return err
-		}
+		data.Config["storage.zfs_pool_name"] = storagePool
+
 	}
 
-	if defaultPrivileged == 0 {
-		err = cmd.setProfileConfigItem(c, "default", "security.privileged", "")
-		if err != nil {
-			return err
-		}
+	if defaultPrivileged != -1 && getDefaultProfileErr != nil {
+		return getDefaultProfileErr
+	} else if defaultPrivileged == 0 {
+		defaultProfile.Config["security.privileged"] = ""
 	} else if defaultPrivileged == 1 {
-		err = cmd.setProfileConfigItem(c, "default", "security.privileged", "true")
-		if err != nil {
-		}
+		defaultProfile.Config["security.privileged"] = "true"
 	}
 
 	if networkAddress != "" {
@@ -296,21 +298,145 @@ they otherwise would.
 			networkPort = 8443
 		}
 
-		err = cmd.setServerConfig(c, "core.https_address", fmt.Sprintf("%s:%d", networkAddress, networkPort))
+		data.Config["core.https_address"] = fmt.Sprintf("%s:%d", networkAddress, networkPort)
+		if trustPassword != "" {
+			data.Config["core.trust_password"] = trustPassword
+		}
+	}
+
+	if getDefaultProfileErr == nil {
+		// Copy the default profile configuration (that we have
+		// possibly modified above).
+		data.Profiles = []api.ProfilesPost{{Name: "default"}}
+		data.Profiles[0].ProfilePut = defaultProfile.ProfilePut
+	}
+
+	err = cmd.run(c, data)
+	if err != nil {
+		return nil
+	}
+
+	return nil
+}
+
+// Apply the configuration specified in the given init data.
+func (cmd *CmdInit) run(client lxd.ContainerServer, data *cmdInitData) error {
+	// Functions that should be invoked to revert back to initial
+	// state any change that was successfully applied, in case
+	// anything goes wrong after that change.
+	reverters := make([]reverter, 0)
+
+	// Functions to apply the desired changes.
+	changers := make([](func() (reverter, error)), 0)
+
+	// Server config changer
+	changers = append(changers, func() (reverter, error) {
+		return cmd.initConfig(client, data.Config)
+	})
+
+	// Profile changers
+	for i := range data.Profiles {
+		profile := data.Profiles[i] // Local variable for the closure
+		changers = append(changers, func() (reverter, error) {
+			return cmd.initProfile(client, profile)
+		})
+	}
+
+	// Apply all changes. If anything goes wrong at any iteration
+	// of the loop, we'll try to revert any change performed in
+	// earlier iterations.
+	for _, changer := range changers {
+		reverter, err := changer()
 		if err != nil {
+			cmd.revert(reverters)
 			return err
 		}
+		// Save the revert function for later.
+		reverters = append(reverters, reverter)
+	}
 
-		if trustPassword != "" {
-			err = cmd.setServerConfig(c, "core.trust_password", trustPassword)
-			if err != nil {
-				return err
-			}
+	return nil
+}
+
+// Try to revert the state to what it was before running the "lxd init" command.
+func (cmd *CmdInit) revert(reverters []reverter) {
+	for _, reverter := range reverters {
+		err := reverter()
+		if err != nil {
+			logger.Warnf("Reverting to pre-init state failed: %s", err)
+			break
 		}
 	}
+}
 
-	cmd.Context.Output("LXD has been successfully configured.\n")
-	return nil
+// Apply the server-level configuration in the given map.
+func (cmd *CmdInit) initConfig(client lxd.ContainerServer, config map[string]interface{}) (reverter, error) {
+	server, etag, err := client.GetServer()
+	if err != nil {
+		return nil, err
+	}
+
+	// Build a function that can be used to revert the config to
+	// its original values.
+	reverter := func() error {
+		return client.UpdateServer(server.Writable(), "")
+	}
+
+	// The underlying code expects all values to be string, even if when
+	// using preseed the yaml.v2 package unmarshals them as integers.
+	for key, value := range config {
+		if number, ok := value.(int); ok {
+			value = strconv.Itoa(number)
+		}
+		config[key] = value
+	}
+
+	err = client.UpdateServer(api.ServerPut{Config: config}, etag)
+	if err != nil {
+		return nil, err
+	}
+
+	// Updating the server was sucessful, so return the reverter function
+	// in case it's needed later.
+	return reverter, nil
+}
+
+// Create or update a single profile, and return a revert function in case of success.
+func (cmd *CmdInit) initProfile(client lxd.ContainerServer, profile api.ProfilesPost) (reverter, error) {
+	var reverter func() error
+	currentProfile, _, err := client.GetProfile(profile.Name)
+	if err == nil {
+		reverter, err = cmd.initProfileUpdate(client, profile, currentProfile.Writable())
+	} else {
+		reverter, err = cmd.initProfileCreate(client, profile)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return reverter, nil
+}
+
+// Create a single new profile, and return a revert function to delete it.
+func (cmd *CmdInit) initProfileCreate(client lxd.ContainerServer, profile api.ProfilesPost) (reverter, error) {
+	reverter := func() error {
+		return client.DeleteProfile(profile.Name)
+	}
+	err := client.CreateProfile(profile)
+	return reverter, err
+}
+
+// Update a single profile, and return a function that can be used to
+// revert it to its original state.
+func (cmd *CmdInit) initProfileUpdate(client lxd.ContainerServer, profile api.ProfilesPost, currentProfile api.ProfilePut) (reverter, error) {
+	reverter := func() error {
+		return client.UpdateProfile(profile.Name, currentProfile, "")
+	}
+	err := client.UpdateProfile(profile.Name, api.ProfilePut{
+		Config:      profile.Config,
+		Description: profile.Description,
+		Devices:     profile.Devices,
+	}, "")
+	return reverter, err
 }
 
 // Check that the arguments passed via command line are consistent,
@@ -373,71 +499,28 @@ func (cmd *CmdInit) availableStoragePoolsDrivers() []string {
 	return drivers
 }
 
-func (cmd *CmdInit) setServerConfig(c lxd.ContainerServer, key string, value string) error {
-	server, etag, err := c.GetServer()
-	if err != nil {
-		return err
-	}
-
-	if server.Config == nil {
-		server.Config = map[string]interface{}{}
-	}
-
-	server.Config[key] = value
-
-	err = c.UpdateServer(server.Writable(), etag)
-	if err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func (cmd *CmdInit) profileDeviceAdd(c lxd.ContainerServer, profileName string, deviceName string, deviceConfig map[string]string) error {
-	profile, etag, err := c.GetProfile(profileName)
-	if err != nil {
-		return err
-	}
-
-	if profile.Devices == nil {
-		profile.Devices = map[string]map[string]string{}
-	}
-
+// Return an error if the given profile has already a device with the
+// given name.
+func (cmd *CmdInit) profileDeviceAlreadyExists(profile *api.Profile, deviceName string) error {
 	_, ok := profile.Devices[deviceName]
 	if ok {
 		return fmt.Errorf("Device already exists: %s", deviceName)
 	}
-
-	profile.Devices[deviceName] = deviceConfig
-
-	err = c.UpdateProfile(profileName, profile.Writable(), etag)
-	if err != nil {
-		return err
-	}
-
 	return nil
 }
 
-func (cmd *CmdInit) setProfileConfigItem(c lxd.ContainerServer, profileName string, key string, value string) error {
-	profile, etag, err := c.GetProfile(profileName)
-	if err != nil {
-		return err
-	}
-
-	if profile.Config == nil {
-		profile.Config = map[string]string{}
-	}
-
-	profile.Config[key] = value
-
-	err = c.UpdateProfile(profileName, profile.Writable(), etag)
-	if err != nil {
-		return err
-	}
-
-	return nil
+// Defines the schema for all possible configuration knobs supported by the
+// lxd init command, either directly fed via --preseed or populated by
+// the auto/interactive modes.
+type cmdInitData struct {
+	api.ServerPut `yaml:",inline"`
+	Profiles      []api.ProfilesPost
 }
 
+// Shortcut for closure/anonymous functions that are meant to revert
+// some change, and that are passed around as parameters.
+type reverter func() error
+
 func cmdInit() error {
 	context := cmd.NewContext(os.Stdin, os.Stdout, os.Stderr)
 	args := &CmdInitArgs{
diff --git a/test/main.sh b/test/main.sh
index 13b768c0d..169e8339c 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -560,6 +560,7 @@ run_test test_migration "migration"
 run_test test_fdleak "fd leak"
 run_test test_cpu_profiling "CPU profiling"
 run_test test_mem_profiling "memory profiling"
-run_test test_lxd_autoinit "lxd init auto"
+run_test test_init_auto "lxd init auto"
+run_test test_init_interactive "lxd init interactive"
 
 TEST_RESULT=success
diff --git a/test/suites/init.sh b/test/suites/init_auto.sh
similarity index 99%
rename from test/suites/init.sh
rename to test/suites/init_auto.sh
index 336c524cb..be37b5cd9 100644
--- a/test/suites/init.sh
+++ b/test/suites/init_auto.sh
@@ -1,4 +1,4 @@
-test_lxd_autoinit() {
+test_init_auto() {
   # - lxd init --auto --storage-backend zfs
   # and
   # - lxd init --auto
diff --git a/test/suites/init_interactive.sh b/test/suites/init_interactive.sh
new file mode 100644
index 000000000..fce7c14ef
--- /dev/null
+++ b/test/suites/init_interactive.sh
@@ -0,0 +1,23 @@
+test_init_interactive() {
+  # - lxd init
+  LXD_INIT_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+  chmod +x "${LXD_INIT_DIR}"
+  spawn_lxd "${LXD_INIT_DIR}"
+
+  (
+    set -e
+    # shellcheck disable=SC2034
+    LXD_DIR=${LXD_INIT_DIR}
+
+    cat <<EOF | lxd init
+dir
+no
+no
+EOF
+
+    lxc profile delete default
+  )
+  kill_lxd "${LXD_INIT_DIR}"
+
+  return
+}

From d3e61dc985d9bd750d2304efe0ad14716c8d4c4c Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 5 May 2017 07:40:02 +0200
Subject: [PATCH 0944/1193] Extract code asking init questions to individual
 methods

As a step towards making CmdInit.runAutoOrInteractive() shorter and
easier to split, I mechanically extracted the code that asks for the
various configuration choices into standalone methods.

This makes both the higher-level logic more readable (i.e. it's now
immediate to check which questions get asked and in which order), and
isolates the lower-level logic of individual questions, so it's easier
to see and have confidence of what are the moving parts and variables
involved (without interference between the various questions).

The logic has been moved around without any change, I've just added an
"XXX" comment in the new askStorage() method since there are a couple
of checks which seem redundant and could be dropped.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/main_init.go | 335 ++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 198 insertions(+), 137 deletions(-)

diff --git a/lxd/main_init.go b/lxd/main_init.go
index 5a0f26a95..9a3db166c 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -6,6 +6,7 @@ import (
 	"os"
 	"os/exec"
 	"strconv"
+	"strings"
 	"syscall"
 
 	"golang.org/x/crypto/ssh/terminal"
@@ -74,18 +75,10 @@ func (cmd *CmdInit) Run() error {
 // passed to the common run() method.
 func (cmd *CmdInit) runAutoOrInteractive(c lxd.ContainerServer, backendsAvailable []string) error {
 	var defaultPrivileged int // controls whether we set security.privileged=true
-	var storageBackend string // dir or zfs
-	var storageMode string    // existing, loop or device
-	var storageLoopSize int64 // Size in GB
-	var storageDevice string  // Path
-	var storagePool string    // pool name
-	var networkAddress string // Address
-	var networkPort int64     // Port
-	var trustPassword string  // Trust password
-
-	// Detect userns
+	var storage *cmdInitStorageParams
+	var networking *cmdInitNetworkingParams
+
 	defaultPrivileged = -1
-	runningInUserns = cmd.RunningInUserns
 
 	// Check that we have no containers or images in the store
 	containers, err := c.GetContainerNames()
@@ -112,121 +105,38 @@ func (cmd *CmdInit) runAutoOrInteractive(c lxd.ContainerServer, backendsAvailabl
 			return err
 		}
 
-		// Set the local variables
-		if cmd.Args.StorageCreateDevice != "" {
-			storageMode = "device"
-		} else if cmd.Args.StorageCreateLoop != -1 {
-			storageMode = "loop"
-		} else {
-			storageMode = "existing"
+		networking = &cmdInitNetworkingParams{
+			Address:       cmd.Args.NetworkAddress,
+			Port:          cmd.Args.NetworkPort,
+			TrustPassword: cmd.Args.TrustPassword,
 		}
 
-		storageBackend = cmd.Args.StorageBackend
-		storageLoopSize = cmd.Args.StorageCreateLoop
-		storageDevice = cmd.Args.StorageCreateDevice
-		storagePool = cmd.Args.StoragePool
-		networkAddress = cmd.Args.NetworkAddress
-		networkPort = cmd.Args.NetworkPort
-		trustPassword = cmd.Args.TrustPassword
-	} else {
-		defaultStorage := "dir"
-		if shared.StringInSlice("zfs", backendsAvailable) {
-			defaultStorage = "zfs"
-		}
-
-		storageBackend = cmd.Context.AskChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), supportedStoragePoolDrivers, defaultStorage)
-
-		if !shared.StringInSlice(storageBackend, supportedStoragePoolDrivers) {
-			return fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storageBackend)
-		}
-
-		if !shared.StringInSlice(storageBackend, backendsAvailable) {
-			return fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", storageBackend)
-		}
-
-		if storageBackend == "zfs" {
-			if cmd.Context.AskBool("Create a new ZFS pool (yes/no) [default=yes]? ", "yes") {
-				storagePool = cmd.Context.AskString("Name of the new ZFS pool [default=lxd]: ", "lxd", nil)
-				if cmd.Context.AskBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
-					deviceExists := func(path string) error {
-						if !shared.IsBlockdevPath(path) {
-							return fmt.Errorf("'%s' is not a block device", path)
-						}
-						return nil
-					}
-					storageDevice = cmd.Context.AskString("Path to the existing block device: ", "", deviceExists)
-					storageMode = "device"
-				} else {
-					st := syscall.Statfs_t{}
-					err := syscall.Statfs(shared.VarPath(), &st)
-					if err != nil {
-						return fmt.Errorf("couldn't statfs %s: %s", shared.VarPath(), err)
-					}
-
-					/* choose 15 GB < x < 100GB, where x is 20% of the disk size */
-					def := uint64(st.Frsize) * st.Blocks / (1024 * 1024 * 1024) / 5
-					if def > 100 {
-						def = 100
-					}
-					if def < 15 {
-						def = 15
-					}
-
-					q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%d]: ", def)
-					storageLoopSize = cmd.Context.AskInt(q, 1, -1, fmt.Sprintf("%d", def))
-					storageMode = "loop"
-				}
-			} else {
-				storagePool = cmd.Context.AskString("Name of the existing ZFS pool or dataset: ", "", nil)
-				storageMode = "existing"
+		if cmd.Args.StorageBackend == "zfs" {
+			storage = &cmdInitStorageParams{
+				Backend:  cmd.Args.StorageBackend,
+				LoopSize: cmd.Args.StorageCreateLoop,
+				Device:   cmd.Args.StorageCreateDevice,
+				Pool:     cmd.Args.StoragePool,
 			}
-		}
-
-		// Detect lack of uid/gid
-		needPrivileged := false
-		idmapset, err := shared.DefaultIdmapSet()
-		if err != nil || len(idmapset.Idmap) == 0 || idmapset.Usable() != nil {
-			needPrivileged = true
-		}
-
-		if runningInUserns && needPrivileged {
-			fmt.Printf(`
-We detected that you are running inside an unprivileged container.
-This means that unless you manually configured your host otherwise,
-you will not have enough uid and gid to allocate to your containers.
 
-LXD can re-use your container's own allocation to avoid the problem.
-Doing so makes your nested containers slightly less safe as they could
-in theory attack their parent container and gain more privileges than
-they otherwise would.
-
-`)
-			if cmd.Context.AskBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
-				defaultPrivileged = 1
+			if cmd.Args.StorageCreateDevice != "" {
+				storage.Mode = "device"
+			} else if cmd.Args.StorageCreateLoop != -1 {
+				storage.Mode = "loop"
 			} else {
-				defaultPrivileged = 0
+				storage.Mode = "existing"
 			}
 		}
 
-		if cmd.Context.AskBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
-			isIPAddress := func(s string) error {
-				if s != "all" && net.ParseIP(s) == nil {
-					return fmt.Errorf("'%s' is not an IP address", s)
-				}
-				return nil
-			}
+	} else {
+		storage, err = cmd.askStorage(c, backendsAvailable)
+		if err != nil {
+			return err
+		}
 
-			networkAddress = cmd.Context.AskString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
-			if networkAddress == "all" {
-				networkAddress = "::"
-			}
+		defaultPrivileged = cmd.askDefaultPrivileged()
+		networking = cmd.askNetworking()
 
-			if net.ParseIP(networkAddress).To4() == nil {
-				networkAddress = fmt.Sprintf("[%s]", networkAddress)
-			}
-			networkPort = cmd.Context.AskInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
-			trustPassword = cmd.Context.AskPassword("Trust password for new clients: ", cmd.PasswordReader)
-		}
 	}
 
 	// Destroy any existing loop device
@@ -247,42 +157,41 @@ they otherwise would.
 	// device, as well as its privileged mode (see below).
 	defaultProfile, _, getDefaultProfileErr := c.GetProfile("default")
 
-	if storageBackend == "zfs" {
-		if storageMode == "loop" {
-			storageDevice = shared.VarPath("zfs.img")
-			f, err := os.Create(storageDevice)
+	if storage != nil {
+		if storage.Mode == "loop" {
+			storage.Device = shared.VarPath("zfs.img")
+			f, err := os.Create(storage.Device)
 			if err != nil {
-				return fmt.Errorf("Failed to open %s: %s", storageDevice, err)
+				return fmt.Errorf("Failed to open %s: %s", storage.Device, err)
 			}
 
 			err = f.Chmod(0600)
 			if err != nil {
-				return fmt.Errorf("Failed to chmod %s: %s", storageDevice, err)
+				return fmt.Errorf("Failed to chmod %s: %s", storage.Device, err)
 			}
 
-			err = f.Truncate(int64(storageLoopSize * 1024 * 1024 * 1024))
+			err = f.Truncate(int64(storage.LoopSize * 1024 * 1024 * 1024))
 			if err != nil {
-				return fmt.Errorf("Failed to create sparse file %s: %s", storageDevice, err)
+				return fmt.Errorf("Failed to create sparse file %s: %s", storage.Device, err)
 			}
 
 			err = f.Close()
 			if err != nil {
-				return fmt.Errorf("Failed to close %s: %s", storageDevice, err)
+				return fmt.Errorf("Failed to close %s: %s", storage.Device, err)
 			}
 		}
 
-		if shared.StringInSlice(storageMode, []string{"loop", "device"}) {
+		if shared.StringInSlice(storage.Mode, []string{"loop", "device"}) {
 			output, err := shared.RunCommand(
 				"zpool",
-				"create", storagePool, storageDevice,
+				"create", storage.Pool, storage.Device,
 				"-f", "-m", "none", "-O", "compression=on")
 			if err != nil {
 				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
 			}
 		}
 
-		data.Config["storage.zfs_pool_name"] = storagePool
-
+		data.Config["storage.zfs_pool_name"] = storage.Pool
 	}
 
 	if defaultPrivileged != -1 && getDefaultProfileErr != nil {
@@ -293,14 +202,10 @@ they otherwise would.
 		defaultProfile.Config["security.privileged"] = "true"
 	}
 
-	if networkAddress != "" {
-		if networkPort == -1 {
-			networkPort = 8443
-		}
-
-		data.Config["core.https_address"] = fmt.Sprintf("%s:%d", networkAddress, networkPort)
-		if trustPassword != "" {
-			data.Config["core.trust_password"] = trustPassword
+	if networking != nil {
+		data.Config["core.https_address"] = fmt.Sprintf("%s:%d", networking.Address, networking.Port)
+		if networking.TrustPassword != "" {
+			data.Config["core.trust_password"] = networking.TrustPassword
 		}
 	}
 
@@ -509,6 +414,144 @@ func (cmd *CmdInit) profileDeviceAlreadyExists(profile *api.Profile, deviceName
 	return nil
 }
 
+// Ask if the user wants to create a new storage pool, and return
+// the relevant parameters if so.
+func (cmd *CmdInit) askStorage(client lxd.ContainerServer, availableBackends []string) (*cmdInitStorageParams, error) {
+	if !cmd.Context.AskBool("Do you want to configure a new storage pool (yes/no) [default=yes]? ", "yes") {
+		return nil, nil
+	}
+	storage := &cmdInitStorageParams{}
+	defaultStorage := "dir"
+	if shared.StringInSlice("zfs", availableBackends) {
+		defaultStorage = "zfs"
+	}
+	for {
+		storage.Backend = cmd.Context.AskChoice(fmt.Sprintf("Name of the storage backend to use (dir or zfs) [default=%s]: ", defaultStorage), availableBackends, defaultStorage)
+
+		// XXX The following to checks don't make much sense, since
+		// AskChoice will always re-ask the question if the answer
+		// is not among supportedStoragePoolDrivers. It seems legacy
+		// code that we should drop?
+		if !shared.StringInSlice(storage.Backend, supportedStoragePoolDrivers) {
+			return nil, fmt.Errorf("The requested backend '%s' isn't supported by lxd init.", storage.Backend)
+		}
+
+		// XXX Instead of manually checking if the provided choice is
+		// among availableBackends, we could just pass to askChoice the
+		// availableBackends list instead of supportedStoragePoolDrivers.
+		if !shared.StringInSlice(storage.Backend, availableBackends) {
+			return nil, fmt.Errorf("The requested backend '%s' isn't available on your system (missing tools).", storage.Backend)
+		}
+
+		if storage.Backend == "dir" {
+			break
+		}
+
+		storage.LoopSize = -1
+		question := fmt.Sprintf("Create a new %s pool (yes/no) [default=yes]? ", strings.ToUpper(storage.Backend))
+		if cmd.Context.AskBool(question, "yes") {
+			if cmd.Context.AskBool("Would you like to use an existing block device (yes/no) [default=no]? ", "no") {
+				deviceExists := func(path string) error {
+					if !shared.IsBlockdevPath(path) {
+						return fmt.Errorf("'%s' is not a block device", path)
+					}
+					return nil
+				}
+				storage.Device = cmd.Context.AskString("Path to the existing block device: ", "", deviceExists)
+				storage.Mode = "device"
+			} else {
+				st := syscall.Statfs_t{}
+				err := syscall.Statfs(shared.VarPath(), &st)
+				if err != nil {
+					return nil, fmt.Errorf("couldn't statfs %s: %s", shared.VarPath(), err)
+				}
+
+				/* choose 15 GB < x < 100GB, where x is 20% of the disk size */
+				def := uint64(st.Frsize) * st.Blocks / (1024 * 1024 * 1024) / 5
+				if def > 100 {
+					def = 100
+				}
+				if def < 15 {
+					def = 15
+				}
+
+				q := fmt.Sprintf("Size in GB of the new loop device (1GB minimum) [default=%d]: ", def)
+				storage.LoopSize = cmd.Context.AskInt(q, 1, -1, fmt.Sprintf("%d", def))
+				storage.Mode = "loop"
+			}
+		} else {
+			question := fmt.Sprintf("Name of the existing %s pool or dataset: ", strings.ToUpper(storage.Backend))
+			storage.Pool = cmd.Context.AskString(question, "", nil)
+			storage.Mode = "existing"
+		}
+		break
+	}
+	return storage, nil
+}
+
+// If we detect that we are running inside an unprivileged container,
+// ask if the user wants to the default profile to be a privileged
+// one.
+func (cmd *CmdInit) askDefaultPrivileged() int {
+	// Detect lack of uid/gid
+	defaultPrivileged := -1
+	needPrivileged := false
+	idmapset, err := shared.DefaultIdmapSet()
+	if err != nil || len(idmapset.Idmap) == 0 || idmapset.Usable() != nil {
+		needPrivileged = true
+	}
+
+	if cmd.RunningInUserns && needPrivileged {
+		fmt.Printf(`
+We detected that you are running inside an unprivileged container.
+This means that unless you manually configured your host otherwise,
+you will not have enough uid and gid to allocate to your containers.
+
+LXD can re-use your container's own allocation to avoid the problem.
+Doing so makes your nested containers slightly less safe as they could
+in theory attack their parent container and gain more privileges than
+they otherwise would.
+
+`)
+
+		if cmd.Context.AskBool("Would you like to have your containers share their parent's allocation (yes/no) [default=yes]? ", "yes") {
+			defaultPrivileged = 1
+		} else {
+			defaultPrivileged = 0
+		}
+	}
+	return defaultPrivileged
+}
+
+// Ask if the user wants to expose LXD over the network, and collect
+// the relevant parameters if so.
+func (cmd *CmdInit) askNetworking() *cmdInitNetworkingParams {
+	if !cmd.Context.AskBool("Would you like LXD to be available over the network (yes/no) [default=no]? ", "no") {
+		return nil
+	}
+	networking := &cmdInitNetworkingParams{}
+
+	isIPAddress := func(s string) error {
+		if s != "all" && net.ParseIP(s) == nil {
+			return fmt.Errorf("'%s' is not an IP address", s)
+		}
+		return nil
+	}
+
+	networking.Address = cmd.Context.AskString("Address to bind LXD to (not including port) [default=all]: ", "all", isIPAddress)
+	if networking.Address == "all" {
+		networking.Address = "::"
+	}
+
+	if net.ParseIP(networking.Address).To4() == nil {
+		networking.Address = fmt.Sprintf("[%s]", networking.Address)
+	}
+	networking.Port = cmd.Context.AskInt("Port to bind LXD to [default=8443]: ", 1, 65535, "8443")
+	networking.TrustPassword = cmd.Context.AskPassword("Trust password for new clients: ", cmd.PasswordReader)
+
+	return networking
+}
+
 // Defines the schema for all possible configuration knobs supported by the
 // lxd init command, either directly fed via --preseed or populated by
 // the auto/interactive modes.
@@ -517,6 +560,24 @@ type cmdInitData struct {
 	Profiles      []api.ProfilesPost
 }
 
+// Parameters needed when creating a storage pool in interactive or auto
+// mode.
+type cmdInitStorageParams struct {
+	Backend  string // == supportedStoragePoolDrivers
+	LoopSize int64  // Size in GB
+	Device   string // Path
+	Pool     string // pool name
+	Mode     string
+}
+
+// Parameters needed when configuring the LXD server networking options in interactive
+// mode or auto mode.
+type cmdInitNetworkingParams struct {
+	Address       string // Address
+	Port          int64  // Port
+	TrustPassword string // Trust password
+}
+
 // Shortcut for closure/anonymous functions that are meant to revert
 // some change, and that are passed around as parameters.
 type reverter func() error

From 19b27faf072fba42579dd58f1550f72cef1db409 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Fri, 5 May 2017 08:42:17 +0200
Subject: [PATCH 0945/1193] Extract logic to fill init data to standalone
 methods

This is a mechanical refactoring of the logic that fills in the
cmdInitData structure given the configuration parameters passed via
auto or collected in interactive mode.

It makes it a bit easier to both see in a blick what the high-level
logic workflow is and what individual configuration parameters
actually translate to low-level data to send via APIs.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/main_init.go  | 285 ++++++++++++++++++++++++++++++++----------------------
 shared/network.go |  15 +++
 2 files changed, 183 insertions(+), 117 deletions(-)

diff --git a/lxd/main_init.go b/lxd/main_init.go
index 9a3db166c..e752c9533 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -41,10 +41,6 @@ type CmdInit struct {
 
 // Run triggers the execution of the init command.
 func (cmd *CmdInit) Run() error {
-	// Figure what storage drivers among the supported ones are actually
-	// available on this system.
-	availableStoragePoolsDrivers := cmd.availableStoragePoolsDrivers()
-
 	// Check that command line arguments don't conflict with each other
 	err := cmd.validateArgs()
 	if err != nil {
@@ -57,36 +53,13 @@ func (cmd *CmdInit) Run() error {
 		return fmt.Errorf("Unable to talk to LXD: %s", err)
 	}
 
-	err = cmd.runAutoOrInteractive(client, availableStoragePoolsDrivers)
-
-	if err == nil {
-		cmd.Context.Output("LXD has been successfully configured.\n")
-	}
-
-	return err
-}
-
-// Run the logic for auto or interactive mode.
-//
-// XXX: this logic is going to be refactored into two separate runAuto
-// and runInteractive methods, sharing relevant logic with
-// runPreseed. The idea being that both runAuto and runInteractive
-// will end up populating the same low-level cmdInitData structure
-// passed to the common run() method.
-func (cmd *CmdInit) runAutoOrInteractive(c lxd.ContainerServer, backendsAvailable []string) error {
-	var defaultPrivileged int // controls whether we set security.privileged=true
-	var storage *cmdInitStorageParams
-	var networking *cmdInitNetworkingParams
-
-	defaultPrivileged = -1
-
 	// Check that we have no containers or images in the store
-	containers, err := c.GetContainerNames()
+	containers, err := client.GetContainerNames()
 	if err != nil {
 		return fmt.Errorf("Unable to list the LXD containers: %s", err)
 	}
 
-	images, err := c.GetImageFingerprints()
+	images, err := client.GetImageFingerprints()
 	if err != nil {
 		return fmt.Errorf("Unable to list the LXD images: %s", err)
 	}
@@ -95,137 +68,215 @@ func (cmd *CmdInit) runAutoOrInteractive(c lxd.ContainerServer, backendsAvailabl
 		return fmt.Errorf("You have existing containers or images. lxd init requires an empty LXD.")
 	}
 
+	data := &cmdInitData{}
+
+	// Copy the data from the current default profile, if it exists.
+	cmd.fillDataWithCurrentServerConfig(data, client)
+
+	// Copy the data from the current server config.
+	cmd.fillDataWithCurrentDefaultProfile(data, client)
+
+	// Figure what storage drivers among the supported ones are actually
+	// available on this system.
+	backendsAvailable := cmd.availableStoragePoolsDrivers()
+
 	if cmd.Args.Auto {
-		if cmd.Args.StorageBackend == "" {
-			cmd.Args.StorageBackend = "dir"
-		}
+		err = cmd.fillDataAuto(data, client, backendsAvailable)
+	} else {
+		err = cmd.fillDataInteractive(data, client, backendsAvailable)
+	}
 
-		err = cmd.validateArgsAuto(backendsAvailable)
-		if err != nil {
-			return err
-		}
+	if err != nil {
+		return err
+	}
+
+	// Apply the desired configuration.
+	err = cmd.apply(client, data)
+	if err != nil {
+		return err
+	}
+	cmd.Context.Output("LXD has been successfully configured.\n")
 
-		networking = &cmdInitNetworkingParams{
+	return nil
+}
+
+// Fill the given configuration data with parameters collected from
+// the --auto command line.
+func (cmd *CmdInit) fillDataAuto(data *cmdInitData, client lxd.ContainerServer, backendsAvailable []string) error {
+	if cmd.Args.StorageBackend == "" {
+		cmd.Args.StorageBackend = "dir"
+	}
+	err := cmd.validateArgsAuto(backendsAvailable)
+	if err != nil {
+		return err
+	}
+
+	if cmd.Args.NetworkAddress != "" {
+		// If no port was provided, use the default one
+		if cmd.Args.NetworkPort == -1 {
+			cmd.Args.NetworkPort = 8443
+		}
+		networking := &cmdInitNetworkingParams{
 			Address:       cmd.Args.NetworkAddress,
 			Port:          cmd.Args.NetworkPort,
 			TrustPassword: cmd.Args.TrustPassword,
 		}
+		cmd.fillDataWithNetworking(data, networking)
+	}
 
-		if cmd.Args.StorageBackend == "zfs" {
-			storage = &cmdInitStorageParams{
-				Backend:  cmd.Args.StorageBackend,
-				LoopSize: cmd.Args.StorageCreateLoop,
-				Device:   cmd.Args.StorageCreateDevice,
-				Pool:     cmd.Args.StoragePool,
-			}
-
-			if cmd.Args.StorageCreateDevice != "" {
-				storage.Mode = "device"
-			} else if cmd.Args.StorageCreateLoop != -1 {
-				storage.Mode = "loop"
-			} else {
-				storage.Mode = "existing"
-			}
+	if cmd.Args.StorageBackend == "zfs" {
+		storage := &cmdInitStorageParams{
+			Backend:  cmd.Args.StorageBackend,
+			LoopSize: cmd.Args.StorageCreateLoop,
+			Device:   cmd.Args.StorageCreateDevice,
+			Pool:     cmd.Args.StoragePool,
 		}
 
-	} else {
-		storage, err = cmd.askStorage(c, backendsAvailable)
+		if cmd.Args.StorageCreateDevice != "" {
+			storage.Mode = "device"
+		} else if cmd.Args.StorageCreateLoop != -1 {
+			storage.Mode = "loop"
+		} else {
+			storage.Mode = "existing"
+		}
+		err = cmd.fillDataWithStorage(data, storage)
 		if err != nil {
 			return err
 		}
+	}
+	return nil
+}
 
-		defaultPrivileged = cmd.askDefaultPrivileged()
-		networking = cmd.askNetworking()
-
+// Fill the given configuration data with parameters collected with
+// interactive questions.
+func (cmd *CmdInit) fillDataInteractive(data *cmdInitData, client lxd.ContainerServer, backendsAvailable []string) error {
+	storage, err := cmd.askStorage(client, backendsAvailable)
+	if err != nil {
+		return err
 	}
+	defaultPrivileged := cmd.askDefaultPrivileged()
+	networking := cmd.askNetworking()
 
-	// Destroy any existing loop device
-	for _, file := range []string{"zfs.img"} {
-		os.Remove(shared.VarPath(file))
+	err = cmd.fillDataWithStorage(data, storage)
+	if err != nil {
+		return err
 	}
 
-	server, _, err := c.GetServer()
+	err = cmd.fillDataWithDefaultPrivileged(data, defaultPrivileged)
 	if err != nil {
 		return err
 	}
 
-	data := &cmdInitData{}
+	cmd.fillDataWithNetworking(data, networking)
+
+	return nil
+}
+
+// Fill the given data with the current server configuration.
+func (cmd *CmdInit) fillDataWithCurrentServerConfig(data *cmdInitData, client lxd.ContainerServer) error {
+	server, _, err := client.GetServer()
+	if err != nil {
+		return err
+	}
 	data.ServerPut = server.Writable()
+	return nil
+}
 
-	// If there's a default profile, and certain conditions are
-	// met we'll update its root disk device and/or eth0 network
-	// device, as well as its privileged mode (see below).
-	defaultProfile, _, getDefaultProfileErr := c.GetProfile("default")
-
-	if storage != nil {
-		if storage.Mode == "loop" {
-			storage.Device = shared.VarPath("zfs.img")
-			f, err := os.Create(storage.Device)
-			if err != nil {
-				return fmt.Errorf("Failed to open %s: %s", storage.Device, err)
-			}
+// Fill the given data with the current default profile, if it exists.
+func (cmd *CmdInit) fillDataWithCurrentDefaultProfile(data *cmdInitData, client lxd.ContainerServer) {
+	defaultProfile, _, err := client.GetProfile("default")
+	if err == nil {
+		// Copy the default profile configuration (that we have
+		// possibly modified above).
+		data.Profiles = []api.ProfilesPost{{Name: "default"}}
+		data.Profiles[0].ProfilePut = defaultProfile.ProfilePut
+	}
+}
 
-			err = f.Chmod(0600)
-			if err != nil {
-				return fmt.Errorf("Failed to chmod %s: %s", storage.Device, err)
-			}
+// Fill the given init data with a new storage pool structure matching the
+// given storage parameters.
+func (cmd *CmdInit) fillDataWithStorage(data *cmdInitData, storage *cmdInitStorageParams) error {
+	// Destroy any existing loop device
+	for _, file := range []string{"zfs.img"} {
+		os.Remove(shared.VarPath(file))
+	}
 
-			err = f.Truncate(int64(storage.LoopSize * 1024 * 1024 * 1024))
-			if err != nil {
-				return fmt.Errorf("Failed to create sparse file %s: %s", storage.Device, err)
-			}
+	if storage == nil {
+		return nil
+	}
 
-			err = f.Close()
-			if err != nil {
-				return fmt.Errorf("Failed to close %s: %s", storage.Device, err)
-			}
+	if storage.Mode == "loop" {
+		storage.Device = shared.VarPath("zfs.img")
+		f, err := os.Create(storage.Device)
+		if err != nil {
+			return fmt.Errorf("Failed to open %s: %s", storage.Device, err)
 		}
 
-		if shared.StringInSlice(storage.Mode, []string{"loop", "device"}) {
-			output, err := shared.RunCommand(
-				"zpool",
-				"create", storage.Pool, storage.Device,
-				"-f", "-m", "none", "-O", "compression=on")
-			if err != nil {
-				return fmt.Errorf("Failed to create the ZFS pool: %s", output)
-			}
+		err = f.Chmod(0600)
+		if err != nil {
+			return fmt.Errorf("Failed to chmod %s: %s", storage.Device, err)
 		}
 
-		data.Config["storage.zfs_pool_name"] = storage.Pool
-	}
+		err = f.Truncate(int64(storage.LoopSize * 1024 * 1024 * 1024))
+		if err != nil {
+			return fmt.Errorf("Failed to create sparse file %s: %s", storage.Device, err)
+		}
 
-	if defaultPrivileged != -1 && getDefaultProfileErr != nil {
-		return getDefaultProfileErr
-	} else if defaultPrivileged == 0 {
-		defaultProfile.Config["security.privileged"] = ""
-	} else if defaultPrivileged == 1 {
-		defaultProfile.Config["security.privileged"] = "true"
+		err = f.Close()
+		if err != nil {
+			return fmt.Errorf("Failed to close %s: %s", storage.Device, err)
+		}
 	}
 
-	if networking != nil {
-		data.Config["core.https_address"] = fmt.Sprintf("%s:%d", networking.Address, networking.Port)
-		if networking.TrustPassword != "" {
-			data.Config["core.trust_password"] = networking.TrustPassword
+	if shared.StringInSlice(storage.Mode, []string{"loop", "device"}) {
+		output, err := shared.RunCommand(
+			"zpool",
+			"create", storage.Pool, storage.Device,
+			"-f", "-m", "none", "-O", "compression=on")
+		if err != nil {
+			return fmt.Errorf("Failed to create the ZFS pool: %s", output)
 		}
+	} else {
+		logger.Warnf("Did not find profile \"default\" so no default storage pool will be set. Manual intervention needed.")
 	}
 
-	if getDefaultProfileErr == nil {
-		// Copy the default profile configuration (that we have
-		// possibly modified above).
-		data.Profiles = []api.ProfilesPost{{Name: "default"}}
-		data.Profiles[0].ProfilePut = defaultProfile.ProfilePut
-	}
+	data.Config["storage.zfs_pool_name"] = storage.Pool
 
-	err = cmd.run(c, data)
-	if err != nil {
+	return nil
+}
+
+// Fill the default profile in the given init data with options about whether
+// to run in privileged mode.
+func (cmd *CmdInit) fillDataWithDefaultPrivileged(data *cmdInitData, defaultPrivileged int) error {
+	if defaultPrivileged == -1 {
 		return nil
 	}
-
+	if len(data.Profiles) == 0 {
+		return fmt.Errorf("error: profile 'default' profile not found")
+	}
+	defaultProfile := data.Profiles[0]
+	if defaultPrivileged == 0 {
+		defaultProfile.Config["security.privileged"] = ""
+	} else if defaultPrivileged == 1 {
+		defaultProfile.Config["security.privileged"] = "true"
+	}
 	return nil
 }
 
+// Fill the given init data with server config details matching the
+// given networking parameters.
+func (cmd *CmdInit) fillDataWithNetworking(data *cmdInitData, networking *cmdInitNetworkingParams) {
+	if networking == nil {
+		return
+	}
+	data.Config["core.https_address"] = fmt.Sprintf("%s:%d", networking.Address, networking.Port)
+	if networking.TrustPassword != "" {
+		data.Config["core.trust_password"] = networking.TrustPassword
+	}
+}
+
 // Apply the configuration specified in the given init data.
-func (cmd *CmdInit) run(client lxd.ContainerServer, data *cmdInitData) error {
+func (cmd *CmdInit) apply(client lxd.ContainerServer, data *cmdInitData) error {
 	// Functions that should be invoked to revert back to initial
 	// state any change that was successfully applied, in case
 	// anything goes wrong after that change.
@@ -406,7 +457,7 @@ func (cmd *CmdInit) availableStoragePoolsDrivers() []string {
 
 // Return an error if the given profile has already a device with the
 // given name.
-func (cmd *CmdInit) profileDeviceAlreadyExists(profile *api.Profile, deviceName string) error {
+func (cmd *CmdInit) profileDeviceAlreadyExists(profile *api.ProfilesPost, deviceName string) error {
 	_, ok := profile.Devices[deviceName]
 	if ok {
 		return fmt.Errorf("Device already exists: %s", deviceName)
diff --git a/shared/network.go b/shared/network.go
index e9857e547..51f1d796a 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -332,3 +332,18 @@ func WebsocketMirror(conn *websocket.Conn, w io.WriteCloser, r io.ReadCloser, Re
 var WebsocketUpgrader = websocket.Upgrader{
 	CheckOrigin: func(r *http.Request) bool { return true },
 }
+
+// AllocatePort asks the kernel for a free open port that is ready to use
+func AllocatePort() (int, error) {
+	addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
+	if err != nil {
+		return -1, err
+	}
+
+	l, err := net.ListenTCP("tcp", addr)
+	if err != nil {
+		return -1, err
+	}
+	defer l.Close()
+	return l.Addr().(*net.TCPAddr).Port, nil
+}

From 97be8b6fcfedcb023df7c386a181439e27e3a54f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 17 Sep 2017 16:56:20 -0700
Subject: [PATCH 0946/1193] network: Don't require ipt_checksum
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This should fix things for Solus users.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd-bridge/lxd-bridge | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd-bridge/lxd-bridge b/lxd-bridge/lxd-bridge
index 219efb54d..bb2cd650c 100755
--- a/lxd-bridge/lxd-bridge
+++ b/lxd-bridge/lxd-bridge
@@ -146,7 +146,7 @@ start() {
         iptables "${use_iptables_lock}" -I FORWARD -i "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
         iptables "${use_iptables_lock}" -I FORWARD -o "${LXD_BRIDGE}" -j ACCEPT -m comment --comment "managed by lxd-bridge"
     fi
-    iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
+    iptables "${use_iptables_lock}" -t mangle -A POSTROUTING -o "${LXD_BRIDGE}" -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge" || true
 
     if [ "${HAS_IPV6}" = "true" ]; then
         ip6tables "${use_iptables_lock}" -I INPUT -i "${LXD_BRIDGE}" -p udp --dport 67 -j ACCEPT -m comment --comment "managed by lxd-bridge"
@@ -203,7 +203,7 @@ stop() {
             iptables ${use_iptables_lock} -D FORWARD -i ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
             iptables ${use_iptables_lock} -D FORWARD -o ${LXD_BRIDGE} -j ACCEPT -m comment --comment "managed by lxd-bridge"
         fi
-        iptables ${use_iptables_lock} -t mangle -D POSTROUTING -o ${LXD_BRIDGE} -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge"
+        iptables ${use_iptables_lock} -t mangle -D POSTROUTING -o ${LXD_BRIDGE} -p udp -m udp --dport 68 -j CHECKSUM --checksum-fill -m comment --comment "managed by lxd-bridge" || true
 
         if [ -n "${LXD_IPV4_NETWORK}" ] && [ "${LXD_IPV4_NAT}" = "true" ]; then
             iptables ${use_iptables_lock} -t nat -D POSTROUTING -s ${LXD_IPV4_NETWORK} ! -d ${LXD_IPV4_NETWORK} -j MASQUERADE -m comment --comment "managed by lxd-bridge"

From 7317c1c027e19b0e110163ca2d03a6c5a67768df Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 31 May 2017 15:35:32 +0200
Subject: [PATCH 0947/1193] Use in-memory db for tests (makes them faster)

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/main_test.go | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/lxd/main_test.go b/lxd/main_test.go
index efd5e292d..ea95ec4d2 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -71,15 +71,11 @@ func (suite *lxdTestSuite) TearDownSuite() {
 }
 
 func (suite *lxdTestSuite) SetupTest() {
-	initializeDbObject(suite.d, shared.VarPath("lxd.db"))
+	initializeDbObject(suite.d, ":memory:")
 	daemonConfigInit(suite.d.db)
 	suite.Req = require.New(suite.T())
 }
 
 func (suite *lxdTestSuite) TearDownTest() {
 	suite.d.db.Close()
-	err := os.Remove(shared.VarPath("lxd.db"))
-	if err != nil {
-		os.Exit(1)
-	}
 }

From 98cd6281e8248b1ad836876d54de7b1e1b03cc83 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 30 May 2017 10:46:30 +0200
Subject: [PATCH 0948/1193] Replace some uses of InternalError with SmartError.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/api_1.0.go         |  2 +-
 lxd/container_get.go   |  2 +-
 lxd/containers_get.go  |  2 +-
 lxd/containers_post.go |  6 +++---
 lxd/images.go          |  2 +-
 lxd/operations.go      |  4 ++--
 lxd/profiles.go        |  6 +++---
 lxd/profiles_utils.go  | 12 ++++++------
 8 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 3a3b568c4..41c9c5ac9 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -160,7 +160,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 func api10Put(d *Daemon, r *http.Request) Response {
 	oldConfig, err := dbConfigValuesGet(d.db)
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	req := api.ServerPut{}
diff --git a/lxd/container_get.go b/lxd/container_get.go
index 3df94ea48..53743a1d7 100644
--- a/lxd/container_get.go
+++ b/lxd/container_get.go
@@ -15,7 +15,7 @@ func containerGet(d *Daemon, r *http.Request) Response {
 
 	state, err := c.Render()
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	return SyncResponse(true, state)
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 62e2682dd..bb5809b47 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -18,7 +18,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 		}
 		if !isDbLockedError(err) {
 			logger.Debugf("DBERR: containersGet: error %q", err)
-			return InternalError(err)
+			return SmartError(err)
 		}
 		// 1 s may seem drastic, but we really don't want to thrash
 		// perhaps we should use a random amount
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 76edaa366..333f51bd1 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -32,7 +32,7 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 		} else {
 			_, alias, err := dbImageAliasGet(d.db, req.Source.Alias, true)
 			if err != nil {
-				return InternalError(err)
+				return SmartError(err)
 			}
 
 			hash = alias.Target
@@ -44,7 +44,7 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 
 		hashes, err := dbImagesGet(d.db, false)
 		if err != nil {
-			return InternalError(err)
+			return SmartError(err)
 		}
 
 		var image *api.Image
@@ -356,7 +356,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	if req.Name == "" {
 		cs, err := dbContainersList(d.db, cTypeRegular)
 		if err != nil {
-			return InternalError(err)
+			return SmartError(err)
 		}
 
 		i := 0
diff --git a/lxd/images.go b/lxd/images.go
index dcda5560a..646434f9c 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1105,7 +1105,7 @@ func aliasesPost(d *Daemon, r *http.Request) Response {
 
 	err = dbImageAliasAdd(d.db, req.Name, id, req.Description)
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
diff --git a/lxd/operations.go b/lxd/operations.go
index 161ff036f..0794bc5d0 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -428,7 +428,7 @@ func operationAPIGet(d *Daemon, r *http.Request) Response {
 
 	_, body, err := op.Render()
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	return SyncResponse(true, body)
@@ -511,7 +511,7 @@ func operationAPIWaitGet(d *Daemon, r *http.Request) Response {
 
 	_, body, err := op.Render()
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	return SyncResponse(true, body)
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 4900e071f..2dabcf68c 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -88,7 +88,7 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 	// Update DB entry
 	_, err = dbProfileCreate(d.db, req.Name, req.Description, req.Config, req.Devices)
 	if err != nil {
-		return InternalError(
+		return SmartError(
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}
 
@@ -141,7 +141,7 @@ func profilePut(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 	id, profile, err := dbProfileGet(d.db, name)
 	if err != nil {
-		return InternalError(fmt.Errorf("Failed to retrieve profile='%s'", name))
+		return SmartError(fmt.Errorf("Failed to retrieve profile='%s'", name))
 	}
 
 	req := api.ProfilePut{}
@@ -182,7 +182,7 @@ func profilePost(d *Daemon, r *http.Request) Response {
 
 	err := dbProfileUpdate(d.db, name, req.Name)
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index 89aa1caae..355f53e0a 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -24,14 +24,14 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 	// Update the database
 	tx, err := dbBegin(d.db)
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	if profile.Description != req.Description {
 		err = dbProfileDescriptionUpdate(tx, id, req.Description)
 		if err != nil {
 			tx.Rollback()
-			return InternalError(err)
+			return SmartError(err)
 		}
 	}
 
@@ -39,7 +39,7 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 	if reflect.DeepEqual(profile.Config, req.Config) && reflect.DeepEqual(profile.Devices, req.Devices) {
 		err = txCommit(tx)
 		if err != nil {
-			return InternalError(err)
+			return SmartError(err)
 		}
 
 		return EmptySyncResponse
@@ -48,7 +48,7 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 	err = dbProfileConfigClear(tx, id)
 	if err != nil {
 		tx.Rollback()
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	err = dbProfileConfigAdd(tx, id, req.Config)
@@ -65,7 +65,7 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 
 	err = txCommit(tx)
 	if err != nil {
-		return InternalError(err)
+		return SmartError(err)
 	}
 
 	// Update all the containers using the profile. Must be done after txCommit due to DB lock.
@@ -88,7 +88,7 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 		for cname, err := range failures {
 			msg += fmt.Sprintf(" - %s: %s\n", cname, err)
 		}
-		return InternalError(fmt.Errorf("%s", msg))
+		return SmartError(fmt.Errorf("%s", msg))
 	}
 
 	return EmptySyncResponse

From a7548817566034f2bdf43a0faf1fb64d59b5cbe0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 2 Jun 2017 11:32:16 -0400
Subject: [PATCH 0949/1193] doc: Add a note for blkio limits
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3378

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/storage-backends.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/doc/storage-backends.md b/doc/storage-backends.md
index 736b3f12f..4ede1d404 100644
--- a/doc/storage-backends.md
+++ b/doc/storage-backends.md
@@ -26,6 +26,22 @@ using the new backend and converting older images to the new backend as needed.
 When the filesystem on the source and target hosts differs or when there is no faster way,  
 rsync is used to transfer the container content across.
 
+## I/O limits
+I/O limits in IOp/s or MB/s can be set on storage devices when attached to a container (see containers.md).
+
+Those are applied through the Linux "blkio" cgroup controller which makes it possible  
+to restrict I/O at the disk level (but nothing finer grained than that).
+
+Because those apply to a whole physical disk rather than a partition or path, the following restrictions apply:
+ - Limits will not apply to filesystems that are backed by virtual devices (e.g. device mapper).
+ - If a fileystem is backed by multiple block devices, each device will get the same limit.
+ - If the container is passed two disk devices that are each backed by the same disk,  
+   the limits of the two devices will be averaged.
+
+It's also worth noting that all I/O limits only apply to actual block device access,  
+so you will need to consider the filesystem's own overhead when setting limits.  
+This also means that access to cached data will not be affected by the limit.
+
 ## Notes
 ### Directory
 
@@ -81,6 +97,10 @@ rsync is used to transfer the container content across.
  - Note that LXD will assume it has full control over the zfs pool or dataset.
    It is recommended to not maintain any non-LXD owned filesystem entities in
    a LXD zfs pool or dataset since LXD might delete them.
+ - I/O quotas (IOps/MBs) are unlikely to affect ZFS filesystems very
+   much. That's because of ZFS being a port of a Solaris module (using SPL)
+   and not a native Linux filesystem using the Linux VFS API which is where
+   I/O limits are applied.
 
 #### Growing a loop backed ZFS pool
 LXD doesn't let you directly grow a loop backed ZFS pool, but you can do so with:

From fbbd4fa8c8ae3b8e9bc8e2aef0b16be9e6c33672 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 1 Jun 2017 22:06:37 -0400
Subject: [PATCH 0950/1193] shared/api: Extensions go at the bottom
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/container.go | 12 ++++++------
 shared/api/network.go   |  4 ++--
 shared/api/storage.go   |  8 ++++----
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/shared/api/container.go b/shared/api/container.go
index a9d916164..a300a0731 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -29,9 +29,6 @@ type ContainerPost struct {
 
 // ContainerPut represents the modifiable fields of a LXD container
 type ContainerPut struct {
-	// API extension: entity_description
-	Description string `json:"description" yaml:"description"`
-
 	Architecture string                       `json:"architecture" yaml:"architecture"`
 	Config       map[string]string            `json:"config" yaml:"config"`
 	Devices      map[string]map[string]string `json:"devices" yaml:"devices"`
@@ -41,6 +38,9 @@ type ContainerPut struct {
 	// For snapshot restore
 	Restore  string `json:"restore,omitempty" yaml:"restore,omitempty"`
 	Stateful bool   `json:"stateful" yaml:"stateful"`
+
+	// API extension: entity_description
+	Description string `json:"description" yaml:"description"`
 }
 
 // Container represents a LXD container
@@ -96,12 +96,12 @@ type ContainerSource struct {
 	Operation  string            `json:"operation,omitempty" yaml:"operation,omitempty"`
 	Websockets map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
 
-	// API extension: container_push
-	Live bool `json:"live,omitempty" yaml:"live,omitempty"`
-
 	// For "copy" type
 	Source string `json:"source,omitempty" yaml:"source,omitempty"`
 
+	// API extension: container_push
+	Live bool `json:"live,omitempty" yaml:"live,omitempty"`
+
 	// API extension: container_only_migration
 	ContainerOnly bool `json:"container_only,omitempty" yaml:"container_only,omitempty"`
 }
diff --git a/shared/api/network.go b/shared/api/network.go
index 2b390ff23..4fe3e8705 100644
--- a/shared/api/network.go
+++ b/shared/api/network.go
@@ -22,10 +22,10 @@ type NetworkPost struct {
 //
 // API extension: network
 type NetworkPut struct {
+	Config map[string]string `json:"config" yaml:"config"`
+
 	// API extension: entity_description
 	Description string `json:"description" yaml:"description"`
-
-	Config map[string]string `json:"config" yaml:"config"`
 }
 
 // Network represents a LXD network
diff --git a/shared/api/storage.go b/shared/api/storage.go
index 7a1be284b..f598ae8b0 100644
--- a/shared/api/storage.go
+++ b/shared/api/storage.go
@@ -25,10 +25,10 @@ type StoragePool struct {
 //
 // API extension: storage
 type StoragePoolPut struct {
+	Config map[string]string `json:"config" yaml:"config"`
+
 	// API extension: entity_description
 	Description string `json:"description" yaml:"description"`
-
-	Config map[string]string `json:"config" yaml:"config"`
 }
 
 // StorageVolumesPost represents the fields of a new LXD storage pool volume
@@ -55,10 +55,10 @@ type StorageVolume struct {
 //
 // API extension: storage
 type StorageVolumePut struct {
+	Config map[string]string `json:"config" yaml:"config"`
+
 	// API extension: entity_description
 	Description string `json:"description" yaml:"description"`
-
-	Config map[string]string `json:"config" yaml:"config"`
 }
 
 // Writable converts a full StoragePool struct into a StoragePoolPut struct

From a2b4430caadbcdd935bf1363e1c675a6459bb376 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 2 Jun 2017 01:09:08 -0400
Subject: [PATCH 0951/1193] Makefile: Update pot before po
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 Makefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index ab0a295fa..a2e77ec15 100644
--- a/Makefile
+++ b/Makefile
@@ -80,7 +80,7 @@ dist:
 	rm -Rf $(TMP)
 
 .PHONY: i18n update-po update-pot build-mo static-analysis
-i18n: update-po update-pot
+i18n: update-pot update-po
 
 po/%.mo: po/%.po
 	msgfmt --statistics -o $@ $<

From ed6efca63186ca7cc2f3b9722e8d902460e1e235 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Sat, 3 Jun 2017 22:36:42 +0200
Subject: [PATCH 0952/1193] network: use lxc.network.<n>.*

This deprecates defining multiple networks in the following format:

	lxc.network.type = veth
	lxc.network.flags = up
	lxc.network.link = lxdbr0
	lxc.network.hwaddr = 00:16:3e:09:7a:39
	lxc.network.name = eth0

	lxc.network.type = veth
	lxc.network.flags = up
	lxc.network.link = lxdbr1
	lxc.network.hwaddr = 00:16:3e:b2:8f:56
	lxc.network.name = eth1

in favor of:

	lxc.network.0.type = veth
	lxc.network.0.flags = up
	lxc.network.0.link = lxdbr0
	lxc.network.0.hwaddr = 00:16:3e:09:7a:39
	lxc.network.0.name = eth0

	lxc.network.1.type = veth
	lxc.network.1.flags = up
	lxc.network.1.link = lxdbr1
	lxc.network.1.hwaddr = 00:16:3e:b2:8f:56
	lxc.network.1.name = eth1

The former syntax is utterly misleading and difficult to handle in liblxc while
the latter will let liblxc nicely handle network configuration even if you
specify everything in random order. Cleaning out and updating will also work
much more reliable.

The regression potential for LXD is basically 0 since we regenerate the config
for the container each time we start it up.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fdd52984b..aa5a36a6f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1169,6 +1169,7 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup devices
+	networkidx := 0
 	for _, k := range c.expandedDevices.DeviceNames() {
 		m := c.expandedDevices[k]
 		if shared.StringInSlice(m["type"], []string{"unix-char", "unix-block"}) {
@@ -1192,34 +1193,34 @@ func (c *containerLXC) initLXC() error {
 
 			// Interface type specific configuration
 			if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p"}) {
-				err = lxcSetConfigItem(cc, "lxc.network.type", "veth")
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.type", networkidx), "veth")
 				if err != nil {
 					return err
 				}
 			} else if m["nictype"] == "physical" {
-				err = lxcSetConfigItem(cc, "lxc.network.type", "phys")
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.type", networkidx), "phys")
 				if err != nil {
 					return err
 				}
 			} else if m["nictype"] == "macvlan" {
-				err = lxcSetConfigItem(cc, "lxc.network.type", "macvlan")
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.type", networkidx), "macvlan")
 				if err != nil {
 					return err
 				}
 
-				err = lxcSetConfigItem(cc, "lxc.network.macvlan.mode", "bridge")
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.macvlan.mode", networkidx), "bridge")
 				if err != nil {
 					return err
 				}
 			}
 
-			err = lxcSetConfigItem(cc, "lxc.network.flags", "up")
+			err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.flags", networkidx), "up")
 			if err != nil {
 				return err
 			}
 
 			if shared.StringInSlice(m["nictype"], []string{"bridged", "physical", "macvlan"}) {
-				err = lxcSetConfigItem(cc, "lxc.network.link", m["parent"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.link", networkidx), m["parent"])
 				if err != nil {
 					return err
 				}
@@ -1227,7 +1228,7 @@ func (c *containerLXC) initLXC() error {
 
 			// Host Virtual NIC name
 			if m["host_name"] != "" {
-				err = lxcSetConfigItem(cc, "lxc.network.veth.pair", m["host_name"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.veth.pair", networkidx), m["host_name"])
 				if err != nil {
 					return err
 				}
@@ -1235,7 +1236,7 @@ func (c *containerLXC) initLXC() error {
 
 			// MAC address
 			if m["hwaddr"] != "" {
-				err = lxcSetConfigItem(cc, "lxc.network.hwaddr", m["hwaddr"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.hwaddr", networkidx), m["hwaddr"])
 				if err != nil {
 					return err
 				}
@@ -1243,7 +1244,7 @@ func (c *containerLXC) initLXC() error {
 
 			// MTU
 			if m["mtu"] != "" {
-				err = lxcSetConfigItem(cc, "lxc.network.mtu", m["mtu"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.mtu", networkidx), m["mtu"])
 				if err != nil {
 					return err
 				}
@@ -1251,7 +1252,7 @@ func (c *containerLXC) initLXC() error {
 
 			// Name
 			if m["name"] != "" {
-				err = lxcSetConfigItem(cc, "lxc.network.name", m["name"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.name", networkidx), m["name"])
 				if err != nil {
 					return err
 				}
@@ -1320,6 +1321,8 @@ func (c *containerLXC) initLXC() error {
 				}
 			}
 		}
+		// bump network index
+		networkidx++
 	}
 
 	// Setup shmounts

From 82ce76aa66d966ed55044f2aec01c129eaf09af2 Mon Sep 17 00:00:00 2001
From: Leon Klingele <git at leonklingele.de>
Date: Mon, 12 Jun 2017 18:23:09 +0200
Subject: [PATCH 0953/1193] dir: unfreeze on rsync error

This ensures that we unfreeze the container even if rsync fails
for some reason.

Signed-off-by: Leon Klingele <git at leonklingele.de>
---
 lxd/storage_dir.go | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 63003c6ac..346b36c57 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -229,12 +229,11 @@ func (s *storageDir) ContainerSnapshotCreate(
 			s.log.Warn("ContainerSnapshotCreate: trying to freeze and rsync again failed.")
 			return nil
 		}
+		defer sourceContainer.Unfreeze()
 
 		if err := rsync(snapshotContainer, oldPath, newPath); err != nil {
 			return err
 		}
-
-		defer sourceContainer.Unfreeze()
 	}
 
 	return nil

From 0ded50452015f0f5ce02eebaee4db031e8664b04 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 13 Jun 2017 12:03:15 +0200
Subject: [PATCH 0954/1193] network: fix network indices

Right now, liblxc is not yet able to handle configuring network devices
starting from arbitrary indices. For example, you couldn't start a network
configuration with:

lxc.network.1.type=veth
lxc.network.1.name=eth0

Also, arbitrary ordering the keys, e.g.

lxc.network.0.name=eth0
lxc.network.0.type=veth

does as of now also not work correctly. That is why we need to ensure in LXD
that the configuration keys are always set in correct order.

Now, about this commit. LXD did something dumb before. It bumped the index of
the network keys independent of whether the next configuration item was
actually a network key. This meant we would confuse liblxc by inserting keys
with different indices that actually should all have the same index since they
were meant to define a single network. Let's not do this and only bump the
index when we actually set a network key.

However, the real fix for this problem is obviously to enable liblxc to parse
network configuration way smarter. This goes hand-in-hand with deprecating the
aweful ordering based network configuration.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index aa5a36a6f..683294f16 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1257,6 +1257,8 @@ func (c *containerLXC) initLXC() error {
 					return err
 				}
 			}
+			// bump network index
+			networkidx++
 		} else if m["type"] == "disk" {
 			// Prepare all the paths
 			srcPath := m["source"]
@@ -1321,8 +1323,6 @@ func (c *containerLXC) initLXC() error {
 				}
 			}
 		}
-		// bump network index
-		networkidx++
 	}
 
 	// Setup shmounts

From dc95c74cf74f92260ca5ca90e2c791b763281cd3 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 13 Jun 2017 17:03:55 +0200
Subject: [PATCH 0955/1193] rsync: handle sparse files when rsyncing

Closes #3287.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/rsync.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/rsync.go b/lxd/rsync.go
index ca982996e..d4b71552b 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -59,6 +59,7 @@ func rsyncSendSetup(name string, path string) (*exec.Cmd, net.Conn, io.ReadClose
 		"--devices",
 		"--numeric-ids",
 		"--partial",
+		"--sparse",
 		path,
 		"localhost:/tmp/foo",
 		"-e",
@@ -126,6 +127,7 @@ func RsyncRecv(path string, conn *websocket.Conn) error {
 		"--numeric-ids",
 		"--devices",
 		"--partial",
+		"--sparse",
 		".",
 		path)
 

From a734cb32da446ce3a6ccd4daa601cb6560638c32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Jun 2017 17:54:06 -0400
Subject: [PATCH 0956/1193] client: Fail if source isn't listening on network
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This checks that the source image server lists at least one address for
network connections.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_images.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index f9065e3e5..edc7a4530 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -443,6 +443,10 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
 
 // tryCopyImage iterates through the source server URLs until one lets it download the image
 func (r *ProtocolLXD) tryCopyImage(req api.ImagesPost, urls []string) (*RemoteOperation, error) {
+	if len(urls) == 0 {
+		return nil, fmt.Errorf("The source server isn't listening on the network")
+	}
+
 	rop := RemoteOperation{
 		chDone: make(chan bool),
 	}

From e50a9ace4ec7ecd35fafebffba7421fc4e0b3414 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Jun 2017 18:02:10 -0400
Subject: [PATCH 0957/1193] client: Implement container and snapshot copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go     |  20 +++++
 client/lxd_containers.go | 186 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 203 insertions(+), 3 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index e5541564d..1d76196b5 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -57,6 +57,7 @@ type ContainerServer interface {
 	GetContainer(name string) (container *api.Container, ETag string, err error)
 	CreateContainer(container api.ContainersPost) (op *Operation, err error)
 	CreateContainerFromImage(source ImageServer, image api.Image, imgcontainer api.ContainersPost) (op *RemoteOperation, err error)
+	CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (op *RemoteOperation, err error)
 	UpdateContainer(name string, container api.ContainerPut, ETag string) (op *Operation, err error)
 	RenameContainer(name string, container api.ContainerPost) (op *Operation, err error)
 	MigrateContainer(name string, container api.ContainerPost) (op *Operation, err error)
@@ -72,6 +73,7 @@ type ContainerServer interface {
 	GetContainerSnapshots(containerName string) (snapshots []api.ContainerSnapshot, err error)
 	GetContainerSnapshot(containerName string, name string) (snapshot *api.ContainerSnapshot, ETag string, err error)
 	CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (op *Operation, err error)
+	CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (op *RemoteOperation, err error)
 	RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
 	MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
 	DeleteContainerSnapshot(containerName string, name string) (op *Operation, err error)
@@ -224,6 +226,24 @@ type ImageCopyArgs struct {
 	Public bool
 }
 
+// The ContainerCopyArgs struct is used to pass additional options during container copy
+type ContainerCopyArgs struct {
+	// If set, the container will be renamed on copy
+	Name string
+
+	// If set, the container running state will be transferred (live migration)
+	Live bool
+
+	// If set, only the container will copied, its snapshots won't
+	ContainerOnly bool
+}
+
+// The ContainerSnapshotCopyArgs struct is used to pass additional options during container copy
+type ContainerSnapshotCopyArgs struct {
+	// If set, the container will be renamed on copy
+	Name string
+}
+
 // The ContainerExecArgs struct is used to pass additional options during container exec
 type ContainerExecArgs struct {
 	// Standard input
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index ead43a04a..4f1b6c402 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -78,17 +78,27 @@ func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (*Operation,
 	return op, nil
 }
 
-func (r *ProtocolLXD) tryCreateContainerFromImage(req api.ContainersPost, urls []string) (*RemoteOperation, error) {
+func (r *ProtocolLXD) tryCreateContainer(req api.ContainersPost, urls []string) (*RemoteOperation, error) {
+	if len(urls) == 0 {
+		return nil, fmt.Errorf("The source server isn't listening on the network")
+	}
+
 	rop := RemoteOperation{
 		chDone: make(chan bool),
 	}
 
+	operation := req.Source.Operation
+
 	// Forward targetOp to remote op
 	go func() {
 		success := false
 		errors := []string{}
 		for _, serverURL := range urls {
-			req.Source.Server = serverURL
+			if operation == "" {
+				req.Source.Server = serverURL
+			} else {
+				req.Source.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, operation)
+			}
 
 			op, err := r.CreateContainer(req)
 			if err != nil {
@@ -182,7 +192,92 @@ func (r *ProtocolLXD) CreateContainerFromImage(source ImageServer, image api.Ima
 		req.Source.Secret = secret
 	}
 
-	return r.tryCreateContainerFromImage(req, info.Addresses)
+	return r.tryCreateContainer(req, info.Addresses)
+}
+
+// CopyContainer copies a container from a remote server. Additional options can be passed using ContainerCopyArgs
+func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (*RemoteOperation, error) {
+	// Base request
+	req := api.ContainersPost{
+		Name:         container.Name,
+		ContainerPut: container.Writable(),
+	}
+	req.Source.BaseImage = container.Config["volatile.base_image"]
+	req.Source.Live = container.StatusCode == api.Running
+
+	// Process the copy arguments
+	if args != nil {
+		// Sanity checks
+		if args.ContainerOnly && !r.HasExtension("container_only_migration") {
+			return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+		}
+
+		// Allow overriding the target name
+		if args.Name != "" {
+			req.Name = args.Name
+		}
+
+		req.Source.Live = args.Live
+		req.Source.ContainerOnly = args.ContainerOnly
+	}
+
+	// Optimization for the local copy case
+	if r == source {
+		// Local copy source fields
+		req.Source.Type = "copy"
+		req.Source.Source = container.Name
+
+		// Copy the container
+		op, err := r.CreateContainer(req)
+		if err != nil {
+			return nil, err
+		}
+
+		rop := RemoteOperation{
+			targetOp: op,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Get source server connection information
+	info, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, err
+	}
+
+	// Get a source operation
+	sourceReq := api.ContainerPost{
+		Migration:     true,
+		Live:          req.Source.Live,
+		ContainerOnly: req.Source.ContainerOnly,
+	}
+
+	op, err := source.MigrateContainer(container.Name, sourceReq)
+	if err != nil {
+		return nil, err
+	}
+
+	sourceSecrets := map[string]string{}
+	for k, v := range op.Metadata {
+		sourceSecrets[k] = v.(string)
+	}
+
+	// Migration source fields
+	req.Source.Type = "migration"
+	req.Source.Mode = "pull"
+	req.Source.Operation = op.ID
+	req.Source.Websockets = sourceSecrets
+	req.Source.Certificate = info.Certificate
+
+	return r.tryCreateContainer(req, info.Addresses)
 }
 
 // UpdateContainer updates the container definition
@@ -540,6 +635,91 @@ func (r *ProtocolLXD) CreateContainerSnapshot(containerName string, snapshot api
 	return op, nil
 }
 
+// CopyContainerSnapshot copies a snapshot from a remote server into a new container. Additional options can be passed using ContainerCopyArgs
+func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (*RemoteOperation, error) {
+	// Base request
+	fields := strings.SplitN(snapshot.Name, shared.SnapshotDelimiter, 2)
+	cName := fields[0]
+	sName := fields[1]
+
+	req := api.ContainersPost{
+		Name: cName,
+		ContainerPut: api.ContainerPut{
+			Architecture: snapshot.Architecture,
+			Config:       snapshot.Config,
+			Devices:      snapshot.Devices,
+			Ephemeral:    snapshot.Ephemeral,
+			Profiles:     snapshot.Profiles,
+			Stateful:     snapshot.Stateful,
+		},
+	}
+	req.Source.BaseImage = snapshot.Config["volatile.base_image"]
+
+	// Process the copy arguments
+	if args != nil {
+		// Allow overriding the target name
+		if args.Name != "" {
+			req.Name = args.Name
+		}
+	}
+
+	// Optimization for the local copy case
+	if r == source {
+		// Local copy source fields
+		req.Source.Type = "copy"
+		req.Source.Source = snapshot.Name
+
+		// Copy the container
+		op, err := r.CreateContainer(req)
+		if err != nil {
+			return nil, err
+		}
+
+		rop := RemoteOperation{
+			targetOp: op,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Get source server connection information
+	info, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, err
+	}
+
+	// Get a source operation
+	sourceReq := api.ContainerSnapshotPost{
+		Migration: true,
+	}
+
+	op, err := source.MigrateContainerSnapshot(cName, sName, sourceReq)
+	if err != nil {
+		return nil, err
+	}
+
+	sourceSecrets := map[string]string{}
+	for k, v := range op.Metadata {
+		sourceSecrets[k] = v.(string)
+	}
+
+	// Migration source fields
+	req.Source.Type = "migration"
+	req.Source.Mode = "pull"
+	req.Source.Operation = op.ID
+	req.Source.Websockets = sourceSecrets
+	req.Source.Certificate = info.Certificate
+
+	return r.tryCreateContainer(req, info.Addresses)
+}
+
 // RenameContainerSnapshot requests that LXD renames the snapshot
 func (r *ProtocolLXD) RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (*Operation, error) {
 	// Sanity check

From 5e45741ccf0df762d4eb95f56c2843f5d884cfd8 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 19 Jun 2017 15:44:43 +0200
Subject: [PATCH 0958/1193] exec: detect POLLNVAL when poll()ing

In case the child simply dies or the client exits the fd will be closed and we
will receive a POLLNVAL event. We need to handle this case otherwise we will
keep poll()ing without ever exiting.

Closes #2964.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_exec.go |  2 ++
 shared/util_linux.go  | 13 +++++++++----
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 2dc81bf00..d7023b71b 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -219,10 +219,12 @@ func (s *execWs) Do(op *operation) error {
 			conn := s.conns[0]
 			s.connsLock.Unlock()
 
+			logger.Debugf("Starting to mirror websocket")
 			readDone, writeDone := shared.WebsocketExecMirror(conn, ptys[0], ptys[0], attachedChildIsDead, int(ptys[0].Fd()))
 
 			<-readDone
 			<-writeDone
+			logger.Debugf("Finished to mirror websocket")
 
 			conn.Close()
 			wgEOF.Done()
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 90ec824e2..b8aefe63c 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -570,12 +570,14 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 
 		atomic.StoreInt32(&attachedChildIsDead, 1)
 
-		ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP))
+		ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP | POLLNVAL))
 		if ret < 0 {
 			logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLHUP | POLLRDHUP) on file descriptor: %s.", err)
 		} else if ret > 0 {
 			if (revents & POLLERR) > 0 {
 				logger.Warnf("Detected poll(POLLERR) event.")
+			} else if (revents & POLLNVAL) > 0 {
+				logger.Warnf("Detected poll(POLLNVAL) event.")
 			}
 		} else if ret == 0 {
 			logger.Debugf("No data in stdout: exiting.")
@@ -595,7 +597,7 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 			nr := 0
 			var err error
 
-			ret, revents, err := GetPollRevents(fd, -1, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP))
+			ret, revents, err := GetPollRevents(fd, -1, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP | POLLNVAL))
 			if ret < 0 {
 				// COMMENT(brauner):
 				// This condition is only reached in cases where we are massively f*cked since we even handle
@@ -618,6 +620,9 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 			if (revents & POLLERR) > 0 {
 				logger.Warnf("Detected poll(POLLERR) event: exiting.")
 				return
+			} else if (revents & POLLNVAL) > 0 {
+				logger.Warnf("Detected poll(POLLNVAL) event: exiting.")
+				return
 			}
 
 			if ((revents & (POLLIN | POLLPRI)) > 0) && !both {
@@ -675,11 +680,11 @@ func ExecReaderToChannel(r io.Reader, bufferSize int, exited <-chan bool, fd int
 					//   or (POLLHUP | POLLRDHUP). Both will trigger another codepath (See [2].)
 					//   that takes care that all data of the child that is buffered in
 					//   stdout is written out.
-					ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP))
+					ret, revents, err := GetPollRevents(fd, 0, (POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP | POLLNVAL))
 					if ret < 0 {
 						logger.Errorf("Failed to poll(POLLIN | POLLPRI | POLLERR | POLLHUP | POLLRDHUP) on file descriptor: %s. Exiting.", err)
 						return
-					} else if (revents & (POLLHUP | POLLRDHUP)) == 0 {
+					} else if (revents & (POLLHUP | POLLRDHUP | POLLERR | POLLNVAL)) == 0 {
 						logger.Debugf("Exiting but background processes are still running.")
 						return
 					}

From 85c0e5fcc14a1ae1ec3ee71b8a3bcc50fad5f5c7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 20 Jun 2017 11:18:14 -0400
Subject: [PATCH 0959/1193] lxd/images: Initialize image info in direct case
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index fa2f6d848..a195aa4d4 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -426,6 +426,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			return nil, err
 		}
 
+		info = &api.Image{}
+		info.Fingerprint = fp
 		info.Size = size
 		info.Architecture = imageMeta.Architecture
 		info.CreatedAt = time.Unix(imageMeta.CreationDate, 0)

From a0cd5829490c87cb268d8f99c9d45b4e04154701 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Jun 2017 23:48:55 -0400
Subject: [PATCH 0960/1193] client: Only set file headers if value is provided
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_containers.go | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 4f1b6c402..ba5fcee7e 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -537,9 +537,17 @@ func (r *ProtocolLXD) CreateContainerFile(containerName string, path string, arg
 	}
 
 	// Set the various headers
-	req.Header.Set("X-LXD-uid", fmt.Sprintf("%d", args.UID))
-	req.Header.Set("X-LXD-gid", fmt.Sprintf("%d", args.GID))
-	req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", args.Mode))
+	if args.UID > -1 {
+		req.Header.Set("X-LXD-uid", fmt.Sprintf("%d", args.UID))
+	}
+
+	if args.GID > -1 {
+		req.Header.Set("X-LXD-gid", fmt.Sprintf("%d", args.GID))
+	}
+
+	if args.Mode > -1 {
+		req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", args.Mode))
+	}
 
 	if args.Type != "" {
 		req.Header.Set("X-LXD-type", args.Type)

From a139d857c772149b192d43f1e76c97214ab18057 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 00:00:14 -0400
Subject: [PATCH 0961/1193] config: Implement temporary Legacy function
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This will be used for easier porting of the command line client.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config/legacy.go | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 lxc/config/legacy.go

diff --git a/lxc/config/legacy.go b/lxc/config/legacy.go
new file mode 100644
index 000000000..d286f1f03
--- /dev/null
+++ b/lxc/config/legacy.go
@@ -0,0 +1,31 @@
+package config
+
+import (
+	"github.com/lxc/lxd"
+)
+
+// Legacy returns a legacy *lxd.Config
+func (c *Config) Legacy() *lxd.Config {
+	conf := &lxd.Config{
+		DefaultRemote: c.DefaultRemote,
+		Aliases:       c.Aliases,
+		ConfigDir:     c.ConfigDir,
+	}
+
+	remotes := map[string]lxd.RemoteConfig{}
+
+	for k, v := range c.Remotes {
+		remote := lxd.RemoteConfig{
+			Addr:     v.Addr,
+			Public:   v.Public,
+			Protocol: v.Protocol,
+			Static:   v.Static,
+		}
+
+		remotes[k] = remote
+	}
+
+	conf.Remotes = remotes
+
+	return conf
+}

From e84109d4cb1c8de73e883de33749d645e0b75b5e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Jun 2017 23:58:19 -0400
Subject: [PATCH 0962/1193] client: Drop experimental tag from new client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/doc.go | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/client/doc.go b/client/doc.go
index f54f5b9db..f2de23949 100644
--- a/client/doc.go
+++ b/client/doc.go
@@ -1,14 +1,5 @@
 // Package lxd implements a client for the LXD API
 //
-// Warning
-//
-// This API isn't considered STABLE yet!
-//
-// This client library is planned to become the new supported Go library
-// for LXD which will come with guaranteed API stability. New functions and
-// struct arguments may be added over time but no existing signature or
-// type will be changed and structs will only gain new members.
-//
 // Overview
 //
 // This package lets you connect to LXD daemons or SimpleStream image

From 1099558b1c3c3af6d2b00712914645b08fb42abe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 23 Jun 2017 02:52:15 -0400
Subject: [PATCH 0963/1193] client: Fix potential race in event handler setup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

On extremely loaded LXD, it can be that the setup of a new event handler
will take long enough to miss critical events. Instead, lets setup the
event handler ahead of the operation query.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd.go        | 15 +++++++++++++++
 client/operations.go | 46 ++++++++++++++++++++++++++--------------------
 2 files changed, 41 insertions(+), 20 deletions(-)

diff --git a/client/lxd.go b/client/lxd.go
index 4c02eabe4..df1c6dbf9 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -181,15 +181,29 @@ func (r *ProtocolLXD) queryStruct(method string, path string, data interface{},
 }
 
 func (r *ProtocolLXD) queryOperation(method string, path string, data interface{}, ETag string) (*Operation, string, error) {
+	// Attempt to setup an early event listener
+	listener, err := r.GetEvents()
+	if err != nil {
+		listener = nil
+	}
+
 	// Send the query
 	resp, etag, err := r.query(method, path, data, ETag)
 	if err != nil {
+		if listener != nil {
+			listener.Disconnect()
+		}
+
 		return nil, "", err
 	}
 
 	// Get to the operation
 	respOperation, err := resp.MetadataAsOperation()
 	if err != nil {
+		if listener != nil {
+			listener.Disconnect()
+		}
+
 		return nil, "", err
 	}
 
@@ -197,6 +211,7 @@ func (r *ProtocolLXD) queryOperation(method string, path string, data interface{
 	op := Operation{
 		Operation: *respOperation,
 		r:         r,
+		listener:  listener,
 		chActive:  make(chan bool),
 	}
 
diff --git a/client/operations.go b/client/operations.go
index ec092644c..7bf1cbd60 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -16,8 +16,10 @@ type Operation struct {
 
 	r            *ProtocolLXD
 	listener     *EventListener
-	listenerLock sync.Mutex
-	chActive     chan bool
+	handlerReady bool
+	handlerLock  sync.Mutex
+
+	chActive chan bool
 }
 
 // AddHandler adds a function to be called whenever an event is received
@@ -29,8 +31,8 @@ func (op *Operation) AddHandler(function func(api.Operation)) (*EventTarget, err
 	}
 
 	// Make sure we're not racing with ourselves
-	op.listenerLock.Lock()
-	defer op.listenerLock.Unlock()
+	op.handlerLock.Lock()
+	defer op.handlerLock.Unlock()
 
 	// If we're done already, just return
 	if op.StatusCode.IsFinal() {
@@ -63,8 +65,8 @@ func (op *Operation) GetWebsocket(secret string) (*websocket.Conn, error) {
 // RemoveHandler removes a function to be called whenever an event is received
 func (op *Operation) RemoveHandler(target *EventTarget) error {
 	// Make sure we're not racing with ourselves
-	op.listenerLock.Lock()
-	defer op.listenerLock.Unlock()
+	op.handlerLock.Lock()
+	defer op.handlerLock.Unlock()
 
 	// If the listener is gone, just return
 	if op.listener == nil {
@@ -77,7 +79,7 @@ func (op *Operation) RemoveHandler(target *EventTarget) error {
 // Refresh pulls the current version of the operation and updates the struct
 func (op *Operation) Refresh() error {
 	// Don't bother with a manual update if we are listening for events
-	if op.listener != nil {
+	if op.handlerReady {
 		return nil
 	}
 
@@ -122,23 +124,27 @@ func (op *Operation) Wait() error {
 
 func (op *Operation) setupListener() error {
 	// Make sure we're not racing with ourselves
-	op.listenerLock.Lock()
-	defer op.listenerLock.Unlock()
+	op.handlerLock.Lock()
+	defer op.handlerLock.Unlock()
 
 	// We already have a listener setup
-	if op.listener != nil {
+	if op.handlerReady {
 		return nil
 	}
 
 	// Get a new listener
-	listener, err := op.r.GetEvents()
-	if err != nil {
-		return err
+	if op.listener == nil {
+		listener, err := op.r.GetEvents()
+		if err != nil {
+			return err
+		}
+
+		op.listener = listener
 	}
 
 	// Setup the handler
 	chReady := make(chan bool)
-	_, err = listener.AddHandler([]string{"operation"}, func(data interface{}) {
+	_, err := op.listener.AddHandler([]string{"operation"}, func(data interface{}) {
 		<-chReady
 
 		// Get an operation struct out of this data
@@ -148,8 +154,8 @@ func (op *Operation) setupListener() error {
 		}
 
 		// We don't want concurrency while processing events
-		op.listenerLock.Lock()
-		defer op.listenerLock.Unlock()
+		op.handlerLock.Lock()
+		defer op.handlerLock.Unlock()
 
 		// Check if we're done already (because of another event)
 		if op.listener == nil {
@@ -168,20 +174,20 @@ func (op *Operation) setupListener() error {
 		}
 	})
 	if err != nil {
-		listener.Disconnect()
+		op.listener.Disconnect()
 		return err
 	}
 
 	// And do a manual refresh to avoid races
 	err = op.Refresh()
 	if err != nil {
-		listener.Disconnect()
+		op.listener.Disconnect()
 		return err
 	}
 
 	// Check if not done already
 	if op.StatusCode.IsFinal() {
-		listener.Disconnect()
+		op.listener.Disconnect()
 		close(op.chActive)
 
 		if op.Err != "" {
@@ -192,7 +198,7 @@ func (op *Operation) setupListener() error {
 	}
 
 	// Start processing background updates
-	op.listener = listener
+	op.handlerReady = true
 	close(chReady)
 
 	return nil

From a5a7abacdbdb9afa9365cfc7b2eb2d385180adfd Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 14 Jun 2017 10:50:46 +0200
Subject: [PATCH 0964/1193] Cleanup.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/images.go | 24 +++++++-----------------
 1 file changed, 7 insertions(+), 17 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 646434f9c..6a3035dc1 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -700,35 +700,25 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		// Setup the cleanup function
 		defer cleanup(builddir, post)
 
-		if !imageUpload {
+		if imageUpload {
+			/* Processing image upload */
+			info, err = getImgPostInfo(d, r, builddir, post)
+		} else {
 			if req.Source.Type == "image" {
 				/* Processing image copy from remote */
 				info, err = imgPostRemoteInfo(d, req, op)
-				if err != nil {
-					return err
-				}
 			} else if req.Source.Type == "url" {
 				/* Processing image copy from URL */
 				info, err = imgPostURLInfo(d, req, op)
-				if err != nil {
-					return err
-				}
 			} else {
 				/* Processing image creation from container */
 				imagePublishLock.Lock()
 				info, err = imgPostContInfo(d, r, req, builddir)
-				if err != nil {
-					imagePublishLock.Unlock()
-					return err
-				}
 				imagePublishLock.Unlock()
 			}
-		} else {
-			/* Processing image upload */
-			info, err = getImgPostInfo(d, r, builddir, post)
-			if err != nil {
-				return err
-			}
+		}
+		if err != nil {
+			return err
 		}
 
 		// Set the metadata

From eaa31d39b2ec779617f71129a5dd760b2fc8ee7a Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 15 Jun 2017 15:01:42 +0200
Subject: [PATCH 0965/1193] Add logic to cancel HTTP request.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 client/interfaces.go           |  4 ++++
 client/simplestreams_images.go |  8 ++++----
 client/util.go                 |  8 +++++---
 lxd/daemon_images.go           |  5 ++++-
 lxd/images.go                  |  1 +
 lxd/operations.go              | 28 +++++++++++++++++++++++-----
 shared/operation/operation.go  | 31 +++++++++++++++++++++++++++++++
 7 files changed, 72 insertions(+), 13 deletions(-)
 create mode 100644 shared/operation/operation.go

diff --git a/client/interfaces.go b/client/interfaces.go
index 1d76196b5..dddac03d7 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -6,6 +6,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/operation"
 )
 
 // The Server type represents a generic read-only server.
@@ -194,6 +195,9 @@ type ImageFileRequest struct {
 
 	// Progress handler (called whenever some progress is made)
 	ProgressHandler func(progress ProgressData)
+
+	// The cancellable operation that's handling the request
+	Operation operation.CancellableOperation
 }
 
 // The ImageFileResponse struct is used as the response for image downloads
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index d84f8ed09..ef803105f 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -63,11 +63,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 		// Try over http
 		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), meta.Path)
 
-		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
+		size, err := downloadFileSha256(req.Operation, r.http, r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
 		if err != nil {
 			// Try over https
 			url = fmt.Sprintf("%s/%s", r.httpHost, meta.Path)
-			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
+			size, err = downloadFileSha256(req.Operation, r.http, r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
 			if err != nil {
 				return nil, err
 			}
@@ -84,11 +84,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 		// Try over http
 		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), rootfs.Path)
 
-		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+		size, err := downloadFileSha256(req.Operation, r.http, r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
 		if err != nil {
 			// Try over https
 			url = fmt.Sprintf("%s/%s", r.httpHost, rootfs.Path)
-			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+			size, err = downloadFileSha256(req.Operation, r.http, r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
 			if err != nil {
 				return nil, err
 			}
diff --git a/client/util.go b/client/util.go
index 973f1d562..2b35e606b 100644
--- a/client/util.go
+++ b/client/util.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
+	"github.com/lxc/lxd/shared/operation"
 )
 
 func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsCA string, tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
@@ -81,7 +82,7 @@ func unixHTTPClient(path string) (*http.Client, error) {
 	return &client, nil
 }
 
-func downloadFileSha256(httpClient *http.Client, useragent string, progress func(progress ProgressData), filename string, url string, hash string, target io.WriteSeeker) (int64, error) {
+func downloadFileSha256(op operation.CancellableOperation, httpClient *http.Client, useragent string, progress func(progress ProgressData), filename string, url string, hash string, target io.WriteSeeker) (int64, error) {
 	// Always seek to the beginning
 	target.Seek(0, 0)
 
@@ -95,12 +96,13 @@ func downloadFileSha256(httpClient *http.Client, useragent string, progress func
 		req.Header.Set("User-Agent", useragent)
 	}
 
-	// Start the request
-	r, err := httpClient.Do(req)
+	// Perform the request
+	r, err, doneCh := operation.CancellableDownload(op, httpClient, req)
 	if err != nil {
 		return -1, err
 	}
 	defer r.Body.Close()
+	defer close(doneCh)
 
 	if r.StatusCode != http.StatusOK {
 		return -1, fmt.Errorf("Unable to fetch %s: %s", url, r.Status)
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index a195aa4d4..d65639f98 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -19,6 +19,7 @@ import (
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/logger"
+	cancellable_op "github.com/lxc/lxd/shared/operation"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -345,6 +346,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			MetaFile:        io.WriteSeeker(dest),
 			RootfsFile:      io.WriteSeeker(destRootfs),
 			ProgressHandler: progress,
+			Operation:       op,
 		}
 
 		if secret != "" {
@@ -378,7 +380,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		req.Header.Set("User-Agent", version.UserAgent)
 
 		// Make the request
-		raw, err := httpClient.Do(req)
+		raw, err, doneCh := cancellable_op.CancellableDownload(op, httpClient, req)
+		defer close(doneCh)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/images.go b/lxd/images.go
index 6a3035dc1..238715c52 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -695,6 +695,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 
 	// Begin background operation
 	run := func(op *operation) error {
+		var err error
 		var info *api.Image
 
 		// Setup the cleanup function
diff --git a/lxd/operations.go b/lxd/operations.go
index 0794bc5d0..b3953187d 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -54,7 +54,8 @@ type operation struct {
 	onConnect func(*operation, *http.Request, http.ResponseWriter) error
 
 	// Channels used for error reporting and state tracking of background actions
-	chanDone chan error
+	chanDone   chan error
+	chanCancel chan error
 
 	// Locking for concurent access to the operation
 	lock sync.Mutex
@@ -153,17 +154,21 @@ func (op *operation) Cancel() (chan error, error) {
 		return nil, fmt.Errorf("Only running operations can be cancelled")
 	}
 
+	op.lock.Lock()
 	if !op.mayCancel() {
+		op.lock.Unlock()
 		return nil, fmt.Errorf("This operation can't be cancelled")
 	}
 
-	chanCancel := make(chan error, 1)
-
-	op.lock.Lock()
 	oldStatus := op.status
 	op.status = api.Cancelling
+	if op.chanCancel != nil {
+		// operationClassToken doesn't have the channel set
+		close(op.chanCancel)
+	}
 	op.lock.Unlock()
 
+	chanCancel := make(chan error, 1)
 	if op.onCancel != nil {
 		go func(op *operation, oldStatus api.StatusCode, chanCancel chan error) {
 			err := op.onCancel(op)
@@ -244,7 +249,20 @@ func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error
 }
 
 func (op *operation) mayCancel() bool {
-	return op.onCancel != nil || op.class == operationClassToken
+	return op.chanCancel != nil || op.class == operationClassToken
+}
+
+// Toggle whether the operation is cancellable. If `true` is passed, the
+// channel to cancel the operation is returned, otherwise nil.
+func (op *operation) Cancellable(flag bool) chan error {
+	var ch chan error
+	if flag {
+		ch = make(chan error)
+	}
+	op.lock.Lock()
+	op.chanCancel = ch
+	op.lock.Unlock()
+	return ch
 }
 
 func (op *operation) Render() (string, *api.Operation, error) {
diff --git a/shared/operation/operation.go b/shared/operation/operation.go
new file mode 100644
index 000000000..b0ee2c0d9
--- /dev/null
+++ b/shared/operation/operation.go
@@ -0,0 +1,31 @@
+package operation
+
+import (
+	"net/http"
+)
+
+// An operation that can be canceled.
+type CancellableOperation interface {
+
+	// Toggle whether the operation is cancellable
+	Cancellable(flag bool) chan error
+}
+
+func CancellableDownload(op CancellableOperation, client *http.Client, req *http.Request) (*http.Response, error, chan bool) {
+	chDone := make(chan bool)
+
+	go func() {
+		chCancel := op.Cancellable(true)
+		select {
+		case <-chCancel:
+			if transport, ok := client.Transport.(*http.Transport); ok {
+				transport.CancelRequest(req)
+			}
+		case <-chDone:
+		}
+		op.Cancellable(false)
+	}()
+
+	resp, err := client.Do(req)
+	return resp, err, chDone
+}

From 73a632bb0506839aaa9ffff4d7af73aba9929d75 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Jun 2017 02:22:36 -0400
Subject: [PATCH 0966/1193] Tweak wording and concept a bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go           |  6 ++---
 client/simplestreams_images.go |  8 +++----
 client/util.go                 |  6 ++---
 lxd/daemon_images.go           | 12 +++++++---
 lxd/operations.go              | 45 ++++++++++++++++++++-----------------
 shared/cancel/canceler.go      | 51 ++++++++++++++++++++++++++++++++++++++++++
 shared/operation/operation.go  | 31 -------------------------
 7 files changed, 94 insertions(+), 65 deletions(-)
 create mode 100644 shared/cancel/canceler.go
 delete mode 100644 shared/operation/operation.go

diff --git a/client/interfaces.go b/client/interfaces.go
index dddac03d7..eda7fa84e 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -6,7 +6,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared/api"
-	"github.com/lxc/lxd/shared/operation"
+	"github.com/lxc/lxd/shared/cancel"
 )
 
 // The Server type represents a generic read-only server.
@@ -196,8 +196,8 @@ type ImageFileRequest struct {
 	// Progress handler (called whenever some progress is made)
 	ProgressHandler func(progress ProgressData)
 
-	// The cancellable operation that's handling the request
-	Operation operation.CancellableOperation
+	// A canceler that can be used to interrupt some part of the image download request
+	Canceler *cancel.Canceler
 }
 
 // The ImageFileResponse struct is used as the response for image downloads
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index ef803105f..fd82d9912 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -63,11 +63,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 		// Try over http
 		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), meta.Path)
 
-		size, err := downloadFileSha256(req.Operation, r.http, r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
+		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "metadata", url, meta.Sha256, req.MetaFile)
 		if err != nil {
 			// Try over https
 			url = fmt.Sprintf("%s/%s", r.httpHost, meta.Path)
-			size, err = downloadFileSha256(req.Operation, r.http, r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
+			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "metadata", url, meta.Sha256, req.MetaFile)
 			if err != nil {
 				return nil, err
 			}
@@ -84,11 +84,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 		// Try over http
 		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), rootfs.Path)
 
-		size, err := downloadFileSha256(req.Operation, r.http, r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
 		if err != nil {
 			// Try over https
 			url = fmt.Sprintf("%s/%s", r.httpHost, rootfs.Path)
-			size, err = downloadFileSha256(req.Operation, r.http, r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
 			if err != nil {
 				return nil, err
 			}
diff --git a/client/util.go b/client/util.go
index 2b35e606b..1a166787d 100644
--- a/client/util.go
+++ b/client/util.go
@@ -9,8 +9,8 @@ import (
 	"net/url"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/cancel"
 	"github.com/lxc/lxd/shared/ioprogress"
-	"github.com/lxc/lxd/shared/operation"
 )
 
 func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsCA string, tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
@@ -82,7 +82,7 @@ func unixHTTPClient(path string) (*http.Client, error) {
 	return &client, nil
 }
 
-func downloadFileSha256(op operation.CancellableOperation, httpClient *http.Client, useragent string, progress func(progress ProgressData), filename string, url string, hash string, target io.WriteSeeker) (int64, error) {
+func downloadFileSha256(httpClient *http.Client, useragent string, progress func(progress ProgressData), canceler *cancel.Canceler, filename string, url string, hash string, target io.WriteSeeker) (int64, error) {
 	// Always seek to the beginning
 	target.Seek(0, 0)
 
@@ -97,7 +97,7 @@ func downloadFileSha256(op operation.CancellableOperation, httpClient *http.Clie
 	}
 
 	// Perform the request
-	r, err, doneCh := operation.CancellableDownload(op, httpClient, req)
+	r, err, doneCh := cancel.CancelableDownload(canceler, httpClient, req)
 	if err != nil {
 		return -1, err
 	}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index d65639f98..167e9717e 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -17,9 +17,9 @@ import (
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/cancel"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/logger"
-	cancellable_op "github.com/lxc/lxd/shared/operation"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -314,6 +314,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 	}
 
+	var canceler *cancel.Canceler
+	if op != nil {
+		canceler = &cancel.Canceler{}
+		op.canceler = canceler
+	}
+
 	if protocol == "lxd" || protocol == "simplestreams" {
 		// Create the target files
 		dest, err := os.Create(destName)
@@ -346,7 +352,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			MetaFile:        io.WriteSeeker(dest),
 			RootfsFile:      io.WriteSeeker(destRootfs),
 			ProgressHandler: progress,
-			Operation:       op,
+			Canceler:        canceler,
 		}
 
 		if secret != "" {
@@ -380,7 +386,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		req.Header.Set("User-Agent", version.UserAgent)
 
 		// Make the request
-		raw, err, doneCh := cancellable_op.CancellableDownload(op, httpClient, req)
+		raw, err, doneCh := cancel.CancelableDownload(canceler, httpClient, req)
 		defer close(doneCh)
 		if err != nil {
 			return nil, err
diff --git a/lxd/operations.go b/lxd/operations.go
index b3953187d..043181d81 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/cancel"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -47,6 +48,7 @@ type operation struct {
 	metadata  map[string]interface{}
 	err       string
 	readonly  bool
+	canceler  *cancel.Canceler
 
 	// Those functions are called at various points in the operation lifecycle
 	onRun     func(*operation) error
@@ -54,8 +56,7 @@ type operation struct {
 	onConnect func(*operation, *http.Request, http.ResponseWriter) error
 
 	// Channels used for error reporting and state tracking of background actions
-	chanDone   chan error
-	chanCancel chan error
+	chanDone chan error
 
 	// Locking for concurent access to the operation
 	lock sync.Mutex
@@ -154,21 +155,17 @@ func (op *operation) Cancel() (chan error, error) {
 		return nil, fmt.Errorf("Only running operations can be cancelled")
 	}
 
-	op.lock.Lock()
 	if !op.mayCancel() {
-		op.lock.Unlock()
 		return nil, fmt.Errorf("This operation can't be cancelled")
 	}
 
+	chanCancel := make(chan error, 1)
+
+	op.lock.Lock()
 	oldStatus := op.status
 	op.status = api.Cancelling
-	if op.chanCancel != nil {
-		// operationClassToken doesn't have the channel set
-		close(op.chanCancel)
-	}
 	op.lock.Unlock()
 
-	chanCancel := make(chan error, 1)
 	if op.onCancel != nil {
 		go func(op *operation, oldStatus api.StatusCode, chanCancel chan error) {
 			err := op.onCancel(op)
@@ -200,6 +197,13 @@ func (op *operation) Cancel() (chan error, error) {
 	_, md, _ := op.Render()
 	eventSend("operation", md)
 
+	if op.canceler != nil {
+		err := op.canceler.Cancel()
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	if op.onCancel == nil {
 		op.lock.Lock()
 		op.status = api.Cancelled
@@ -249,20 +253,19 @@ func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error
 }
 
 func (op *operation) mayCancel() bool {
-	return op.chanCancel != nil || op.class == operationClassToken
-}
+	if op.class == operationClassToken {
+		return true
+	}
 
-// Toggle whether the operation is cancellable. If `true` is passed, the
-// channel to cancel the operation is returned, otherwise nil.
-func (op *operation) Cancellable(flag bool) chan error {
-	var ch chan error
-	if flag {
-		ch = make(chan error)
+	if op.onCancel != nil {
+		return true
 	}
-	op.lock.Lock()
-	op.chanCancel = ch
-	op.lock.Unlock()
-	return ch
+
+	if op.canceler != nil && op.canceler.Cancelable() {
+		return true
+	}
+
+	return false
 }
 
 func (op *operation) Render() (string, *api.Operation, error) {
diff --git a/shared/cancel/canceler.go b/shared/cancel/canceler.go
new file mode 100644
index 000000000..7e8685197
--- /dev/null
+++ b/shared/cancel/canceler.go
@@ -0,0 +1,51 @@
+package cancel
+
+import (
+	"fmt"
+	"net/http"
+)
+
+// A struct to track canceleation
+type Canceler struct {
+	chCancel chan bool
+}
+
+func (c *Canceler) Cancelable() bool {
+	return c.chCancel != nil
+}
+
+func (c *Canceler) Cancel() error {
+	if c.chCancel == nil {
+		return fmt.Errorf("This operation cannot be canceled at this time")
+	}
+
+	close(c.chCancel)
+	c.chCancel = nil
+	return nil
+}
+
+func CancelableDownload(c *Canceler, client *http.Client, req *http.Request) (*http.Response, error, chan bool) {
+	chDone := make(chan bool)
+
+	go func() {
+		chCancel := make(chan bool)
+		if c != nil {
+			c.chCancel = chCancel
+		}
+
+		select {
+		case <-c.chCancel:
+			if transport, ok := client.Transport.(*http.Transport); ok {
+				transport.CancelRequest(req)
+			}
+		case <-chDone:
+		}
+
+		if c != nil {
+			c.chCancel = nil
+		}
+	}()
+
+	resp, err := client.Do(req)
+	return resp, err, chDone
+}
diff --git a/shared/operation/operation.go b/shared/operation/operation.go
deleted file mode 100644
index b0ee2c0d9..000000000
--- a/shared/operation/operation.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package operation
-
-import (
-	"net/http"
-)
-
-// An operation that can be canceled.
-type CancellableOperation interface {
-
-	// Toggle whether the operation is cancellable
-	Cancellable(flag bool) chan error
-}
-
-func CancellableDownload(op CancellableOperation, client *http.Client, req *http.Request) (*http.Response, error, chan bool) {
-	chDone := make(chan bool)
-
-	go func() {
-		chCancel := op.Cancellable(true)
-		select {
-		case <-chCancel:
-			if transport, ok := client.Transport.(*http.Transport); ok {
-				transport.CancelRequest(req)
-			}
-		case <-chDone:
-		}
-		op.Cancellable(false)
-	}()
-
-	resp, err := client.Do(req)
-	return resp, err, chDone
-}

From 7818cd420ba93fa410a90bfb4dc9be3bf22d688b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Jun 2017 13:59:46 -0400
Subject: [PATCH 0967/1193] tests: Don't attempt to finger public remotes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/remote.sh | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index 474a03e0a..c2c9671f5 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -28,7 +28,6 @@ test_remote_url() {
 
   for url in ${urls}; do
     lxc_remote remote add test "${url}"
-    lxc_remote finger test:
     lxc_remote remote remove test
   done
 }

From 660d4a3bd7c4abcd91349858754bccdeeca745dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Jun 2017 15:01:14 -0400
Subject: [PATCH 0968/1193] client: Fix non-interactive exec hangs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When in a non-interactive exec case with an active stdin, we shouldn't
wait for stdin to be done before closing the rest of the connections as
stdin needs to be closed separately.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_containers.go | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index ba5fcee7e..45d67c51d 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -392,10 +392,14 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 					<-shared.WebsocketRecvStream(args.Stdout, conn)
 					conn.Close()
 				}()
+			} else {
+				if args.DataDone != nil {
+					close(args.DataDone)
+				}
 			}
 		} else {
 			// Handle non-interactive sessions
-			dones := []chan bool{}
+			dones := map[int]chan bool{}
 			conns := []*websocket.Conn{}
 
 			// Handle stdin
@@ -406,7 +410,7 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 				}
 
 				conns = append(conns, conn)
-				dones = append(dones, shared.WebsocketSendStream(conn, args.Stdin, -1))
+				dones[0] = shared.WebsocketSendStream(conn, args.Stdin, -1)
 			}
 
 			// Handle stdout
@@ -417,7 +421,7 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 				}
 
 				conns = append(conns, conn)
-				dones = append(dones, shared.WebsocketRecvStream(args.Stdout, conn))
+				dones[1] = shared.WebsocketRecvStream(args.Stdout, conn)
 			}
 
 			// Handle stderr
@@ -428,12 +432,17 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 				}
 
 				conns = append(conns, conn)
-				dones = append(dones, shared.WebsocketRecvStream(args.Stderr, conn))
+				dones[2] = shared.WebsocketRecvStream(args.Stderr, conn)
 			}
 
 			// Wait for everything to be done
 			go func() {
-				for _, chDone := range dones {
+				for i, chDone := range dones {
+					// Skip stdin, dealing with it separately below
+					if i == 0 {
+						continue
+					}
+
 					<-chDone
 				}
 

From 4e3c852bccfbdedaabe250b5196942024b3359ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Jun 2017 15:10:15 -0400
Subject: [PATCH 0969/1193] lxd/shutdown: Only timeout if told to
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This changes the behavior from a default timeout of 60s, to no timeout
at all unless one is directly passed by the user.

LXD has an internal 30s timeout per container (shutdown is parallelize)
which can be overriden through container configuration.

Closes #3434

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_shutdown.go | 22 +++++++++-------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/lxd/main_shutdown.go b/lxd/main_shutdown.go
index 56be2aa12..7c982fbb7 100644
--- a/lxd/main_shutdown.go
+++ b/lxd/main_shutdown.go
@@ -8,14 +8,6 @@ import (
 )
 
 func cmdShutdown() error {
-	var timeout int
-
-	if *argTimeout == -1 {
-		timeout = 60
-	} else {
-		timeout = *argTimeout
-	}
-
 	c, err := lxd.ConnectLXDUnix("", nil)
 	if err != nil {
 		return err
@@ -38,11 +30,15 @@ func cmdShutdown() error {
 		close(chMonitor)
 	}()
 
-	select {
-	case <-chMonitor:
-		break
-	case <-time.After(time.Second * time.Duration(timeout)):
-		return fmt.Errorf("LXD still running after %ds timeout.", timeout)
+	if *argTimeout > 0 {
+		select {
+		case <-chMonitor:
+			break
+		case <-time.After(time.Second * time.Duration(*argTimeout)):
+			return fmt.Errorf("LXD still running after %ds timeout.", *argTimeout)
+		}
+	} else {
+		<-chMonitor
 	}
 
 	return nil

From ab022a610dbbfbceadc91230bd1ff54afd1c3e0c Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 28 Jun 2017 00:20:54 +0200
Subject: [PATCH 0970/1193] container_lxc: check whether disk device exists

We should check whether the disk device exists under the containers disk device
entries and only then try to forkumount.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 683294f16..f8bcd6514 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5297,6 +5297,11 @@ func (c *containerLXC) removeDiskDevice(name string, m types.Device) error {
 	devName := fmt.Sprintf("disk.%s", strings.Replace(tgtPath, "/", "-", -1))
 	devPath := filepath.Join(c.DevicesPath(), devName)
 
+	// The dsk device doesn't exist and cannot be mounted.
+	if !shared.PathExists(devPath) {
+		return nil
+	}
+
 	// Remove the bind-mount from the container
 	if c.FileExists(tgtPath) == nil {
 		err := c.removeMount(m["path"])

From d1359b2edac7b57d8ec8d158c2c2bc8b61331580 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 19 Sep 2017 11:20:57 -0400
Subject: [PATCH 0971/1193] Fix new golint warning
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/response.go | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/shared/api/response.go b/shared/api/response.go
index 71c7a0ecb..2bfda07d6 100644
--- a/shared/api/response.go
+++ b/shared/api/response.go
@@ -65,11 +65,7 @@ func (r *Response) MetadataAsStringSlice() ([]string, error) {
 
 // MetadataAsStruct parses the Response metadata into a provided struct
 func (r *Response) MetadataAsStruct(target interface{}) error {
-	if err := json.Unmarshal(r.Metadata, &target); err != nil {
-		return err
-	}
-
-	return nil
+	return json.Unmarshal(r.Metadata, &target)
 }
 
 // ResponseType represents a valid LXD response type

From f757e70254cbf0f5e296f7137ac22f81df581fa0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 00:27:02 -0400
Subject: [PATCH 0972/1193] lxc: Port to lxc/config for config management
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/action.go    |  15 ++--
 lxc/config.go    | 216 +++++++++++++++++++++++++++++++++++--------------------
 lxc/copy.go      |  22 ++++--
 lxc/delete.go    |  10 ++-
 lxc/exec.go      |  11 ++-
 lxc/file.go      |  36 ++++++----
 lxc/finger.go    |  14 ++--
 lxc/help.go      |   4 +-
 lxc/image.go     | 131 ++++++++++++++++++++++++---------
 lxc/info.go      |  16 +++--
 lxc/init.go      |  26 ++++---
 lxc/launch.go    |  22 ++++--
 lxc/list.go      |  16 +++--
 lxc/main.go      |  36 ++++++----
 lxc/main_test.go |   4 +-
 lxc/manpage.go   |   4 +-
 lxc/monitor.go   |  16 +++--
 lxc/move.go      |  20 ++++--
 lxc/profile.go   |  57 +++++++++------
 lxc/publish.go   |  25 ++++---
 lxc/remote.go    |  72 +++++++++----------
 lxc/restore.go   |  11 ++-
 lxc/snapshot.go  |  11 ++-
 lxc/utils.go     |  39 ++++++++++
 lxc/version.go   |   4 +-
 25 files changed, 567 insertions(+), 271 deletions(-)

diff --git a/lxc/action.go b/lxc/action.go
index d545cbb5c..5666d59ae 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -50,7 +51,7 @@ func (c *actionCmd) flags() {
 	gnuflag.BoolVar(&c.stateless, "stateless", false, i18n.G("Ignore the container state (only for start)"))
 }
 
-func (c *actionCmd) doAction(config *lxd.Config, nameArg string) error {
+func (c *actionCmd) doAction(conf *config.Config, nameArg string) error {
 	state := false
 
 	// Only store state if asked to
@@ -58,8 +59,12 @@ func (c *actionCmd) doAction(config *lxd.Config, nameArg string) error {
 		state = true
 	}
 
-	remote, name := config.ParseRemoteAndContainer(nameArg)
-	d, err := lxd.NewClient(config, remote)
+	remote, name, err := conf.ParseRemote(nameArg)
+	if err != nil {
+		return err
+	}
+
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -101,13 +106,13 @@ func (c *actionCmd) doAction(config *lxd.Config, nameArg string) error {
 	return nil
 }
 
-func (c *actionCmd) run(config *lxd.Config, args []string) error {
+func (c *actionCmd) run(conf *config.Config, args []string) error {
 	if len(args) == 0 {
 		return errArgs
 	}
 
 	// Run the action for every listed container
-	results := runBatch(args, func(name string) error { return c.doAction(config, name) })
+	results := runBatch(args, func(name string) error { return c.doAction(conf, name) })
 
 	// Single container is easy
 	if len(results) == 1 {
diff --git a/lxc/config.go b/lxc/config.go
index 7be59aca8..5702ed42e 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -14,6 +14,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -129,14 +130,18 @@ lxc config set core.trust_password blah
     Will set the server's trust password to blah.`)
 }
 
-func (c *configCmd) doSet(config *lxd.Config, args []string, unset bool) error {
+func (c *configCmd) doSet(conf *config.Config, args []string, unset bool) error {
 	if len(args) != 4 {
 		return errArgs
 	}
 
 	// [[lxc config]] set dakara:c1 limits.memory 200000
-	remote, container := config.ParseRemoteAndContainer(args[1])
-	d, err := lxd.NewClient(config, remote)
+	remote, container, err := conf.ParseRemote(args[1])
+	if err != nil {
+		return err
+	}
+
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -167,7 +172,7 @@ func (c *configCmd) doSet(config *lxd.Config, args []string, unset bool) error {
 	return d.SetContainerConfig(container, key, value)
 }
 
-func (c *configCmd) run(config *lxd.Config, args []string) error {
+func (c *configCmd) run(conf *config.Config, args []string) error {
 	if len(args) < 1 {
 		return errUsage
 	}
@@ -181,7 +186,7 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 
 		// Deal with local server
 		if len(args) == 2 {
-			c, err := lxd.NewClient(config, config.DefaultRemote)
+			c, err := lxd.NewClient(conf.Legacy(), conf.DefaultRemote)
 			if err != nil {
 				return err
 			}
@@ -201,9 +206,13 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 		}
 
 		// Deal with remote server
-		remote, container := config.ParseRemoteAndContainer(args[1])
+		remote, container, err := conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if container == "" {
-			c, err := lxd.NewClient(config, remote)
+			c, err := lxd.NewClient(conf.Legacy(), remote)
 			if err != nil {
 				return err
 			}
@@ -224,7 +233,7 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 
 		// Deal with container
 		args = append(args, "")
-		return c.doSet(config, args, true)
+		return c.doSet(conf, args, true)
 
 	case "set":
 		if len(args) < 3 {
@@ -233,7 +242,7 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 
 		// Deal with local server
 		if len(args) == 3 {
-			c, err := lxd.NewClient(config, config.DefaultRemote)
+			c, err := lxd.NewClient(conf.Legacy(), conf.DefaultRemote)
 			if err != nil {
 				return err
 			}
@@ -243,9 +252,13 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 		}
 
 		// Deal with remote server
-		remote, container := config.ParseRemoteAndContainer(args[1])
+		remote, container, err := conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if container == "" {
-			c, err := lxd.NewClient(config, remote)
+			c, err := lxd.NewClient(conf.Legacy(), remote)
 			if err != nil {
 				return err
 			}
@@ -255,7 +268,7 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 		}
 
 		// Deal with container
-		return c.doSet(config, args, false)
+		return c.doSet(conf, args, false)
 
 	case "trust":
 		if len(args) < 2 {
@@ -266,12 +279,16 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 		case "list":
 			var remote string
 			if len(args) == 3 {
-				remote = config.ParseRemote(args[2])
+				var err error
+				remote, _, err = conf.ParseRemote(args[2])
+				if err != nil {
+					return err
+				}
 			} else {
-				remote = config.DefaultRemote
+				remote = conf.DefaultRemote
 			}
 
-			d, err := lxd.NewClient(config, remote)
+			d, err := lxd.NewClient(conf.Legacy(), remote)
 			if err != nil {
 				return err
 			}
@@ -320,12 +337,16 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 			if len(args) < 3 {
 				return fmt.Errorf(i18n.G("No certificate provided to add"))
 			} else if len(args) == 4 {
-				remote = config.ParseRemote(args[2])
+				var err error
+				remote, _, err = conf.ParseRemote(args[2])
+				if err != nil {
+					return err
+				}
 			} else {
-				remote = config.DefaultRemote
+				remote = conf.DefaultRemote
 			}
 
-			d, err := lxd.NewClient(config, remote)
+			d, err := lxd.NewClient(conf.Legacy(), remote)
 			if err != nil {
 				return err
 			}
@@ -343,12 +364,16 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 			if len(args) < 3 {
 				return fmt.Errorf(i18n.G("No fingerprint specified."))
 			} else if len(args) == 4 {
-				remote = config.ParseRemote(args[2])
+				var err error
+				remote, _, err = conf.ParseRemote(args[2])
+				if err != nil {
+					return err
+				}
 			} else {
-				remote = config.DefaultRemote
+				remote = conf.DefaultRemote
 			}
 
-			d, err := lxd.NewClient(config, remote)
+			d, err := lxd.NewClient(conf.Legacy(), remote)
 			if err != nil {
 				return err
 			}
@@ -359,13 +384,17 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 		}
 
 	case "show":
-		remote := config.DefaultRemote
+		remote := conf.DefaultRemote
 		container := ""
 		if len(args) > 1 {
-			remote, container = config.ParseRemoteAndContainer(args[1])
+			var err error
+			remote, container, err = conf.ParseRemote(args[1])
+			if err != nil {
+				return err
+			}
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -373,12 +402,12 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 		var data []byte
 
 		if len(args) == 1 || container == "" {
-			config, err := d.ServerStatus()
+			server, err := d.ServerStatus()
 			if err != nil {
 				return err
 			}
 
-			brief := config.Writable()
+			brief := server.Writable()
 			data, err = yaml.Marshal(&brief)
 			if err != nil {
 				return err
@@ -386,35 +415,35 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 		} else {
 			var brief api.ContainerPut
 			if shared.IsSnapshot(container) {
-				config, err := d.SnapshotInfo(container)
+				snap, err := d.SnapshotInfo(container)
 				if err != nil {
 					return err
 				}
 
 				brief = api.ContainerPut{
-					Profiles:  config.Profiles,
-					Config:    config.Config,
-					Devices:   config.Devices,
-					Ephemeral: config.Ephemeral,
+					Profiles:  snap.Profiles,
+					Config:    snap.Config,
+					Devices:   snap.Devices,
+					Ephemeral: snap.Ephemeral,
 				}
 				if c.expanded {
 					brief = api.ContainerPut{
-						Profiles:  config.Profiles,
-						Config:    config.ExpandedConfig,
-						Devices:   config.ExpandedDevices,
-						Ephemeral: config.Ephemeral,
+						Profiles:  snap.Profiles,
+						Config:    snap.ExpandedConfig,
+						Devices:   snap.ExpandedDevices,
+						Ephemeral: snap.Ephemeral,
 					}
 				}
 			} else {
-				config, err := d.ContainerInfo(container)
+				container, err := d.ContainerInfo(container)
 				if err != nil {
 					return err
 				}
 
-				brief = config.Writable()
+				brief = container.Writable()
 				if c.expanded {
-					brief.Config = config.ExpandedConfig
-					brief.Devices = config.ExpandedDevices
+					brief.Config = container.ExpandedConfig
+					brief.Devices = container.ExpandedDevices
 				}
 			}
 
@@ -433,15 +462,19 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		remote := config.DefaultRemote
+		remote := conf.DefaultRemote
 		container := ""
 		key := args[1]
 		if len(args) > 2 {
-			remote, container = config.ParseRemoteAndContainer(args[1])
+			var err error
+			remote, container, err = conf.ParseRemote(args[1])
+			if err != nil {
+				return err
+			}
 			key = args[2]
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -478,19 +511,19 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 		}
 		switch args[1] {
 		case "list":
-			return c.deviceList(config, "container", args)
+			return c.deviceList(conf, "container", args)
 		case "add":
-			return c.deviceAdd(config, "container", args)
+			return c.deviceAdd(conf, "container", args)
 		case "remove":
-			return c.deviceRm(config, "container", args)
+			return c.deviceRm(conf, "container", args)
 		case "get":
-			return c.deviceGet(config, "container", args)
+			return c.deviceGet(conf, "container", args)
 		case "set":
-			return c.deviceSet(config, "container", args)
+			return c.deviceSet(conf, "container", args)
 		case "unset":
-			return c.deviceUnset(config, "container", args)
+			return c.deviceUnset(conf, "container", args)
 		case "show":
-			return c.deviceShow(config, "container", args)
+			return c.deviceShow(conf, "container", args)
 		default:
 			return errArgs
 		}
@@ -500,13 +533,17 @@ func (c *configCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		remote := config.DefaultRemote
+		remote := conf.DefaultRemote
 		container := ""
 		if len(args) > 1 {
-			remote, container = config.ParseRemoteAndContainer(args[1])
+			var err error
+			remote, container, err = conf.ParseRemote(args[1])
+			if err != nil {
+				return err
+			}
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -541,12 +578,12 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 	}
 
 	// Extract the current value
-	config, err := client.ContainerInfo(cont)
+	container, err := client.ContainerInfo(cont)
 	if err != nil {
 		return err
 	}
 
-	brief := config.Writable()
+	brief := container.Writable()
 	data, err := yaml.Marshal(&brief)
 	if err != nil {
 		return err
@@ -606,12 +643,12 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 	}
 
 	// Extract the current value
-	config, err := client.ServerStatus()
+	server, err := client.ServerStatus()
 	if err != nil {
 		return err
 	}
 
-	brief := config.Writable()
+	brief := server.Writable()
 	data, err := yaml.Marshal(&brief)
 	if err != nil {
 		return err
@@ -652,13 +689,17 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 	return nil
 }
 
-func (c *configCmd) deviceAdd(config *lxd.Config, which string, args []string) error {
+func (c *configCmd) deviceAdd(conf *config.Config, which string, args []string) error {
 	if len(args) < 5 {
 		return errArgs
 	}
-	remote, name := config.ParseRemoteAndContainer(args[2])
 
-	client, err := lxd.NewClient(config, remote)
+	remote, name, err := conf.ParseRemote(args[2])
+	if err != nil {
+		return err
+	}
+
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -690,14 +731,17 @@ func (c *configCmd) deviceAdd(config *lxd.Config, which string, args []string) e
 	return err
 }
 
-func (c *configCmd) deviceGet(config *lxd.Config, which string, args []string) error {
+func (c *configCmd) deviceGet(conf *config.Config, which string, args []string) error {
 	if len(args) < 5 {
 		return errArgs
 	}
 
-	remote, name := config.ParseRemoteAndContainer(args[2])
+	remote, name, err := conf.ParseRemote(args[2])
+	if err != nil {
+		return err
+	}
 
-	client, err := lxd.NewClient(config, remote)
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -734,14 +778,17 @@ func (c *configCmd) deviceGet(config *lxd.Config, which string, args []string) e
 	return nil
 }
 
-func (c *configCmd) deviceSet(config *lxd.Config, which string, args []string) error {
+func (c *configCmd) deviceSet(conf *config.Config, which string, args []string) error {
 	if len(args) < 6 {
 		return errArgs
 	}
 
-	remote, name := config.ParseRemoteAndContainer(args[2])
+	remote, name, err := conf.ParseRemote(args[2])
+	if err != nil {
+		return err
+	}
 
-	client, err := lxd.NewClient(config, remote)
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -791,14 +838,17 @@ func (c *configCmd) deviceSet(config *lxd.Config, which string, args []string) e
 	return err
 }
 
-func (c *configCmd) deviceUnset(config *lxd.Config, which string, args []string) error {
+func (c *configCmd) deviceUnset(conf *config.Config, which string, args []string) error {
 	if len(args) < 5 {
 		return errArgs
 	}
 
-	remote, name := config.ParseRemoteAndContainer(args[2])
+	remote, name, err := conf.ParseRemote(args[2])
+	if err != nil {
+		return err
+	}
 
-	client, err := lxd.NewClient(config, remote)
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -847,13 +897,17 @@ func (c *configCmd) deviceUnset(config *lxd.Config, which string, args []string)
 	return err
 }
 
-func (c *configCmd) deviceRm(config *lxd.Config, which string, args []string) error {
+func (c *configCmd) deviceRm(conf *config.Config, which string, args []string) error {
 	if len(args) < 4 {
 		return errArgs
 	}
-	remote, name := config.ParseRemoteAndContainer(args[2])
 
-	client, err := lxd.NewClient(config, remote)
+	remote, name, err := conf.ParseRemote(args[2])
+	if err != nil {
+		return err
+	}
+
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -877,13 +931,17 @@ func (c *configCmd) deviceRm(config *lxd.Config, which string, args []string) er
 	return err
 }
 
-func (c *configCmd) deviceList(config *lxd.Config, which string, args []string) error {
+func (c *configCmd) deviceList(conf *config.Config, which string, args []string) error {
 	if len(args) < 3 {
 		return errArgs
 	}
-	remote, name := config.ParseRemoteAndContainer(args[2])
 
-	client, err := lxd.NewClient(config, remote)
+	remote, name, err := conf.ParseRemote(args[2])
+	if err != nil {
+		return err
+	}
+
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -902,13 +960,17 @@ func (c *configCmd) deviceList(config *lxd.Config, which string, args []string)
 	return nil
 }
 
-func (c *configCmd) deviceShow(config *lxd.Config, which string, args []string) error {
+func (c *configCmd) deviceShow(conf *config.Config, which string, args []string) error {
 	if len(args) < 3 {
 		return errArgs
 	}
-	remote, name := config.ParseRemoteAndContainer(args[2])
 
-	client, err := lxd.NewClient(config, remote)
+	remote, name, err := conf.ParseRemote(args[2])
+	if err != nil {
+		return err
+	}
+
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/copy.go b/lxc/copy.go
index 4112d8ee8..531945c15 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -31,9 +32,16 @@ func (c *copyCmd) flags() {
 	gnuflag.BoolVar(&c.ephem, "e", false, i18n.G("Ephemeral container"))
 }
 
-func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int) error {
-	sourceRemote, sourceName := config.ParseRemoteAndContainer(sourceResource)
-	destRemote, destName := config.ParseRemoteAndContainer(destResource)
+func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int) error {
+	sourceRemote, sourceName, err := conf.ParseRemote(sourceResource)
+	if err != nil {
+		return err
+	}
+
+	destRemote, destName, err := conf.ParseRemote(destResource)
+	if err != nil {
+		return err
+	}
 
 	if sourceName == "" {
 		return fmt.Errorf(i18n.G("you must specify a source container name"))
@@ -43,7 +51,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 		destName = sourceName
 	}
 
-	source, err := lxd.NewClient(config, sourceRemote)
+	source, err := lxd.NewClient(conf.Legacy(), sourceRemote)
 	if err != nil {
 		return err
 	}
@@ -108,7 +116,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 		return source.WaitForSuccess(cp.Operation)
 	}
 
-	dest, err := lxd.NewClient(config, destRemote)
+	dest, err := lxd.NewClient(conf.Legacy(), destRemote)
 	if err != nil {
 		return err
 	}
@@ -218,7 +226,7 @@ func (c *copyCmd) copyContainer(config *lxd.Config, sourceResource string, destR
 	return fmt.Errorf(i18n.G("Migration failed on target host: %s"), migrationErrFromClient)
 }
 
-func (c *copyCmd) run(config *lxd.Config, args []string) error {
+func (c *copyCmd) run(conf *config.Config, args []string) error {
 	if len(args) != 2 {
 		return errArgs
 	}
@@ -228,5 +236,5 @@ func (c *copyCmd) run(config *lxd.Config, args []string) error {
 		ephem = 1
 	}
 
-	return c.copyContainer(config, args[0], args[1], false, ephem)
+	return c.copyContainer(conf, args[0], args[1], false, ephem)
 }
diff --git a/lxc/delete.go b/lxc/delete.go
index efe565fb9..2b1a00235 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -57,15 +58,18 @@ func (c *deleteCmd) doDelete(d *lxd.Client, name string) error {
 	return d.WaitForSuccess(resp.Operation)
 }
 
-func (c *deleteCmd) run(config *lxd.Config, args []string) error {
+func (c *deleteCmd) run(conf *config.Config, args []string) error {
 	if len(args) == 0 {
 		return errArgs
 	}
 
 	for _, nameArg := range args {
-		remote, name := config.ParseRemoteAndContainer(nameArg)
+		remote, name, err := conf.ParseRemote(nameArg)
+		if err != nil {
+			return err
+		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
diff --git a/lxc/exec.go b/lxc/exec.go
index d76787975..8d3039aa4 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -11,6 +11,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -85,13 +86,17 @@ func (c *execCmd) sendTermSize(control *websocket.Conn) error {
 	return err
 }
 
-func (c *execCmd) run(config *lxd.Config, args []string) error {
+func (c *execCmd) run(conf *config.Config, args []string) error {
 	if len(args) < 2 {
 		return errArgs
 	}
 
-	remote, name := config.ParseRemoteAndContainer(args[0])
-	d, err := lxd.NewClient(config, remote)
+	remote, name, err := conf.ParseRemote(args[0])
+	if err != nil {
+		return err
+	}
+
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/file.go b/lxc/file.go
index d0a859f88..12c637c4e 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -12,6 +12,7 @@ import (
 	"syscall"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -57,7 +58,7 @@ func (c *fileCmd) flags() {
 	gnuflag.StringVar(&c.mode, "mode", "", i18n.G("Set the file's perms on push"))
 }
 
-func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string) error {
+func (c *fileCmd) push(conf *config.Config, send_file_perms bool, args []string) error {
 	if len(args) < 2 {
 		return errArgs
 	}
@@ -70,9 +71,12 @@ func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string)
 	}
 
 	targetPath := pathSpec[1]
-	remote, container := config.ParseRemoteAndContainer(pathSpec[0])
+	remote, container, err := conf.ParseRemote(pathSpec[0])
+	if err != nil {
+		return err
+	}
 
-	d, err := lxd.NewClient(config, remote)
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -170,7 +174,7 @@ func (c *fileCmd) push(config *lxd.Config, send_file_perms bool, args []string)
 	return nil
 }
 
-func (c *fileCmd) pull(config *lxd.Config, args []string) error {
+func (c *fileCmd) pull(conf *config.Config, args []string) error {
 	if len(args) < 2 {
 		return errArgs
 	}
@@ -207,8 +211,12 @@ func (c *fileCmd) pull(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("Invalid source %s"), f)
 		}
 
-		remote, container := config.ParseRemoteAndContainer(pathSpec[0])
-		d, err := lxd.NewClient(config, remote)
+		remote, container, err := conf.ParseRemote(pathSpec[0])
+		if err != nil {
+			return err
+		}
+
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -245,14 +253,14 @@ func (c *fileCmd) pull(config *lxd.Config, args []string) error {
 	return nil
 }
 
-func (c *fileCmd) edit(config *lxd.Config, args []string) error {
+func (c *fileCmd) edit(conf *config.Config, args []string) error {
 	if len(args) != 1 {
 		return errArgs
 	}
 
 	// If stdin isn't a terminal, read text from it
 	if !termios.IsTerminal(int(syscall.Stdin)) {
-		return c.push(config, false, append([]string{os.Stdin.Name()}, args[0]))
+		return c.push(conf, false, append([]string{os.Stdin.Name()}, args[0]))
 	}
 
 	// Create temp file
@@ -263,7 +271,7 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
 	defer os.Remove(fname)
 
 	// Extract current value
-	err = c.pull(config, append([]string{args[0]}, fname))
+	err = c.pull(conf, append([]string{args[0]}, fname))
 	if err != nil {
 		return err
 	}
@@ -273,7 +281,7 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
 		return err
 	}
 
-	err = c.push(config, false, append([]string{fname}, args[0]))
+	err = c.push(conf, false, append([]string{fname}, args[0]))
 	if err != nil {
 		return err
 	}
@@ -281,18 +289,18 @@ func (c *fileCmd) edit(config *lxd.Config, args []string) error {
 	return nil
 }
 
-func (c *fileCmd) run(config *lxd.Config, args []string) error {
+func (c *fileCmd) run(conf *config.Config, args []string) error {
 	if len(args) < 1 {
 		return errUsage
 	}
 
 	switch args[0] {
 	case "push":
-		return c.push(config, true, args[1:])
+		return c.push(conf, true, args[1:])
 	case "pull":
-		return c.pull(config, args[1:])
+		return c.pull(conf, args[1:])
 	case "edit":
-		return c.edit(config, args[1:])
+		return c.edit(conf, args[1:])
 	default:
 		return errArgs
 	}
diff --git a/lxc/finger.go b/lxc/finger.go
index ed192954f..911fee24b 100644
--- a/lxc/finger.go
+++ b/lxc/finger.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/i18n"
 )
 
@@ -20,25 +21,30 @@ Check if the LXD server is alive.`)
 
 func (c *fingerCmd) flags() {}
 
-func (c *fingerCmd) run(config *lxd.Config, args []string) error {
+func (c *fingerCmd) run(conf *config.Config, args []string) error {
 	if len(args) > 1 {
 		return errArgs
 	}
 
 	var remote string
 	if len(args) == 1 {
-		remote = config.ParseRemote(args[0])
+		var err error
+		remote, _, err = conf.ParseRemote(args[0])
+		if err != nil {
+			return err
+		}
 	} else {
-		remote = config.DefaultRemote
+		remote = conf.DefaultRemote
 	}
 
 	// New client may or may not need to connect to the remote host, but
 	// client.ServerStatus will at least request the basic information from
 	// the server.
-	client, err := lxd.NewClient(config, remote)
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
+
 	_, err = client.ServerStatus()
 	return err
 }
diff --git a/lxc/help.go b/lxc/help.go
index 27f0a82d7..fce7dfbd0 100644
--- a/lxc/help.go
+++ b/lxc/help.go
@@ -5,7 +5,7 @@ import (
 	"os"
 	"sort"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -29,7 +29,7 @@ func (c *helpCmd) flags() {
 	gnuflag.BoolVar(&c.showAll, "all", false, i18n.G("Show all commands (not just interesting ones)"))
 }
 
-func (c *helpCmd) run(config *lxd.Config, args []string) error {
+func (c *helpCmd) run(conf *config.Config, args []string) error {
 	if len(args) > 0 {
 		for _, name := range args {
 			cmd, ok := commands[name]
diff --git a/lxc/image.go b/lxc/image.go
index 482ec10e8..7cabee816 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -13,6 +13,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -134,8 +135,10 @@ func (c *imageCmd) flags() {
 	gnuflag.Var(&c.addAliases, "alias", i18n.G("New alias to define at target"))
 }
 
-func (c *imageCmd) doImageAlias(config *lxd.Config, args []string) error {
+func (c *imageCmd) doImageAlias(conf *config.Config, args []string) error {
 	var remote string
+	var err error
+
 	switch args[1] {
 	case "list":
 		filters := []string{}
@@ -144,12 +147,21 @@ func (c *imageCmd) doImageAlias(config *lxd.Config, args []string) error {
 			result := strings.SplitN(args[2], ":", 2)
 			if len(result) == 1 {
 				filters = append(filters, args[2])
-				remote, _ = config.ParseRemoteAndContainer("")
+				remote, _, err = conf.ParseRemote("")
+				if err != nil {
+					return err
+				}
 			} else {
-				remote, _ = config.ParseRemoteAndContainer(args[2])
+				remote, _, err = conf.ParseRemote(args[2])
+				if err != nil {
+					return err
+				}
 			}
 		} else {
-			remote, _ = config.ParseRemoteAndContainer("")
+			remote, _, err = conf.ParseRemote("")
+			if err != nil {
+				return err
+			}
 		}
 
 		if len(args) > 3 {
@@ -158,7 +170,7 @@ func (c *imageCmd) doImageAlias(config *lxd.Config, args []string) error {
 			}
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -176,12 +188,18 @@ func (c *imageCmd) doImageAlias(config *lxd.Config, args []string) error {
 		if len(args) < 4 {
 			return errArgs
 		}
-		remote, alias := config.ParseRemoteAndContainer(args[2])
+
+		remote, alias, err := conf.ParseRemote(args[2])
+		if err != nil {
+			return err
+		}
+
 		target := args[3]
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
+
 		/* TODO - what about description? */
 		err = d.PostAlias(alias, alias, target)
 		return err
@@ -190,18 +208,24 @@ func (c *imageCmd) doImageAlias(config *lxd.Config, args []string) error {
 		if len(args) < 3 {
 			return errArgs
 		}
-		remote, alias := config.ParseRemoteAndContainer(args[2])
-		d, err := lxd.NewClient(config, remote)
+
+		remote, alias, err := conf.ParseRemote(args[2])
 		if err != nil {
 			return err
 		}
+
+		d, err := lxd.NewClient(conf.Legacy(), remote)
+		if err != nil {
+			return err
+		}
+
 		err = d.DeleteAlias(alias)
 		return err
 	}
 	return errArgs
 }
 
-func (c *imageCmd) run(config *lxd.Config, args []string) error {
+func (c *imageCmd) run(conf *config.Config, args []string) error {
 	var remote string
 
 	if len(args) < 1 {
@@ -213,7 +237,8 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		if len(args) < 2 {
 			return errArgs
 		}
-		return c.doImageAlias(config, args)
+
+		return c.doImageAlias(conf, args)
 
 	case "copy":
 		/* copy [<remote>:]<image> [<rmeote>:]<image> */
@@ -221,22 +246,30 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		remote, inName := config.ParseRemoteAndContainer(args[1])
+		remote, inName, err := conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if inName == "" {
 			inName = "default"
 		}
 
-		destRemote, outName := config.ParseRemoteAndContainer(args[2])
+		destRemote, outName, err := conf.ParseRemote(args[2])
+		if err != nil {
+			return err
+		}
+
 		if outName != "" {
 			return errArgs
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
 
-		dest, err := lxd.NewClient(config, destRemote)
+		dest, err := lxd.NewClient(conf.Legacy(), destRemote)
 		if err != nil {
 			return err
 		}
@@ -256,7 +289,11 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		remote, inName := config.ParseRemoteAndContainer(args[1])
+		remote, inName, err := conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if inName == "" {
 			inName = "default"
 		}
@@ -269,7 +306,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			}
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -289,12 +326,16 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		remote, inName := config.ParseRemoteAndContainer(args[1])
+		remote, inName, err := conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if inName == "" {
 			inName = "default"
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -371,7 +412,11 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			split := strings.Split(arg, "=")
 			if len(split) == 1 || shared.PathExists(arg) {
 				if strings.HasSuffix(arg, ":") {
-					remote = config.ParseRemote(arg)
+					var err error
+					remote, _, err = conf.ParseRemote(arg)
+					if err != nil {
+						return err
+					}
 				} else {
 					if imageFile == "" {
 						imageFile = args[1]
@@ -385,7 +430,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		}
 
 		if remote == "" {
-			remote = config.DefaultRemote
+			remote = conf.DefaultRemote
 		}
 
 		if imageFile == "" {
@@ -393,7 +438,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			properties = properties[1:]
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -425,18 +470,28 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 		return nil
 
 	case "list":
+		var err error
 		filters := []string{}
 
 		if len(args) > 1 {
 			result := strings.SplitN(args[1], ":", 2)
 			if len(result) == 1 {
 				filters = append(filters, args[1])
-				remote, _ = config.ParseRemoteAndContainer("")
+				remote, _, err = conf.ParseRemote("")
+				if err != nil {
+					return err
+				}
 			} else {
-				remote, _ = config.ParseRemoteAndContainer(args[1])
+				remote, _, err = conf.ParseRemote(args[1])
+				if err != nil {
+					return err
+				}
 			}
 		} else {
-			remote, _ = config.ParseRemoteAndContainer("")
+			remote, _, err = conf.ParseRemote("")
+			if err != nil {
+				return err
+			}
 		}
 
 		if len(args) > 2 {
@@ -445,7 +500,7 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			}
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -462,12 +517,16 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		remote, inName := config.ParseRemoteAndContainer(args[1])
+		remote, inName, err := conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if inName == "" {
 			inName = "default"
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -484,12 +543,16 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		remote, inName := config.ParseRemoteAndContainer(args[1])
+		remote, inName, err := conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if inName == "" {
 			inName = "default"
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
@@ -516,12 +579,16 @@ func (c *imageCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		remote, inName := config.ParseRemoteAndContainer(args[1])
+		remote, inName, err := conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if inName == "" {
 			inName = "default"
 		}
 
-		d, err := lxd.NewClient(config, remote)
+		d, err := lxd.NewClient(conf.Legacy(), remote)
 		if err != nil {
 			return err
 		}
diff --git a/lxc/info.go b/lxc/info.go
index 357fd567d..53c45ff28 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -8,6 +8,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -38,16 +39,23 @@ func (c *infoCmd) flags() {
 	gnuflag.BoolVar(&c.showLog, "show-log", false, i18n.G("Show the container's last 100 log lines?"))
 }
 
-func (c *infoCmd) run(config *lxd.Config, args []string) error {
+func (c *infoCmd) run(conf *config.Config, args []string) error {
 	var remote string
 	var cName string
+	var err error
 	if len(args) == 1 {
-		remote, cName = config.ParseRemoteAndContainer(args[0])
+		remote, cName, err = conf.ParseRemote(args[0])
+		if err != nil {
+			return err
+		}
 	} else {
-		remote, cName = config.ParseRemoteAndContainer("")
+		remote, cName, err = conf.ParseRemote("")
+		if err != nil {
+			return err
+		}
 	}
 
-	d, err := lxd.NewClient(config, remote)
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/init.go b/lxc/init.go
index 15a115cbe..0882fafb4 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -138,22 +139,31 @@ func (c *initCmd) flags() {
 	gnuflag.BoolVar(&c.ephem, "e", false, i18n.G("Ephemeral container"))
 }
 
-func (c *initCmd) run(config *lxd.Config, args []string) error {
+func (c *initCmd) run(conf *config.Config, args []string) error {
 	if len(args) > 2 || len(args) < 1 {
 		return errArgs
 	}
 
-	iremote, image := config.ParseRemoteAndContainer(args[0])
+	iremote, image, err := conf.ParseRemote(args[0])
+	if err != nil {
+		return err
+	}
 
 	var name string
 	var remote string
 	if len(args) == 2 {
-		remote, name = config.ParseRemoteAndContainer(args[1])
+		remote, name, err = conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
 	} else {
-		remote, name = config.ParseRemoteAndContainer("")
+		remote, name, err = conf.ParseRemote("")
+		if err != nil {
+			return err
+		}
 	}
 
-	d, err := lxd.NewClient(config, remote)
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -176,7 +186,7 @@ func (c *initCmd) run(config *lxd.Config, args []string) error {
 		fmt.Printf(i18n.G("Creating %s")+"\n", name)
 	}
 
-	iremote, image = c.guessImage(config, d, remote, iremote, image)
+	iremote, image = c.guessImage(conf, d, remote, iremote, image)
 
 	if !initRequestedEmptyProfiles && len(profiles) == 0 {
 		resp, err = d.Init(name, iremote, image, nil, configMap, c.ephem)
@@ -251,12 +261,12 @@ func (c *initCmd) initProgressTracker(d *lxd.Client, progress *ProgressRenderer,
 	go d.Monitor([]string{"operation"}, handler, nil)
 }
 
-func (c *initCmd) guessImage(config *lxd.Config, d *lxd.Client, remote string, iremote string, image string) (string, string) {
+func (c *initCmd) guessImage(conf *config.Config, d *lxd.Client, remote string, iremote string, image string) (string, string) {
 	if remote != iremote {
 		return iremote, image
 	}
 
-	_, ok := config.Remotes[image]
+	_, ok := conf.Remotes[image]
 	if !ok {
 		return iremote, image
 	}
diff --git a/lxc/launch.go b/lxc/launch.go
index e3720fcc2..ca85d6e6b 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
@@ -37,22 +38,31 @@ func (c *launchCmd) flags() {
 	c.init.flags()
 }
 
-func (c *launchCmd) run(config *lxd.Config, args []string) error {
+func (c *launchCmd) run(conf *config.Config, args []string) error {
 	if len(args) > 2 || len(args) < 1 {
 		return errArgs
 	}
 
-	iremote, image := config.ParseRemoteAndContainer(args[0])
+	iremote, image, err := conf.ParseRemote(args[0])
+	if err != nil {
+		return err
+	}
 
 	var name string
 	var remote string
 	if len(args) == 2 {
-		remote, name = config.ParseRemoteAndContainer(args[1])
+		remote, name, err = conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
 	} else {
-		remote, name = config.ParseRemoteAndContainer("")
+		remote, name, err = conf.ParseRemote("")
+		if err != nil {
+			return err
+		}
 	}
 
-	d, err := lxd.NewClient(config, remote)
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -67,7 +77,7 @@ func (c *launchCmd) run(config *lxd.Config, args []string) error {
 		profiles = append(profiles, p)
 	}
 
-	iremote, image = c.init.guessImage(config, d, remote, iremote, image)
+	iremote, image = c.init.guessImage(conf, d, remote, iremote, image)
 
 	if !initRequestedEmptyProfiles && len(profiles) == 0 {
 		resp, err = d.Init(name, iremote, image, nil, configMap, c.init.ephem)
diff --git a/lxc/list.go b/lxc/list.go
index aa301e9f5..630133976 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -12,6 +12,7 @@ import (
 	"github.com/olekukonko/tablewriter"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -362,7 +363,7 @@ type listContainerItem struct {
 	Snapshots []api.ContainerSnapshot `json:"snapshots" yaml:"snapshots"`
 }
 
-func (c *listCmd) run(config *lxd.Config, args []string) error {
+func (c *listCmd) run(conf *config.Config, args []string) error {
 	var remote string
 	name := ""
 
@@ -371,20 +372,25 @@ func (c *listCmd) run(config *lxd.Config, args []string) error {
 	if len(args) != 0 {
 		filters = args
 		if strings.Contains(args[0], ":") && !strings.Contains(args[0], "=") {
-			remote, name = config.ParseRemoteAndContainer(args[0])
+			var err error
+			remote, name, err = conf.ParseRemote(args[0])
+			if err != nil {
+				return err
+			}
+
 			filters = args[1:]
 		} else if !strings.Contains(args[0], "=") {
-			remote = config.DefaultRemote
+			remote = conf.DefaultRemote
 			name = args[0]
 		}
 	}
 	filters = append(filters, name)
 
 	if remote == "" {
-		remote = config.DefaultRemote
+		remote = conf.DefaultRemote
 	}
 
-	d, err := lxd.NewClient(config, remote)
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/main.go b/lxc/main.go
index 4c60244da..b22c9feb9 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -5,15 +5,17 @@ import (
 	"os"
 	"os/exec"
 	"path"
+	"path/filepath"
 	"strings"
 	"syscall"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
+	"github.com/lxc/lxd/shared/version"
 )
 
 var configPath string
@@ -25,7 +27,7 @@ func main() {
 	if err := run(); err != nil {
 		msg := fmt.Sprintf(i18n.G("error: %v"), err)
 
-		lxdErr := lxd.GetLocalLXDErr(err)
+		lxdErr := getLocalErr(err)
 		switch lxdErr {
 		case syscall.ENOENT:
 			msg = i18n.G("LXD socket not found; is LXD installed and running?")
@@ -74,18 +76,24 @@ func run() error {
 		os.Exit(1)
 	}
 
-	var config *lxd.Config
+	var conf *config.Config
 	var err error
 
 	if *forceLocal {
-		config = &lxd.DefaultConfig
-	} else {
-		config, err = lxd.LoadConfig(configPath)
+		conf = &config.DefaultConfig
+	} else if shared.PathExists(configPath) {
+		conf, err = config.LoadConfig(configPath)
 		if err != nil {
 			return err
 		}
+	} else {
+		conf = &config.DefaultConfig
+		conf.ConfigDir = filepath.Dir(configPath)
 	}
 
+	// Set the user agent
+	conf.UserAgent = version.UserAgent
+
 	// This is quite impolite, but it seems gnuflag needs us to shift our
 	// own exename out of the arguments before parsing them. However, this
 	// is useful for execIfAlias, which wants to know exactly the command
@@ -98,7 +106,7 @@ func run() error {
 	 * --no-alias by hand.
 	 */
 	if !shared.StringInSlice("--no-alias", origArgs) {
-		execIfAliases(config, origArgs)
+		execIfAliases(conf, origArgs)
 	}
 	cmd, ok := commands[name]
 	if !ok {
@@ -124,8 +132,8 @@ func run() error {
 		return err
 	}
 
-	certf := config.ConfigPath("client.crt")
-	keyf := config.ConfigPath("client.key")
+	certf := conf.ConfigPath("client.crt")
+	keyf := conf.ConfigPath("client.key")
 
 	if !*forceLocal && os.Args[0] != "help" && os.Args[0] != "version" && (!shared.PathExists(certf) || !shared.PathExists(keyf)) {
 		fmt.Fprintf(os.Stderr, i18n.G("Generating a client certificate. This may take a minute...")+"\n")
@@ -141,7 +149,7 @@ func run() error {
 		}
 	}
 
-	err = cmd.run(config, gnuflag.Args())
+	err = cmd.run(conf, gnuflag.Args())
 	if err == errArgs || err == errUsage {
 		out := os.Stdout
 		if err == errArgs {
@@ -149,7 +157,7 @@ func run() error {
 			 * expand this as an alias
 			 */
 			if !*noAlias {
-				execIfAliases(config, origArgs)
+				execIfAliases(conf, origArgs)
 			}
 
 			out = os.Stderr
@@ -178,7 +186,7 @@ type command interface {
 	usage() string
 	flags()
 	showByDefault() bool
-	run(config *lxd.Config, args []string) error
+	run(config *config.Config, args []string) error
 }
 
 var commands = map[string]command{
@@ -234,7 +242,7 @@ var commands = map[string]command{
 var errArgs = fmt.Errorf(i18n.G("wrong number of subcommand arguments"))
 var errUsage = fmt.Errorf("show usage")
 
-func expandAlias(config *lxd.Config, origArgs []string) ([]string, bool) {
+func expandAlias(config *config.Config, origArgs []string) ([]string, bool) {
 	foundAlias := false
 	aliasKey := []string{}
 	aliasValue := []string{}
@@ -284,7 +292,7 @@ func expandAlias(config *lxd.Config, origArgs []string) ([]string, bool) {
 	return newArgs, true
 }
 
-func execIfAliases(config *lxd.Config, origArgs []string) {
+func execIfAliases(config *config.Config, origArgs []string) {
 	newArgs, expanded := expandAlias(config, origArgs)
 	if !expanded {
 		return
diff --git a/lxc/main_test.go b/lxc/main_test.go
index 30fc0cb6f..cb7fd81d6 100644
--- a/lxc/main_test.go
+++ b/lxc/main_test.go
@@ -3,7 +3,7 @@ package main
 import (
 	"testing"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 )
 
 type aliasTestcase struct {
@@ -54,7 +54,7 @@ func TestExpandAliases(t *testing.T) {
 		},
 	}
 
-	conf := &lxd.Config{Aliases: aliases}
+	conf := &config.Config{Aliases: aliases}
 
 	for _, tc := range testcases {
 		result, expanded := expandAlias(conf, tc.input)
diff --git a/lxc/manpage.go b/lxc/manpage.go
index 697f015d4..edf3ceb9e 100644
--- a/lxc/manpage.go
+++ b/lxc/manpage.go
@@ -6,7 +6,7 @@ import (
 	"os/exec"
 	"path/filepath"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/i18n"
 )
 
@@ -26,7 +26,7 @@ Generate all the LXD manpages.`)
 func (c *manpageCmd) flags() {
 }
 
-func (c *manpageCmd) run(_ *lxd.Config, args []string) error {
+func (c *manpageCmd) run(conf *config.Config, args []string) error {
 	if len(args) != 1 {
 		return errArgs
 	}
diff --git a/lxc/monitor.go b/lxc/monitor.go
index 3b5d1b8c7..4c428afe4 100644
--- a/lxc/monitor.go
+++ b/lxc/monitor.go
@@ -6,6 +6,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -56,7 +57,8 @@ func (c *monitorCmd) flags() {
 	gnuflag.Var(&c.typeArgs, "type", i18n.G("Event type to listen for"))
 }
 
-func (c *monitorCmd) run(config *lxd.Config, args []string) error {
+func (c *monitorCmd) run(conf *config.Config, args []string) error {
+	var err error
 	var remote string
 
 	if len(args) > 1 {
@@ -64,12 +66,18 @@ func (c *monitorCmd) run(config *lxd.Config, args []string) error {
 	}
 
 	if len(args) == 0 {
-		remote, _ = config.ParseRemoteAndContainer("")
+		remote, _, err = conf.ParseRemote("")
+		if err != nil {
+			return err
+		}
 	} else {
-		remote, _ = config.ParseRemoteAndContainer(args[0])
+		remote, _, err = conf.ParseRemote(args[0])
+		if err != nil {
+			return err
+		}
 	}
 
-	d, err := lxd.NewClient(config, remote)
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/move.go b/lxc/move.go
index 12b43b296..de044b63b 100644
--- a/lxc/move.go
+++ b/lxc/move.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/i18n"
 )
 
@@ -30,13 +31,20 @@ lxc move <container>/<old snapshot name> <container>/<new snapshot name>
 
 func (c *moveCmd) flags() {}
 
-func (c *moveCmd) run(config *lxd.Config, args []string) error {
+func (c *moveCmd) run(conf *config.Config, args []string) error {
 	if len(args) != 2 {
 		return errArgs
 	}
 
-	sourceRemote, sourceName := config.ParseRemoteAndContainer(args[0])
-	destRemote, destName := config.ParseRemoteAndContainer(args[1])
+	sourceRemote, sourceName, err := conf.ParseRemote(args[0])
+	if err != nil {
+		return err
+	}
+
+	destRemote, destName, err := conf.ParseRemote(args[1])
+	if err != nil {
+		return err
+	}
 
 	// As an optimization, if the source an destination are the same, do
 	// this via a simple rename. This only works for containers that aren't
@@ -44,7 +52,7 @@ func (c *moveCmd) run(config *lxd.Config, args []string) error {
 	// course, this changing of hostname isn't supported right now, so this
 	// simply won't work).
 	if sourceRemote == destRemote {
-		source, err := lxd.NewClient(config, sourceRemote)
+		source, err := lxd.NewClient(conf.Legacy(), sourceRemote)
 		if err != nil {
 			return err
 		}
@@ -61,10 +69,10 @@ func (c *moveCmd) run(config *lxd.Config, args []string) error {
 
 	// A move is just a copy followed by a delete; however, we want to
 	// keep the volatile entries around since we are moving the container.
-	err := cpy.copyContainer(config, args[0], args[1], true, -1)
+	err = cpy.copyContainer(conf, args[0], args[1], true, -1)
 	if err != nil {
 		return err
 	}
 
-	return commands["delete"].run(config, args[:1])
+	return commands["delete"].run(conf, args[:1])
 }
diff --git a/lxc/profile.go b/lxc/profile.go
index 21b6752d5..7812f361c 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -10,6 +10,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
@@ -120,21 +121,25 @@ lxc profile apply foo ''
 
 func (c *profileCmd) flags() {}
 
-func (c *profileCmd) run(config *lxd.Config, args []string) error {
+func (c *profileCmd) run(conf *config.Config, args []string) error {
 	if len(args) < 1 {
 		return errUsage
 	}
 
 	if args[0] == "list" {
-		return c.doProfileList(config, args)
+		return c.doProfileList(conf, args)
 	}
 
 	if len(args) < 2 {
 		return errArgs
 	}
 
-	remote, profile := config.ParseRemoteAndContainer(args[1])
-	client, err := lxd.NewClient(config, remote)
+	remote, profile, err := conf.ParseRemote(args[1])
+	if err != nil {
+		return err
+	}
+
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -145,7 +150,7 @@ func (c *profileCmd) run(config *lxd.Config, args []string) error {
 	case "delete":
 		return c.doProfileDelete(client, profile)
 	case "device":
-		return c.doProfileDevice(config, args)
+		return c.doProfileDevice(conf, args)
 	case "edit":
 		return c.doProfileEdit(client, profile)
 	case "apply":
@@ -166,7 +171,7 @@ func (c *profileCmd) run(config *lxd.Config, args []string) error {
 	case "unset":
 		return c.doProfileUnset(client, profile, args[2:])
 	case "copy":
-		return c.doProfileCopy(config, client, profile, args[2:])
+		return c.doProfileCopy(conf, client, profile, args[2:])
 	case "show":
 		return c.doProfileShow(client, profile)
 	default:
@@ -285,16 +290,21 @@ func (c *profileCmd) doProfileShow(client *lxd.Client, p string) error {
 	return nil
 }
 
-func (c *profileCmd) doProfileCopy(config *lxd.Config, client *lxd.Client, p string, args []string) error {
+func (c *profileCmd) doProfileCopy(conf *config.Config, client *lxd.Client, p string, args []string) error {
 	if len(args) != 1 {
 		return errArgs
 	}
-	remote, newname := config.ParseRemoteAndContainer(args[0])
+
+	remote, newname, err := conf.ParseRemote(args[0])
+	if err != nil {
+		return err
+	}
+
 	if newname == "" {
 		newname = p
 	}
 
-	dest, err := lxd.NewClient(config, remote)
+	dest, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
@@ -302,7 +312,7 @@ func (c *profileCmd) doProfileCopy(config *lxd.Config, client *lxd.Client, p str
 	return client.ProfileCopy(p, newname, dest)
 }
 
-func (c *profileCmd) doProfileDevice(config *lxd.Config, args []string) error {
+func (c *profileCmd) doProfileDevice(conf *config.Config, args []string) error {
 	// device add b1 eth0 nic type=bridged
 	// device list b1
 	// device remove b1 eth0
@@ -314,19 +324,19 @@ func (c *profileCmd) doProfileDevice(config *lxd.Config, args []string) error {
 
 	switch args[1] {
 	case "add":
-		return cfg.deviceAdd(config, "profile", args)
+		return cfg.deviceAdd(conf, "profile", args)
 	case "remove":
-		return cfg.deviceRm(config, "profile", args)
+		return cfg.deviceRm(conf, "profile", args)
 	case "list":
-		return cfg.deviceList(config, "profile", args)
+		return cfg.deviceList(conf, "profile", args)
 	case "show":
-		return cfg.deviceShow(config, "profile", args)
+		return cfg.deviceShow(conf, "profile", args)
 	case "get":
-		return cfg.deviceGet(config, "profile", args)
+		return cfg.deviceGet(conf, "profile", args)
 	case "set":
-		return cfg.deviceSet(config, "profile", args)
+		return cfg.deviceSet(conf, "profile", args)
 	case "unset":
-		return cfg.deviceUnset(config, "profile", args)
+		return cfg.deviceUnset(conf, "profile", args)
 	default:
 		return errArgs
 	}
@@ -385,19 +395,24 @@ func (c *profileCmd) doProfileUnset(client *lxd.Client, p string, args []string)
 	return c.doProfileSet(client, p, args)
 }
 
-func (c *profileCmd) doProfileList(config *lxd.Config, args []string) error {
+func (c *profileCmd) doProfileList(conf *config.Config, args []string) error {
 	var remote string
 	if len(args) > 1 {
 		var name string
-		remote, name = config.ParseRemoteAndContainer(args[1])
+		var err error
+		remote, name, err = conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
+
 		if name != "" {
 			return fmt.Errorf(i18n.G("Cannot provide container name to list"))
 		}
 	} else {
-		remote = config.DefaultRemote
+		remote = conf.DefaultRemote
 	}
 
-	client, err := lxd.NewClient(config, remote)
+	client, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/publish.go b/lxc/publish.go
index 846d41764..98aaa615f 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -36,9 +37,7 @@ func (c *publishCmd) flags() {
 	gnuflag.BoolVar(&c.Force, "f", false, i18n.G("Stop the container if currently running"))
 }
 
-func (c *publishCmd) run(config *lxd.Config, args []string) error {
-	var cRemote string
-	var cName string
+func (c *publishCmd) run(conf *config.Config, args []string) error {
 	iName := ""
 	iRemote := ""
 	properties := map[string]string{}
@@ -48,12 +47,22 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 		return errArgs
 	}
 
-	cRemote, cName = config.ParseRemoteAndContainer(args[0])
+	cRemote, cName, err := conf.ParseRemote(args[0])
+	if err != nil {
+		return err
+	}
+
 	if len(args) >= 2 && !strings.Contains(args[1], "=") {
 		firstprop = 2
-		iRemote, iName = config.ParseRemoteAndContainer(args[1])
+		iRemote, iName, err = conf.ParseRemote(args[1])
+		if err != nil {
+			return err
+		}
 	} else {
-		iRemote, iName = config.ParseRemoteAndContainer("")
+		iRemote, iName, err = conf.ParseRemote("")
+		if err != nil {
+			return err
+		}
 	}
 
 	if cName == "" {
@@ -63,14 +72,14 @@ func (c *publishCmd) run(config *lxd.Config, args []string) error {
 		return fmt.Errorf(i18n.G("There is no \"image name\".  Did you want an alias?"))
 	}
 
-	d, err := lxd.NewClient(config, iRemote)
+	d, err := lxd.NewClient(conf.Legacy(), iRemote)
 	if err != nil {
 		return err
 	}
 
 	s := d
 	if cRemote != iRemote {
-		s, err = lxd.NewClient(config, cRemote)
+		s, err = lxd.NewClient(conf.Legacy(), cRemote)
 		if err != nil {
 			return err
 		}
diff --git a/lxc/remote.go b/lxc/remote.go
index 5eff2ab2d..ec4ec44a5 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -17,6 +17,7 @@ import (
 	"golang.org/x/crypto/ssh/terminal"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -98,14 +99,14 @@ func (c *remoteCmd) getRemoteCertificate(address string) (*x509.Certificate, err
 	return resp.TLS.PeerCertificates[0], nil
 }
 
-func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, acceptCert bool, password string, public bool, protocol string) error {
+func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, acceptCert bool, password string, public bool, protocol string) error {
 	var rScheme string
 	var rHost string
 	var rPort string
 
 	// Setup the remotes list
-	if config.Remotes == nil {
-		config.Remotes = make(map[string]lxd.RemoteConfig)
+	if conf.Remotes == nil {
+		conf.Remotes = make(map[string]config.Remote)
 	}
 
 	/* Complex remote URL parsing */
@@ -120,7 +121,7 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 			return fmt.Errorf(i18n.G("Only https URLs are supported for simplestreams"))
 		}
 
-		config.Remotes[server] = lxd.RemoteConfig{Addr: addr, Public: true, Protocol: protocol}
+		conf.Remotes[server] = config.Remote{Addr: addr, Public: true, Protocol: protocol}
 		return nil
 	}
 
@@ -176,10 +177,9 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 	}
 
 	/* Actually add the remote */
-	config.Remotes[server] = lxd.RemoteConfig{Addr: addr, Protocol: protocol}
+	conf.Remotes[server] = config.Remote{Addr: addr, Protocol: protocol}
 
-	remote := config.ParseRemote(server)
-	d, err := lxd.NewClient(config, remote)
+	d, err := lxd.NewClient(conf.Legacy(), server)
 	if err != nil {
 		return err
 	}
@@ -234,14 +234,14 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 		certOut.Close()
 
 		// Setup a new connection, this time with the remote certificate
-		d, err = lxd.NewClient(config, remote)
+		d, err = lxd.NewClient(conf.Legacy(), server)
 		if err != nil {
 			return err
 		}
 	}
 
 	if d.IsPublic() || public {
-		config.Remotes[server] = lxd.RemoteConfig{Addr: addr, Public: true}
+		conf.Remotes[server] = config.Remote{Addr: addr, Public: true}
 
 		if _, err := d.GetServerConfig(); err != nil {
 			return err
@@ -283,14 +283,14 @@ func (c *remoteCmd) addServer(config *lxd.Config, server string, addr string, ac
 	return nil
 }
 
-func (c *remoteCmd) removeCertificate(config *lxd.Config, remote string) {
-	certf := config.ServerCertPath(remote)
+func (c *remoteCmd) removeCertificate(conf *config.Config, remote string) {
+	certf := conf.ServerCertPath(remote)
 	logger.Debugf("Trying to remove %s", certf)
 
 	os.Remove(certf)
 }
 
-func (c *remoteCmd) run(config *lxd.Config, args []string) error {
+func (c *remoteCmd) run(conf *config.Config, args []string) error {
 	if len(args) < 1 {
 		return errUsage
 	}
@@ -301,14 +301,14 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		if rc, ok := config.Remotes[args[1]]; ok {
+		if rc, ok := conf.Remotes[args[1]]; ok {
 			return fmt.Errorf(i18n.G("remote %s exists as <%s>"), args[1], rc.Addr)
 		}
 
-		err := c.addServer(config, args[1], args[2], c.acceptCert, c.password, c.public, c.protocol)
+		err := c.addServer(conf, args[1], args[2], c.acceptCert, c.password, c.public, c.protocol)
 		if err != nil {
-			delete(config.Remotes, args[1])
-			c.removeCertificate(config, args[1])
+			delete(conf.Remotes, args[1])
+			c.removeCertificate(conf, args[1])
 			return err
 		}
 
@@ -317,7 +317,7 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		rc, ok := config.Remotes[args[1]]
+		rc, ok := conf.Remotes[args[1]]
 		if !ok {
 			return fmt.Errorf(i18n.G("remote %s doesn't exist"), args[1])
 		}
@@ -326,17 +326,17 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("remote %s is static and cannot be modified"), args[1])
 		}
 
-		if config.DefaultRemote == args[1] {
+		if conf.DefaultRemote == args[1] {
 			return fmt.Errorf(i18n.G("can't remove the default remote"))
 		}
 
-		delete(config.Remotes, args[1])
+		delete(conf.Remotes, args[1])
 
-		c.removeCertificate(config, args[1])
+		c.removeCertificate(conf, args[1])
 
 	case "list":
 		data := [][]string{}
-		for name, rc := range config.Remotes {
+		for name, rc := range conf.Remotes {
 			strPublic := i18n.G("NO")
 			if rc.Public {
 				strPublic = i18n.G("YES")
@@ -352,7 +352,7 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			}
 
 			strName := name
-			if name == config.DefaultRemote {
+			if name == conf.DefaultRemote {
 				strName = fmt.Sprintf("%s (%s)", name, i18n.G("default"))
 			}
 			data = append(data, []string{strName, rc.Addr, rc.Protocol, strPublic, strStatic})
@@ -379,7 +379,7 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		rc, ok := config.Remotes[args[1]]
+		rc, ok := conf.Remotes[args[1]]
 		if !ok {
 			return fmt.Errorf(i18n.G("remote %s doesn't exist"), args[1])
 		}
@@ -388,13 +388,13 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("remote %s is static and cannot be modified"), args[1])
 		}
 
-		if _, ok := config.Remotes[args[2]]; ok {
+		if _, ok := conf.Remotes[args[2]]; ok {
 			return fmt.Errorf(i18n.G("remote %s already exists"), args[2])
 		}
 
 		// Rename the certificate file
-		oldPath := filepath.Join(config.ConfigPath("servercerts"), fmt.Sprintf("%s.crt", args[1]))
-		newPath := filepath.Join(config.ConfigPath("servercerts"), fmt.Sprintf("%s.crt", args[2]))
+		oldPath := filepath.Join(conf.ConfigPath("servercerts"), fmt.Sprintf("%s.crt", args[1]))
+		newPath := filepath.Join(conf.ConfigPath("servercerts"), fmt.Sprintf("%s.crt", args[2]))
 		if shared.PathExists(oldPath) {
 			err := os.Rename(oldPath, newPath)
 			if err != nil {
@@ -402,11 +402,11 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			}
 		}
 
-		config.Remotes[args[2]] = rc
-		delete(config.Remotes, args[1])
+		conf.Remotes[args[2]] = rc
+		delete(conf.Remotes, args[1])
 
-		if config.DefaultRemote == args[1] {
-			config.DefaultRemote = args[2]
+		if conf.DefaultRemote == args[1] {
+			conf.DefaultRemote = args[2]
 		}
 
 	case "set-url":
@@ -414,7 +414,7 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			return errArgs
 		}
 
-		rc, ok := config.Remotes[args[1]]
+		rc, ok := conf.Remotes[args[1]]
 		if !ok {
 			return fmt.Errorf(i18n.G("remote %s doesn't exist"), args[1])
 		}
@@ -423,28 +423,28 @@ func (c *remoteCmd) run(config *lxd.Config, args []string) error {
 			return fmt.Errorf(i18n.G("remote %s is static and cannot be modified"), args[1])
 		}
 
-		config.Remotes[args[1]] = lxd.RemoteConfig{Addr: args[2]}
+		conf.Remotes[args[1]] = config.Remote{Addr: args[2]}
 
 	case "set-default":
 		if len(args) != 2 {
 			return errArgs
 		}
 
-		_, ok := config.Remotes[args[1]]
+		_, ok := conf.Remotes[args[1]]
 		if !ok {
 			return fmt.Errorf(i18n.G("remote %s doesn't exist"), args[1])
 		}
-		config.DefaultRemote = args[1]
+		conf.DefaultRemote = args[1]
 
 	case "get-default":
 		if len(args) != 1 {
 			return errArgs
 		}
-		fmt.Println(config.DefaultRemote)
+		fmt.Println(conf.DefaultRemote)
 		return nil
 	default:
 		return errArgs
 	}
 
-	return lxd.SaveConfig(config, configPath)
+	return conf.SaveConfig(configPath)
 }
diff --git a/lxc/restore.go b/lxc/restore.go
index cd4024b44..e9d02a947 100644
--- a/lxc/restore.go
+++ b/lxc/restore.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -37,15 +38,19 @@ func (c *restoreCmd) flags() {
 	gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Whether or not to restore the container's running state from snapshot (if available)"))
 }
 
-func (c *restoreCmd) run(config *lxd.Config, args []string) error {
+func (c *restoreCmd) run(conf *config.Config, args []string) error {
 	if len(args) < 2 {
 		return errArgs
 	}
 
 	var snapname = args[1]
 
-	remote, name := config.ParseRemoteAndContainer(args[0])
-	d, err := lxd.NewClient(config, remote)
+	remote, name, err := conf.ParseRemote(args[0])
+	if err != nil {
+		return err
+	}
+
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/snapshot.go b/lxc/snapshot.go
index 7f92b0432..9472e3b47 100644
--- a/lxc/snapshot.go
+++ b/lxc/snapshot.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -35,7 +36,7 @@ func (c *snapshotCmd) flags() {
 	gnuflag.BoolVar(&c.stateful, "stateful", false, i18n.G("Whether or not to snapshot the container's running state"))
 }
 
-func (c *snapshotCmd) run(config *lxd.Config, args []string) error {
+func (c *snapshotCmd) run(conf *config.Config, args []string) error {
 	if len(args) < 1 {
 		return errArgs
 	}
@@ -47,8 +48,12 @@ func (c *snapshotCmd) run(config *lxd.Config, args []string) error {
 		snapname = args[1]
 	}
 
-	remote, name := config.ParseRemoteAndContainer(args[0])
-	d, err := lxd.NewClient(config, remote)
+	remote, name, err := conf.ParseRemote(args[0])
+	if err != nil {
+		return err
+	}
+
+	d, err := lxd.NewClient(conf.Legacy(), remote)
 	if err != nil {
 		return err
 	}
diff --git a/lxc/utils.go b/lxc/utils.go
index e9c01873a..383862191 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -2,7 +2,11 @@ package main
 
 import (
 	"fmt"
+	"net"
+	"net/url"
+	"os"
 	"strings"
+	"syscall"
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared/api"
@@ -192,3 +196,38 @@ func summaryLine(usage string) string {
 
 	return i18n.G("Missing summary.")
 }
+
+// Used to return a user friendly error
+func getLocalErr(err error) error {
+	t, ok := err.(*url.Error)
+	if !ok {
+		return nil
+	}
+
+	u, ok := t.Err.(*net.OpError)
+	if !ok {
+		return nil
+	}
+
+	if u.Op == "dial" && u.Net == "unix" {
+		var lxdErr error
+
+		sysErr, ok := u.Err.(*os.SyscallError)
+		if ok {
+			lxdErr = sysErr.Err
+		} else {
+			// syscall.Errno may be returned on some systems, e.g. CentOS
+			lxdErr, ok = u.Err.(syscall.Errno)
+			if !ok {
+				return nil
+			}
+		}
+
+		switch lxdErr {
+		case syscall.ENOENT, syscall.ECONNREFUSED, syscall.EACCES:
+			return lxdErr
+		}
+	}
+
+	return nil
+}
diff --git a/lxc/version.go b/lxc/version.go
index f07bf6842..37484a4a4 100644
--- a/lxc/version.go
+++ b/lxc/version.go
@@ -3,7 +3,7 @@ package main
 import (
 	"fmt"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -24,7 +24,7 @@ Print the version number of this client tool.`)
 func (c *versionCmd) flags() {
 }
 
-func (c *versionCmd) run(_ *lxd.Config, args []string) error {
+func (c *versionCmd) run(conf *config.Config, args []string) error {
 	if len(args) > 0 {
 		return errArgs
 	}

From e02502da0686ac95fd3528291278105914846ef0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 21:51:55 -0400
Subject: [PATCH 0973/1193] lxc/finger: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/finger.go | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/lxc/finger.go b/lxc/finger.go
index 911fee24b..ec449b5f1 100644
--- a/lxc/finger.go
+++ b/lxc/finger.go
@@ -1,7 +1,6 @@
 package main
 
 import (
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -26,25 +25,21 @@ func (c *fingerCmd) run(conf *config.Config, args []string) error {
 		return errArgs
 	}
 
-	var remote string
-	if len(args) == 1 {
+	// Parse the remote
+	remote := conf.DefaultRemote
+	if len(args) > 0 {
 		var err error
 		remote, _, err = conf.ParseRemote(args[0])
 		if err != nil {
 			return err
 		}
-	} else {
-		remote = conf.DefaultRemote
 	}
 
-	// New client may or may not need to connect to the remote host, but
-	// client.ServerStatus will at least request the basic information from
-	// the server.
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	// Attempt to connect
+	_, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
 
-	_, err = client.ServerStatus()
-	return err
+	return nil
 }

From a3c827ff0f49fbb1498e1fa6aac235cacec9a4e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 22:18:41 -0400
Subject: [PATCH 0974/1193] lxc/action: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/action.go | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/lxc/action.go b/lxc/action.go
index 5666d59ae..50351e408 100644
--- a/lxc/action.go
+++ b/lxc/action.go
@@ -5,7 +5,6 @@ import (
 	"os"
 	"strings"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -64,7 +63,7 @@ func (c *actionCmd) doAction(conf *config.Config, nameArg string) error {
 		return err
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -74,7 +73,7 @@ func (c *actionCmd) doAction(conf *config.Config, nameArg string) error {
 	}
 
 	if c.action == shared.Start {
-		current, err := d.ContainerInfo(name)
+		current, _, err := d.GetContainer(name)
 		if err != nil {
 			return err
 		}
@@ -90,16 +89,20 @@ func (c *actionCmd) doAction(conf *config.Config, nameArg string) error {
 		}
 	}
 
-	resp, err := d.Action(name, c.action, c.timeout, c.force, state)
-	if err != nil {
-		return err
+	req := api.ContainerStatePut{
+		Action:   string(c.action),
+		Timeout:  c.timeout,
+		Force:    c.force,
+		Stateful: state,
 	}
 
-	if resp.Type != api.AsyncResponse {
-		return fmt.Errorf(i18n.G("bad result type from action"))
+	op, err := d.UpdateContainerState(name, req, "")
+	if err != nil {
+		return err
 	}
 
-	if err := d.WaitForSuccess(resp.Operation); err != nil {
+	err = op.Wait()
+	if err != nil {
 		return fmt.Errorf("%s\n"+i18n.G("Try `lxc info --show-log %s` for more info"), err, nameArg)
 	}
 

From e6c352fada6b0bbca84983045ce658db4d3dcf04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 22:35:56 -0400
Subject: [PATCH 0975/1193] lxc/monitor: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/monitor.go | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/lxc/monitor.go b/lxc/monitor.go
index 4c428afe4..41ec3ead3 100644
--- a/lxc/monitor.go
+++ b/lxc/monitor.go
@@ -5,7 +5,6 @@ import (
 
 	"gopkg.in/yaml.v2"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
@@ -77,7 +76,12 @@ func (c *monitorCmd) run(conf *config.Config, args []string) error {
 		}
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
+	if err != nil {
+		return err
+	}
+
+	listener, err := d.GetEvents()
 	if err != nil {
 		return err
 	}
@@ -92,5 +96,10 @@ func (c *monitorCmd) run(conf *config.Config, args []string) error {
 		fmt.Printf("%s\n\n", render)
 	}
 
-	return d.Monitor(c.typeArgs, handler, nil)
+	_, err = listener.AddHandler(c.typeArgs, handler)
+	if err != nil {
+		return err
+	}
+
+	return listener.Wait()
 }

From f94217a8d2794b07376155de85aa2d484c13a9de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 22:40:22 -0400
Subject: [PATCH 0976/1193] lxc/delete: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/delete.go         | 36 ++++++++++++++++++++++++------------
 test/suites/remote.sh |  1 +
 2 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/lxc/delete.go b/lxc/delete.go
index 2b1a00235..d112c3d03 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -6,7 +6,7 @@ import (
 	"os"
 	"strings"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -49,13 +49,23 @@ func (c *deleteCmd) promptDelete(name string) error {
 	return nil
 }
 
-func (c *deleteCmd) doDelete(d *lxd.Client, name string) error {
-	resp, err := d.Delete(name)
+func (c *deleteCmd) doDelete(d lxd.ContainerServer, name string) error {
+	var op *lxd.Operation
+	var err error
+
+	if shared.IsSnapshot(name) {
+		// Snapshot delete
+		fields := strings.SplitN(name, shared.SnapshotDelimiter, 2)
+		op, err = d.DeleteContainerSnapshot(fields[0], fields[1])
+	} else {
+		// Container delete
+		op, err = d.DeleteContainer(name)
+	}
 	if err != nil {
 		return err
 	}
 
-	return d.WaitForSuccess(resp.Operation)
+	return op.Wait()
 }
 
 func (c *deleteCmd) run(conf *config.Config, args []string) error {
@@ -69,7 +79,7 @@ func (c *deleteCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
@@ -85,7 +95,7 @@ func (c *deleteCmd) run(conf *config.Config, args []string) error {
 			return c.doDelete(d, name)
 		}
 
-		ct, err := d.ContainerInfo(name)
+		ct, _, err := d.GetContainer(name)
 		if err != nil {
 			return err
 		}
@@ -95,18 +105,20 @@ func (c *deleteCmd) run(conf *config.Config, args []string) error {
 				return fmt.Errorf(i18n.G("The container is currently running, stop it first or pass --force."))
 			}
 
-			resp, err := d.Action(name, shared.Stop, -1, true, false)
-			if err != nil {
-				return err
+			req := api.ContainerStatePut{
+				Action:  "stop",
+				Timeout: -1,
+				Force:   true,
 			}
 
-			op, err := d.WaitFor(resp.Operation)
+			op, err := d.UpdateContainerState(name, req, "")
 			if err != nil {
 				return err
 			}
 
-			if op.StatusCode == api.Failure {
-				return fmt.Errorf(i18n.G("Stopping container failed!"))
+			err = op.Wait()
+			if err != nil {
+				return fmt.Errorf(i18n.G("Stopping the container failed: %s"), err)
 			}
 
 			if ct.Ephemeral == true {
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index c2c9671f5..474a03e0a 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -28,6 +28,7 @@ test_remote_url() {
 
   for url in ${urls}; do
     lxc_remote remote add test "${url}"
+    lxc_remote finger test:
     lxc_remote remote remove test
   done
 }

From 630a4fa9d6dd8a3f8ad2fb7d8b50edd10afba834 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 22:44:29 -0400
Subject: [PATCH 0977/1193] lxc/snapshot: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/snapshot.go | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/lxc/snapshot.go b/lxc/snapshot.go
index 9472e3b47..dd3f0a88f 100644
--- a/lxc/snapshot.go
+++ b/lxc/snapshot.go
@@ -3,9 +3,9 @@ package main
 import (
 	"fmt"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -48,25 +48,30 @@ func (c *snapshotCmd) run(conf *config.Config, args []string) error {
 		snapname = args[1]
 	}
 
+	// we don't allow '/' in snapshot names
+	if shared.IsSnapshot(snapname) {
+		return fmt.Errorf(i18n.G("'/' not allowed in snapshot name"))
+	}
+
 	remote, name, err := conf.ParseRemote(args[0])
 	if err != nil {
 		return err
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
 
-	// we don't allow '/' in snapshot names
-	if shared.IsSnapshot(snapname) {
-		return fmt.Errorf(i18n.G("'/' not allowed in snapshot name"))
+	req := api.ContainerSnapshotsPost{
+		Name:     snapname,
+		Stateful: c.stateful,
 	}
 
-	resp, err := d.Snapshot(name, snapname, c.stateful)
+	op, err := d.CreateContainerSnapshot(name, req)
 	if err != nil {
 		return err
 	}
 
-	return d.WaitForSuccess(resp.Operation)
+	return op.Wait()
 }

From 421143dda5f0932d9f1461e2fa65dc61629a4482 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 22:53:29 -0400
Subject: [PATCH 0978/1193] lxc/restore: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/restore.go | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/lxc/restore.go b/lxc/restore.go
index e9d02a947..abffd32ca 100644
--- a/lxc/restore.go
+++ b/lxc/restore.go
@@ -3,9 +3,9 @@ package main
 import (
 	"fmt"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -50,7 +50,7 @@ func (c *restoreCmd) run(conf *config.Config, args []string) error {
 		return err
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -59,10 +59,15 @@ func (c *restoreCmd) run(conf *config.Config, args []string) error {
 		snapname = fmt.Sprintf("%s/%s", name, snapname)
 	}
 
-	resp, err := d.RestoreSnapshot(name, snapname, c.stateful)
+	req := api.ContainerPut{
+		Restore:  snapname,
+		Stateful: c.stateful,
+	}
+
+	op, err := d.UpdateContainer(name, req, "")
 	if err != nil {
 		return err
 	}
 
-	return d.WaitForSuccess(resp.Operation)
+	return op.Wait()
 }

From ad34b81613c11e9e11d3435ded082012d6b59f51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 23:11:30 -0400
Subject: [PATCH 0979/1193] lxc/exec: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/exec.go         | 32 +++++++++++++++++++++++++++-----
 lxc/exec_unix.go    |  3 +--
 lxc/exec_windows.go |  3 +--
 3 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/lxc/exec.go b/lxc/exec.go
index 8d3039aa4..5aa059604 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -10,7 +10,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -96,7 +96,7 @@ func (c *execCmd) run(conf *config.Config, args []string) error {
 		return err
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -152,7 +152,29 @@ func (c *execCmd) run(conf *config.Config, args []string) error {
 	}
 
 	stdout := c.getStdout()
-	ret, err := d.Exec(name, args[1:], env, os.Stdin, stdout, os.Stderr, handler, width, height)
+
+	req := api.ContainerExecPost{
+		Command:     args[1:],
+		WaitForWS:   true,
+		Interactive: interactive,
+		Environment: env,
+		Width:       width,
+		Height:      height,
+	}
+
+	execArgs := lxd.ContainerExecArgs{
+		Stdin:   os.Stdin,
+		Stdout:  stdout,
+		Stderr:  os.Stderr,
+		Control: handler,
+	}
+
+	op, err := d.ExecContainer(name, req, &execArgs)
+	if err != nil {
+		return err
+	}
+
+	err = op.Wait()
 	if err != nil {
 		return err
 	}
@@ -168,6 +190,6 @@ func (c *execCmd) run(conf *config.Config, args []string) error {
 		termios.Restore(cfd, oldttystate)
 	}
 
-	os.Exit(ret)
-	return fmt.Errorf(i18n.G("unreachable return reached"))
+	os.Exit(int(op.Metadata["return"].(float64)))
+	return nil
 }
diff --git a/lxc/exec_unix.go b/lxc/exec_unix.go
index ee7bb41de..104d232e5 100644
--- a/lxc/exec_unix.go
+++ b/lxc/exec_unix.go
@@ -10,7 +10,6 @@ import (
 
 	"github.com/gorilla/websocket"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared/logger"
 )
 
@@ -22,7 +21,7 @@ func (c *execCmd) getTERM() (string, bool) {
 	return os.LookupEnv("TERM")
 }
 
-func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) {
+func (c *execCmd) controlSocketHandler(control *websocket.Conn) {
 	ch := make(chan os.Signal)
 	signal.Notify(ch, syscall.SIGWINCH)
 
diff --git a/lxc/exec_windows.go b/lxc/exec_windows.go
index 21ec1b8f6..105219e29 100644
--- a/lxc/exec_windows.go
+++ b/lxc/exec_windows.go
@@ -9,7 +9,6 @@ import (
 	"github.com/gorilla/websocket"
 	"github.com/mattn/go-colorable"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/shared/logger"
 )
 
@@ -32,7 +31,7 @@ func (c *execCmd) getTERM() (string, bool) {
 	return "dumb", true
 }
 
-func (c *execCmd) controlSocketHandler(d *lxd.Client, control *websocket.Conn) {
+func (c *execCmd) controlSocketHandler(control *websocket.Conn) {
 	// TODO: figure out what the equivalent of signal.SIGWINCH is on
 	// windows and use that; for now if you resize your terminal it just
 	// won't work quite correctly.

From ac82cf6c6390f69f27ff6789b63875cdc340d2d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 23 Jun 2017 03:37:19 -0400
Subject: [PATCH 0980/1193] client: Add extra exec option to block on I/O
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This adds a channel that a client can use to block until all I/O
operations are done.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go     |  3 +++
 client/lxd_containers.go |  8 ++++++++
 lxc/exec.go              | 14 ++++++++++----
 3 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index eda7fa84e..410c4f726 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -261,6 +261,9 @@ type ContainerExecArgs struct {
 
 	// Control message handler (window resize, signals, ...)
 	Control func(conn *websocket.Conn)
+
+	// Channel that will be closed when all data operations are done
+	DataDone chan bool
 }
 
 // The ContainerFileArgs struct is used to pass the various options for a container file upload
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 45d67c51d..c3786914b 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -391,6 +391,10 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 					shared.WebsocketSendStream(conn, args.Stdin, -1)
 					<-shared.WebsocketRecvStream(args.Stdout, conn)
 					conn.Close()
+
+					if args.DataDone != nil {
+						close(args.DataDone)
+					}
 				}()
 			} else {
 				if args.DataDone != nil {
@@ -453,6 +457,10 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 				for _, conn := range conns {
 					conn.Close()
 				}
+
+				if args.DataDone != nil {
+					close(args.DataDone)
+				}
 			}()
 		}
 	}
diff --git a/lxc/exec.go b/lxc/exec.go
index 5aa059604..1fcd72531 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -163,22 +163,28 @@ func (c *execCmd) run(conf *config.Config, args []string) error {
 	}
 
 	execArgs := lxd.ContainerExecArgs{
-		Stdin:   os.Stdin,
-		Stdout:  stdout,
-		Stderr:  os.Stderr,
-		Control: handler,
+		Stdin:    os.Stdin,
+		Stdout:   stdout,
+		Stderr:   os.Stderr,
+		Control:  handler,
+		DataDone: make(chan bool),
 	}
 
+	// Run the command in the container
 	op, err := d.ExecContainer(name, req, &execArgs)
 	if err != nil {
 		return err
 	}
 
+	// Wait for the operation to complete
 	err = op.Wait()
 	if err != nil {
 		return err
 	}
 
+	// Wait for any remaining I/O to be flushed
+	<-execArgs.DataDone
+
 	if oldttystate != nil {
 		/* A bit of a special case here: we want to exit with the same code as
 		 * the process inside the container, so we explicitly exit here

From 520379c59e40e422507cee6a4b1142e334bfe368 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 23:19:40 -0400
Subject: [PATCH 0981/1193] lxc/info: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/info.go | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)

diff --git a/lxc/info.go b/lxc/info.go
index 53c45ff28..dd89c7416 100644
--- a/lxc/info.go
+++ b/lxc/info.go
@@ -7,7 +7,7 @@ import (
 
 	"gopkg.in/yaml.v2"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -55,7 +55,7 @@ func (c *infoCmd) run(conf *config.Config, args []string) error {
 		}
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -63,12 +63,12 @@ func (c *infoCmd) run(conf *config.Config, args []string) error {
 	if cName == "" {
 		return c.remoteInfo(d)
 	} else {
-		return c.containerInfo(d, cName, c.showLog)
+		return c.containerInfo(d, conf.Remotes[remote], cName, c.showLog)
 	}
 }
 
-func (c *infoCmd) remoteInfo(d *lxd.Client) error {
-	serverStatus, err := d.ServerStatus()
+func (c *infoCmd) remoteInfo(d lxd.ContainerServer) error {
+	serverStatus, _, err := d.GetServer()
 	if err != nil {
 		return err
 	}
@@ -83,13 +83,13 @@ func (c *infoCmd) remoteInfo(d *lxd.Client) error {
 	return nil
 }
 
-func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error {
-	ct, err := d.ContainerInfo(name)
+func (c *infoCmd) containerInfo(d lxd.ContainerServer, remote config.Remote, name string, showLog bool) error {
+	ct, _, err := d.GetContainer(name)
 	if err != nil {
 		return err
 	}
 
-	cs, err := d.ContainerState(name)
+	cs, _, err := d.GetContainerState(name)
 	if err != nil {
 		return err
 	}
@@ -97,9 +97,10 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 	const layout = "2006/01/02 15:04 UTC"
 
 	fmt.Printf(i18n.G("Name: %s")+"\n", ct.Name)
-	if d.Remote != nil && d.Remote.Addr != "" {
-		fmt.Printf(i18n.G("Remote: %s")+"\n", d.Remote.Addr)
+	if remote.Addr != "" {
+		fmt.Printf(i18n.G("Remote: %s")+"\n", remote.Addr)
 	}
+
 	fmt.Printf(i18n.G("Architecture: %s")+"\n", ct.Architecture)
 	if shared.TimeIsSet(ct.CreatedAt) {
 		fmt.Printf(i18n.G("Created: %s")+"\n", ct.CreatedAt.UTC().Format(layout))
@@ -197,7 +198,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 
 	// List snapshots
 	first_snapshot := true
-	snaps, err := d.ListSnapshots(name)
+	snaps, err := d.GetContainerSnapshots(name)
 	if err != nil {
 		return nil
 	}
@@ -225,7 +226,7 @@ func (c *infoCmd) containerInfo(d *lxd.Client, name string, showLog bool) error
 	}
 
 	if showLog {
-		log, err := d.GetLog(name, "lxc.log")
+		log, err := d.GetContainerLogfile(name, "lxc.log")
 		if err != nil {
 			return err
 		}

From 8584038a4506c2f50ada63f3a1e1bf550444f833 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 23:31:47 -0400
Subject: [PATCH 0982/1193] lxc/remote: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/remote.go | 59 +++++++++++++++++++++++++++++++++++------------------------
 1 file changed, 35 insertions(+), 24 deletions(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index ec4ec44a5..951651353 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -16,9 +16,9 @@ import (
 
 	"golang.org/x/crypto/ssh/terminal"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 	"github.com/lxc/lxd/shared/logger"
@@ -179,21 +179,16 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 	/* Actually add the remote */
 	conf.Remotes[server] = config.Remote{Addr: addr, Protocol: protocol}
 
-	d, err := lxd.NewClient(conf.Legacy(), server)
-	if err != nil {
-		return err
-	}
+	// Attempt to connect
+	d, err := conf.GetContainerServer(server)
 
+	// Handle Unix socket connections
 	if strings.HasPrefix(addr, "unix:") {
-		// NewClient succeeded so there was a lxd there (we fingered
-		// it) so just accept it
-		return nil
+		return err
 	}
 
+	// Check if the system CA worked for the TLS connection
 	var certificate *x509.Certificate
-
-	/* Attempt to connect using the system root CA */
-	_, err = d.GetServerConfig()
 	if err != nil {
 		// Failed to connect using the system CA, so retrieve the remote certificate
 		certificate, err = c.getRemoteCertificate(addr)
@@ -202,6 +197,7 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 		}
 	}
 
+	// Handle certificate prompt
 	if certificate != nil {
 		if !acceptCert {
 			digest := shared.CertFingerprint(certificate)
@@ -218,13 +214,13 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 			}
 		}
 
-		dnam := d.Config.ConfigPath("servercerts")
+		dnam := conf.ConfigPath("servercerts")
 		err := os.MkdirAll(dnam, 0750)
 		if err != nil {
 			return fmt.Errorf(i18n.G("Could not create server cert dir"))
 		}
 
-		certf := fmt.Sprintf("%s/%s.crt", dnam, d.Name)
+		certf := fmt.Sprintf("%s/%s.crt", dnam, server)
 		certOut, err := os.Create(certf)
 		if err != nil {
 			return err
@@ -234,27 +230,30 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 		certOut.Close()
 
 		// Setup a new connection, this time with the remote certificate
-		d, err = lxd.NewClient(conf.Legacy(), server)
+		d, err = conf.GetContainerServer(server)
 		if err != nil {
 			return err
 		}
 	}
 
-	if d.IsPublic() || public {
-		conf.Remotes[server] = config.Remote{Addr: addr, Public: true}
-
-		if _, err := d.GetServerConfig(); err != nil {
-			return err
-		}
+	// Get server information
+	srv, _, err := d.GetServer()
+	if err != nil {
+		return err
+	}
 
+	// Detect a public remote
+	if srv.Public || public {
+		conf.Remotes[server] = config.Remote{Addr: addr, Public: true}
 		return nil
 	}
 
-	if d.AmTrusted() {
-		// server already has our cert, so we're done
+	// Check if our cert is already trusted
+	if srv.Auth == "trusted" {
 		return nil
 	}
 
+	// Prompt for trust password
 	if password == "" {
 		fmt.Printf(i18n.G("Admin password for %s: "), server)
 		pwd, err := terminal.ReadPassword(0)
@@ -270,12 +269,24 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 		password = string(pwd)
 	}
 
-	err = d.AddMyCertToServer(password)
+	// Add client certificate to trust store
+	req := api.CertificatesPost{
+		Password: password,
+	}
+	req.Type = "client"
+
+	err = d.CreateCertificate(req)
+	if err != nil {
+		return err
+	}
+
+	// And check if trusted now
+	srv, _, err = d.GetServer()
 	if err != nil {
 		return err
 	}
 
-	if !d.AmTrusted() {
+	if srv.Auth != "trusted" {
 		return fmt.Errorf(i18n.G("Server doesn't trust us after adding our cert"))
 	}
 

From 3da6b9868d4e0f529ce0641b253b863ccdaf4965 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 24 Sep 2017 02:25:14 -0400
Subject: [PATCH 0983/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 435 ++++++++++++++++++++++++++++++------------------------------
 po/fr.po   | 441 +++++++++++++++++++++++++++++++------------------------------
 po/ja.po   | 441 +++++++++++++++++++++++++++++++------------------------------
 po/lxd.pot | 433 ++++++++++++++++++++++++++++++-----------------------------
 4 files changed, 875 insertions(+), 875 deletions(-)

diff --git a/po/de.po b/po/de.po
index c9d061caf..8c11f8eeb 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-05-10 00:17-0400\n"
+"POT-Creation-Date: 2017-09-24 02:25-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -16,7 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/config.go:37
+#: lxc/config.go:38
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the configuration.\n"
@@ -55,7 +55,7 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:50
+#: lxc/image.go:51
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the image properties.\n"
@@ -72,7 +72,7 @@ msgstr ""
 "### Zum Beispiel:\n"
 "###  description: Mein eigenes Abbild\n"
 
-#: lxc/profile.go:27
+#: lxc/profile.go:28
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the profile.\n"
@@ -111,96 +111,96 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:587
+#: lxc/image.go:654
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/snapshot.go:58
+#: lxc/snapshot.go:53
 #, fuzzy
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 
-#: lxc/profile.go:264
+#: lxc/profile.go:269
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:608 lxc/image.go:637
+#: lxc/image.go:675 lxc/image.go:704
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:612
+#: lxc/image.go:679
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:409
+#: lxc/list.go:415
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:66
+#: lxc/remote.go:67
 msgid "Accept certificate"
 msgstr "Akzeptiere Zertifikat"
 
-#: lxc/remote.go:259
+#: lxc/remote.go:258
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
 
-#: lxc/image.go:342
+#: lxc/image.go:383
 #, fuzzy
 msgid "Aliases:"
 msgstr "Aliasse:\n"
 
-#: lxc/image.go:320 lxc/info.go:95
+#: lxc/image.go:361 lxc/info.go:104
 #, fuzzy, c-format
 msgid "Architecture: %s"
 msgstr "Architektur: %s\n"
 
-#: lxc/image.go:350
+#: lxc/image.go:391
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/info.go:177
+#: lxc/info.go:186
 msgid "Bytes received"
 msgstr ""
 
-#: lxc/info.go:178
+#: lxc/info.go:187
 msgid "Bytes sent"
 msgstr ""
 
-#: lxc/config.go:310
+#: lxc/config.go:327
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:410
+#: lxc/list.go:416
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:150
+#: lxc/config.go:155
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr ""
 
-#: lxc/config.go:163 lxc/config.go:196 lxc/config.go:218
+#: lxc/config.go:168 lxc/config.go:201 lxc/config.go:227
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:394
+#: lxc/profile.go:409
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:209
+#: lxc/remote.go:205
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %s"
 msgstr "Fingerabdruck des Zertifikats: % x\n"
 
-#: lxc/remote.go:282
+#: lxc/remote.go:293
 msgid "Client certificate stored at server: "
 msgstr "Gespeichertes Nutzerzertifikat auf dem Server: "
 
-#: lxc/list.go:109 lxc/list.go:110
+#: lxc/list.go:110 lxc/list.go:111
 msgid "Columns"
 msgstr ""
 
@@ -208,99 +208,99 @@ msgstr ""
 msgid "Commands:"
 msgstr ""
 
-#: lxc/init.go:133 lxc/init.go:134
+#: lxc/init.go:134 lxc/init.go:135
 #, fuzzy
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:691 lxc/profile.go:228
+#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:758 lxc/profile.go:233
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
 
-#: lxc/main.go:33
+#: lxc/main.go:35
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
-#: lxc/publish.go:60
+#: lxc/publish.go:69
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:211
+#: lxc/init.go:221
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:158 lxc/publish.go:173
+#: lxc/publish.go:167 lxc/publish.go:182
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/image.go:132
+#: lxc/image.go:133
 msgid "Copy aliases from source"
 msgstr "Kopiere Aliasse von der Quelle"
 
-#: lxc/image.go:244
+#: lxc/image.go:277
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:224
+#: lxc/remote.go:220
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
-#: lxc/image.go:325 lxc/info.go:97
+#: lxc/image.go:366 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:176 lxc/launch.go:116
+#: lxc/init.go:186 lxc/launch.go:126
 #, c-format
 msgid "Creating %s"
 msgstr ""
 
-#: lxc/init.go:174
+#: lxc/init.go:184
 #, fuzzy
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:611 lxc/image.go:639
+#: lxc/image.go:678 lxc/image.go:706
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/config.go:688
+#: lxc/config.go:729
 #, fuzzy, c-format
 msgid "Device %s added to %s"
 msgstr "Gerät %s wurde zu %s hinzugefügt\n"
 
-#: lxc/config.go:875
+#: lxc/config.go:929
 #, fuzzy, c-format
 msgid "Device %s removed from %s"
 msgstr "Gerät %s wurde von %s entfernt\n"
 
-#: lxc/info.go:145
+#: lxc/info.go:154
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:495
+#: lxc/list.go:501
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:312
+#: lxc/config.go:329
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:45
+#: lxc/main.go:47
 #, fuzzy
 msgid "Enable debug mode"
 msgstr "Aktiviert Debug Modus"
 
-#: lxc/main.go:44
+#: lxc/main.go:46
 #, fuzzy
 msgid "Enable verbose mode"
 msgstr "Aktiviert ausführliche Ausgabe"
 
-#: lxc/exec.go:55
+#: lxc/exec.go:56
 msgid "Environment variable to set (e.g. HOME=/home/foo)"
 msgstr ""
 
@@ -308,7 +308,7 @@ msgstr ""
 msgid "Environment:"
 msgstr ""
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:137 lxc/init.go:138
+#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:138 lxc/init.go:139
 msgid "Ephemeral container"
 msgstr "Flüchtiger Container"
 
@@ -316,16 +316,16 @@ msgstr "Flüchtiger Container"
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:329
+#: lxc/image.go:370
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:331
+#: lxc/image.go:372
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:309 lxc/image.go:609 lxc/image.go:638
+#: lxc/config.go:326 lxc/image.go:676 lxc/image.go:705
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -339,11 +339,11 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:112
+#: lxc/list.go:113
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:318
+#: lxc/image.go:359
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Fingerabdruck: %s\n"
@@ -353,41 +353,41 @@ msgstr "Fingerabdruck: %s\n"
 msgid "Force the container to shutdown"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/delete.go:33 lxc/delete.go:34
+#: lxc/delete.go:34 lxc/delete.go:35
 msgid "Force the removal of stopped containers"
 msgstr ""
 
-#: lxc/main.go:46
+#: lxc/main.go:48
 msgid "Force using the local unix socket"
 msgstr ""
 
-#: lxc/list.go:111
+#: lxc/list.go:112
 msgid "Format (table|json)"
 msgstr ""
 
-#: lxc/main.go:131
+#: lxc/main.go:139
 #, fuzzy
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Generiere Nutzerzertifikat. Dies kann wenige Minuten dauern...\n"
 
-#: lxc/list.go:407
+#: lxc/list.go:413
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:408
+#: lxc/list.go:414
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:311
+#: lxc/config.go:328
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:139
+#: lxc/main.go:147
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:47
+#: lxc/main.go:49
 msgid "Ignore aliases when determining what command to run"
 msgstr ""
 
@@ -396,190 +396,190 @@ msgstr ""
 msgid "Ignore the container state (only for start)"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/image.go:247
+#: lxc/image.go:280
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:405 lxc/image.go:417
+#: lxc/image.go:450 lxc/image.go:462
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/image.go:402
+#: lxc/image.go:447
 #, c-format
 msgid "Importing the image: %s"
 msgstr ""
 
-#: lxc/remote.go:135
+#: lxc/remote.go:136
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
 
-#: lxc/config.go:290
+#: lxc/config.go:307
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "Akzeptiere Zertifikat"
 
-#: lxc/init.go:30 lxc/init.go:35
+#: lxc/init.go:31 lxc/init.go:36
 msgid "Invalid configuration key"
 msgstr ""
 
-#: lxc/file.go:207
+#: lxc/file.go:211
 #, c-format
 msgid "Invalid source %s"
 msgstr "Ungültige Quelle %s"
 
-#: lxc/file.go:69
+#: lxc/file.go:70
 #, c-format
 msgid "Invalid target %s"
 msgstr "Ungültiges Ziel %s"
 
-#: lxc/info.go:126
+#: lxc/info.go:135
 msgid "Ips:"
 msgstr ""
 
-#: lxc/image.go:133
+#: lxc/image.go:134
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:31
+#: lxc/main.go:33
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:334
+#: lxc/image.go:375
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:336
+#: lxc/image.go:377
 msgid "Last used: never"
 msgstr ""
 
-#: lxc/info.go:230
+#: lxc/info.go:239
 msgid "Log:"
 msgstr ""
 
-#: lxc/image.go:131
+#: lxc/image.go:132
 msgid "Make image public"
 msgstr "Veröffentliche Abbild"
 
-#: lxc/publish.go:33
+#: lxc/publish.go:34
 #, fuzzy
 msgid "Make the image public"
 msgstr "Veröffentliche Abbild"
 
-#: lxc/info.go:152
+#: lxc/info.go:161
 msgid "Memory (current)"
 msgstr ""
 
-#: lxc/info.go:156
+#: lxc/info.go:165
 msgid "Memory (peak)"
 msgstr ""
 
-#: lxc/info.go:168
+#: lxc/info.go:177
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/copy.go:214
+#: lxc/copy.go:222
 #, c-format
 msgid "Migration failed on source host: %s"
 msgstr ""
 
-#: lxc/copy.go:218
+#: lxc/copy.go:226
 #, c-format
 msgid "Migration failed on target host: %s"
 msgstr ""
 
-#: lxc/utils.go:193
+#: lxc/utils.go:197
 msgid "Missing summary."
 msgstr "Fehlende Zusammenfassung."
 
-#: lxc/file.go:195
+#: lxc/file.go:199
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "Mehr als eine Datei herunterzuladen, aber das Ziel ist kein Verzeichnis"
 
-#: lxc/action.go:68
+#: lxc/action.go:72
 #, fuzzy
 msgid "Must supply container name for: "
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
-#: lxc/list.go:411 lxc/remote.go:366
+#: lxc/list.go:417 lxc/remote.go:377
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:340 lxc/remote.go:345
+#: lxc/remote.go:351 lxc/remote.go:356
 msgid "NO"
 msgstr ""
 
-#: lxc/info.go:91
+#: lxc/info.go:99
 #, c-format
 msgid "Name: %s"
 msgstr ""
 
-#: lxc/info.go:185
+#: lxc/info.go:194
 msgid "Network usage:"
 msgstr ""
 
-#: lxc/image.go:134 lxc/publish.go:34
+#: lxc/image.go:135 lxc/publish.go:35
 msgid "New alias to define at target"
 msgstr ""
 
-#: lxc/config.go:321
+#: lxc/config.go:338
 #, fuzzy
 msgid "No certificate provided to add"
 msgstr "Kein Zertifikat zum hinzufügen bereitgestellt"
 
-#: lxc/config.go:344
+#: lxc/config.go:365
 msgid "No fingerprint specified."
 msgstr "Kein Fingerabdruck angegeben."
 
-#: lxc/remote.go:120
+#: lxc/remote.go:121
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:408
+#: lxc/image.go:453
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
-#: lxc/help.go:71 lxc/main.go:112 lxc/main.go:164
+#: lxc/help.go:71 lxc/main.go:120 lxc/main.go:172
 msgid "Options:"
 msgstr ""
 
-#: lxc/image.go:510
+#: lxc/image.go:573
 #, c-format
 msgid "Output is in %s"
 msgstr ""
 
-#: lxc/exec.go:56
+#: lxc/exec.go:57
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:497
+#: lxc/list.go:503
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:412
+#: lxc/list.go:418
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:413
+#: lxc/list.go:419
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:368
+#: lxc/remote.go:379
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:610 lxc/remote.go:369
+#: lxc/image.go:677 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:179
+#: lxc/info.go:188
 msgid "Packets received"
 msgstr ""
 
-#: lxc/info.go:180
+#: lxc/info.go:189
 msgid "Packets sent"
 msgstr ""
 
@@ -593,25 +593,25 @@ msgstr "Alternatives config Verzeichnis."
 msgid "Path to an alternate server directory"
 msgstr "Alternatives config Verzeichnis."
 
-#: lxc/main.go:201
+#: lxc/main.go:209
 #, fuzzy
 msgid "Pause containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/main.go:35
+#: lxc/main.go:37
 msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
-#: lxc/info.go:108
+#: lxc/info.go:117
 #, c-format
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/profile.go:229
+#: lxc/profile.go:234
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:692
+#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:759
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -627,120 +627,120 @@ msgstr ""
 msgid "Print verbose information"
 msgstr ""
 
-#: lxc/info.go:132
+#: lxc/info.go:141
 #, fuzzy, c-format
 msgid "Processes: %d"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:266
+#: lxc/profile.go:271
 #, fuzzy, c-format
 msgid "Profile %s applied to %s"
 msgstr "Profil %s wurde auf %s angewandt\n"
 
-#: lxc/profile.go:180
+#: lxc/profile.go:185
 #, fuzzy, c-format
 msgid "Profile %s created"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:250
+#: lxc/profile.go:255
 #, fuzzy, c-format
 msgid "Profile %s deleted"
 msgstr "Profil %s gelöscht\n"
 
-#: lxc/init.go:135 lxc/init.go:136
+#: lxc/init.go:136 lxc/init.go:137
 #, fuzzy
 msgid "Profile to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/info.go:106
+#: lxc/info.go:115
 #, fuzzy, c-format
 msgid "Profiles: %s"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/image.go:338
+#: lxc/image.go:379
 #, fuzzy
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
 
-#: lxc/remote.go:69
+#: lxc/remote.go:70
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:321
+#: lxc/image.go:362
 #, fuzzy, c-format
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
 
-#: lxc/remote.go:67
+#: lxc/remote.go:68
 msgid "Remote admin password"
 msgstr "Entferntes Administrator Passwort"
 
-#: lxc/info.go:93
+#: lxc/info.go:101
 #, c-format
 msgid "Remote: %s"
 msgstr ""
 
-#: lxc/delete.go:41
+#: lxc/delete.go:42
 #, c-format
 msgid "Remove %s (yes/no): "
 msgstr ""
 
-#: lxc/delete.go:35 lxc/delete.go:36
+#: lxc/delete.go:36 lxc/delete.go:37
 msgid "Require user confirmation"
 msgstr ""
 
-#: lxc/info.go:129
+#: lxc/info.go:138
 msgid "Resources:"
 msgstr ""
 
-#: lxc/main.go:209
+#: lxc/main.go:217
 #, fuzzy
 msgid "Restart containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/init.go:217
+#: lxc/init.go:227
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:613
+#: lxc/image.go:680
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:414
+#: lxc/list.go:420
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:415
+#: lxc/list.go:421
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:370
+#: lxc/remote.go:381
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:217
+#: lxc/remote.go:213
 msgid "Server certificate NACKed by user"
 msgstr "Server Zertifikat vom Benutzer nicht akzeptiert"
 
-#: lxc/remote.go:279
+#: lxc/remote.go:290
 msgid "Server doesn't trust us after adding our cert"
 msgstr ""
 "Der Server vertraut uns nicht nachdem er unser Zertifikat hinzugefügt hat"
 
-#: lxc/remote.go:68
+#: lxc/remote.go:69
 msgid "Server protocol (lxd or simplestreams)"
 msgstr ""
 
-#: lxc/file.go:56
+#: lxc/file.go:57
 msgid "Set the file's gid on push"
 msgstr "Setzt die gid der Datei beim Übertragen"
 
-#: lxc/file.go:57
+#: lxc/file.go:58
 msgid "Set the file's perms on push"
 msgstr "Setzt die Dateiberechtigungen beim Übertragen"
 
-#: lxc/file.go:55
+#: lxc/file.go:56
 msgid "Set the file's uid on push"
 msgstr "Setzt die uid der Datei beim Übertragen"
 
@@ -752,94 +752,99 @@ msgstr "Zeigt alle Befehle (nicht nur die interessanten)"
 msgid "Show client version"
 msgstr ""
 
-#: lxc/info.go:38
+#: lxc/info.go:39
 msgid "Show the container's last 100 log lines?"
 msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 
-#: lxc/config.go:33
+#: lxc/config.go:34
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:319
+#: lxc/image.go:360
 #, fuzzy, c-format
 msgid "Size: %.2fMB"
 msgstr "Größe: %.2vMB\n"
 
-#: lxc/info.go:199
+#: lxc/info.go:208
 msgid "Snapshots:"
 msgstr ""
 
-#: lxc/action.go:134
+#: lxc/action.go:142
 #, fuzzy, c-format
 msgid "Some containers failed to %s"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/image.go:352
+#: lxc/image.go:393
 msgid "Source:"
 msgstr ""
 
-#: lxc/main.go:219
+#: lxc/main.go:227
 #, fuzzy
 msgid "Start containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/launch.go:124
+#: lxc/launch.go:134
 #, c-format
 msgid "Starting %s"
 msgstr ""
 
-#: lxc/info.go:100
+#: lxc/info.go:109
 #, c-format
 msgid "Status: %s"
 msgstr ""
 
-#: lxc/main.go:225
+#: lxc/main.go:233
 #, fuzzy
 msgid "Stop containers."
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/publish.go:35 lxc/publish.go:36
+#: lxc/publish.go:36 lxc/publish.go:37
 msgid "Stop the container if currently running"
 msgstr ""
 
-#: lxc/delete.go:105 lxc/publish.go:112
+#: lxc/publish.go:121
 msgid "Stopping container failed!"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
+#: lxc/delete.go:121
+#, fuzzy, c-format
+msgid "Stopping the container failed: %s"
+msgstr "Anhalten des Containers fehlgeschlagen!"
+
 #: lxc/action.go:49
 #, fuzzy
 msgid "Store the container state (only for stop)"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/info.go:160
+#: lxc/info.go:169
 msgid "Swap (current)"
 msgstr ""
 
-#: lxc/info.go:164
+#: lxc/info.go:173
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:416
+#: lxc/list.go:422
 msgid "TYPE"
 msgstr ""
 
-#: lxc/delete.go:91
+#: lxc/delete.go:105
 msgid "The container is currently running, stop it first or pass --force."
 msgstr ""
 
-#: lxc/publish.go:90
+#: lxc/publish.go:99
 msgid ""
 "The container is currently running. Use --force to have it stopped and "
 "restarted."
 msgstr ""
 
-#: lxc/config.go:716 lxc/config.go:728 lxc/config.go:761 lxc/config.go:779
-#: lxc/config.go:817 lxc/config.go:835
+#: lxc/config.go:760 lxc/config.go:772 lxc/config.go:808 lxc/config.go:826
+#: lxc/config.go:867 lxc/config.go:885
 #, fuzzy
 msgid "The device doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/init.go:274
+#: lxc/init.go:284
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -848,7 +853,7 @@ msgstr ""
 msgid "The opposite of \"lxc pause\" is \"lxc start\"."
 msgstr ""
 
-#: lxc/publish.go:63
+#: lxc/publish.go:72
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
 
@@ -865,38 +870,38 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Wartezeit bevor der Container gestoppt wird."
 
-#: lxc/image.go:322
+#: lxc/image.go:363
 #, fuzzy
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
 
-#: lxc/main.go:140
+#: lxc/main.go:148
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:410
+#: lxc/image.go:455
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
 
-#: lxc/action.go:98 lxc/launch.go:137
+#: lxc/action.go:106 lxc/launch.go:147
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
 
-#: lxc/info.go:102
+#: lxc/info.go:111
 msgid "Type: ephemeral"
 msgstr ""
 
-#: lxc/info.go:104
+#: lxc/info.go:113
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:614
+#: lxc/image.go:681
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:378
 msgid "URL"
 msgstr ""
 
@@ -904,11 +909,11 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/remote.go:95
+#: lxc/remote.go:96
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:327
+#: lxc/image.go:368
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -928,7 +933,7 @@ msgstr ""
 "Benutzung: lxc [Unterbefehl] [Optionen]\n"
 "Verfügbare Befehle:\n"
 
-#: lxc/config.go:58
+#: lxc/config.go:59
 msgid ""
 "Usage: lxc config <subcommand> [options]\n"
 "\n"
@@ -1005,7 +1010,7 @@ msgid ""
 "    Will set the server's trust password to blah."
 msgstr ""
 
-#: lxc/copy.go:23
+#: lxc/copy.go:24
 msgid ""
 "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
 "[--ephemeral|e]\n"
@@ -1013,7 +1018,7 @@ msgid ""
 "Copy containers within or in between LXD instances."
 msgstr ""
 
-#: lxc/delete.go:26
+#: lxc/delete.go:27
 #, fuzzy
 msgid ""
 "Usage: lxc delete [<remote>:]<container>[/<snapshot>] "
@@ -1026,7 +1031,7 @@ msgstr ""
 "Entfernt einen Container (oder Sicherungspunkt) und alle dazugehörigen\n"
 "Daten (Konfiguration, Sicherungspunkte, ...).\n"
 
-#: lxc/exec.go:46
+#: lxc/exec.go:47
 #, fuzzy
 msgid ""
 "Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-"
@@ -1041,7 +1046,7 @@ msgstr ""
 "\n"
 "lxc exec <Container> [--env EDITOR=/usr/bin/vim]... <Befehl>\n"
 
-#: lxc/file.go:32
+#: lxc/file.go:33
 msgid ""
 "Usage: lxc file <subcommand> [options]\n"
 "\n"
@@ -1081,7 +1086,7 @@ msgid ""
 "Help page for the LXD client."
 msgstr ""
 
-#: lxc/image.go:60
+#: lxc/image.go:61
 msgid ""
 "Usage: lxc image <subcommand> [options]\n"
 "\n"
@@ -1156,7 +1161,7 @@ msgid ""
 "image alias name."
 msgstr ""
 
-#: lxc/info.go:25
+#: lxc/info.go:26
 msgid ""
 "Usage: lxc info [<remote>:][<container>] [--show-log]\n"
 "\n"
@@ -1169,7 +1174,7 @@ msgid ""
 "    For LXD server information."
 msgstr ""
 
-#: lxc/init.go:73
+#: lxc/init.go:74
 #, fuzzy
 msgid ""
 "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
@@ -1193,7 +1198,7 @@ msgstr ""
 "Beispiel:\n"
 "lxc init ubuntu u1\n"
 
-#: lxc/launch.go:23
+#: lxc/launch.go:24
 #, fuzzy
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
@@ -1219,7 +1224,7 @@ msgstr ""
 "Beispiel:\n"
 "lxc launch ubuntu u1\n"
 
-#: lxc/list.go:46
+#: lxc/list.go:47
 #, fuzzy
 msgid ""
 "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
@@ -1325,7 +1330,7 @@ msgid ""
 "    Only show log message."
 msgstr ""
 
-#: lxc/move.go:16
+#: lxc/move.go:17
 #, fuzzy
 msgid ""
 "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
@@ -1347,7 +1352,7 @@ msgstr ""
 "\n"
 "lxc move <Quelle> <Ziel>\n"
 
-#: lxc/profile.go:48
+#: lxc/profile.go:49
 #, fuzzy
 msgid ""
 "Usage: lxc profile <subcommand> [options]\n"
@@ -1457,7 +1462,7 @@ msgstr ""
 "Containern hinzu,\n"
 "    die dieses Profil verwenden.\n"
 
-#: lxc/publish.go:26
+#: lxc/publish.go:27
 msgid ""
 "Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--"
 "alias=ALIAS...] [prop-key=prop-value...]\n"
@@ -1465,7 +1470,7 @@ msgid ""
 "Publish containers as images."
 msgstr ""
 
-#: lxc/remote.go:38
+#: lxc/remote.go:39
 #, fuzzy
 msgid ""
 "Usage: lxc remote <subcommand> [options]\n"
@@ -1511,7 +1516,7 @@ msgstr ""
 "lxc remote get-default                                                    "
 "Gibt die Standard Instanz aus.\n"
 
-#: lxc/restore.go:21
+#: lxc/restore.go:22
 msgid ""
 "Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
 "\n"
@@ -1527,7 +1532,7 @@ msgid ""
 "    Restore the snapshot."
 msgstr ""
 
-#: lxc/snapshot.go:21
+#: lxc/snapshot.go:22
 msgid ""
 "Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
 "\n"
@@ -1552,11 +1557,11 @@ msgstr ""
 "\n"
 "lxc version\n"
 
-#: lxc/delete.go:45
+#: lxc/delete.go:46
 msgid "User aborted delete operation."
 msgstr ""
 
-#: lxc/restore.go:37
+#: lxc/restore.go:38
 msgid ""
 "Whether or not to restore the container's running state from snapshot (if "
 "available)"
@@ -1564,129 +1569,121 @@ msgstr ""
 "Laufenden Zustand des Containers aus dem Sicherungspunkt (falls vorhanden) "
 "wiederherstellen oder nicht"
 
-#: lxc/snapshot.go:35
+#: lxc/snapshot.go:36
 msgid "Whether or not to snapshot the container's running state"
 msgstr "Zustand des laufenden Containers sichern oder nicht"
 
-#: lxc/remote.go:342 lxc/remote.go:347
+#: lxc/remote.go:353 lxc/remote.go:358
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:56
+#: lxc/main.go:58
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:108
+#: lxc/launch.go:118
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "Falsche Anzahl an Objekten im Abbild, Container oder Sicherungspunkt gelesen."
 
-#: lxc/action.go:94
-msgid "bad result type from action"
-msgstr ""
-
-#: lxc/copy.go:100
+#: lxc/copy.go:108
 msgid "can't copy to the same container name"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/remote.go:330
+#: lxc/remote.go:341
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:356
+#: lxc/remote.go:367
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:89 lxc/launch.go:95
+#: lxc/init.go:211 lxc/init.go:216 lxc/launch.go:99 lxc/launch.go:105
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
-#: lxc/image.go:313
+#: lxc/image.go:354
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:315
+#: lxc/image.go:356
 msgid "enabled"
 msgstr ""
 
-#: lxc/action.go:126 lxc/main.go:26 lxc/main.go:160
+#: lxc/action.go:134 lxc/main.go:28 lxc/main.go:168
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "Fehler: %v\n"
 
-#: lxc/help.go:37 lxc/main.go:106
+#: lxc/help.go:37 lxc/main.go:114
 #, fuzzy, c-format
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/launch.go:113
+#: lxc/launch.go:123
 msgid "got bad version"
 msgstr "Versionskonflikt"
 
-#: lxc/image.go:308 lxc/image.go:590
+#: lxc/image.go:349 lxc/image.go:657
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:123
+#: lxc/copy.go:131
 msgid "not all the profiles from the source exist on the target"
 msgstr "nicht alle Profile der Quelle sind am Ziel vorhanden."
 
-#: lxc/remote.go:210
+#: lxc/remote.go:206
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "OK (y/n)? "
 
-#: lxc/main.go:295 lxc/main.go:299
+#: lxc/main.go:303 lxc/main.go:307
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:392
+#: lxc/remote.go:403
 #, c-format
 msgid "remote %s already exists"
 msgstr "entfernte Instanz %s existiert bereits"
 
-#: lxc/remote.go:322 lxc/remote.go:384 lxc/remote.go:419 lxc/remote.go:435
+#: lxc/remote.go:333 lxc/remote.go:395 lxc/remote.go:430 lxc/remote.go:446
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/remote.go:305
+#: lxc/remote.go:316
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "entfernte Instanz %s existiert als <%s>"
 
-#: lxc/remote.go:326 lxc/remote.go:388 lxc/remote.go:423
+#: lxc/remote.go:337 lxc/remote.go:399 lxc/remote.go:434
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
 
-#: lxc/info.go:210
+#: lxc/info.go:219
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:212
+#: lxc/info.go:221
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:206
+#: lxc/info.go:215
 #, c-format
 msgid "taken at %s"
 msgstr ""
 
-#: lxc/exec.go:167
-msgid "unreachable return reached"
-msgstr ""
-
-#: lxc/main.go:234
+#: lxc/main.go:242
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:44 lxc/image.go:310 lxc/image.go:594
+#: lxc/delete.go:45 lxc/image.go:351 lxc/image.go:661
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:39
+#: lxc/copy.go:47
 msgid "you must specify a source container name"
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
diff --git a/po/fr.po b/po/fr.po
index 4ed6a2c68..0831b2b53 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-05-10 00:17-0400\n"
+"POT-Creation-Date: 2017-09-24 02:25-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -16,7 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/config.go:37
+#: lxc/config.go:38
 msgid ""
 "### This is a yaml representation of the configuration.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -37,7 +37,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:50
+#: lxc/image.go:51
 msgid ""
 "### This is a yaml representation of the image properties.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -47,7 +47,7 @@ msgid ""
 "###  description: My custom image"
 msgstr ""
 
-#: lxc/profile.go:27
+#: lxc/profile.go:28
 msgid ""
 "### This is a yaml representation of the profile.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -68,95 +68,95 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:587
+#: lxc/image.go:654
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/snapshot.go:58
+#: lxc/snapshot.go:53
 #, fuzzy
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 
-#: lxc/profile.go:264
+#: lxc/profile.go:269
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:608 lxc/image.go:637
+#: lxc/image.go:675 lxc/image.go:704
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:612
+#: lxc/image.go:679
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:409
+#: lxc/list.go:415
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:66
+#: lxc/remote.go:67
 msgid "Accept certificate"
 msgstr ""
 
-#: lxc/remote.go:259
+#: lxc/remote.go:258
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
 
-#: lxc/image.go:342
+#: lxc/image.go:383
 msgid "Aliases:"
 msgstr ""
 
-#: lxc/image.go:320 lxc/info.go:95
+#: lxc/image.go:361 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr ""
 
-#: lxc/image.go:350
+#: lxc/image.go:391
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/info.go:177
+#: lxc/info.go:186
 msgid "Bytes received"
 msgstr ""
 
-#: lxc/info.go:178
+#: lxc/info.go:187
 msgid "Bytes sent"
 msgstr ""
 
-#: lxc/config.go:310
+#: lxc/config.go:327
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:410
+#: lxc/list.go:416
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:150
+#: lxc/config.go:155
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr ""
 
-#: lxc/config.go:163 lxc/config.go:196 lxc/config.go:218
+#: lxc/config.go:168 lxc/config.go:201 lxc/config.go:227
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:394
+#: lxc/profile.go:409
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:209
+#: lxc/remote.go:205
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/remote.go:282
+#: lxc/remote.go:293
 msgid "Client certificate stored at server: "
 msgstr "Certificat client enregistré avec le serveur: "
 
-#: lxc/list.go:109 lxc/list.go:110
+#: lxc/list.go:110 lxc/list.go:111
 msgid "Columns"
 msgstr ""
 
@@ -164,97 +164,97 @@ msgstr ""
 msgid "Commands:"
 msgstr ""
 
-#: lxc/init.go:133 lxc/init.go:134
+#: lxc/init.go:134 lxc/init.go:135
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:691 lxc/profile.go:228
+#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:758 lxc/profile.go:233
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
 
-#: lxc/main.go:33
+#: lxc/main.go:35
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
-#: lxc/publish.go:60
+#: lxc/publish.go:69
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:211
+#: lxc/init.go:221
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:158 lxc/publish.go:173
+#: lxc/publish.go:167 lxc/publish.go:182
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/image.go:132
+#: lxc/image.go:133
 msgid "Copy aliases from source"
 msgstr ""
 
-#: lxc/image.go:244
+#: lxc/image.go:277
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:224
+#: lxc/remote.go:220
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
-#: lxc/image.go:325 lxc/info.go:97
+#: lxc/image.go:366 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:176 lxc/launch.go:116
+#: lxc/init.go:186 lxc/launch.go:126
 #, c-format
 msgid "Creating %s"
 msgstr ""
 
-#: lxc/init.go:174
+#: lxc/init.go:184
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:611 lxc/image.go:639
+#: lxc/image.go:678 lxc/image.go:706
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/config.go:688
+#: lxc/config.go:729
 #, c-format
 msgid "Device %s added to %s"
 msgstr ""
 
-#: lxc/config.go:875
+#: lxc/config.go:929
 #, c-format
 msgid "Device %s removed from %s"
 msgstr ""
 
-#: lxc/info.go:145
+#: lxc/info.go:154
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:495
+#: lxc/list.go:501
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:312
+#: lxc/config.go:329
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:45
+#: lxc/main.go:47
 #, fuzzy
 msgid "Enable debug mode"
 msgstr "Active le mode de déboguage."
 
-#: lxc/main.go:44
+#: lxc/main.go:46
 #, fuzzy
 msgid "Enable verbose mode"
 msgstr "Active le mode verbeux."
 
-#: lxc/exec.go:55
+#: lxc/exec.go:56
 msgid "Environment variable to set (e.g. HOME=/home/foo)"
 msgstr ""
 
@@ -262,7 +262,7 @@ msgstr ""
 msgid "Environment:"
 msgstr ""
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:137 lxc/init.go:138
+#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:138 lxc/init.go:139
 msgid "Ephemeral container"
 msgstr ""
 
@@ -270,16 +270,16 @@ msgstr ""
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:329
+#: lxc/image.go:370
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:331
+#: lxc/image.go:372
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:309 lxc/image.go:609 lxc/image.go:638
+#: lxc/config.go:326 lxc/image.go:676 lxc/image.go:705
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -293,11 +293,11 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:112
+#: lxc/list.go:113
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:318
+#: lxc/image.go:359
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -307,41 +307,41 @@ msgstr "Empreinte du certificat: % x\n"
 msgid "Force the container to shutdown"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/delete.go:33 lxc/delete.go:34
+#: lxc/delete.go:34 lxc/delete.go:35
 msgid "Force the removal of stopped containers"
 msgstr ""
 
-#: lxc/main.go:46
+#: lxc/main.go:48
 msgid "Force using the local unix socket"
 msgstr ""
 
-#: lxc/list.go:111
+#: lxc/list.go:112
 msgid "Format (table|json)"
 msgstr ""
 
-#: lxc/main.go:131
+#: lxc/main.go:139
 #, fuzzy
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Géneration d'un certificat client. Ceci peut prendre une minute...\n"
 
-#: lxc/list.go:407
+#: lxc/list.go:413
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:408
+#: lxc/list.go:414
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:311
+#: lxc/config.go:328
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:139
+#: lxc/main.go:147
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:47
+#: lxc/main.go:49
 msgid "Ignore aliases when determining what command to run"
 msgstr ""
 
@@ -350,190 +350,190 @@ msgstr ""
 msgid "Ignore the container state (only for start)"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/image.go:247
+#: lxc/image.go:280
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:405 lxc/image.go:417
+#: lxc/image.go:450 lxc/image.go:462
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/image.go:402
+#: lxc/image.go:447
 #, c-format
 msgid "Importing the image: %s"
 msgstr ""
 
-#: lxc/remote.go:135
+#: lxc/remote.go:136
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
 
-#: lxc/config.go:290
+#: lxc/config.go:307
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "Gérer la configuration.\n"
 
-#: lxc/init.go:30 lxc/init.go:35
+#: lxc/init.go:31 lxc/init.go:36
 #, fuzzy
 msgid "Invalid configuration key"
 msgstr "Gérer la configuration.\n"
 
-#: lxc/file.go:207
+#: lxc/file.go:211
 #, c-format
 msgid "Invalid source %s"
 msgstr "Source invalide %s"
 
-#: lxc/file.go:69
+#: lxc/file.go:70
 #, c-format
 msgid "Invalid target %s"
 msgstr "Destination invalide %s"
 
-#: lxc/info.go:126
+#: lxc/info.go:135
 msgid "Ips:"
 msgstr ""
 
-#: lxc/image.go:133
+#: lxc/image.go:134
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:31
+#: lxc/main.go:33
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:334
+#: lxc/image.go:375
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:336
+#: lxc/image.go:377
 msgid "Last used: never"
 msgstr ""
 
-#: lxc/info.go:230
+#: lxc/info.go:239
 msgid "Log:"
 msgstr ""
 
-#: lxc/image.go:131
+#: lxc/image.go:132
 msgid "Make image public"
 msgstr ""
 
-#: lxc/publish.go:33
+#: lxc/publish.go:34
 msgid "Make the image public"
 msgstr ""
 
-#: lxc/info.go:152
+#: lxc/info.go:161
 msgid "Memory (current)"
 msgstr ""
 
-#: lxc/info.go:156
+#: lxc/info.go:165
 msgid "Memory (peak)"
 msgstr ""
 
-#: lxc/info.go:168
+#: lxc/info.go:177
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/copy.go:214
+#: lxc/copy.go:222
 #, c-format
 msgid "Migration failed on source host: %s"
 msgstr ""
 
-#: lxc/copy.go:218
+#: lxc/copy.go:226
 #, c-format
 msgid "Migration failed on target host: %s"
 msgstr ""
 
-#: lxc/utils.go:193
+#: lxc/utils.go:197
 msgid "Missing summary."
 msgstr "Sommaire manquant."
 
-#: lxc/file.go:195
+#: lxc/file.go:199
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "Plusieurs fichiers à télécharger mais la destination n'est pas un dossier"
 
-#: lxc/action.go:68
+#: lxc/action.go:72
 msgid "Must supply container name for: "
 msgstr ""
 
-#: lxc/list.go:411 lxc/remote.go:366
+#: lxc/list.go:417 lxc/remote.go:377
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:340 lxc/remote.go:345
+#: lxc/remote.go:351 lxc/remote.go:356
 msgid "NO"
 msgstr ""
 
-#: lxc/info.go:91
+#: lxc/info.go:99
 #, c-format
 msgid "Name: %s"
 msgstr ""
 
-#: lxc/info.go:185
+#: lxc/info.go:194
 msgid "Network usage:"
 msgstr ""
 
-#: lxc/image.go:134 lxc/publish.go:34
+#: lxc/image.go:135 lxc/publish.go:35
 msgid "New alias to define at target"
 msgstr ""
 
-#: lxc/config.go:321
+#: lxc/config.go:338
 #, fuzzy
 msgid "No certificate provided to add"
 msgstr "Un certificat n'a pas été fournis"
 
-#: lxc/config.go:344
+#: lxc/config.go:365
 msgid "No fingerprint specified."
 msgstr "Aucune empreinte n'a été spécifié."
 
-#: lxc/remote.go:120
+#: lxc/remote.go:121
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:408
+#: lxc/image.go:453
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
-#: lxc/help.go:71 lxc/main.go:112 lxc/main.go:164
+#: lxc/help.go:71 lxc/main.go:120 lxc/main.go:172
 #, fuzzy
 msgid "Options:"
 msgstr "Opération %s"
 
-#: lxc/image.go:510
+#: lxc/image.go:573
 #, c-format
 msgid "Output is in %s"
 msgstr ""
 
-#: lxc/exec.go:56
+#: lxc/exec.go:57
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:497
+#: lxc/list.go:503
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:412
+#: lxc/list.go:418
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:413
+#: lxc/list.go:419
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:368
+#: lxc/remote.go:379
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:610 lxc/remote.go:369
+#: lxc/image.go:677 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:179
+#: lxc/info.go:188
 msgid "Packets received"
 msgstr ""
 
-#: lxc/info.go:180
+#: lxc/info.go:189
 msgid "Packets sent"
 msgstr ""
 
@@ -547,25 +547,25 @@ msgstr "Dossier de configuration alternatif."
 msgid "Path to an alternate server directory"
 msgstr "Dossier de configuration alternatif."
 
-#: lxc/main.go:201
+#: lxc/main.go:209
 #, fuzzy
 msgid "Pause containers."
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/main.go:35
+#: lxc/main.go:37
 msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
-#: lxc/info.go:108
+#: lxc/info.go:117
 #, c-format
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/profile.go:229
+#: lxc/profile.go:234
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:692
+#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:759
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -581,117 +581,117 @@ msgstr ""
 msgid "Print verbose information"
 msgstr ""
 
-#: lxc/info.go:132
+#: lxc/info.go:141
 #, fuzzy, c-format
 msgid "Processes: %d"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:266
+#: lxc/profile.go:271
 #, fuzzy, c-format
 msgid "Profile %s applied to %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:180
+#: lxc/profile.go:185
 #, c-format
 msgid "Profile %s created"
 msgstr ""
 
-#: lxc/profile.go:250
+#: lxc/profile.go:255
 #, c-format
 msgid "Profile %s deleted"
 msgstr ""
 
-#: lxc/init.go:135 lxc/init.go:136
+#: lxc/init.go:136 lxc/init.go:137
 msgid "Profile to apply to the new container"
 msgstr ""
 
-#: lxc/info.go:106
+#: lxc/info.go:115
 #, fuzzy, c-format
 msgid "Profiles: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:338
+#: lxc/image.go:379
 msgid "Properties:"
 msgstr ""
 
-#: lxc/remote.go:69
+#: lxc/remote.go:70
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:321
+#: lxc/image.go:362
 #, c-format
 msgid "Public: %s"
 msgstr ""
 
-#: lxc/remote.go:67
+#: lxc/remote.go:68
 msgid "Remote admin password"
 msgstr ""
 
-#: lxc/info.go:93
+#: lxc/info.go:101
 #, c-format
 msgid "Remote: %s"
 msgstr ""
 
-#: lxc/delete.go:41
+#: lxc/delete.go:42
 #, c-format
 msgid "Remove %s (yes/no): "
 msgstr ""
 
-#: lxc/delete.go:35 lxc/delete.go:36
+#: lxc/delete.go:36 lxc/delete.go:37
 msgid "Require user confirmation"
 msgstr ""
 
-#: lxc/info.go:129
+#: lxc/info.go:138
 msgid "Resources:"
 msgstr ""
 
-#: lxc/main.go:209
+#: lxc/main.go:217
 #, fuzzy
 msgid "Restart containers."
 msgstr "Liste de l'information sur les conteneurs.\n"
 
-#: lxc/init.go:217
+#: lxc/init.go:227
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:613
+#: lxc/image.go:680
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:414
+#: lxc/list.go:420
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:415
+#: lxc/list.go:421
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:370
+#: lxc/remote.go:381
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:217
+#: lxc/remote.go:213
 msgid "Server certificate NACKed by user"
 msgstr "Le certificat serveur a été rejeté par l'utilisateur"
 
-#: lxc/remote.go:279
+#: lxc/remote.go:290
 msgid "Server doesn't trust us after adding our cert"
 msgstr "Identification refuse après l'ajout du certificat client"
 
-#: lxc/remote.go:68
+#: lxc/remote.go:69
 msgid "Server protocol (lxd or simplestreams)"
 msgstr ""
 
-#: lxc/file.go:56
+#: lxc/file.go:57
 msgid "Set the file's gid on push"
 msgstr "Définit le gid lors de l'envoi"
 
-#: lxc/file.go:57
+#: lxc/file.go:58
 msgid "Set the file's perms on push"
 msgstr "Définit les permissions lors de l'envoi"
 
-#: lxc/file.go:55
+#: lxc/file.go:56
 msgid "Set the file's uid on push"
 msgstr "Définit le uid lors de l'envoi"
 
@@ -703,94 +703,99 @@ msgstr "Affiche toutes les comandes (pas seulement les intéresantes)"
 msgid "Show client version"
 msgstr ""
 
-#: lxc/info.go:38
+#: lxc/info.go:39
 msgid "Show the container's last 100 log lines?"
 msgstr ""
 
-#: lxc/config.go:33
+#: lxc/config.go:34
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:319
+#: lxc/image.go:360
 #, c-format
 msgid "Size: %.2fMB"
 msgstr ""
 
-#: lxc/info.go:199
+#: lxc/info.go:208
 msgid "Snapshots:"
 msgstr ""
 
-#: lxc/action.go:134
+#: lxc/action.go:142
 #, fuzzy, c-format
 msgid "Some containers failed to %s"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/image.go:352
+#: lxc/image.go:393
 msgid "Source:"
 msgstr ""
 
-#: lxc/main.go:219
+#: lxc/main.go:227
 #, fuzzy
 msgid "Start containers."
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/launch.go:124
+#: lxc/launch.go:134
 #, c-format
 msgid "Starting %s"
 msgstr ""
 
-#: lxc/info.go:100
+#: lxc/info.go:109
 #, c-format
 msgid "Status: %s"
 msgstr ""
 
-#: lxc/main.go:225
+#: lxc/main.go:233
 #, fuzzy
 msgid "Stop containers."
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/publish.go:35 lxc/publish.go:36
+#: lxc/publish.go:36 lxc/publish.go:37
 msgid "Stop the container if currently running"
 msgstr ""
 
-#: lxc/delete.go:105 lxc/publish.go:112
+#: lxc/publish.go:121
 msgid "Stopping container failed!"
 msgstr "L'arrêt du conteneur a échoué!"
 
+#: lxc/delete.go:121
+#, fuzzy, c-format
+msgid "Stopping the container failed: %s"
+msgstr "L'arrêt du conteneur a échoué!"
+
 #: lxc/action.go:49
 #, fuzzy
 msgid "Store the container state (only for stop)"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/info.go:160
+#: lxc/info.go:169
 msgid "Swap (current)"
 msgstr ""
 
-#: lxc/info.go:164
+#: lxc/info.go:173
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:416
+#: lxc/list.go:422
 msgid "TYPE"
 msgstr ""
 
-#: lxc/delete.go:91
+#: lxc/delete.go:105
 msgid "The container is currently running, stop it first or pass --force."
 msgstr ""
 
-#: lxc/publish.go:90
+#: lxc/publish.go:99
 msgid ""
 "The container is currently running. Use --force to have it stopped and "
 "restarted."
 msgstr ""
 
-#: lxc/config.go:716 lxc/config.go:728 lxc/config.go:761 lxc/config.go:779
-#: lxc/config.go:817 lxc/config.go:835
+#: lxc/config.go:760 lxc/config.go:772 lxc/config.go:808 lxc/config.go:826
+#: lxc/config.go:867 lxc/config.go:885
 #, fuzzy
 msgid "The device doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/init.go:274
+#: lxc/init.go:284
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -799,7 +804,7 @@ msgstr ""
 msgid "The opposite of \"lxc pause\" is \"lxc start\"."
 msgstr ""
 
-#: lxc/publish.go:63
+#: lxc/publish.go:72
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
 
@@ -816,37 +821,37 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Temps d'attente avant de tuer le conteneur."
 
-#: lxc/image.go:322
+#: lxc/image.go:363
 msgid "Timestamps:"
 msgstr ""
 
-#: lxc/main.go:140
+#: lxc/main.go:148
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:410
+#: lxc/image.go:455
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
 
-#: lxc/action.go:98 lxc/launch.go:137
+#: lxc/action.go:106 lxc/launch.go:147
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
 
-#: lxc/info.go:102
+#: lxc/info.go:111
 msgid "Type: ephemeral"
 msgstr ""
 
-#: lxc/info.go:104
+#: lxc/info.go:113
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:614
+#: lxc/image.go:681
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:378
 msgid "URL"
 msgstr ""
 
@@ -854,11 +859,11 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/remote.go:95
+#: lxc/remote.go:96
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:327
+#: lxc/image.go:368
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -878,7 +883,7 @@ msgstr ""
 "Utilisation: lxc [sous commande] [options]\n"
 "Comande disponibles:\n"
 
-#: lxc/config.go:58
+#: lxc/config.go:59
 msgid ""
 "Usage: lxc config <subcommand> [options]\n"
 "\n"
@@ -955,7 +960,7 @@ msgid ""
 "    Will set the server's trust password to blah."
 msgstr ""
 
-#: lxc/copy.go:23
+#: lxc/copy.go:24
 msgid ""
 "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
 "[--ephemeral|e]\n"
@@ -963,7 +968,7 @@ msgid ""
 "Copy containers within or in between LXD instances."
 msgstr ""
 
-#: lxc/delete.go:26
+#: lxc/delete.go:27
 msgid ""
 "Usage: lxc delete [<remote>:]<container>[/<snapshot>] "
 "[[<remote>:]<container>[/<snapshot>]...]\n"
@@ -971,7 +976,7 @@ msgid ""
 "Delete containers and snapshots."
 msgstr ""
 
-#: lxc/exec.go:46
+#: lxc/exec.go:47
 #, fuzzy
 msgid ""
 "Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-"
@@ -983,7 +988,7 @@ msgid ""
 "AND stdout are terminals (stderr is ignored)."
 msgstr "Exécute la commande spécifiée dans un conteneur.\n"
 
-#: lxc/file.go:32
+#: lxc/file.go:33
 msgid ""
 "Usage: lxc file <subcommand> [options]\n"
 "\n"
@@ -1023,7 +1028,7 @@ msgid ""
 "Help page for the LXD client."
 msgstr ""
 
-#: lxc/image.go:60
+#: lxc/image.go:61
 msgid ""
 "Usage: lxc image <subcommand> [options]\n"
 "\n"
@@ -1098,7 +1103,7 @@ msgid ""
 "image alias name."
 msgstr ""
 
-#: lxc/info.go:25
+#: lxc/info.go:26
 msgid ""
 "Usage: lxc info [<remote>:][<container>] [--show-log]\n"
 "\n"
@@ -1111,7 +1116,7 @@ msgid ""
 "    For LXD server information."
 msgstr ""
 
-#: lxc/init.go:73
+#: lxc/init.go:74
 msgid ""
 "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
 "profile|-p <profile>...] [--config|-c <key=value>...]\n"
@@ -1125,7 +1130,7 @@ msgid ""
 "    lxc init ubuntu:16.04 u1"
 msgstr ""
 
-#: lxc/launch.go:23
+#: lxc/launch.go:24
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
 "profile|-p <profile>...] [--config|-c <key=value>...]\n"
@@ -1139,7 +1144,7 @@ msgid ""
 "    lxc launch ubuntu:16.04 u1"
 msgstr ""
 
-#: lxc/list.go:46
+#: lxc/list.go:47
 msgid ""
 "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
 "[--fast]\n"
@@ -1230,7 +1235,7 @@ msgid ""
 "    Only show log message."
 msgstr ""
 
-#: lxc/move.go:16
+#: lxc/move.go:17
 msgid ""
 "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
 "<snapshot>]]\n"
@@ -1248,7 +1253,7 @@ msgid ""
 "    Rename a snapshot."
 msgstr ""
 
-#: lxc/profile.go:48
+#: lxc/profile.go:49
 msgid ""
 "Usage: lxc profile <subcommand> [options]\n"
 "\n"
@@ -1324,7 +1329,7 @@ msgid ""
 "    Remove all profile from \"foo\""
 msgstr ""
 
-#: lxc/publish.go:26
+#: lxc/publish.go:27
 msgid ""
 "Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--"
 "alias=ALIAS...] [prop-key=prop-value...]\n"
@@ -1332,7 +1337,7 @@ msgid ""
 "Publish containers as images."
 msgstr ""
 
-#: lxc/remote.go:38
+#: lxc/remote.go:39
 msgid ""
 "Usage: lxc remote <subcommand> [options]\n"
 "\n"
@@ -1361,7 +1366,7 @@ msgid ""
 "    Print the default remote."
 msgstr ""
 
-#: lxc/restore.go:21
+#: lxc/restore.go:22
 msgid ""
 "Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
 "\n"
@@ -1377,7 +1382,7 @@ msgid ""
 "    Restore the snapshot."
 msgstr ""
 
-#: lxc/snapshot.go:21
+#: lxc/snapshot.go:22
 msgid ""
 "Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
 "\n"
@@ -1399,11 +1404,11 @@ msgid ""
 "Print the version number of this client tool."
 msgstr "Montre le numéro de version de LXD.\n"
 
-#: lxc/delete.go:45
+#: lxc/delete.go:46
 msgid "User aborted delete operation."
 msgstr ""
 
-#: lxc/restore.go:37
+#: lxc/restore.go:38
 #, fuzzy
 msgid ""
 "Whether or not to restore the container's running state from snapshot (if "
@@ -1412,135 +1417,133 @@ msgstr ""
 "Est-ce que l'état de fonctionement du conteneur doit être inclus dans "
 "l'instantané (snapshot)"
 
-#: lxc/snapshot.go:35
+#: lxc/snapshot.go:36
 msgid "Whether or not to snapshot the container's running state"
 msgstr ""
 "Est-ce que l'état de fonctionement du conteneur doit être inclus dans "
 "l'instantané (snapshot)"
 
-#: lxc/remote.go:342 lxc/remote.go:347
+#: lxc/remote.go:353 lxc/remote.go:358
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:56
+#: lxc/main.go:58
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:108
+#: lxc/launch.go:118
 #, fuzzy
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr "nombre de propriété invalide pour la ressource"
 
-#: lxc/action.go:94
-msgid "bad result type from action"
-msgstr "mauvais type de réponse pour l'action!"
-
-#: lxc/copy.go:100
+#: lxc/copy.go:108
 msgid "can't copy to the same container name"
 msgstr ""
 
-#: lxc/remote.go:330
+#: lxc/remote.go:341
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:356
+#: lxc/remote.go:367
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:89 lxc/launch.go:95
+#: lxc/init.go:211 lxc/init.go:216 lxc/launch.go:99 lxc/launch.go:105
 #, fuzzy
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
 
-#: lxc/image.go:313
+#: lxc/image.go:354
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:315
+#: lxc/image.go:356
 msgid "enabled"
 msgstr ""
 
-#: lxc/action.go:126 lxc/main.go:26 lxc/main.go:160
+#: lxc/action.go:134 lxc/main.go:28 lxc/main.go:168
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "erreur: %v\n"
 
-#: lxc/help.go:37 lxc/main.go:106
+#: lxc/help.go:37 lxc/main.go:114
 #, fuzzy, c-format
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/launch.go:113
+#: lxc/launch.go:123
 msgid "got bad version"
 msgstr "reçu une version invalide"
 
-#: lxc/image.go:308 lxc/image.go:590
+#: lxc/image.go:349 lxc/image.go:657
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:123
+#: lxc/copy.go:131
 msgid "not all the profiles from the source exist on the target"
 msgstr ""
 
-#: lxc/remote.go:210
+#: lxc/remote.go:206
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:295 lxc/main.go:299
+#: lxc/main.go:303 lxc/main.go:307
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:392
+#: lxc/remote.go:403
 #, c-format
 msgid "remote %s already exists"
 msgstr "le serveur distant %s existe déjà"
 
-#: lxc/remote.go:322 lxc/remote.go:384 lxc/remote.go:419 lxc/remote.go:435
+#: lxc/remote.go:333 lxc/remote.go:395 lxc/remote.go:430 lxc/remote.go:446
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/remote.go:305
+#: lxc/remote.go:316
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "le serveur distant %s existe en tant que <%s>"
 
-#: lxc/remote.go:326 lxc/remote.go:388 lxc/remote.go:423
+#: lxc/remote.go:337 lxc/remote.go:399 lxc/remote.go:434
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
 
-#: lxc/info.go:210
+#: lxc/info.go:219
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:212
+#: lxc/info.go:221
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:206
+#: lxc/info.go:215
 #, c-format
 msgid "taken at %s"
 msgstr ""
 
-#: lxc/exec.go:167
-msgid "unreachable return reached"
-msgstr "Un retour inacessible à été atteint"
-
-#: lxc/main.go:234
+#: lxc/main.go:242
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:44 lxc/image.go:310 lxc/image.go:594
+#: lxc/delete.go:45 lxc/image.go:351 lxc/image.go:661
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:39
+#: lxc/copy.go:47
 msgid "you must specify a source container name"
 msgstr ""
 
+#~ msgid "bad result type from action"
+#~ msgstr "mauvais type de réponse pour l'action!"
+
+#~ msgid "unreachable return reached"
+#~ msgstr "Un retour inacessible à été atteint"
+
 #, fuzzy
 #~ msgid ""
 #~ "Changes state of one or more containers to %s.\n"
diff --git a/po/ja.po b/po/ja.po
index d493b13ad..053368199 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-05-10 00:17-0400\n"
+"POT-Creation-Date: 2017-09-24 02:25-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -16,7 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/config.go:37
+#: lxc/config.go:38
 msgid ""
 "### This is a yaml representation of the configuration.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -37,7 +37,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:50
+#: lxc/image.go:51
 msgid ""
 "### This is a yaml representation of the image properties.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -47,7 +47,7 @@ msgid ""
 "###  description: My custom image"
 msgstr ""
 
-#: lxc/profile.go:27
+#: lxc/profile.go:28
 msgid ""
 "### This is a yaml representation of the profile.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -68,94 +68,94 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:587
+#: lxc/image.go:654
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/snapshot.go:58
+#: lxc/snapshot.go:53
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' はスナップショットの名前には使用できません。"
 
-#: lxc/profile.go:264
+#: lxc/profile.go:269
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:608 lxc/image.go:637
+#: lxc/image.go:675 lxc/image.go:704
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:612
+#: lxc/image.go:679
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:409
+#: lxc/list.go:415
 msgid "ARCHITECTURE"
 msgstr ""
 
-#: lxc/remote.go:66
+#: lxc/remote.go:67
 msgid "Accept certificate"
 msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
 
-#: lxc/remote.go:259
+#: lxc/remote.go:258
 #, c-format
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
 
-#: lxc/image.go:342
+#: lxc/image.go:383
 msgid "Aliases:"
 msgstr "エイリアス:"
 
-#: lxc/image.go:320 lxc/info.go:95
+#: lxc/image.go:361 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr "アーキテクチャ: %s"
 
-#: lxc/image.go:350
+#: lxc/image.go:391
 #, c-format
 msgid "Auto update: %s"
 msgstr "自動更新: %s"
 
-#: lxc/info.go:177
+#: lxc/info.go:186
 msgid "Bytes received"
 msgstr "受信バイト数"
 
-#: lxc/info.go:178
+#: lxc/info.go:187
 msgid "Bytes sent"
 msgstr "送信バイト数"
 
-#: lxc/config.go:310
+#: lxc/config.go:327
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:410
+#: lxc/list.go:416
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:150
+#: lxc/config.go:155
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr "標準入力から読み込めません: %s"
 
-#: lxc/config.go:163 lxc/config.go:196 lxc/config.go:218
+#: lxc/config.go:168 lxc/config.go:201 lxc/config.go:227
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr "キー '%s' が指定されていないので削除できません。"
 
-#: lxc/profile.go:394
+#: lxc/profile.go:409
 msgid "Cannot provide container name to list"
 msgstr "コンテナ名を取得できません"
 
-#: lxc/remote.go:209
+#: lxc/remote.go:205
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %s"
 msgstr "証明書のフィンガープリント: %x"
 
-#: lxc/remote.go:282
+#: lxc/remote.go:293
 msgid "Client certificate stored at server: "
 msgstr "クライアント証明書がサーバに格納されました: "
 
-#: lxc/list.go:109 lxc/list.go:110
+#: lxc/list.go:110 lxc/list.go:111
 msgid "Columns"
 msgstr "カラムレイアウト"
 
@@ -163,98 +163,98 @@ msgstr "カラムレイアウト"
 msgid "Commands:"
 msgstr ""
 
-#: lxc/init.go:133 lxc/init.go:134
+#: lxc/init.go:134 lxc/init.go:135
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:691 lxc/profile.go:228
+#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:758 lxc/profile.go:233
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
 
-#: lxc/main.go:33
+#: lxc/main.go:35
 msgid "Connection refused; is LXD running?"
 msgstr "接続が拒否されました。LXDが実行されていますか?"
 
-#: lxc/publish.go:60
+#: lxc/publish.go:69
 msgid "Container name is mandatory"
 msgstr "コンテナ名を指定する必要があります"
 
-#: lxc/init.go:211
+#: lxc/init.go:221
 #, c-format
 msgid "Container name is: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/publish.go:158 lxc/publish.go:173
+#: lxc/publish.go:167 lxc/publish.go:182
 #, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "コンテナは以下のフィンガープリントで publish されます: %s"
 
-#: lxc/image.go:132
+#: lxc/image.go:133
 msgid "Copy aliases from source"
 msgstr "ソースからエイリアスをコピーしました"
 
-#: lxc/image.go:244
+#: lxc/image.go:277
 #, c-format
 msgid "Copying the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/remote.go:224
+#: lxc/remote.go:220
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
-#: lxc/image.go:325 lxc/info.go:97
+#: lxc/image.go:366 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr "作成日時: %s"
 
-#: lxc/init.go:176 lxc/launch.go:116
+#: lxc/init.go:186 lxc/launch.go:126
 #, c-format
 msgid "Creating %s"
 msgstr "%s を作成中"
 
-#: lxc/init.go:174
+#: lxc/init.go:184
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:611 lxc/image.go:639
+#: lxc/image.go:678 lxc/image.go:706
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/config.go:688
+#: lxc/config.go:729
 #, c-format
 msgid "Device %s added to %s"
 msgstr "デバイス %s が %s に追加されました"
 
-#: lxc/config.go:875
+#: lxc/config.go:929
 #, c-format
 msgid "Device %s removed from %s"
 msgstr "デバイス %s が %s から削除されました"
 
-#: lxc/info.go:145
+#: lxc/info.go:154
 #, fuzzy
 msgid "Disk usage:"
 msgstr "  ディスク使用量:"
 
-#: lxc/list.go:495
+#: lxc/list.go:501
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:312
+#: lxc/config.go:329
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:45
+#: lxc/main.go:47
 #, fuzzy
 msgid "Enable debug mode"
 msgstr "デバッグモードを有効にします。"
 
-#: lxc/main.go:44
+#: lxc/main.go:46
 #, fuzzy
 msgid "Enable verbose mode"
 msgstr "詳細モードを有効にします。"
 
-#: lxc/exec.go:55
+#: lxc/exec.go:56
 #, fuzzy
 msgid "Environment variable to set (e.g. HOME=/home/foo)"
 msgstr "環境変数を HOME=/home/foo の形式で指定します"
@@ -263,7 +263,7 @@ msgstr "環境変数を HOME=/home/foo の形式で指定します"
 msgid "Environment:"
 msgstr "環境変数:"
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:137 lxc/init.go:138
+#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:138 lxc/init.go:139
 msgid "Ephemeral container"
 msgstr "Ephemeral コンテナ"
 
@@ -271,16 +271,16 @@ msgstr "Ephemeral コンテナ"
 msgid "Event type to listen for"
 msgstr "Listenするイベントタイプ"
 
-#: lxc/image.go:329
+#: lxc/image.go:370
 #, c-format
 msgid "Expires: %s"
 msgstr "失効日時: %s"
 
-#: lxc/image.go:331
+#: lxc/image.go:372
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/config.go:309 lxc/image.go:609 lxc/image.go:638
+#: lxc/config.go:326 lxc/image.go:676 lxc/image.go:705
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -294,12 +294,12 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:112
+#: lxc/list.go:113
 #, fuzzy
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
-#: lxc/image.go:318
+#: lxc/image.go:359
 #, c-format
 msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
@@ -309,42 +309,42 @@ msgstr "証明書のフィンガープリント: %s"
 msgid "Force the container to shutdown"
 msgstr "コンテナを強制シャットダウンします。"
 
-#: lxc/delete.go:33 lxc/delete.go:34
+#: lxc/delete.go:34 lxc/delete.go:35
 #, fuzzy
 msgid "Force the removal of stopped containers"
 msgstr "停止したコンテナを強制的に削除します。"
 
-#: lxc/main.go:46
+#: lxc/main.go:48
 #, fuzzy
 msgid "Force using the local unix socket"
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
-#: lxc/list.go:111
+#: lxc/list.go:112
 msgid "Format (table|json)"
 msgstr ""
 
-#: lxc/main.go:131
+#: lxc/main.go:139
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "クライアント証明書を生成します。1分ぐらいかかります..."
 
-#: lxc/list.go:407
+#: lxc/list.go:413
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:408
+#: lxc/list.go:414
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:311
+#: lxc/config.go:328
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:139
+#: lxc/main.go:147
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要があります"
 
-#: lxc/main.go:47
+#: lxc/main.go:49
 #, fuzzy
 msgid "Ignore aliases when determining what command to run"
 msgstr "どのコマンドを実行するか決める際にエイリアスを無視します。"
@@ -354,190 +354,190 @@ msgstr "どのコマンドを実行するか決める際にエイリアスを無
 msgid "Ignore the container state (only for start)"
 msgstr "コンテナの状態を無視します (startのみ)。"
 
-#: lxc/image.go:247
+#: lxc/image.go:280
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:405 lxc/image.go:417
+#: lxc/image.go:450 lxc/image.go:462
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
 
-#: lxc/image.go:402
+#: lxc/image.go:447
 #, fuzzy, c-format
 msgid "Importing the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/remote.go:135
+#: lxc/remote.go:136
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr "不正な URL スキーム \"%s\" (\"%s\" 内)"
 
-#: lxc/config.go:290
+#: lxc/config.go:307
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
 
-#: lxc/init.go:30 lxc/init.go:35
+#: lxc/init.go:31 lxc/init.go:36
 msgid "Invalid configuration key"
 msgstr "正しくない設定項目 (key) です"
 
-#: lxc/file.go:207
+#: lxc/file.go:211
 #, c-format
 msgid "Invalid source %s"
 msgstr "不正なソース %s"
 
-#: lxc/file.go:69
+#: lxc/file.go:70
 #, c-format
 msgid "Invalid target %s"
 msgstr "不正な送り先 %s"
 
-#: lxc/info.go:126
+#: lxc/info.go:135
 msgid "Ips:"
 msgstr "IPアドレス:"
 
-#: lxc/image.go:133
+#: lxc/image.go:134
 msgid "Keep the image up to date after initial copy"
 msgstr "最初にコピーした後も常にイメージを最新の状態に保つ"
 
-#: lxc/main.go:31
+#: lxc/main.go:33
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
-#: lxc/image.go:334
+#: lxc/image.go:375
 #, fuzzy, c-format
 msgid "Last used: %s"
 msgstr "状態: %s"
 
-#: lxc/image.go:336
+#: lxc/image.go:377
 msgid "Last used: never"
 msgstr ""
 
-#: lxc/info.go:230
+#: lxc/info.go:239
 msgid "Log:"
 msgstr "ログ:"
 
-#: lxc/image.go:131
+#: lxc/image.go:132
 msgid "Make image public"
 msgstr "イメージを public にする"
 
-#: lxc/publish.go:33
+#: lxc/publish.go:34
 msgid "Make the image public"
 msgstr "イメージを public にする"
 
-#: lxc/info.go:152
+#: lxc/info.go:161
 msgid "Memory (current)"
 msgstr "メモリ (現在値)"
 
-#: lxc/info.go:156
+#: lxc/info.go:165
 msgid "Memory (peak)"
 msgstr "メモリ (ピーク)"
 
-#: lxc/info.go:168
+#: lxc/info.go:177
 #, fuzzy
 msgid "Memory usage:"
 msgstr "  メモリ消費量:"
 
-#: lxc/copy.go:214
+#: lxc/copy.go:222
 #, c-format
 msgid "Migration failed on source host: %s"
 msgstr ""
 
-#: lxc/copy.go:218
+#: lxc/copy.go:226
 #, c-format
 msgid "Migration failed on target host: %s"
 msgstr ""
 
-#: lxc/utils.go:193
+#: lxc/utils.go:197
 msgid "Missing summary."
 msgstr "サマリーはありません。"
 
-#: lxc/file.go:195
+#: lxc/file.go:199
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "ダウンロード対象のファイルが複数ありますが、コピー先がディレクトリではありま"
 "せん。"
 
-#: lxc/action.go:68
+#: lxc/action.go:72
 msgid "Must supply container name for: "
 msgstr "コンテナ名を指定する必要があります: "
 
-#: lxc/list.go:411 lxc/remote.go:366
+#: lxc/list.go:417 lxc/remote.go:377
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:340 lxc/remote.go:345
+#: lxc/remote.go:351 lxc/remote.go:356
 msgid "NO"
 msgstr ""
 
-#: lxc/info.go:91
+#: lxc/info.go:99
 #, c-format
 msgid "Name: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/info.go:185
+#: lxc/info.go:194
 #, fuzzy
 msgid "Network usage:"
 msgstr "  ネットワーク使用状況:"
 
-#: lxc/image.go:134 lxc/publish.go:34
+#: lxc/image.go:135 lxc/publish.go:35
 msgid "New alias to define at target"
 msgstr "新しいエイリアスを定義する"
 
-#: lxc/config.go:321
+#: lxc/config.go:338
 msgid "No certificate provided to add"
 msgstr "追加すべき証明書が提供されていません"
 
-#: lxc/config.go:344
+#: lxc/config.go:365
 msgid "No fingerprint specified."
 msgstr "フィンガープリントが指定されていません。"
 
-#: lxc/remote.go:120
+#: lxc/remote.go:121
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
-#: lxc/image.go:408
+#: lxc/image.go:453
 msgid "Only https:// is supported for remote image import."
 msgstr "リモートイメージのインポートは https:// のみをサポートします。"
 
-#: lxc/help.go:71 lxc/main.go:112 lxc/main.go:164
+#: lxc/help.go:71 lxc/main.go:120 lxc/main.go:172
 msgid "Options:"
 msgstr "オプション:"
 
-#: lxc/image.go:510
+#: lxc/image.go:573
 #, c-format
 msgid "Output is in %s"
 msgstr "%s に出力されます"
 
-#: lxc/exec.go:56
+#: lxc/exec.go:57
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)"
 
-#: lxc/list.go:497
+#: lxc/list.go:503
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:412
+#: lxc/list.go:418
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:413
+#: lxc/list.go:419
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:368
+#: lxc/remote.go:379
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:610 lxc/remote.go:369
+#: lxc/image.go:677 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
-#: lxc/info.go:179
+#: lxc/info.go:188
 msgid "Packets received"
 msgstr "受信パケット"
 
-#: lxc/info.go:180
+#: lxc/info.go:189
 msgid "Packets sent"
 msgstr "送信パケット"
 
@@ -551,26 +551,26 @@ msgstr "別のクライアント用設定ディレクトリ"
 msgid "Path to an alternate server directory"
 msgstr "別のサーバ用設定ディレクトリ"
 
-#: lxc/main.go:201
+#: lxc/main.go:209
 #, fuzzy
 msgid "Pause containers."
 msgstr "コンテナの不正なURL %s"
 
-#: lxc/main.go:35
+#: lxc/main.go:37
 #, fuzzy
 msgid "Permission denied, are you in the lxd group?"
 msgstr "アクセスが拒否されました。lxd グループに所属していますか?"
 
-#: lxc/info.go:108
+#: lxc/info.go:117
 #, c-format
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/profile.go:229
+#: lxc/profile.go:234
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:692
+#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:759
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -589,118 +589,118 @@ msgstr "全てのコマンドを表示します (主なコマンドだけでは
 msgid "Print verbose information"
 msgstr "詳細情報を表示します。"
 
-#: lxc/info.go:132
+#: lxc/info.go:141
 #, c-format
 msgid "Processes: %d"
 msgstr "プロセス数: %d"
 
-#: lxc/profile.go:266
+#: lxc/profile.go:271
 #, fuzzy, c-format
 msgid "Profile %s applied to %s"
 msgstr "プロファイル %s が %s に追加されました"
 
-#: lxc/profile.go:180
+#: lxc/profile.go:185
 #, c-format
 msgid "Profile %s created"
 msgstr "プロファイル %s を作成しました"
 
-#: lxc/profile.go:250
+#: lxc/profile.go:255
 #, c-format
 msgid "Profile %s deleted"
 msgstr "プロファイル %s を削除しました"
 
-#: lxc/init.go:135 lxc/init.go:136
+#: lxc/init.go:136 lxc/init.go:137
 msgid "Profile to apply to the new container"
 msgstr "新しいコンテナに適用するプロファイル"
 
-#: lxc/info.go:106
+#: lxc/info.go:115
 #, c-format
 msgid "Profiles: %s"
 msgstr "プロファイル: %s"
 
-#: lxc/image.go:338
+#: lxc/image.go:379
 msgid "Properties:"
 msgstr "プロパティ:"
 
-#: lxc/remote.go:69
+#: lxc/remote.go:70
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
-#: lxc/image.go:321
+#: lxc/image.go:362
 #, c-format
 msgid "Public: %s"
 msgstr ""
 
-#: lxc/remote.go:67
+#: lxc/remote.go:68
 msgid "Remote admin password"
 msgstr "リモートの管理者パスワード"
 
-#: lxc/info.go:93
+#: lxc/info.go:101
 #, fuzzy, c-format
 msgid "Remote: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/delete.go:41
+#: lxc/delete.go:42
 #, c-format
 msgid "Remove %s (yes/no): "
 msgstr "%s を消去しますか (yes/no): "
 
-#: lxc/delete.go:35 lxc/delete.go:36
+#: lxc/delete.go:36 lxc/delete.go:37
 #, fuzzy
 msgid "Require user confirmation"
 msgstr "ユーザの確認を要求する。"
 
-#: lxc/info.go:129
+#: lxc/info.go:138
 msgid "Resources:"
 msgstr "リソース:"
 
-#: lxc/main.go:209
+#: lxc/main.go:217
 #, fuzzy
 msgid "Restart containers."
 msgstr "コンテナを作成中"
 
-#: lxc/init.go:217
+#: lxc/init.go:227
 #, c-format
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:613
+#: lxc/image.go:680
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:414
+#: lxc/list.go:420
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:415
+#: lxc/list.go:421
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:370
+#: lxc/remote.go:381
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:217
+#: lxc/remote.go:213
 msgid "Server certificate NACKed by user"
 msgstr "ユーザによりサーバ証明書が拒否されました"
 
-#: lxc/remote.go:279
+#: lxc/remote.go:290
 msgid "Server doesn't trust us after adding our cert"
 msgstr "サーバが我々の証明書を追加した後我々を信頼していません"
 
-#: lxc/remote.go:68
+#: lxc/remote.go:69
 msgid "Server protocol (lxd or simplestreams)"
 msgstr "サーバのプロトコル (lxd or simplestreams)"
 
-#: lxc/file.go:56
+#: lxc/file.go:57
 msgid "Set the file's gid on push"
 msgstr "プッシュ時にファイルのgidを設定します"
 
-#: lxc/file.go:57
+#: lxc/file.go:58
 msgid "Set the file's perms on push"
 msgstr "プッシュ時にファイルのパーミションを設定します"
 
-#: lxc/file.go:55
+#: lxc/file.go:56
 msgid "Set the file's uid on push"
 msgstr "プッシュ時にファイルのuidを設定します"
 
@@ -712,83 +712,88 @@ msgstr "全てコマンドを表示します (主なコマンドだけではな
 msgid "Show client version"
 msgstr ""
 
-#: lxc/info.go:38
+#: lxc/info.go:39
 msgid "Show the container's last 100 log lines?"
 msgstr "コンテナログの最後の 100 行を表示しますか?"
 
-#: lxc/config.go:33
+#: lxc/config.go:34
 #, fuzzy
 msgid "Show the expanded configuration"
 msgstr "拡張した設定を表示するかどうか"
 
-#: lxc/image.go:319
+#: lxc/image.go:360
 #, c-format
 msgid "Size: %.2fMB"
 msgstr "サイズ: %.2fMB"
 
-#: lxc/info.go:199
+#: lxc/info.go:208
 msgid "Snapshots:"
 msgstr "スナップショット:"
 
-#: lxc/action.go:134
+#: lxc/action.go:142
 #, fuzzy, c-format
 msgid "Some containers failed to %s"
 msgstr "コンテナの停止に失敗しました!"
 
-#: lxc/image.go:352
+#: lxc/image.go:393
 msgid "Source:"
 msgstr "取得元:"
 
-#: lxc/main.go:219
+#: lxc/main.go:227
 #, fuzzy
 msgid "Start containers."
 msgstr "コンテナの不正なURL %s"
 
-#: lxc/launch.go:124
+#: lxc/launch.go:134
 #, c-format
 msgid "Starting %s"
 msgstr "%s を起動中"
 
-#: lxc/info.go:100
+#: lxc/info.go:109
 #, c-format
 msgid "Status: %s"
 msgstr "状態: %s"
 
-#: lxc/main.go:225
+#: lxc/main.go:233
 #, fuzzy
 msgid "Stop containers."
 msgstr "コンテナの停止に失敗しました!"
 
-#: lxc/publish.go:35 lxc/publish.go:36
+#: lxc/publish.go:36 lxc/publish.go:37
 msgid "Stop the container if currently running"
 msgstr "実行中の場合、コンテナを停止します"
 
-#: lxc/delete.go:105 lxc/publish.go:112
+#: lxc/publish.go:121
 msgid "Stopping container failed!"
 msgstr "コンテナの停止に失敗しました!"
 
+#: lxc/delete.go:121
+#, fuzzy, c-format
+msgid "Stopping the container failed: %s"
+msgstr "コンテナの停止に失敗しました!"
+
 #: lxc/action.go:49
 #, fuzzy
 msgid "Store the container state (only for stop)"
 msgstr "コンテナの状態を保存します (stopのみ)。"
 
-#: lxc/info.go:160
+#: lxc/info.go:169
 msgid "Swap (current)"
 msgstr "Swap (現在値)"
 
-#: lxc/info.go:164
+#: lxc/info.go:173
 msgid "Swap (peak)"
 msgstr "Swap (ピーク)"
 
-#: lxc/list.go:416
+#: lxc/list.go:422
 msgid "TYPE"
 msgstr ""
 
-#: lxc/delete.go:91
+#: lxc/delete.go:105
 msgid "The container is currently running, stop it first or pass --force."
 msgstr "コンテナは実行中です。先に停止させるか、--force を指定してください。"
 
-#: lxc/publish.go:90
+#: lxc/publish.go:99
 msgid ""
 "The container is currently running. Use --force to have it stopped and "
 "restarted."
@@ -796,12 +801,12 @@ msgstr ""
 "コンテナは現在実行中です。停止して、再起動するために --force を使用してくだ\n"
 "さい。"
 
-#: lxc/config.go:716 lxc/config.go:728 lxc/config.go:761 lxc/config.go:779
-#: lxc/config.go:817 lxc/config.go:835
+#: lxc/config.go:760 lxc/config.go:772 lxc/config.go:808 lxc/config.go:826
+#: lxc/config.go:867 lxc/config.go:885
 msgid "The device doesn't exist"
 msgstr "デバイスが存在しません"
 
-#: lxc/init.go:274
+#: lxc/init.go:284
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -811,7 +816,7 @@ msgstr ""
 msgid "The opposite of \"lxc pause\" is \"lxc start\"."
 msgstr ""
 
-#: lxc/publish.go:63
+#: lxc/publish.go:72
 msgid "There is no \"image name\".  Did you want an alias?"
 msgstr ""
 "publish 先にはイメージ名は指定できません。\"--alias\" オプションを使ってく"
@@ -831,40 +836,40 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "コンテナを強制停止するまでの時間"
 
-#: lxc/image.go:322
+#: lxc/image.go:363
 msgid "Timestamps:"
 msgstr "タイムスタンプ:"
 
-#: lxc/main.go:140
+#: lxc/main.go:148
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 "初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてく"
 "だ\n"
 "さい。"
 
-#: lxc/image.go:410
+#: lxc/image.go:455
 #, fuzzy, c-format
 msgid "Transferring image: %s"
 msgstr "イメージを転送中: %d%%"
 
-#: lxc/action.go:98 lxc/launch.go:137
+#: lxc/action.go:106 lxc/launch.go:147
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr "更に情報を得るために `lxc info --show-log` を実行してみてください"
 
-#: lxc/info.go:102
+#: lxc/info.go:111
 msgid "Type: ephemeral"
 msgstr "タイプ: ephemeral"
 
-#: lxc/info.go:104
+#: lxc/info.go:113
 msgid "Type: persistent"
 msgstr "タイプ: persistent"
 
-#: lxc/image.go:614
+#: lxc/image.go:681
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:378
 msgid "URL"
 msgstr ""
 
@@ -872,11 +877,11 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/remote.go:95
+#: lxc/remote.go:96
 msgid "Unable to read remote TLS certificate"
 msgstr "リモートの TLS 証明書を読めません"
 
-#: lxc/image.go:327
+#: lxc/image.go:368
 #, c-format
 msgid "Uploaded: %s"
 msgstr "アップロード日時: %s"
@@ -894,7 +899,7 @@ msgstr ""
 msgid "Usage: lxc <command> [options]"
 msgstr "使い方: lxc [サブコマンド] [オプション]"
 
-#: lxc/config.go:58
+#: lxc/config.go:59
 msgid ""
 "Usage: lxc config <subcommand> [options]\n"
 "\n"
@@ -971,7 +976,7 @@ msgid ""
 "    Will set the server's trust password to blah."
 msgstr ""
 
-#: lxc/copy.go:23
+#: lxc/copy.go:24
 msgid ""
 "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
 "[--ephemeral|e]\n"
@@ -979,7 +984,7 @@ msgid ""
 "Copy containers within or in between LXD instances."
 msgstr ""
 
-#: lxc/delete.go:26
+#: lxc/delete.go:27
 #, fuzzy
 msgid ""
 "Usage: lxc delete [<remote>:]<container>[/<snapshot>] "
@@ -995,7 +1000,7 @@ msgstr ""
 "付属するデータ (設定、スナップショット、...) と一緒にコンテナもしくはコンテ\n"
 "ナのスナップショットを消去します。"
 
-#: lxc/exec.go:46
+#: lxc/exec.go:47
 #, fuzzy
 msgid ""
 "Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-"
@@ -1014,7 +1019,7 @@ msgstr ""
 "デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナ\n"
 "ルの場合は interactive モードが選択されます (標準エラー出力は無視されます)。"
 
-#: lxc/file.go:32
+#: lxc/file.go:33
 msgid ""
 "Usage: lxc file <subcommand> [options]\n"
 "\n"
@@ -1054,7 +1059,7 @@ msgid ""
 "Help page for the LXD client."
 msgstr ""
 
-#: lxc/image.go:60
+#: lxc/image.go:61
 #, fuzzy
 msgid ""
 "Usage: lxc image <subcommand> [options]\n"
@@ -1192,7 +1197,7 @@ msgstr ""
 "ス\n"
 "    名の一部をフィルタとして指定できます。\n"
 
-#: lxc/info.go:25
+#: lxc/info.go:26
 msgid ""
 "Usage: lxc info [<remote>:][<container>] [--show-log]\n"
 "\n"
@@ -1205,7 +1210,7 @@ msgid ""
 "    For LXD server information."
 msgstr ""
 
-#: lxc/init.go:73
+#: lxc/init.go:74
 #, fuzzy
 msgid ""
 "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
@@ -1232,7 +1237,7 @@ msgstr ""
 "例:\n"
 "lxc init ubuntu u1"
 
-#: lxc/launch.go:23
+#: lxc/launch.go:24
 #, fuzzy
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
@@ -1259,7 +1264,7 @@ msgstr ""
 "例:\n"
 "lxc launch ubuntu:16.04 u1"
 
-#: lxc/list.go:46
+#: lxc/list.go:47
 #, fuzzy
 msgid ""
 "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
@@ -1398,7 +1403,7 @@ msgstr ""
 "例:\n"
 "lxc monitor --type=logging"
 
-#: lxc/move.go:16
+#: lxc/move.go:17
 #, fuzzy
 msgid ""
 "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
@@ -1425,7 +1430,7 @@ msgstr ""
 "lxc move <old name> <new name>\n"
 "    ローカルのコンテナをリネームします。\n"
 
-#: lxc/profile.go:48
+#: lxc/profile.go:49
 #, fuzzy
 msgid ""
 "Usage: lxc profile <subcommand> [options]\n"
@@ -1551,7 +1556,7 @@ msgstr ""
 "    ディスクやNICのようなプロファイルデバイスを指定したプロファイルを使って\n"
 "    コンテナに追加します。"
 
-#: lxc/publish.go:26
+#: lxc/publish.go:27
 #, fuzzy
 msgid ""
 "Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--"
@@ -1564,7 +1569,7 @@ msgstr ""
 "lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
 "value]..."
 
-#: lxc/remote.go:38
+#: lxc/remote.go:39
 #, fuzzy
 msgid ""
 "Usage: lxc remote <subcommand> [options]\n"
@@ -1612,7 +1617,7 @@ msgstr ""
 "lxc remote get-default\n"
 "    デフォルトに設定されているリモートホストを表示します。"
 
-#: lxc/restore.go:21
+#: lxc/restore.go:22
 #, fuzzy
 msgid ""
 "Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
@@ -1639,7 +1644,7 @@ msgstr ""
 "lxc snapshot u1 snap0 # スナップショットの作成\n"
 "lxc restore u1 snap0 # スナップショットからリストア"
 
-#: lxc/snapshot.go:21
+#: lxc/snapshot.go:22
 #, fuzzy
 msgid ""
 "Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
@@ -1677,144 +1682,142 @@ msgstr ""
 "\n"
 "lxc version"
 
-#: lxc/delete.go:45
+#: lxc/delete.go:46
 msgid "User aborted delete operation."
 msgstr "ユーザが削除操作を中断しました。"
 
-#: lxc/restore.go:37
+#: lxc/restore.go:38
 msgid ""
 "Whether or not to restore the container's running state from snapshot (if "
 "available)"
 msgstr ""
 "スナップショットからコンテナの稼動状態をリストアするかどうか (取得可能な場合)"
 
-#: lxc/snapshot.go:35
+#: lxc/snapshot.go:36
 msgid "Whether or not to snapshot the container's running state"
 msgstr "コンテナの稼動状態のスナップショットを取得するかどうか"
 
-#: lxc/remote.go:342 lxc/remote.go:347
+#: lxc/remote.go:353 lxc/remote.go:358
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:56
+#: lxc/main.go:58
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
-#: lxc/launch.go:108
+#: lxc/launch.go:118
 msgid "bad number of things scanned from image, container or snapshot"
 msgstr ""
 "イメージ、コンテナ、スナップショットのいずれかからスキャンされた数が不正"
 
-#: lxc/action.go:94
-msgid "bad result type from action"
-msgstr "アクションからの結果タイプが不正です"
-
-#: lxc/copy.go:100
+#: lxc/copy.go:108
 msgid "can't copy to the same container name"
 msgstr "同じコンテナ名へはコピーできません"
 
-#: lxc/remote.go:330
+#: lxc/remote.go:341
 msgid "can't remove the default remote"
 msgstr "デフォルトのリモートは削除できません"
 
-#: lxc/remote.go:356
+#: lxc/remote.go:367
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:89 lxc/launch.go:95
+#: lxc/init.go:211 lxc/init.go:216 lxc/launch.go:99 lxc/launch.go:105
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
 "した"
 
-#: lxc/image.go:313
+#: lxc/image.go:354
 msgid "disabled"
 msgstr "無効"
 
-#: lxc/image.go:315
+#: lxc/image.go:356
 msgid "enabled"
 msgstr "有効"
 
-#: lxc/action.go:126 lxc/main.go:26 lxc/main.go:160
+#: lxc/action.go:134 lxc/main.go:28 lxc/main.go:168
 #, c-format
 msgid "error: %v"
 msgstr "エラー: %v"
 
-#: lxc/help.go:37 lxc/main.go:106
+#: lxc/help.go:37 lxc/main.go:114
 #, c-format
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/launch.go:113
+#: lxc/launch.go:123
 msgid "got bad version"
 msgstr "不正なバージョンを得ました"
 
-#: lxc/image.go:308 lxc/image.go:590
+#: lxc/image.go:349 lxc/image.go:657
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:123
+#: lxc/copy.go:131
 msgid "not all the profiles from the source exist on the target"
 msgstr "コピー元の全てのプロファイルがターゲットに存在しません"
 
-#: lxc/remote.go:210
+#: lxc/remote.go:206
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:295 lxc/main.go:299
+#: lxc/main.go:303 lxc/main.go:307
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr "エイリアスの処理が失敗しました %s\n"
 
-#: lxc/remote.go:392
+#: lxc/remote.go:403
 #, c-format
 msgid "remote %s already exists"
 msgstr "リモート %s は既に存在します"
 
-#: lxc/remote.go:322 lxc/remote.go:384 lxc/remote.go:419 lxc/remote.go:435
+#: lxc/remote.go:333 lxc/remote.go:395 lxc/remote.go:430 lxc/remote.go:446
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "リモート %s は存在しません"
 
-#: lxc/remote.go:305
+#: lxc/remote.go:316
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "リモート %s は <%s> として存在します"
 
-#: lxc/remote.go:326 lxc/remote.go:388 lxc/remote.go:423
+#: lxc/remote.go:337 lxc/remote.go:399 lxc/remote.go:434
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr "リモート %s は static ですので変更できません"
 
-#: lxc/info.go:210
+#: lxc/info.go:219
 msgid "stateful"
 msgstr ""
 
-#: lxc/info.go:212
+#: lxc/info.go:221
 msgid "stateless"
 msgstr ""
 
-#: lxc/info.go:206
+#: lxc/info.go:215
 #, c-format
 msgid "taken at %s"
 msgstr "%s に取得しました"
 
-#: lxc/exec.go:167
-msgid "unreachable return reached"
-msgstr "到達しないはずのreturnに到達しました"
-
-#: lxc/main.go:234
+#: lxc/main.go:242
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:44 lxc/image.go:310 lxc/image.go:594
+#: lxc/delete.go:45 lxc/image.go:351 lxc/image.go:661
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:39
+#: lxc/copy.go:47
 msgid "you must specify a source container name"
 msgstr "コピー元のコンテナ名を指定してください"
 
+#~ msgid "bad result type from action"
+#~ msgstr "アクションからの結果タイプが不正です"
+
+#~ msgid "unreachable return reached"
+#~ msgstr "到達しないはずのreturnに到達しました"
+
 #~ msgid "Available commands:"
 #~ msgstr "使用可能なコマンド:"
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 68efec47a..738be0c99 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-05-10 00:17-0400\n"
+        "POT-Creation-Date: 2017-09-24 02:25-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -16,7 +16,7 @@ msgstr  "Project-Id-Version: lxd\n"
         "Content-Type: text/plain; charset=CHARSET\n"
         "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/config.go:37
+#: lxc/config.go:38
 msgid   "### This is a yaml representation of the configuration.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -36,7 +36,7 @@ msgid   "### This is a yaml representation of the configuration.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:50
+#: lxc/image.go:51
 msgid   "### This is a yaml representation of the image properties.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -45,7 +45,7 @@ msgid   "### This is a yaml representation of the image properties.\n"
         "###  description: My custom image"
 msgstr  ""
 
-#: lxc/profile.go:27
+#: lxc/profile.go:28
 msgid   "### This is a yaml representation of the profile.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -65,94 +65,94 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:587
+#: lxc/image.go:654
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
 
-#: lxc/snapshot.go:58
+#: lxc/snapshot.go:53
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/profile.go:264
+#: lxc/profile.go:269
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:608 lxc/image.go:637
+#: lxc/image.go:675 lxc/image.go:704
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:612
+#: lxc/image.go:679
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:409
+#: lxc/list.go:415
 msgid   "ARCHITECTURE"
 msgstr  ""
 
-#: lxc/remote.go:66
+#: lxc/remote.go:67
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:259
+#: lxc/remote.go:258
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:342
+#: lxc/image.go:383
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:320 lxc/info.go:95
+#: lxc/image.go:361 lxc/info.go:104
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:350
+#: lxc/image.go:391
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
 
-#: lxc/info.go:177
+#: lxc/info.go:186
 msgid   "Bytes received"
 msgstr  ""
 
-#: lxc/info.go:178
+#: lxc/info.go:187
 msgid   "Bytes sent"
 msgstr  ""
 
-#: lxc/config.go:310
+#: lxc/config.go:327
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:410
+#: lxc/list.go:416
 msgid   "CREATED AT"
 msgstr  ""
 
-#: lxc/config.go:150
+#: lxc/config.go:155
 #, c-format
 msgid   "Can't read from stdin: %s"
 msgstr  ""
 
-#: lxc/config.go:163 lxc/config.go:196 lxc/config.go:218
+#: lxc/config.go:168 lxc/config.go:201 lxc/config.go:227
 #, c-format
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:394
+#: lxc/profile.go:409
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:209
+#: lxc/remote.go:205
 #, c-format
 msgid   "Certificate fingerprint: %s"
 msgstr  ""
 
-#: lxc/remote.go:282
+#: lxc/remote.go:293
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
-#: lxc/list.go:109 lxc/list.go:110
+#: lxc/list.go:110 lxc/list.go:111
 msgid   "Columns"
 msgstr  ""
 
@@ -160,95 +160,95 @@ msgstr  ""
 msgid   "Commands:"
 msgstr  ""
 
-#: lxc/init.go:133 lxc/init.go:134
+#: lxc/init.go:134 lxc/init.go:135
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:571 lxc/config.go:636 lxc/image.go:691 lxc/profile.go:228
+#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:758 lxc/profile.go:233
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
 
-#: lxc/main.go:33
+#: lxc/main.go:35
 msgid   "Connection refused; is LXD running?"
 msgstr  ""
 
-#: lxc/publish.go:60
+#: lxc/publish.go:69
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/init.go:211
+#: lxc/init.go:221
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
 
-#: lxc/publish.go:158 lxc/publish.go:173
+#: lxc/publish.go:167 lxc/publish.go:182
 #, c-format
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:132
+#: lxc/image.go:133
 msgid   "Copy aliases from source"
 msgstr  ""
 
-#: lxc/image.go:244
+#: lxc/image.go:277
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:224
+#: lxc/remote.go:220
 msgid   "Could not create server cert dir"
 msgstr  ""
 
-#: lxc/image.go:325 lxc/info.go:97
+#: lxc/image.go:366 lxc/info.go:106
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:176 lxc/launch.go:116
+#: lxc/init.go:186 lxc/launch.go:126
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
 
-#: lxc/init.go:174
+#: lxc/init.go:184
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:611 lxc/image.go:639
+#: lxc/image.go:678 lxc/image.go:706
 msgid   "DESCRIPTION"
 msgstr  ""
 
-#: lxc/config.go:688
+#: lxc/config.go:729
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:875
+#: lxc/config.go:929
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
 
-#: lxc/info.go:145
+#: lxc/info.go:154
 msgid   "Disk usage:"
 msgstr  ""
 
-#: lxc/list.go:495
+#: lxc/list.go:501
 msgid   "EPHEMERAL"
 msgstr  ""
 
-#: lxc/config.go:312
+#: lxc/config.go:329
 msgid   "EXPIRY DATE"
 msgstr  ""
 
-#: lxc/main.go:45
+#: lxc/main.go:47
 msgid   "Enable debug mode"
 msgstr  ""
 
-#: lxc/main.go:44
+#: lxc/main.go:46
 msgid   "Enable verbose mode"
 msgstr  ""
 
-#: lxc/exec.go:55
+#: lxc/exec.go:56
 msgid   "Environment variable to set (e.g. HOME=/home/foo)"
 msgstr  ""
 
@@ -256,7 +256,7 @@ msgstr  ""
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:137 lxc/init.go:138
+#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:138 lxc/init.go:139
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -264,16 +264,16 @@ msgstr  ""
 msgid   "Event type to listen for"
 msgstr  ""
 
-#: lxc/image.go:329
+#: lxc/image.go:370
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:331
+#: lxc/image.go:372
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:309 lxc/image.go:609 lxc/image.go:638
+#: lxc/config.go:326 lxc/image.go:676 lxc/image.go:705
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -287,11 +287,11 @@ msgstr  ""
 msgid   "Failed to generate 'lxc.1': %v"
 msgstr  ""
 
-#: lxc/list.go:112
+#: lxc/list.go:113
 msgid   "Fast mode (same as --columns=nsacPt)"
 msgstr  ""
 
-#: lxc/image.go:318
+#: lxc/image.go:359
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -300,39 +300,39 @@ msgstr  ""
 msgid   "Force the container to shutdown"
 msgstr  ""
 
-#: lxc/delete.go:33 lxc/delete.go:34
+#: lxc/delete.go:34 lxc/delete.go:35
 msgid   "Force the removal of stopped containers"
 msgstr  ""
 
-#: lxc/main.go:46
+#: lxc/main.go:48
 msgid   "Force using the local unix socket"
 msgstr  ""
 
-#: lxc/list.go:111
+#: lxc/list.go:112
 msgid   "Format (table|json)"
 msgstr  ""
 
-#: lxc/main.go:131
+#: lxc/main.go:139
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:407
+#: lxc/list.go:413
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:408
+#: lxc/list.go:414
 msgid   "IPV6"
 msgstr  ""
 
-#: lxc/config.go:311
+#: lxc/config.go:328
 msgid   "ISSUE DATE"
 msgstr  ""
 
-#: lxc/main.go:139
+#: lxc/main.go:147
 msgid   "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr  ""
 
-#: lxc/main.go:47
+#: lxc/main.go:49
 msgid   "Ignore aliases when determining what command to run"
 msgstr  ""
 
@@ -340,185 +340,185 @@ msgstr  ""
 msgid   "Ignore the container state (only for start)"
 msgstr  ""
 
-#: lxc/image.go:247
+#: lxc/image.go:280
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:405 lxc/image.go:417
+#: lxc/image.go:450 lxc/image.go:462
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:402
+#: lxc/image.go:447
 #, c-format
 msgid   "Importing the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:135
+#: lxc/remote.go:136
 #, c-format
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
 
-#: lxc/config.go:290
+#: lxc/config.go:307
 msgid   "Invalid certificate"
 msgstr  ""
 
-#: lxc/init.go:30 lxc/init.go:35
+#: lxc/init.go:31 lxc/init.go:36
 msgid   "Invalid configuration key"
 msgstr  ""
 
-#: lxc/file.go:207
+#: lxc/file.go:211
 #, c-format
 msgid   "Invalid source %s"
 msgstr  ""
 
-#: lxc/file.go:69
+#: lxc/file.go:70
 #, c-format
 msgid   "Invalid target %s"
 msgstr  ""
 
-#: lxc/info.go:126
+#: lxc/info.go:135
 msgid   "Ips:"
 msgstr  ""
 
-#: lxc/image.go:133
+#: lxc/image.go:134
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
-#: lxc/main.go:31
+#: lxc/main.go:33
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/image.go:334
+#: lxc/image.go:375
 #, c-format
 msgid   "Last used: %s"
 msgstr  ""
 
-#: lxc/image.go:336
+#: lxc/image.go:377
 msgid   "Last used: never"
 msgstr  ""
 
-#: lxc/info.go:230
+#: lxc/info.go:239
 msgid   "Log:"
 msgstr  ""
 
-#: lxc/image.go:131
+#: lxc/image.go:132
 msgid   "Make image public"
 msgstr  ""
 
-#: lxc/publish.go:33
+#: lxc/publish.go:34
 msgid   "Make the image public"
 msgstr  ""
 
-#: lxc/info.go:152
+#: lxc/info.go:161
 msgid   "Memory (current)"
 msgstr  ""
 
-#: lxc/info.go:156
+#: lxc/info.go:165
 msgid   "Memory (peak)"
 msgstr  ""
 
-#: lxc/info.go:168
+#: lxc/info.go:177
 msgid   "Memory usage:"
 msgstr  ""
 
-#: lxc/copy.go:214
+#: lxc/copy.go:222
 #, c-format
 msgid   "Migration failed on source host: %s"
 msgstr  ""
 
-#: lxc/copy.go:218
+#: lxc/copy.go:226
 #, c-format
 msgid   "Migration failed on target host: %s"
 msgstr  ""
 
-#: lxc/utils.go:193
+#: lxc/utils.go:197
 msgid   "Missing summary."
 msgstr  ""
 
-#: lxc/file.go:195
+#: lxc/file.go:199
 msgid   "More than one file to download, but target is not a directory"
 msgstr  ""
 
-#: lxc/action.go:68
+#: lxc/action.go:72
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:411 lxc/remote.go:366
+#: lxc/list.go:417 lxc/remote.go:377
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/remote.go:340 lxc/remote.go:345
+#: lxc/remote.go:351 lxc/remote.go:356
 msgid   "NO"
 msgstr  ""
 
-#: lxc/info.go:91
+#: lxc/info.go:99
 #, c-format
 msgid   "Name: %s"
 msgstr  ""
 
-#: lxc/info.go:185
+#: lxc/info.go:194
 msgid   "Network usage:"
 msgstr  ""
 
-#: lxc/image.go:134 lxc/publish.go:34
+#: lxc/image.go:135 lxc/publish.go:35
 msgid   "New alias to define at target"
 msgstr  ""
 
-#: lxc/config.go:321
+#: lxc/config.go:338
 msgid   "No certificate provided to add"
 msgstr  ""
 
-#: lxc/config.go:344
+#: lxc/config.go:365
 msgid   "No fingerprint specified."
 msgstr  ""
 
-#: lxc/remote.go:120
+#: lxc/remote.go:121
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:408
+#: lxc/image.go:453
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
-#: lxc/help.go:71 lxc/main.go:112 lxc/main.go:164
+#: lxc/help.go:71 lxc/main.go:120 lxc/main.go:172
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:510
+#: lxc/image.go:573
 #, c-format
 msgid   "Output is in %s"
 msgstr  ""
 
-#: lxc/exec.go:56
+#: lxc/exec.go:57
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:497
+#: lxc/list.go:503
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:412
+#: lxc/list.go:418
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:413
+#: lxc/list.go:419
 msgid   "PROFILES"
 msgstr  ""
 
-#: lxc/remote.go:368
+#: lxc/remote.go:379
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:610 lxc/remote.go:369
+#: lxc/image.go:677 lxc/remote.go:380
 msgid   "PUBLIC"
 msgstr  ""
 
-#: lxc/info.go:179
+#: lxc/info.go:188
 msgid   "Packets received"
 msgstr  ""
 
-#: lxc/info.go:180
+#: lxc/info.go:189
 msgid   "Packets sent"
 msgstr  ""
 
@@ -530,24 +530,24 @@ msgstr  ""
 msgid   "Path to an alternate server directory"
 msgstr  ""
 
-#: lxc/main.go:201
+#: lxc/main.go:209
 msgid   "Pause containers."
 msgstr  ""
 
-#: lxc/main.go:35
+#: lxc/main.go:37
 msgid   "Permission denied, are you in the lxd group?"
 msgstr  ""
 
-#: lxc/info.go:108
+#: lxc/info.go:117
 #, c-format
 msgid   "Pid: %d"
 msgstr  ""
 
-#: lxc/profile.go:229
+#: lxc/profile.go:234
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:572 lxc/config.go:637 lxc/image.go:692
+#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:759
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -563,116 +563,116 @@ msgstr  ""
 msgid   "Print verbose information"
 msgstr  ""
 
-#: lxc/info.go:132
+#: lxc/info.go:141
 #, c-format
 msgid   "Processes: %d"
 msgstr  ""
 
-#: lxc/profile.go:266
+#: lxc/profile.go:271
 #, c-format
 msgid   "Profile %s applied to %s"
 msgstr  ""
 
-#: lxc/profile.go:180
+#: lxc/profile.go:185
 #, c-format
 msgid   "Profile %s created"
 msgstr  ""
 
-#: lxc/profile.go:250
+#: lxc/profile.go:255
 #, c-format
 msgid   "Profile %s deleted"
 msgstr  ""
 
-#: lxc/init.go:135 lxc/init.go:136
+#: lxc/init.go:136 lxc/init.go:137
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
-#: lxc/info.go:106
+#: lxc/info.go:115
 #, c-format
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:338
+#: lxc/image.go:379
 msgid   "Properties:"
 msgstr  ""
 
-#: lxc/remote.go:69
+#: lxc/remote.go:70
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:321
+#: lxc/image.go:362
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
 
-#: lxc/remote.go:67
+#: lxc/remote.go:68
 msgid   "Remote admin password"
 msgstr  ""
 
-#: lxc/info.go:93
+#: lxc/info.go:101
 #, c-format
 msgid   "Remote: %s"
 msgstr  ""
 
-#: lxc/delete.go:41
+#: lxc/delete.go:42
 #, c-format
 msgid   "Remove %s (yes/no): "
 msgstr  ""
 
-#: lxc/delete.go:35 lxc/delete.go:36
+#: lxc/delete.go:36 lxc/delete.go:37
 msgid   "Require user confirmation"
 msgstr  ""
 
-#: lxc/info.go:129
+#: lxc/info.go:138
 msgid   "Resources:"
 msgstr  ""
 
-#: lxc/main.go:209
+#: lxc/main.go:217
 msgid   "Restart containers."
 msgstr  ""
 
-#: lxc/init.go:217
+#: lxc/init.go:227
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:613
+#: lxc/image.go:680
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:414
+#: lxc/list.go:420
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:415
+#: lxc/list.go:421
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:370
+#: lxc/remote.go:381
 msgid   "STATIC"
 msgstr  ""
 
-#: lxc/remote.go:217
+#: lxc/remote.go:213
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:279
+#: lxc/remote.go:290
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
-#: lxc/remote.go:68
+#: lxc/remote.go:69
 msgid   "Server protocol (lxd or simplestreams)"
 msgstr  ""
 
-#: lxc/file.go:56
+#: lxc/file.go:57
 msgid   "Set the file's gid on push"
 msgstr  ""
 
-#: lxc/file.go:57
+#: lxc/file.go:58
 msgid   "Set the file's perms on push"
 msgstr  ""
 
-#: lxc/file.go:55
+#: lxc/file.go:56
 msgid   "Set the file's uid on push"
 msgstr  ""
 
@@ -684,87 +684,92 @@ msgstr  ""
 msgid   "Show client version"
 msgstr  ""
 
-#: lxc/info.go:38
+#: lxc/info.go:39
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/config.go:33
+#: lxc/config.go:34
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:319
+#: lxc/image.go:360
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
 
-#: lxc/info.go:199
+#: lxc/info.go:208
 msgid   "Snapshots:"
 msgstr  ""
 
-#: lxc/action.go:134
+#: lxc/action.go:142
 #, c-format
 msgid   "Some containers failed to %s"
 msgstr  ""
 
-#: lxc/image.go:352
+#: lxc/image.go:393
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/main.go:219
+#: lxc/main.go:227
 msgid   "Start containers."
 msgstr  ""
 
-#: lxc/launch.go:124
+#: lxc/launch.go:134
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
 
-#: lxc/info.go:100
+#: lxc/info.go:109
 #, c-format
 msgid   "Status: %s"
 msgstr  ""
 
-#: lxc/main.go:225
+#: lxc/main.go:233
 msgid   "Stop containers."
 msgstr  ""
 
-#: lxc/publish.go:35 lxc/publish.go:36
+#: lxc/publish.go:36 lxc/publish.go:37
 msgid   "Stop the container if currently running"
 msgstr  ""
 
-#: lxc/delete.go:105 lxc/publish.go:112
+#: lxc/publish.go:121
 msgid   "Stopping container failed!"
 msgstr  ""
 
+#: lxc/delete.go:121
+#, c-format
+msgid   "Stopping the container failed: %s"
+msgstr  ""
+
 #: lxc/action.go:49
 msgid   "Store the container state (only for stop)"
 msgstr  ""
 
-#: lxc/info.go:160
+#: lxc/info.go:169
 msgid   "Swap (current)"
 msgstr  ""
 
-#: lxc/info.go:164
+#: lxc/info.go:173
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:416
+#: lxc/list.go:422
 msgid   "TYPE"
 msgstr  ""
 
-#: lxc/delete.go:91
+#: lxc/delete.go:105
 msgid   "The container is currently running, stop it first or pass --force."
 msgstr  ""
 
-#: lxc/publish.go:90
+#: lxc/publish.go:99
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/config.go:716 lxc/config.go:728 lxc/config.go:761 lxc/config.go:779 lxc/config.go:817 lxc/config.go:835
+#: lxc/config.go:760 lxc/config.go:772 lxc/config.go:808 lxc/config.go:826 lxc/config.go:867 lxc/config.go:885
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:274
+#: lxc/init.go:284
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -773,7 +778,7 @@ msgstr  ""
 msgid   "The opposite of \"lxc pause\" is \"lxc start\"."
 msgstr  ""
 
-#: lxc/publish.go:63
+#: lxc/publish.go:72
 msgid   "There is no \"image name\".  Did you want an alias?"
 msgstr  ""
 
@@ -788,37 +793,37 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:322
+#: lxc/image.go:363
 msgid   "Timestamps:"
 msgstr  ""
 
-#: lxc/main.go:140
+#: lxc/main.go:148
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:410
+#: lxc/image.go:455
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:98 lxc/launch.go:137
+#: lxc/action.go:106 lxc/launch.go:147
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
 
-#: lxc/info.go:102
+#: lxc/info.go:111
 msgid   "Type: ephemeral"
 msgstr  ""
 
-#: lxc/info.go:104
+#: lxc/info.go:113
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:614
+#: lxc/image.go:681
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:378
 msgid   "URL"
 msgstr  ""
 
@@ -826,11 +831,11 @@ msgstr  ""
 msgid   "Unable to find help2man."
 msgstr  ""
 
-#: lxc/remote.go:95
+#: lxc/remote.go:96
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:327
+#: lxc/image.go:368
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -846,7 +851,7 @@ msgstr  ""
 msgid   "Usage: lxc <command> [options]"
 msgstr  ""
 
-#: lxc/config.go:58
+#: lxc/config.go:59
 msgid   "Usage: lxc config <subcommand> [options]\n"
         "\n"
         "Change container or server configuration options.\n"
@@ -920,19 +925,19 @@ msgid   "Usage: lxc config <subcommand> [options]\n"
         "    Will set the server's trust password to blah."
 msgstr  ""
 
-#: lxc/copy.go:23
+#: lxc/copy.go:24
 msgid   "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e]\n"
         "\n"
         "Copy containers within or in between LXD instances."
 msgstr  ""
 
-#: lxc/delete.go:26
+#: lxc/delete.go:27
 msgid   "Usage: lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]\n"
         "\n"
         "Delete containers and snapshots."
 msgstr  ""
 
-#: lxc/exec.go:46
+#: lxc/exec.go:47
 msgid   "Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>\n"
         "\n"
         "Execute commands in containers.\n"
@@ -940,7 +945,7 @@ msgid   "Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-int
         "Mode defaults to non-interactive, interactive mode is selected if both stdin AND stdout are terminals (stderr is ignored)."
 msgstr  ""
 
-#: lxc/file.go:32
+#: lxc/file.go:33
 msgid   "Usage: lxc file <subcommand> [options]\n"
         "\n"
         "Manage files in containers.\n"
@@ -974,7 +979,7 @@ msgid   "Usage: lxc help [--all]\n"
         "Help page for the LXD client."
 msgstr  ""
 
-#: lxc/image.go:60
+#: lxc/image.go:61
 msgid   "Usage: lxc image <subcommand> [options]\n"
         "\n"
         "Manipulate container images.\n"
@@ -1044,7 +1049,7 @@ msgid   "Usage: lxc image <subcommand> [options]\n"
         "    List the aliases. Filters may be part of the image hash or part of the image alias name."
 msgstr  ""
 
-#: lxc/info.go:25
+#: lxc/info.go:26
 msgid   "Usage: lxc info [<remote>:][<container>] [--show-log]\n"
         "\n"
         "Show container or server information.\n"
@@ -1056,7 +1061,7 @@ msgid   "Usage: lxc info [<remote>:][<container>] [--show-log]\n"
         "    For LXD server information."
 msgstr  ""
 
-#: lxc/init.go:73
+#: lxc/init.go:74
 msgid   "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
         "\n"
         "Create containers from images.\n"
@@ -1068,7 +1073,7 @@ msgid   "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e]
         "    lxc init ubuntu:16.04 u1"
 msgstr  ""
 
-#: lxc/launch.go:23
+#: lxc/launch.go:24
 msgid   "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
         "\n"
         "Create and start containers from images.\n"
@@ -1080,7 +1085,7 @@ msgid   "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-
         "    lxc launch ubuntu:16.04 u1"
 msgstr  ""
 
-#: lxc/list.go:46
+#: lxc/list.go:47
 msgid   "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]\n"
         "\n"
         "List the existing containers.\n"
@@ -1162,7 +1167,7 @@ msgid   "Usage: lxc monitor [<remote>:] [--type=TYPE...]\n"
         "    Only show log message."
 msgstr  ""
 
-#: lxc/move.go:16
+#: lxc/move.go:17
 msgid   "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]]\n"
         "\n"
         "Move containers within or in between LXD instances.\n"
@@ -1177,7 +1182,7 @@ msgid   "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<contai
         "    Rename a snapshot."
 msgstr  ""
 
-#: lxc/profile.go:48
+#: lxc/profile.go:49
 msgid   "Usage: lxc profile <subcommand> [options]\n"
         "\n"
         "Manage container configuration profiles.\n"
@@ -1250,13 +1255,13 @@ msgid   "Usage: lxc profile <subcommand> [options]\n"
         "    Remove all profile from \"foo\""
 msgstr  ""
 
-#: lxc/publish.go:26
+#: lxc/publish.go:27
 msgid   "Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]\n"
         "\n"
         "Publish containers as images."
 msgstr  ""
 
-#: lxc/remote.go:38
+#: lxc/remote.go:39
 msgid   "Usage: lxc remote <subcommand> [options]\n"
         "\n"
         "Manage the list of remote LXD servers.\n"
@@ -1283,7 +1288,7 @@ msgid   "Usage: lxc remote <subcommand> [options]\n"
         "    Print the default remote."
 msgstr  ""
 
-#: lxc/restore.go:21
+#: lxc/restore.go:22
 msgid   "Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
         "\n"
         "Restore containers from snapshots.\n"
@@ -1298,7 +1303,7 @@ msgid   "Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
         "    Restore the snapshot."
 msgstr  ""
 
-#: lxc/snapshot.go:21
+#: lxc/snapshot.go:22
 msgid   "Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
         "\n"
         "Create container snapshots.\n"
@@ -1317,135 +1322,127 @@ msgid   "Usage: lxc version\n"
         "Print the version number of this client tool."
 msgstr  ""
 
-#: lxc/delete.go:45
+#: lxc/delete.go:46
 msgid   "User aborted delete operation."
 msgstr  ""
 
-#: lxc/restore.go:37
+#: lxc/restore.go:38
 msgid   "Whether or not to restore the container's running state from snapshot (if available)"
 msgstr  ""
 
-#: lxc/snapshot.go:35
+#: lxc/snapshot.go:36
 msgid   "Whether or not to snapshot the container's running state"
 msgstr  ""
 
-#: lxc/remote.go:342 lxc/remote.go:347
+#: lxc/remote.go:353 lxc/remote.go:358
 msgid   "YES"
 msgstr  ""
 
-#: lxc/main.go:56
+#: lxc/main.go:58
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:108
+#: lxc/launch.go:118
 msgid   "bad number of things scanned from image, container or snapshot"
 msgstr  ""
 
-#: lxc/action.go:94
-msgid   "bad result type from action"
-msgstr  ""
-
-#: lxc/copy.go:100
+#: lxc/copy.go:108
 msgid   "can't copy to the same container name"
 msgstr  ""
 
-#: lxc/remote.go:330
+#: lxc/remote.go:341
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:356
+#: lxc/remote.go:367
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:201 lxc/init.go:206 lxc/launch.go:89 lxc/launch.go:95
+#: lxc/init.go:211 lxc/init.go:216 lxc/launch.go:99 lxc/launch.go:105
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:313
+#: lxc/image.go:354
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:315
+#: lxc/image.go:356
 msgid   "enabled"
 msgstr  ""
 
-#: lxc/action.go:126 lxc/main.go:26 lxc/main.go:160
+#: lxc/action.go:134 lxc/main.go:28 lxc/main.go:168
 #, c-format
 msgid   "error: %v"
 msgstr  ""
 
-#: lxc/help.go:37 lxc/main.go:106
+#: lxc/help.go:37 lxc/main.go:114
 #, c-format
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:113
+#: lxc/launch.go:123
 msgid   "got bad version"
 msgstr  ""
 
-#: lxc/image.go:308 lxc/image.go:590
+#: lxc/image.go:349 lxc/image.go:657
 msgid   "no"
 msgstr  ""
 
-#: lxc/copy.go:123
+#: lxc/copy.go:131
 msgid   "not all the profiles from the source exist on the target"
 msgstr  ""
 
-#: lxc/remote.go:210
+#: lxc/remote.go:206
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:295 lxc/main.go:299
+#: lxc/main.go:303 lxc/main.go:307
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:392
+#: lxc/remote.go:403
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:322 lxc/remote.go:384 lxc/remote.go:419 lxc/remote.go:435
+#: lxc/remote.go:333 lxc/remote.go:395 lxc/remote.go:430 lxc/remote.go:446
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:305
+#: lxc/remote.go:316
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
-#: lxc/remote.go:326 lxc/remote.go:388 lxc/remote.go:423
+#: lxc/remote.go:337 lxc/remote.go:399 lxc/remote.go:434
 #, c-format
 msgid   "remote %s is static and cannot be modified"
 msgstr  ""
 
-#: lxc/info.go:210
+#: lxc/info.go:219
 msgid   "stateful"
 msgstr  ""
 
-#: lxc/info.go:212
+#: lxc/info.go:221
 msgid   "stateless"
 msgstr  ""
 
-#: lxc/info.go:206
+#: lxc/info.go:215
 #, c-format
 msgid   "taken at %s"
 msgstr  ""
 
-#: lxc/exec.go:167
-msgid   "unreachable return reached"
-msgstr  ""
-
-#: lxc/main.go:234
+#: lxc/main.go:242
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:44 lxc/image.go:310 lxc/image.go:594
+#: lxc/delete.go:45 lxc/image.go:351 lxc/image.go:661
 msgid   "yes"
 msgstr  ""
 
-#: lxc/copy.go:39
+#: lxc/copy.go:47
 msgid   "you must specify a source container name"
 msgstr  ""
 

From 94a650ce87e6b3b355fd364cbc66279ca0531792 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 22 Jun 2017 09:55:57 +0200
Subject: [PATCH 0984/1193] container_lxc: use new network keys

When we have a new enough version we should use the new network configuration
keys.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 45 ++++++++++++++++++++++++++++++---------------
 1 file changed, 30 insertions(+), 15 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f8bcd6514..44f69c38d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -149,7 +149,12 @@ func lxcValidConfig(rawLxc string) error {
 			return fmt.Errorf("Setting lxc.ephemeral is not allowed")
 		}
 
-		if strings.HasPrefix(key, "lxc.network.") {
+		networkKeyPrefix := "lxc.net."
+		if !lxc.VersionAtLeast(2, 1, 0) {
+			networkKeyPrefix = "lxc.network."
+		}
+
+		if strings.HasPrefix(key, networkKeyPrefix) {
 			fields := strings.Split(key, ".")
 			if len(fields) == 4 && shared.StringInSlice(fields[3], []string{"ipv4", "ipv6"}) {
 				continue
@@ -159,7 +164,7 @@ func lxcValidConfig(rawLxc string) error {
 				continue
 			}
 
-			return fmt.Errorf("Only interface-specific ipv4/ipv6 lxc.network keys are allowed")
+			return fmt.Errorf("Only interface-specific ipv4/ipv6 %s keys are allowed", networkKeyPrefix)
 		}
 	}
 
@@ -1191,36 +1196,41 @@ func (c *containerLXC) initLXC() error {
 				return err
 			}
 
+			networkKeyPrefix := "lxc.net"
+			if !lxc.VersionAtLeast(2, 1, 0) {
+				networkKeyPrefix = "lxc.network"
+			}
+
 			// Interface type specific configuration
 			if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p"}) {
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.type", networkidx), "veth")
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.type", networkKeyPrefix, networkidx), "veth")
 				if err != nil {
 					return err
 				}
 			} else if m["nictype"] == "physical" {
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.type", networkidx), "phys")
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.type", networkKeyPrefix, networkidx), "phys")
 				if err != nil {
 					return err
 				}
 			} else if m["nictype"] == "macvlan" {
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.type", networkidx), "macvlan")
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.type", networkKeyPrefix, networkidx), "macvlan")
 				if err != nil {
 					return err
 				}
 
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.macvlan.mode", networkidx), "bridge")
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.macvlan.mode", networkKeyPrefix, networkidx), "bridge")
 				if err != nil {
 					return err
 				}
 			}
 
-			err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.flags", networkidx), "up")
+			err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.flags", networkKeyPrefix, networkidx), "up")
 			if err != nil {
 				return err
 			}
 
 			if shared.StringInSlice(m["nictype"], []string{"bridged", "physical", "macvlan"}) {
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.link", networkidx), m["parent"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.link", networkKeyPrefix, networkidx), m["parent"])
 				if err != nil {
 					return err
 				}
@@ -1228,7 +1238,7 @@ func (c *containerLXC) initLXC() error {
 
 			// Host Virtual NIC name
 			if m["host_name"] != "" {
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.veth.pair", networkidx), m["host_name"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.veth.pair", networkKeyPrefix, networkidx), m["host_name"])
 				if err != nil {
 					return err
 				}
@@ -1236,7 +1246,7 @@ func (c *containerLXC) initLXC() error {
 
 			// MAC address
 			if m["hwaddr"] != "" {
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.hwaddr", networkidx), m["hwaddr"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.hwaddr", networkKeyPrefix, networkidx), m["hwaddr"])
 				if err != nil {
 					return err
 				}
@@ -1244,7 +1254,7 @@ func (c *containerLXC) initLXC() error {
 
 			// MTU
 			if m["mtu"] != "" {
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.mtu", networkidx), m["mtu"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.mtu", networkKeyPrefix, networkidx), m["mtu"])
 				if err != nil {
 					return err
 				}
@@ -1252,7 +1262,7 @@ func (c *containerLXC) initLXC() error {
 
 			// Name
 			if m["name"] != "" {
-				err = lxcSetConfigItem(cc, fmt.Sprintf("lxc.network.%d.name", networkidx), m["name"])
+				err = lxcSetConfigItem(cc, fmt.Sprintf("%s.%d.name", networkKeyPrefix, networkidx), m["name"])
 				if err != nil {
 					return err
 				}
@@ -5557,13 +5567,18 @@ func (c *containerLXC) setNetworkPriority() error {
 
 func (c *containerLXC) getHostInterface(name string) string {
 	if c.IsRunning() {
-		for i := 0; i < len(c.c.ConfigItem("lxc.network")); i++ {
-			nicName := c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.name", i))[0]
+		networkKeyPrefix := "lxc.net"
+		if !lxc.VersionAtLeast(2, 1, 0) {
+			networkKeyPrefix = "lxc.network"
+		}
+
+		for i := 0; i < len(c.c.ConfigItem(networkKeyPrefix)); i++ {
+			nicName := c.c.RunningConfigItem(fmt.Sprintf("%s.%d.name", networkKeyPrefix, i))[0]
 			if nicName != name {
 				continue
 			}
 
-			veth := c.c.RunningConfigItem(fmt.Sprintf("lxc.network.%d.veth.pair", i))[0]
+			veth := c.c.RunningConfigItem(fmt.Sprintf("%s.%d.veth.pair", networkKeyPrefix, i))[0]
 			if veth != "" {
 				return veth
 			}

From 1c8b85603c3c85e539a68469ac3b332af9bdc727 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 4 Apr 2017 22:32:06 -0400
Subject: [PATCH 0985/1193] lxc/list: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/list.go | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index 630133976..af5e23b4b 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -11,7 +11,6 @@ import (
 
 	"github.com/olekukonko/tablewriter"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -196,7 +195,7 @@ func (c *listCmd) shouldShow(filters []string, state *api.Container) bool {
 	return true
 }
 
-func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters []string, columns []column) error {
+func (c *listCmd) listContainers(conf *config.Config, remote string, cinfos []api.Container, filters []string, columns []column) error {
 	headers := []string{}
 	for _, column := range columns {
 		headers = append(headers, column.Name)
@@ -220,7 +219,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 	for i := 0; i < threads; i++ {
 		cStatesWg.Add(1)
 		go func() {
-			d, err := lxd.NewClient(&d.Config, d.Name)
+			d, err := conf.GetContainerServer(remote)
 			if err != nil {
 				cStatesWg.Done()
 				return
@@ -232,7 +231,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 					break
 				}
 
-				state, err := d.ContainerState(cName)
+				state, _, err := d.GetContainerState(cName)
 				if err != nil {
 					continue
 				}
@@ -246,7 +245,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 
 		cSnapshotsWg.Add(1)
 		go func() {
-			d, err := lxd.NewClient(&d.Config, d.Name)
+			d, err := conf.GetContainerServer(remote)
 			if err != nil {
 				cSnapshotsWg.Done()
 				return
@@ -258,7 +257,7 @@ func (c *listCmd) listContainers(d *lxd.Client, cinfos []api.Container, filters
 					break
 				}
 
-				snaps, err := d.ListSnapshots(cName)
+				snaps, err := d.GetContainerSnapshots(cName)
 				if err != nil {
 					continue
 				}
@@ -390,13 +389,13 @@ func (c *listCmd) run(conf *config.Config, args []string) error {
 		remote = conf.DefaultRemote
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
 
 	var cts []api.Container
-	ctslist, err := d.ListContainers()
+	ctslist, err := d.GetContainers()
 	if err != nil {
 		return err
 	}
@@ -435,7 +434,7 @@ func (c *listCmd) run(conf *config.Config, args []string) error {
 		}
 	}
 
-	return c.listContainers(d, cts, filters, columns)
+	return c.listContainers(conf, remote, cts, filters, columns)
 }
 
 func (c *listCmd) nameColumnData(cInfo api.Container, cState *api.ContainerState, cSnaps []api.ContainerSnapshot) string {

From 6844dcae3491e3107172e376e2b71cf05d01a816 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 24 Apr 2017 12:06:01 -0400
Subject: [PATCH 0986/1193] lxc/image: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go          | 341 ++++++++++++++++++++++++++++++++++++--------------
 test/suites/basic.sh  |   4 +-
 test/suites/remote.sh |   7 +-
 3 files changed, 254 insertions(+), 98 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 7cabee816..e34890af6 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -2,8 +2,10 @@ package main
 
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os"
+	"path/filepath"
 	"regexp"
 	"sort"
 	"strings"
@@ -12,7 +14,7 @@ import (
 	"github.com/olekukonko/tablewriter"
 	"gopkg.in/yaml.v2"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -170,12 +172,12 @@ func (c *imageCmd) doImageAlias(conf *config.Config, args []string) error {
 			}
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetImageServer(remote)
 		if err != nil {
 			return err
 		}
 
-		resp, err := d.ListAliases()
+		resp, err := d.GetImageAliases()
 		if err != nil {
 			return err
 		}
@@ -189,20 +191,21 @@ func (c *imageCmd) doImageAlias(conf *config.Config, args []string) error {
 			return errArgs
 		}
 
-		remote, alias, err := conf.ParseRemote(args[2])
+		remote, name, err := conf.ParseRemote(args[2])
 		if err != nil {
 			return err
 		}
 
-		target := args[3]
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
 
-		/* TODO - what about description? */
-		err = d.PostAlias(alias, alias, target)
-		return err
+		alias := api.ImageAliasesPost{}
+		alias.Name = name
+		alias.Target = args[3]
+
+		return d.CreateImageAlias(alias)
 	case "delete":
 		/* alias delete [<remote>:]<alias> */
 		if len(args) < 3 {
@@ -214,12 +217,12 @@ func (c *imageCmd) doImageAlias(conf *config.Config, args []string) error {
 			return err
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
 
-		err = d.DeleteAlias(alias)
+		err = d.DeleteImageAlias(alias)
 		return err
 	}
 	return errArgs
@@ -251,10 +254,6 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		if inName == "" {
-			inName = "default"
-		}
-
 		destRemote, outName, err := conf.ParseRemote(args[2])
 		if err != nil {
 			return err
@@ -264,56 +263,89 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return errArgs
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetImageServer(remote)
 		if err != nil {
 			return err
 		}
 
-		dest, err := lxd.NewClient(conf.Legacy(), destRemote)
+		dest, err := conf.GetContainerServer(destRemote)
 		if err != nil {
 			return err
 		}
 
-		progress := ProgressRenderer{Format: i18n.G("Copying the image: %s")}
-		err = d.CopyImage(inName, dest, c.copyAliases, c.addAliases, c.publicImage, c.autoUpdate, progress.Update)
-		if err == nil {
-			progress.Done(i18n.G("Image copied successfully!"))
+		// Check if an alias
+		fingerprint := c.dereferenceAlias(d, inName)
+
+		// Get the image
+		image, _, err := d.GetImage(fingerprint)
+		if err != nil {
+			return err
 		}
-		progress.Done("")
 
-		return err
+		// Setup the copy arguments
+		aliases := []api.ImageAlias{}
+		for _, entry := range c.addAliases {
+			alias := api.ImageAlias{}
+			alias.Name = entry
+			aliases = append(aliases, alias)
+		}
 
-	case "delete":
-		/* delete [<remote>:]<image> [<image>...] */
-		if len(args) < 2 {
-			return errArgs
+		args := lxd.ImageCopyArgs{
+			Aliases:     aliases,
+			AutoUpdate:  c.autoUpdate,
+			CopyAliases: c.copyAliases,
+			Public:      c.publicImage,
 		}
 
-		remote, inName, err := conf.ParseRemote(args[1])
+		// Do the copy
+		op, err := dest.CopyImage(d, *image, &args)
 		if err != nil {
 			return err
 		}
 
-		if inName == "" {
-			inName = "default"
-		}
-
-		imageNames := []string{inName}
-
-		if len(args) > 2 {
-			for _, extraImage := range args[2:] {
-				imageNames = append(imageNames, extraImage)
-			}
+		// Register progress handler
+		progress := ProgressRenderer{Format: i18n.G("Copying the image: %s")}
+		_, err = op.AddHandler(progress.UpdateOp)
+		if err != nil {
+			progress.Done("")
+			return err
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		// Wait for operation to finish
+		err = op.Wait()
 		if err != nil {
+			progress.Done("")
 			return err
 		}
 
-		for _, imageName := range imageNames {
-			image := c.dereferenceAlias(d, imageName)
-			err = d.DeleteImage(image)
+		progress.Done(i18n.G("Image copied successfully!"))
+		return nil
+
+	case "delete":
+		/* delete [<remote>:]<image> [<image>...] */
+		if len(args) < 2 {
+			return errArgs
+		}
+
+		for _, arg := range args[1:] {
+			var err error
+			remote, inName, err := conf.ParseRemote(arg)
+			if err != nil {
+				return err
+			}
+
+			d, err := conf.GetContainerServer(remote)
+			if err != nil {
+				return err
+			}
+
+			image := c.dereferenceAlias(d, inName)
+			op, err := d.DeleteImage(image)
+			if err != nil {
+				return err
+			}
+
+			err = op.Wait()
 			if err != nil {
 				return err
 			}
@@ -331,17 +363,13 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		if inName == "" {
-			inName = "default"
-		}
-
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetImageServer(remote)
 		if err != nil {
 			return err
 		}
 
 		image := c.dereferenceAlias(d, inName)
-		info, err := d.GetImageInfo(image)
+		info, _, err := d.GetImage(image)
 		if err != nil {
 			return err
 		}
@@ -402,7 +430,6 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return errArgs
 		}
 
-		var fingerprint string
 		var imageFile string
 		var rootfsFile string
 		var properties []string
@@ -438,35 +465,103 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			properties = properties[1:]
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
 
-		if strings.HasPrefix(imageFile, "https://") {
-			progress := ProgressRenderer{Format: i18n.G("Importing the image: %s")}
-			fingerprint, err = d.PostImageURL(imageFile, properties, c.publicImage, c.addAliases, progress.Update)
-			if err == nil {
-				progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
-			}
-		} else if strings.HasPrefix(imageFile, "http://") {
+		if strings.HasPrefix(imageFile, "http://") {
 			return fmt.Errorf(i18n.G("Only https:// is supported for remote image import."))
+		}
+
+		var args *lxd.ImageCreateArgs
+		image := api.ImagesPost{}
+		image.Public = c.publicImage
+
+		// Handle aliases
+		aliases := []api.ImageAlias{}
+		for _, entry := range c.addAliases {
+			alias := api.ImageAlias{}
+			alias.Name = entry
+			aliases = append(aliases, alias)
+		}
+
+		// Handle properties
+		for _, entry := range properties {
+			fields := strings.SplitN(entry, "=", 2)
+			if len(fields) < 2 {
+				return fmt.Errorf(i18n.G("Bad property: %s"), entry)
+			}
+
+			image.Properties[strings.TrimSpace(fields[0])] = strings.TrimSpace(fields[1])
+		}
+
+		progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")}
+		if strings.HasPrefix(imageFile, "https://") {
+			image.Source = &api.ImagesPostSource{}
+			image.Source.Type = "url"
+			image.Source.Mode = "pull"
+			image.Source.Protocol = "direct"
+			image.Source.URL = imageFile
 		} else {
-			progress := ProgressRenderer{Format: i18n.G("Transferring image: %s")}
-			handler := func(percent int64, speed int64) {
-				progress.Update(fmt.Sprintf("%d%% (%s/s)", percent, shared.GetByteSizeString(speed, 2)))
+			var meta io.ReadCloser
+			var rootfs io.ReadCloser
+
+			// Open meta
+			meta, err = os.Open(imageFile)
+			if err != nil {
+				return err
 			}
+			defer meta.Close()
 
-			fingerprint, err = d.PostImage(imageFile, rootfsFile, properties, c.publicImage, c.addAliases, handler)
-			if err == nil {
-				progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
+			// Open rootfs
+			if rootfsFile != "" {
+				rootfs, err = os.Open(rootfsFile)
+				if err != nil {
+					return err
+				}
+				defer rootfs.Close()
 			}
+
+			args = &lxd.ImageCreateArgs{
+				MetaFile:        meta,
+				MetaName:        filepath.Base(imageFile),
+				RootfsFile:      rootfs,
+				RootfsName:      filepath.Base(rootfsFile),
+				ProgressHandler: progress.UpdateProgress,
+			}
+			image.Filename = args.MetaName
 		}
 
+		// Start the transfer
+		op, err := d.CreateImage(image, args)
 		if err != nil {
+			progress.Done("")
 			return err
 		}
 
+		err = op.Wait()
+		if err != nil {
+			progress.Done("")
+			return err
+		}
+
+		// Get the fingerprint
+		fingerprint := op.Metadata["fingerprint"].(string)
+		progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
+
+		// Add the aliases
+		for _, entry := range c.addAliases {
+			alias := api.ImageAliasesPost{}
+			alias.Name = entry
+			alias.Target = fingerprint
+
+			err = d.CreateImageAlias(alias)
+			if err != nil {
+				return err
+			}
+		}
+
 		return nil
 
 	case "list":
@@ -500,12 +595,12 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			}
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetImageServer(remote)
 		if err != nil {
 			return err
 		}
 
-		images, err := d.ListImages()
+		images, err := d.GetImages()
 		if err != nil {
 			return err
 		}
@@ -522,11 +617,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		if inName == "" {
-			inName = "default"
-		}
-
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
@@ -548,30 +639,92 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		if inName == "" {
-			inName = "default"
-		}
-
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetImageServer(remote)
 		if err != nil {
 			return err
 		}
 
-		image := c.dereferenceAlias(d, inName)
+		// Resolve aliases
+		fingerprint := c.dereferenceAlias(d, inName)
 
+		// Default target is current directory
 		target := "."
+		targetMeta := fingerprint
 		if len(args) > 2 {
 			target = args[2]
+			if shared.IsDir(args[2]) {
+				targetMeta = filepath.Join(args[2], targetMeta)
+			} else {
+				targetMeta = args[2]
+			}
+		}
+		targetRootfs := targetMeta + ".root"
+
+		// Prepare the files
+		dest, err := os.Create(targetMeta)
+		if err != nil {
+			return err
 		}
+		defer dest.Close()
 
-		outfile, err := d.ExportImage(image, target)
+		destRootfs, err := os.Create(targetRootfs)
 		if err != nil {
 			return err
 		}
+		defer destRootfs.Close()
+
+		// Prepare the download request
+		progress := ProgressRenderer{Format: i18n.G("Exporting the image: %s")}
+		req := lxd.ImageFileRequest{
+			MetaFile:        io.WriteSeeker(dest),
+			RootfsFile:      io.WriteSeeker(destRootfs),
+			ProgressHandler: progress.UpdateProgress,
+		}
 
-		if target != "-" {
-			fmt.Printf(i18n.G("Output is in %s")+"\n", outfile)
+		// Download the image
+		resp, err := d.GetImageFile(fingerprint, req)
+		if err != nil {
+			os.Remove(targetMeta)
+			os.Remove(targetRootfs)
+			progress.Done("")
+			return err
 		}
+
+		// Cleanup
+		if resp.RootfsSize == 0 {
+			err := os.Remove(targetRootfs)
+			if err != nil {
+				os.Remove(targetMeta)
+				os.Remove(targetRootfs)
+				progress.Done("")
+				return err
+			}
+		}
+
+		// Rename files
+		if shared.IsDir(target) {
+			if resp.MetaName != "" {
+				err := os.Rename(targetMeta, filepath.Join(target, resp.MetaName))
+				if err != nil {
+					os.Remove(targetMeta)
+					os.Remove(targetRootfs)
+					progress.Done("")
+					return err
+				}
+			}
+
+			if resp.RootfsSize > 0 && resp.RootfsName != "" {
+				err := os.Rename(targetRootfs, filepath.Join(target, resp.RootfsName))
+				if err != nil {
+					os.Remove(targetMeta)
+					os.Remove(targetRootfs)
+					progress.Done("")
+					return err
+				}
+			}
+		}
+
+		progress.Done(i18n.G("Image exported successfully!"))
 		return nil
 
 	case "show":
@@ -584,17 +737,13 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		if inName == "" {
-			inName = "default"
-		}
-
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetImageServer(remote)
 		if err != nil {
 			return err
 		}
 
 		image := c.dereferenceAlias(d, inName)
-		info, err := d.GetImageInfo(image)
+		info, _, err := d.GetImage(image)
 		if err != nil {
 			return err
 		}
@@ -610,12 +759,17 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 	}
 }
 
-func (c *imageCmd) dereferenceAlias(d *lxd.Client, inName string) string {
-	result := d.GetAlias(inName)
-	if result == "" {
+func (c *imageCmd) dereferenceAlias(d lxd.ImageServer, inName string) string {
+	if inName == "" {
+		inName = "default"
+	}
+
+	result, _, _ := d.GetImageAlias(inName)
+	if result == nil {
 		return inName
 	}
-	return result
+
+	return result.Target
 }
 
 func (c *imageCmd) shortestAlias(list []api.ImageAlias) string {
@@ -711,7 +865,7 @@ func (c *imageCmd) showAliases(aliases []api.ImageAliasesEntry, filters []string
 	return nil
 }
 
-func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
+func (c *imageCmd) doImageEdit(client lxd.ContainerServer, image string) error {
 	// If stdin isn't a terminal, read text from it
 	if !termios.IsTerminal(int(syscall.Stdin)) {
 		contents, err := ioutil.ReadAll(os.Stdin)
@@ -724,11 +878,12 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 		if err != nil {
 			return err
 		}
-		return client.PutImageInfo(image, newdata)
+
+		return client.UpdateImage(image, newdata, "")
 	}
 
 	// Extract the current value
-	config, err := client.GetImageInfo(image)
+	config, etag, err := client.GetImage(image)
 	if err != nil {
 		return err
 	}
@@ -750,7 +905,7 @@ func (c *imageCmd) doImageEdit(client *lxd.Client, image string) error {
 		newdata := api.ImagePut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
-			err = client.PutImageInfo(image, newdata)
+			err = client.UpdateImage(image, newdata, etag)
 		}
 
 		// Respawn the editor
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 17db957ad..5075054dd 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -57,8 +57,8 @@ test_basic_usage() {
 
   # Test custom filename for image export
   lxc image export testimage "${LXD_DIR}/foo"
-  [ "${sum}" = "$(sha256sum "${LXD_DIR}/foo.tar.xz" | cut -d' ' -f1)" ]
-  rm "${LXD_DIR}/foo.tar.xz"
+  [ "${sum}" = "$(sha256sum "${LXD_DIR}/foo" | cut -d' ' -f1)" ]
+  rm "${LXD_DIR}/foo"
 
 
   # Test image export with a split image.
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index 474a03e0a..01cef366b 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -93,10 +93,11 @@ test_remote_usage() {
   lxc_remote remote add lxd2 "${LXD2_ADDR}" --accept-certificate --password foo
 
   # we need a public image on localhost
-  img=$(lxc_remote image export localhost:testimage "${LXD_DIR}/foo" | grep -o "foo.*")
+
+  lxc_remote image export localhost:testimage "${LXD_DIR}/foo"
   lxc_remote image delete localhost:testimage
-  sum=$(sha256sum "${LXD_DIR}/${img}" | cut -d' ' -f1)
-  lxc_remote image import "${LXD_DIR}/${img}" localhost: --public
+  sum=$(sha256sum "${LXD_DIR}/foo" | cut -d' ' -f1)
+  lxc_remote image import "${LXD_DIR}/foo" localhost: --public
   lxc_remote image alias create localhost:testimage "${sum}"
 
   lxc_remote image delete "lxd2:${sum}" || true

From 45ce5073e205110233f28e5e9b4ca959cce76594 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 18:31:31 -0400
Subject: [PATCH 0987/1193] lxc/publish: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/publish.go | 111 +++++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 88 insertions(+), 23 deletions(-)

diff --git a/lxc/publish.go b/lxc/publish.go
index 98aaa615f..5c494fe2f 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -4,7 +4,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -72,21 +72,21 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 		return fmt.Errorf(i18n.G("There is no \"image name\".  Did you want an alias?"))
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), iRemote)
+	d, err := conf.GetContainerServer(iRemote)
 	if err != nil {
 		return err
 	}
 
 	s := d
 	if cRemote != iRemote {
-		s, err = lxd.NewClient(conf.Legacy(), cRemote)
+		s, err = conf.GetContainerServer(cRemote)
 		if err != nil {
 			return err
 		}
 	}
 
 	if !shared.IsSnapshot(cName) {
-		ct, err := s.ContainerInfo(cName)
+		ct, etag, err := s.GetContainer(cName)
 		if err != nil {
 			return err
 		}
@@ -101,38 +101,57 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 
 			if ct.Ephemeral {
 				ct.Ephemeral = false
-				err := s.UpdateContainerConfig(cName, ct.Writable())
+				op, err := s.UpdateContainer(cName, ct.Writable(), etag)
+				if err != nil {
+					return err
+				}
+
+				err = op.Wait()
+				if err != nil {
+					return err
+				}
+
+				// Refresh the ETag
+				_, etag, err = s.GetContainer(cName)
 				if err != nil {
 					return err
 				}
 			}
 
-			resp, err := s.Action(cName, shared.Stop, -1, true, false)
-			if err != nil {
-				return err
+			req := api.ContainerStatePut{
+				Action:  string(shared.Stop),
+				Timeout: -1,
+				Force:   true,
 			}
 
-			op, err := s.WaitFor(resp.Operation)
+			op, err := s.UpdateContainerState(cName, req, "")
 			if err != nil {
 				return err
 			}
 
-			if op.StatusCode == api.Failure {
+			err = op.Wait()
+			if err != nil {
 				return fmt.Errorf(i18n.G("Stopping container failed!"))
 			}
 
 			defer func() {
-				resp, err := s.Action(cName, shared.Start, -1, true, false)
+				req.Action = string(shared.Start)
+				op, err = s.UpdateContainerState(cName, req, "")
 				if err != nil {
 					return
 				}
 
-				s.WaitFor(resp.Operation)
+				op.Wait()
 			}()
 
 			if wasEphemeral {
 				ct.Ephemeral = true
-				err := s.UpdateContainerConfig(cName, ct.Writable())
+				op, err := s.UpdateContainer(cName, ct.Writable(), etag)
+				if err != nil {
+					return err
+				}
+
+				err = op.Wait()
 				if err != nil {
 					return err
 				}
@@ -158,27 +177,73 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 		properties = nil
 	}
 
-	// Optimized local publish
+	// Reformat aliases
+	aliases := []api.ImageAlias{}
+	for _, entry := range c.pAliases {
+		alias := api.ImageAlias{}
+		alias.Name = entry
+		aliases = append(aliases, alias)
+	}
+
+	// Create the image
+	req := api.ImagesPost{
+		Source: &api.ImagesPostSource{
+			Type: "container",
+			Name: cName,
+		},
+	}
+	req.Properties = properties
+
+	if shared.IsSnapshot(cName) {
+		req.Source.Type = "snapshot"
+	}
+
 	if cRemote == iRemote {
-		fp, err = d.ImageFromContainer(cName, c.makePublic, c.pAliases, properties)
-		if err != nil {
-			return err
-		}
-		fmt.Printf(i18n.G("Container published with fingerprint: %s")+"\n", fp)
-		return nil
+		req.Aliases = aliases
+		req.Public = c.makePublic
 	}
 
-	fp, err = s.ImageFromContainer(cName, false, nil, properties)
+	op, err := s.CreateImage(req, nil)
 	if err != nil {
 		return err
 	}
-	defer s.DeleteImage(fp)
 
-	err = s.CopyImage(fp, d, false, c.pAliases, c.makePublic, false, nil)
+	err = op.Wait()
 	if err != nil {
 		return err
 	}
 
+	// Grab the fingerprint
+	fingerprint := op.Metadata["fingerprint"].(string)
+
+	// For remote publish, copy to target now
+	if cRemote != iRemote {
+		defer s.DeleteImage(fingerprint)
+
+		// Get the source image
+		image, _, err := s.GetImage(fingerprint)
+		if err != nil {
+			return err
+		}
+
+		// Image copy arguments
+		args := lxd.ImageCopyArgs{
+			Aliases: aliases,
+			Public:  c.makePublic,
+		}
+
+		// Copy the image to the destination host
+		op, err := d.CopyImage(s, *image, &args)
+		if err != nil {
+			return err
+		}
+
+		err = op.Wait()
+		if err != nil {
+			return err
+		}
+	}
+
 	fmt.Printf(i18n.G("Container published with fingerprint: %s")+"\n", fp)
 
 	return nil

From 004f20f90cb88f7023667b1138d540bc67c7eea3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 29 May 2017 23:59:11 -0400
Subject: [PATCH 0988/1193] lxc/init: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/init.go   | 150 +++++++++++++++++++++++++++++++++-------------------------
 lxc/launch.go |  94 +++++-------------------------------
 2 files changed, 97 insertions(+), 147 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index 0882fafb4..a60f74120 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -5,7 +5,7 @@ import (
 	"os"
 	"strings"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -140,13 +140,18 @@ func (c *initCmd) flags() {
 }
 
 func (c *initCmd) run(conf *config.Config, args []string) error {
+	_, _, err := c.create(conf, args)
+	return err
+}
+
+func (c *initCmd) create(conf *config.Config, args []string) (lxd.ContainerServer, string, error) {
 	if len(args) > 2 || len(args) < 1 {
-		return errArgs
+		return nil, "", errArgs
 	}
 
 	iremote, image, err := conf.ParseRemote(args[0])
 	if err != nil {
-		return err
+		return nil, "", err
 	}
 
 	var name string
@@ -154,22 +159,20 @@ func (c *initCmd) run(conf *config.Config, args []string) error {
 	if len(args) == 2 {
 		remote, name, err = conf.ParseRemote(args[1])
 		if err != nil {
-			return err
+			return nil, "", err
 		}
 	} else {
 		remote, name, err = conf.ParseRemote("")
 		if err != nil {
-			return err
+			return nil, "", err
 		}
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
-		return err
+		return nil, "", err
 	}
 
-	// TODO: implement the syntax for supporting other image types/remotes
-
 	/*
 	 * initRequestedEmptyProfiles means user requested empty
 	 * !initRequestedEmptyProfiles but len(profArgs) == 0 means use profile default
@@ -179,89 +182,106 @@ func (c *initCmd) run(conf *config.Config, args []string) error {
 		profiles = append(profiles, p)
 	}
 
-	var resp *api.Response
 	if name == "" {
 		fmt.Printf(i18n.G("Creating the container") + "\n")
 	} else {
 		fmt.Printf(i18n.G("Creating %s")+"\n", name)
 	}
 
+	// Get the image server and image info
 	iremote, image = c.guessImage(conf, d, remote, iremote, image)
+	var imgRemote lxd.ImageServer
+	var imgInfo *api.Image
 
+	// Connect to the image server
+	if iremote == remote {
+		imgRemote = d
+	} else {
+		imgRemote, err = conf.GetImageServer(iremote)
+		if err != nil {
+			return nil, "", err
+		}
+	}
+
+	// Deal with the default image
+	if image == "" {
+		image = "default"
+	}
+
+	// Setup container creation request
+	req := api.ContainersPost{
+		Name: name,
+	}
+	req.Config = configMap
 	if !initRequestedEmptyProfiles && len(profiles) == 0 {
-		resp, err = d.Init(name, iremote, image, nil, configMap, c.ephem)
+		req.Profiles = nil
 	} else {
-		resp, err = d.Init(name, iremote, image, &profiles, configMap, c.ephem)
+		req.Profiles = profiles
 	}
-	if err != nil {
-		return err
+	req.Ephemeral = c.ephem
+
+	// Optimisation for simplestreams
+	if conf.Remotes[iremote].Protocol == "simplestreams" {
+		imgInfo = &api.Image{}
+		imgInfo.Fingerprint = image
+		imgInfo.Public = true
+		req.Source.Alias = image
+	} else {
+		// Attempt to resolve an image alias
+		alias, _, err := imgRemote.GetImageAlias(image)
+		if err == nil {
+			req.Source.Alias = image
+			image = alias.Target
+		}
+
+		// Get the image info
+		imgInfo, _, err = imgRemote.GetImage(image)
+		if err != nil {
+			return nil, "", err
+		}
 	}
 
-	progress := ProgressRenderer{}
-	c.initProgressTracker(d, &progress, resp.Operation)
+	// Create the container
+	op, err := d.CreateContainerFromImage(imgRemote, *imgInfo, req)
+	if err != nil {
+		return nil, "", err
+	}
 
-	err = d.WaitForSuccess(resp.Operation)
-	progress.Done("")
+	// Watch the background operation
+	progress := ProgressRenderer{Format: i18n.G("Retrieving image: %s")}
+	_, err = op.AddHandler(progress.UpdateOp)
+	if err != nil {
+		progress.Done("")
+		return nil, "", err
+	}
 
+	err = op.Wait()
 	if err != nil {
-		return err
+		progress.Done("")
+		return nil, "", err
 	}
-	op, err := resp.MetadataAsOperation()
+	progress.Done("")
+
+	// Extract the container name
+	opInfo, err := op.GetTarget()
 	if err != nil {
-		return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
+		return nil, "", err
 	}
 
-	containers, ok := op.Resources["containers"]
+	containers, ok := opInfo.Resources["containers"]
 	if !ok || len(containers) == 0 {
-		return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
+		return nil, "", fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
 	}
 
 	if len(containers) == 1 && name == "" {
 		fields := strings.Split(containers[0], "/")
 		fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
 	}
-	return nil
-}
-
-func (c *initCmd) initProgressTracker(d *lxd.Client, progress *ProgressRenderer, operation string) {
-	progress.Format = i18n.G("Retrieving image: %s")
-	handler := func(msg interface{}) {
-		if msg == nil {
-			return
-		}
-
-		event := msg.(map[string]interface{})
-		if event["type"].(string) != "operation" {
-			return
-		}
-
-		if event["metadata"] == nil {
-			return
-		}
-
-		md := event["metadata"].(map[string]interface{})
-		if !strings.HasSuffix(operation, md["id"].(string)) {
-			return
-		}
-
-		if md["metadata"] == nil {
-			return
-		}
 
-		if api.StatusCode(md["status_code"].(float64)).IsFinal() {
-			return
-		}
-
-		opMd := md["metadata"].(map[string]interface{})
-		_, ok := opMd["download_progress"]
-		if ok {
-			progress.Update(opMd["download_progress"].(string))
-		}
-	}
-	go d.Monitor([]string{"operation"}, handler, nil)
+	return d, name, nil
 }
 
-func (c *initCmd) guessImage(conf *config.Config, d *lxd.Client, remote string, iremote string, image string) (string, string) {
+func (c *initCmd) guessImage(conf *config.Config, d lxd.ContainerServer, remote string, iremote string, image string) (string, string) {
 	if remote != iremote {
 		return iremote, image
 	}
@@ -271,12 +291,12 @@ func (c *initCmd) guessImage(conf *config.Config, d *lxd.Client, remote string,
 		return iremote, image
 	}
 
-	target := d.GetAlias(image)
-	if target != "" {
+	_, _, err := d.GetImageAlias(image)
+	if err == nil {
 		return iremote, image
 	}
 
-	_, err := d.GetImageInfo(image)
+	_, _, err = d.GetImage(image)
 	if err == nil {
 		return iremote, image
 	}
diff --git a/lxc/launch.go b/lxc/launch.go
index ca85d6e6b..68f066c9a 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -2,14 +2,10 @@ package main
 
 import (
 	"fmt"
-	"strings"
 
-	"github.com/lxc/lxd"
 	"github.com/lxc/lxd/lxc/config"
-	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
-	"github.com/lxc/lxd/shared/version"
 )
 
 type launchCmd struct {
@@ -39,105 +35,39 @@ func (c *launchCmd) flags() {
 }
 
 func (c *launchCmd) run(conf *config.Config, args []string) error {
-	if len(args) > 2 || len(args) < 1 {
-		return errArgs
-	}
-
-	iremote, image, err := conf.ParseRemote(args[0])
+	// Call the matching code from init
+	d, name, err := c.init.create(conf, args)
 	if err != nil {
 		return err
 	}
 
-	var name string
+	// Get the remote
 	var remote string
 	if len(args) == 2 {
-		remote, name, err = conf.ParseRemote(args[1])
+		remote, _, err = conf.ParseRemote(args[1])
 		if err != nil {
 			return err
 		}
 	} else {
-		remote, name, err = conf.ParseRemote("")
-		if err != nil {
-			return err
-		}
-	}
-
-	d, err := lxd.NewClient(conf.Legacy(), remote)
-	if err != nil {
-		return err
-	}
-
-	/*
-	 * initRequestedEmptyProfiles means user requested empty
-	 * !initRequestedEmptyProfiles but len(profArgs) == 0 means use profile default
-	 */
-	var resp *api.Response
-	profiles := []string{}
-	for _, p := range c.init.profArgs {
-		profiles = append(profiles, p)
-	}
-
-	iremote, image = c.init.guessImage(conf, d, remote, iremote, image)
-
-	if !initRequestedEmptyProfiles && len(profiles) == 0 {
-		resp, err = d.Init(name, iremote, image, nil, configMap, c.init.ephem)
-	} else {
-		resp, err = d.Init(name, iremote, image, &profiles, configMap, c.init.ephem)
-	}
-
-	if err != nil {
-		return err
-	}
-
-	progress := ProgressRenderer{}
-	c.init.initProgressTracker(d, &progress, resp.Operation)
-
-	if name == "" {
-		op, err := resp.MetadataAsOperation()
+		remote, _, err = conf.ParseRemote("")
 		if err != nil {
-			progress.Done("")
-			return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
-		}
-
-		containers, ok := op.Resources["containers"]
-		if !ok || len(containers) == 0 {
-			progress.Done("")
-			return fmt.Errorf(i18n.G("didn't get any affected image, container or snapshot from server"))
-		}
-
-		var restVersion string
-		toScan := strings.Replace(containers[0], "/", " ", -1)
-		count, err := fmt.Sscanf(toScan, " %s containers %s", &restVersion, &name)
-		if err != nil {
-			progress.Done("")
 			return err
 		}
-
-		if count != 2 {
-			progress.Done("")
-			return fmt.Errorf(i18n.G("bad number of things scanned from image, container or snapshot"))
-		}
-
-		if restVersion != version.APIVersion {
-			progress.Done("")
-			return fmt.Errorf(i18n.G("got bad version"))
-		}
 	}
-	fmt.Printf(i18n.G("Creating %s")+"\n", name)
 
-	if err = d.WaitForSuccess(resp.Operation); err != nil {
-		progress.Done("")
-		return err
+	// Start the container
+	fmt.Printf(i18n.G("Starting %s")+"\n", name)
+	req := api.ContainerStatePut{
+		Action:  "start",
+		Timeout: -1,
 	}
-	progress.Done("")
 
-	fmt.Printf(i18n.G("Starting %s")+"\n", name)
-	resp, err = d.Action(name, shared.Start, -1, false, false)
+	op, err := d.UpdateContainerState(name, req, "")
 	if err != nil {
 		return err
 	}
 
-	err = d.WaitForSuccess(resp.Operation)
+	err = op.Wait()
 	if err != nil {
 		prettyName := name
 		if remote != "" {

From 4193b5113fa880dc82f41adc58b1bc25c52886da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 1 Jun 2017 16:37:20 -0400
Subject: [PATCH 0989/1193] lxc/move: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/move.go | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/lxc/move.go b/lxc/move.go
index de044b63b..86994d642 100644
--- a/lxc/move.go
+++ b/lxc/move.go
@@ -1,8 +1,11 @@
 package main
 
 import (
-	"github.com/lxc/lxd"
+	"strings"
+
 	"github.com/lxc/lxd/lxc/config"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/i18n"
 )
 
@@ -52,17 +55,31 @@ func (c *moveCmd) run(conf *config.Config, args []string) error {
 	// course, this changing of hostname isn't supported right now, so this
 	// simply won't work).
 	if sourceRemote == destRemote {
-		source, err := lxd.NewClient(conf.Legacy(), sourceRemote)
+		source, err := conf.GetContainerServer(sourceRemote)
 		if err != nil {
 			return err
 		}
 
-		rename, err := source.Rename(sourceName, destName)
+		if shared.IsSnapshot(sourceName) {
+			// Snapshot rename
+			srcFields := strings.SplitN(sourceName, shared.SnapshotDelimiter, 2)
+			dstFields := strings.SplitN(destName, shared.SnapshotDelimiter, 2)
+
+			op, err := source.RenameContainerSnapshot(srcFields[0], srcFields[1], api.ContainerSnapshotPost{Name: dstFields[1]})
+			if err != nil {
+				return err
+			}
+
+			return op.Wait()
+		}
+
+		// Container rename
+		op, err := source.RenameContainer(sourceName, api.ContainerPost{Name: destName})
 		if err != nil {
 			return err
 		}
 
-		return source.WaitForSuccess(rename.Operation)
+		return op.Wait()
 	}
 
 	cpy := copyCmd{}

From 9c366e995b8da79d5c6871a900dd31f77390c98d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 25 Sep 2017 10:46:26 -0400
Subject: [PATCH 0990/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 183 ++++++++++++++++++++++++++++++------------------------------
 po/fr.po   | 182 ++++++++++++++++++++++++++++++-----------------------------
 po/ja.po   | 186 +++++++++++++++++++++++++++++++------------------------------
 po/lxd.pot | 168 +++++++++++++++++++++++++++----------------------------
 4 files changed, 357 insertions(+), 362 deletions(-)

diff --git a/po/de.po b/po/de.po
index 8c11f8eeb..c85bac4e3 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-24 02:25-0400\n"
+"POT-Creation-Date: 2017-09-25 10:46-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -55,7 +55,7 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:51
+#: lxc/image.go:53
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the image properties.\n"
@@ -111,7 +111,7 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:654
+#: lxc/image.go:808
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -125,15 +125,15 @@ msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:675 lxc/image.go:704
+#: lxc/image.go:829 lxc/image.go:858
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:679
+#: lxc/image.go:833
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:415
+#: lxc/list.go:414
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -146,21 +146,26 @@ msgstr "Akzeptiere Zertifikat"
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
 
-#: lxc/image.go:383
+#: lxc/image.go:411
 #, fuzzy
 msgid "Aliases:"
 msgstr "Aliasse:\n"
 
-#: lxc/image.go:361 lxc/info.go:104
+#: lxc/image.go:389 lxc/info.go:104
 #, fuzzy, c-format
 msgid "Architecture: %s"
 msgstr "Architektur: %s\n"
 
-#: lxc/image.go:391
+#: lxc/image.go:419
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
+#: lxc/image.go:493
+#, fuzzy, c-format
+msgid "Bad property: %s"
+msgstr "Ungültige Abbild Eigenschaft: %s\n"
+
 #: lxc/info.go:186
 msgid "Bytes received"
 msgstr ""
@@ -173,7 +178,7 @@ msgstr ""
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:416
+#: lxc/list.go:415
 msgid "CREATED AT"
 msgstr ""
 
@@ -200,7 +205,7 @@ msgstr "Fingerabdruck des Zertifikats: % x\n"
 msgid "Client certificate stored at server: "
 msgstr "Gespeichertes Nutzerzertifikat auf dem Server: "
 
-#: lxc/list.go:110 lxc/list.go:111
+#: lxc/list.go:109 lxc/list.go:110
 msgid "Columns"
 msgstr ""
 
@@ -213,7 +218,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:758 lxc/profile.go:233
+#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:913 lxc/profile.go:233
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
@@ -226,21 +231,21 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:221
+#: lxc/init.go:278
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:167 lxc/publish.go:182
+#: lxc/publish.go:247
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/image.go:133
+#: lxc/image.go:135
 msgid "Copy aliases from source"
 msgstr "Kopiere Aliasse von der Quelle"
 
-#: lxc/image.go:277
+#: lxc/image.go:307
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
@@ -249,22 +254,22 @@ msgstr ""
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
-#: lxc/image.go:366 lxc/info.go:106
+#: lxc/image.go:394 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:186 lxc/launch.go:126
+#: lxc/init.go:188
 #, c-format
 msgid "Creating %s"
 msgstr ""
 
-#: lxc/init.go:184
+#: lxc/init.go:186
 #, fuzzy
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:678 lxc/image.go:706
+#: lxc/image.go:832 lxc/image.go:860
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -282,7 +287,7 @@ msgstr "Gerät %s wurde von %s entfernt\n"
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:501
+#: lxc/list.go:500
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -316,16 +321,21 @@ msgstr "Flüchtiger Container"
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:370
+#: lxc/image.go:398
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:372
+#: lxc/image.go:400
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:326 lxc/image.go:676 lxc/image.go:705
+#: lxc/image.go:677
+#, c-format
+msgid "Exporting the image: %s"
+msgstr ""
+
+#: lxc/config.go:326 lxc/image.go:830 lxc/image.go:859
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -339,11 +349,11 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:113
+#: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:359
+#: lxc/image.go:387
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Fingerabdruck: %s\n"
@@ -361,7 +371,7 @@ msgstr ""
 msgid "Force using the local unix socket"
 msgstr ""
 
-#: lxc/list.go:112
+#: lxc/list.go:111
 msgid "Format (table|json)"
 msgstr ""
 
@@ -370,11 +380,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Generiere Nutzerzertifikat. Dies kann wenige Minuten dauern...\n"
 
-#: lxc/list.go:413
+#: lxc/list.go:412
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:414
+#: lxc/list.go:413
 msgid "IPV6"
 msgstr ""
 
@@ -396,20 +406,19 @@ msgstr ""
 msgid "Ignore the container state (only for start)"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/image.go:280
+#: lxc/image.go:321
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:450 lxc/image.go:462
+#: lxc/image.go:727
+msgid "Image exported successfully!"
+msgstr ""
+
+#: lxc/image.go:551
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/image.go:447
-#, c-format
-msgid "Importing the image: %s"
-msgstr ""
-
 #: lxc/remote.go:136
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
@@ -438,7 +447,7 @@ msgstr "Ungültiges Ziel %s"
 msgid "Ips:"
 msgstr ""
 
-#: lxc/image.go:134
+#: lxc/image.go:136
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
@@ -446,12 +455,12 @@ msgstr ""
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:375
+#: lxc/image.go:403
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:377
+#: lxc/image.go:405
 msgid "Last used: never"
 msgstr ""
 
@@ -459,7 +468,7 @@ msgstr ""
 msgid "Log:"
 msgstr ""
 
-#: lxc/image.go:132
+#: lxc/image.go:134
 msgid "Make image public"
 msgstr "Veröffentliche Abbild"
 
@@ -504,7 +513,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
-#: lxc/list.go:417 lxc/remote.go:377
+#: lxc/list.go:416 lxc/remote.go:377
 msgid "NAME"
 msgstr ""
 
@@ -521,7 +530,7 @@ msgstr ""
 msgid "Network usage:"
 msgstr ""
 
-#: lxc/image.go:135 lxc/publish.go:35
+#: lxc/image.go:137 lxc/publish.go:35
 msgid "New alias to define at target"
 msgstr ""
 
@@ -538,7 +547,7 @@ msgstr "Kein Fingerabdruck angegeben."
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:453
+#: lxc/image.go:474
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
@@ -546,24 +555,19 @@ msgstr ""
 msgid "Options:"
 msgstr ""
 
-#: lxc/image.go:573
-#, c-format
-msgid "Output is in %s"
-msgstr ""
-
 #: lxc/exec.go:57
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:503
+#: lxc/list.go:502
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:418
+#: lxc/list.go:417
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:419
+#: lxc/list.go:418
 msgid "PROFILES"
 msgstr ""
 
@@ -571,7 +575,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:677 lxc/remote.go:380
+#: lxc/image.go:831 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
@@ -611,7 +615,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:759
+#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:914
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -657,7 +661,7 @@ msgstr "kann nicht zum selben Container Namen kopieren"
 msgid "Profiles: %s"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/image.go:379
+#: lxc/image.go:407
 #, fuzzy
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
@@ -666,7 +670,7 @@ msgstr "Eigenschaften:\n"
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:362
+#: lxc/image.go:390
 #, fuzzy, c-format
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
@@ -698,20 +702,20 @@ msgstr ""
 msgid "Restart containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/init.go:227
+#: lxc/init.go:251
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:680
+#: lxc/image.go:834
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:420
+#: lxc/list.go:419
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:421
+#: lxc/list.go:420
 msgid "STATE"
 msgstr ""
 
@@ -760,7 +764,7 @@ msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:360
+#: lxc/image.go:388
 #, fuzzy, c-format
 msgid "Size: %.2fMB"
 msgstr "Größe: %.2vMB\n"
@@ -774,7 +778,7 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/image.go:393
+#: lxc/image.go:421
 msgid "Source:"
 msgstr ""
 
@@ -783,7 +787,7 @@ msgstr ""
 msgid "Start containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/launch.go:134
+#: lxc/launch.go:59
 #, c-format
 msgid "Starting %s"
 msgstr ""
@@ -802,7 +806,7 @@ msgstr "Anhalten des Containers fehlgeschlagen!"
 msgid "Stop the container if currently running"
 msgstr ""
 
-#: lxc/publish.go:121
+#: lxc/publish.go:134
 msgid "Stopping container failed!"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
@@ -824,7 +828,7 @@ msgstr ""
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:422
+#: lxc/list.go:421
 msgid "TYPE"
 msgstr ""
 
@@ -844,7 +848,7 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/init.go:284
+#: lxc/init.go:304
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -870,7 +874,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Wartezeit bevor der Container gestoppt wird."
 
-#: lxc/image.go:363
+#: lxc/image.go:391
 #, fuzzy
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
@@ -879,12 +883,12 @@ msgstr "Zeitstempel:\n"
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:455
+#: lxc/image.go:499
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
 
-#: lxc/action.go:106 lxc/launch.go:147
+#: lxc/action.go:106 lxc/launch.go:77
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
@@ -897,7 +901,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:681
+#: lxc/image.go:835
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -913,7 +917,7 @@ msgstr ""
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:368
+#: lxc/image.go:396
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1086,7 +1090,7 @@ msgid ""
 "Help page for the LXD client."
 msgstr ""
 
-#: lxc/image.go:61
+#: lxc/image.go:63
 msgid ""
 "Usage: lxc image <subcommand> [options]\n"
 "\n"
@@ -1198,7 +1202,7 @@ msgstr ""
 "Beispiel:\n"
 "lxc init ubuntu u1\n"
 
-#: lxc/launch.go:24
+#: lxc/launch.go:20
 #, fuzzy
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
@@ -1224,7 +1228,7 @@ msgstr ""
 "Beispiel:\n"
 "lxc launch ubuntu u1\n"
 
-#: lxc/list.go:47
+#: lxc/list.go:46
 #, fuzzy
 msgid ""
 "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
@@ -1330,7 +1334,7 @@ msgid ""
 "    Only show log message."
 msgstr ""
 
-#: lxc/move.go:17
+#: lxc/move.go:20
 #, fuzzy
 msgid ""
 "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
@@ -1581,11 +1585,6 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:118
-msgid "bad number of things scanned from image, container or snapshot"
-msgstr ""
-"Falsche Anzahl an Objekten im Abbild, Container oder Sicherungspunkt gelesen."
-
 #: lxc/copy.go:108
 msgid "can't copy to the same container name"
 msgstr "kann nicht zum selben Container Namen kopieren"
@@ -1598,15 +1597,15 @@ msgstr ""
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:211 lxc/init.go:216 lxc/launch.go:99 lxc/launch.go:105
+#: lxc/init.go:273
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
-#: lxc/image.go:354
+#: lxc/image.go:382
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:356
+#: lxc/image.go:384
 msgid "enabled"
 msgstr ""
 
@@ -1620,11 +1619,7 @@ msgstr "Fehler: %v\n"
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/launch.go:123
-msgid "got bad version"
-msgstr "Versionskonflikt"
-
-#: lxc/image.go:349 lxc/image.go:657
+#: lxc/image.go:377 lxc/image.go:811
 msgid "no"
 msgstr ""
 
@@ -1679,7 +1674,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:45 lxc/image.go:351 lxc/image.go:661
+#: lxc/delete.go:45 lxc/image.go:379 lxc/image.go:815
 msgid "yes"
 msgstr ""
 
@@ -1687,6 +1682,14 @@ msgstr ""
 msgid "you must specify a source container name"
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
+#~ msgid "bad number of things scanned from image, container or snapshot"
+#~ msgstr ""
+#~ "Falsche Anzahl an Objekten im Abbild, Container oder Sicherungspunkt "
+#~ "gelesen."
+
+#~ msgid "got bad version"
+#~ msgstr "Versionskonflikt"
+
 #, fuzzy
 #~ msgid ""
 #~ "Changes state of one or more containers to %s.\n"
@@ -1921,10 +1924,6 @@ msgstr "der Name des Ursprung Containers muss angegeben werden"
 #~ "Falls dies der erste Start ist, sollten Sie mit dem 'lxd-images' Script "
 #~ "Abbilder importieren.\n"
 
-#, fuzzy
-#~ msgid "Bad image property: %s"
-#~ msgstr "Ungültige Abbild Eigenschaft: %s\n"
-
 #~ msgid "Cannot change profile name"
 #~ msgstr "Profilname kann nicht geändert werden"
 
diff --git a/po/fr.po b/po/fr.po
index 0831b2b53..309df4e21 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-24 02:25-0400\n"
+"POT-Creation-Date: 2017-09-25 10:46-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -37,7 +37,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:51
+#: lxc/image.go:53
 msgid ""
 "### This is a yaml representation of the image properties.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -68,7 +68,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:654
+#: lxc/image.go:808
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -82,15 +82,15 @@ msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:675 lxc/image.go:704
+#: lxc/image.go:829 lxc/image.go:858
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:679
+#: lxc/image.go:833
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:415
+#: lxc/list.go:414
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -103,20 +103,25 @@ msgstr ""
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
 
-#: lxc/image.go:383
+#: lxc/image.go:411
 msgid "Aliases:"
 msgstr ""
 
-#: lxc/image.go:361 lxc/info.go:104
+#: lxc/image.go:389 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr ""
 
-#: lxc/image.go:391
+#: lxc/image.go:419
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
+#: lxc/image.go:493
+#, fuzzy, c-format
+msgid "Bad property: %s"
+msgstr "(Image invalide: %s\n"
+
 #: lxc/info.go:186
 msgid "Bytes received"
 msgstr ""
@@ -129,7 +134,7 @@ msgstr ""
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:416
+#: lxc/list.go:415
 msgid "CREATED AT"
 msgstr ""
 
@@ -156,7 +161,7 @@ msgstr "Empreinte du certificat: % x\n"
 msgid "Client certificate stored at server: "
 msgstr "Certificat client enregistré avec le serveur: "
 
-#: lxc/list.go:110 lxc/list.go:111
+#: lxc/list.go:109 lxc/list.go:110
 msgid "Columns"
 msgstr ""
 
@@ -168,7 +173,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:758 lxc/profile.go:233
+#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:913 lxc/profile.go:233
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
@@ -181,21 +186,21 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:221
+#: lxc/init.go:278
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:167 lxc/publish.go:182
+#: lxc/publish.go:247
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/image.go:133
+#: lxc/image.go:135
 msgid "Copy aliases from source"
 msgstr ""
 
-#: lxc/image.go:277
+#: lxc/image.go:307
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
@@ -204,21 +209,21 @@ msgstr ""
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
-#: lxc/image.go:366 lxc/info.go:106
+#: lxc/image.go:394 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:186 lxc/launch.go:126
+#: lxc/init.go:188
 #, c-format
 msgid "Creating %s"
 msgstr ""
 
-#: lxc/init.go:184
+#: lxc/init.go:186
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:678 lxc/image.go:706
+#: lxc/image.go:832 lxc/image.go:860
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -236,7 +241,7 @@ msgstr ""
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:501
+#: lxc/list.go:500
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -270,16 +275,21 @@ msgstr ""
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:370
+#: lxc/image.go:398
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:372
+#: lxc/image.go:400
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/config.go:326 lxc/image.go:676 lxc/image.go:705
+#: lxc/image.go:677
+#, c-format
+msgid "Exporting the image: %s"
+msgstr ""
+
+#: lxc/config.go:326 lxc/image.go:830 lxc/image.go:859
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -293,11 +303,11 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:113
+#: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:359
+#: lxc/image.go:387
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -315,7 +325,7 @@ msgstr ""
 msgid "Force using the local unix socket"
 msgstr ""
 
-#: lxc/list.go:112
+#: lxc/list.go:111
 msgid "Format (table|json)"
 msgstr ""
 
@@ -324,11 +334,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Géneration d'un certificat client. Ceci peut prendre une minute...\n"
 
-#: lxc/list.go:413
+#: lxc/list.go:412
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:414
+#: lxc/list.go:413
 msgid "IPV6"
 msgstr ""
 
@@ -350,20 +360,19 @@ msgstr ""
 msgid "Ignore the container state (only for start)"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/image.go:280
+#: lxc/image.go:321
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:450 lxc/image.go:462
+#: lxc/image.go:727
+msgid "Image exported successfully!"
+msgstr ""
+
+#: lxc/image.go:551
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/image.go:447
-#, c-format
-msgid "Importing the image: %s"
-msgstr ""
-
 #: lxc/remote.go:136
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
@@ -393,7 +402,7 @@ msgstr "Destination invalide %s"
 msgid "Ips:"
 msgstr ""
 
-#: lxc/image.go:134
+#: lxc/image.go:136
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
@@ -401,12 +410,12 @@ msgstr ""
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:375
+#: lxc/image.go:403
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:377
+#: lxc/image.go:405
 msgid "Last used: never"
 msgstr ""
 
@@ -414,7 +423,7 @@ msgstr ""
 msgid "Log:"
 msgstr ""
 
-#: lxc/image.go:132
+#: lxc/image.go:134
 msgid "Make image public"
 msgstr ""
 
@@ -457,7 +466,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr ""
 
-#: lxc/list.go:417 lxc/remote.go:377
+#: lxc/list.go:416 lxc/remote.go:377
 msgid "NAME"
 msgstr ""
 
@@ -474,7 +483,7 @@ msgstr ""
 msgid "Network usage:"
 msgstr ""
 
-#: lxc/image.go:135 lxc/publish.go:35
+#: lxc/image.go:137 lxc/publish.go:35
 msgid "New alias to define at target"
 msgstr ""
 
@@ -491,7 +500,7 @@ msgstr "Aucune empreinte n'a été spécifié."
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:453
+#: lxc/image.go:474
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
@@ -500,24 +509,19 @@ msgstr ""
 msgid "Options:"
 msgstr "Opération %s"
 
-#: lxc/image.go:573
-#, c-format
-msgid "Output is in %s"
-msgstr ""
-
 #: lxc/exec.go:57
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:503
+#: lxc/list.go:502
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:418
+#: lxc/list.go:417
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:419
+#: lxc/list.go:418
 msgid "PROFILES"
 msgstr ""
 
@@ -525,7 +529,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:677 lxc/remote.go:380
+#: lxc/image.go:831 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
@@ -565,7 +569,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:759
+#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:914
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -610,7 +614,7 @@ msgstr ""
 msgid "Profiles: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:379
+#: lxc/image.go:407
 msgid "Properties:"
 msgstr ""
 
@@ -618,7 +622,7 @@ msgstr ""
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:362
+#: lxc/image.go:390
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -650,20 +654,20 @@ msgstr ""
 msgid "Restart containers."
 msgstr "Liste de l'information sur les conteneurs.\n"
 
-#: lxc/init.go:227
+#: lxc/init.go:251
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:680
+#: lxc/image.go:834
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:420
+#: lxc/list.go:419
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:421
+#: lxc/list.go:420
 msgid "STATE"
 msgstr ""
 
@@ -711,7 +715,7 @@ msgstr ""
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:360
+#: lxc/image.go:388
 #, c-format
 msgid "Size: %.2fMB"
 msgstr ""
@@ -725,7 +729,7 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/image.go:393
+#: lxc/image.go:421
 msgid "Source:"
 msgstr ""
 
@@ -734,7 +738,7 @@ msgstr ""
 msgid "Start containers."
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/launch.go:134
+#: lxc/launch.go:59
 #, c-format
 msgid "Starting %s"
 msgstr ""
@@ -753,7 +757,7 @@ msgstr "L'arrêt du conteneur a échoué!"
 msgid "Stop the container if currently running"
 msgstr ""
 
-#: lxc/publish.go:121
+#: lxc/publish.go:134
 msgid "Stopping container failed!"
 msgstr "L'arrêt du conteneur a échoué!"
 
@@ -775,7 +779,7 @@ msgstr ""
 msgid "Swap (peak)"
 msgstr ""
 
-#: lxc/list.go:422
+#: lxc/list.go:421
 msgid "TYPE"
 msgstr ""
 
@@ -795,7 +799,7 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/init.go:284
+#: lxc/init.go:304
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -821,7 +825,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Temps d'attente avant de tuer le conteneur."
 
-#: lxc/image.go:363
+#: lxc/image.go:391
 msgid "Timestamps:"
 msgstr ""
 
@@ -829,12 +833,12 @@ msgstr ""
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:455
+#: lxc/image.go:499
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
 
-#: lxc/action.go:106 lxc/launch.go:147
+#: lxc/action.go:106 lxc/launch.go:77
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr ""
@@ -847,7 +851,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:681
+#: lxc/image.go:835
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -863,7 +867,7 @@ msgstr ""
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:368
+#: lxc/image.go:396
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1028,7 +1032,7 @@ msgid ""
 "Help page for the LXD client."
 msgstr ""
 
-#: lxc/image.go:61
+#: lxc/image.go:63
 msgid ""
 "Usage: lxc image <subcommand> [options]\n"
 "\n"
@@ -1130,7 +1134,7 @@ msgid ""
 "    lxc init ubuntu:16.04 u1"
 msgstr ""
 
-#: lxc/launch.go:24
+#: lxc/launch.go:20
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
 "profile|-p <profile>...] [--config|-c <key=value>...]\n"
@@ -1144,7 +1148,7 @@ msgid ""
 "    lxc launch ubuntu:16.04 u1"
 msgstr ""
 
-#: lxc/list.go:47
+#: lxc/list.go:46
 msgid ""
 "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
 "[--fast]\n"
@@ -1235,7 +1239,7 @@ msgid ""
 "    Only show log message."
 msgstr ""
 
-#: lxc/move.go:17
+#: lxc/move.go:20
 msgid ""
 "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
 "<snapshot>]]\n"
@@ -1431,11 +1435,6 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/launch.go:118
-#, fuzzy
-msgid "bad number of things scanned from image, container or snapshot"
-msgstr "nombre de propriété invalide pour la ressource"
-
 #: lxc/copy.go:108
 msgid "can't copy to the same container name"
 msgstr ""
@@ -1448,16 +1447,16 @@ msgstr ""
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:211 lxc/init.go:216 lxc/launch.go:99 lxc/launch.go:105
+#: lxc/init.go:273
 #, fuzzy
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
 
-#: lxc/image.go:354
+#: lxc/image.go:382
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:356
+#: lxc/image.go:384
 msgid "enabled"
 msgstr ""
 
@@ -1471,11 +1470,7 @@ msgstr "erreur: %v\n"
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/launch.go:123
-msgid "got bad version"
-msgstr "reçu une version invalide"
-
-#: lxc/image.go:349 lxc/image.go:657
+#: lxc/image.go:377 lxc/image.go:811
 msgid "no"
 msgstr ""
 
@@ -1530,7 +1525,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:45 lxc/image.go:351 lxc/image.go:661
+#: lxc/delete.go:45 lxc/image.go:379 lxc/image.go:815
 msgid "yes"
 msgstr ""
 
@@ -1538,6 +1533,13 @@ msgstr ""
 msgid "you must specify a source container name"
 msgstr ""
 
+#, fuzzy
+#~ msgid "bad number of things scanned from image, container or snapshot"
+#~ msgstr "nombre de propriété invalide pour la ressource"
+
+#~ msgid "got bad version"
+#~ msgstr "reçu une version invalide"
+
 #~ msgid "bad result type from action"
 #~ msgstr "mauvais type de réponse pour l'action!"
 
@@ -1574,10 +1576,6 @@ msgstr ""
 #~ "\n"
 
 #, fuzzy
-#~ msgid "Bad image property: %s"
-#~ msgstr "(Image invalide: %s\n"
-
-#, fuzzy
 #~ msgid ""
 #~ "Create a read-only snapshot of a container.\n"
 #~ "\n"
diff --git a/po/ja.po b/po/ja.po
index 053368199..7c262b406 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-24 02:25-0400\n"
+"POT-Creation-Date: 2017-09-25 10:46-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -37,7 +37,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:51
+#: lxc/image.go:53
 msgid ""
 "### This is a yaml representation of the image properties.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -68,7 +68,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:654
+#: lxc/image.go:808
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -81,15 +81,15 @@ msgstr "'/' はスナップショットの名前には使用できません。"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:675 lxc/image.go:704
+#: lxc/image.go:829 lxc/image.go:858
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:679
+#: lxc/image.go:833
 msgid "ARCH"
 msgstr ""
 
-#: lxc/list.go:415
+#: lxc/list.go:414
 msgid "ARCHITECTURE"
 msgstr ""
 
@@ -102,20 +102,25 @@ msgstr "証明書のフィンガープリントの確認なしで証明書を受
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
 
-#: lxc/image.go:383
+#: lxc/image.go:411
 msgid "Aliases:"
 msgstr "エイリアス:"
 
-#: lxc/image.go:361 lxc/info.go:104
+#: lxc/image.go:389 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr "アーキテクチャ: %s"
 
-#: lxc/image.go:391
+#: lxc/image.go:419
 #, c-format
 msgid "Auto update: %s"
 msgstr "自動更新: %s"
 
+#: lxc/image.go:493
+#, fuzzy, c-format
+msgid "Bad property: %s"
+msgstr "(不正なイメージプロパティ形式: %s\n"
+
 #: lxc/info.go:186
 msgid "Bytes received"
 msgstr "受信バイト数"
@@ -128,7 +133,7 @@ msgstr "送信バイト数"
 msgid "COMMON NAME"
 msgstr ""
 
-#: lxc/list.go:416
+#: lxc/list.go:415
 msgid "CREATED AT"
 msgstr ""
 
@@ -155,7 +160,7 @@ msgstr "証明書のフィンガープリント: %x"
 msgid "Client certificate stored at server: "
 msgstr "クライアント証明書がサーバに格納されました: "
 
-#: lxc/list.go:110 lxc/list.go:111
+#: lxc/list.go:109 lxc/list.go:110
 msgid "Columns"
 msgstr "カラムレイアウト"
 
@@ -167,7 +172,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:758 lxc/profile.go:233
+#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:913 lxc/profile.go:233
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
@@ -180,21 +185,21 @@ msgstr "接続が拒否されました。LXDが実行されていますか?"
 msgid "Container name is mandatory"
 msgstr "コンテナ名を指定する必要があります"
 
-#: lxc/init.go:221
+#: lxc/init.go:278
 #, c-format
 msgid "Container name is: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/publish.go:167 lxc/publish.go:182
+#: lxc/publish.go:247
 #, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "コンテナは以下のフィンガープリントで publish されます: %s"
 
-#: lxc/image.go:133
+#: lxc/image.go:135
 msgid "Copy aliases from source"
 msgstr "ソースからエイリアスをコピーしました"
 
-#: lxc/image.go:277
+#: lxc/image.go:307
 #, c-format
 msgid "Copying the image: %s"
 msgstr "イメージのコピー中: %s"
@@ -203,21 +208,21 @@ msgstr "イメージのコピー中: %s"
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
-#: lxc/image.go:366 lxc/info.go:106
+#: lxc/image.go:394 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr "作成日時: %s"
 
-#: lxc/init.go:186 lxc/launch.go:126
+#: lxc/init.go:188
 #, c-format
 msgid "Creating %s"
 msgstr "%s を作成中"
 
-#: lxc/init.go:184
+#: lxc/init.go:186
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:678 lxc/image.go:706
+#: lxc/image.go:832 lxc/image.go:860
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -236,7 +241,7 @@ msgstr "デバイス %s が %s から削除されました"
 msgid "Disk usage:"
 msgstr "  ディスク使用量:"
 
-#: lxc/list.go:501
+#: lxc/list.go:500
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -271,16 +276,21 @@ msgstr "Ephemeral コンテナ"
 msgid "Event type to listen for"
 msgstr "Listenするイベントタイプ"
 
-#: lxc/image.go:370
+#: lxc/image.go:398
 #, c-format
 msgid "Expires: %s"
 msgstr "失効日時: %s"
 
-#: lxc/image.go:372
+#: lxc/image.go:400
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/config.go:326 lxc/image.go:676 lxc/image.go:705
+#: lxc/image.go:677
+#, fuzzy, c-format
+msgid "Exporting the image: %s"
+msgstr "イメージのコピー中: %s"
+
+#: lxc/config.go:326 lxc/image.go:830 lxc/image.go:859
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -294,12 +304,12 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/list.go:113
+#: lxc/list.go:112
 #, fuzzy
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
-#: lxc/image.go:359
+#: lxc/image.go:387
 #, c-format
 msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
@@ -319,7 +329,7 @@ msgstr "停止したコンテナを強制的に削除します。"
 msgid "Force using the local unix socket"
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
-#: lxc/list.go:112
+#: lxc/list.go:111
 msgid "Format (table|json)"
 msgstr ""
 
@@ -327,11 +337,11 @@ msgstr ""
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "クライアント証明書を生成します。1分ぐらいかかります..."
 
-#: lxc/list.go:413
+#: lxc/list.go:412
 msgid "IPV4"
 msgstr ""
 
-#: lxc/list.go:414
+#: lxc/list.go:413
 msgid "IPV6"
 msgstr ""
 
@@ -354,20 +364,20 @@ msgstr "どのコマンドを実行するか決める際にエイリアスを無
 msgid "Ignore the container state (only for start)"
 msgstr "コンテナの状態を無視します (startのみ)。"
 
-#: lxc/image.go:280
+#: lxc/image.go:321
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:450 lxc/image.go:462
+#: lxc/image.go:727
+#, fuzzy
+msgid "Image exported successfully!"
+msgstr "イメージのコピーが成功しました!"
+
+#: lxc/image.go:551
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
 
-#: lxc/image.go:447
-#, fuzzy, c-format
-msgid "Importing the image: %s"
-msgstr "イメージのコピー中: %s"
-
 #: lxc/remote.go:136
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
@@ -396,7 +406,7 @@ msgstr "不正な送り先 %s"
 msgid "Ips:"
 msgstr "IPアドレス:"
 
-#: lxc/image.go:134
+#: lxc/image.go:136
 msgid "Keep the image up to date after initial copy"
 msgstr "最初にコピーした後も常にイメージを最新の状態に保つ"
 
@@ -404,12 +414,12 @@ msgstr "最初にコピーした後も常にイメージを最新の状態に保
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
-#: lxc/image.go:375
+#: lxc/image.go:403
 #, fuzzy, c-format
 msgid "Last used: %s"
 msgstr "状態: %s"
 
-#: lxc/image.go:377
+#: lxc/image.go:405
 msgid "Last used: never"
 msgstr ""
 
@@ -417,7 +427,7 @@ msgstr ""
 msgid "Log:"
 msgstr "ログ:"
 
-#: lxc/image.go:132
+#: lxc/image.go:134
 msgid "Make image public"
 msgstr "イメージを public にする"
 
@@ -462,7 +472,7 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "コンテナ名を指定する必要があります: "
 
-#: lxc/list.go:417 lxc/remote.go:377
+#: lxc/list.go:416 lxc/remote.go:377
 msgid "NAME"
 msgstr ""
 
@@ -480,7 +490,7 @@ msgstr "コンテナ名: %s"
 msgid "Network usage:"
 msgstr "  ネットワーク使用状況:"
 
-#: lxc/image.go:135 lxc/publish.go:35
+#: lxc/image.go:137 lxc/publish.go:35
 msgid "New alias to define at target"
 msgstr "新しいエイリアスを定義する"
 
@@ -496,7 +506,7 @@ msgstr "フィンガープリントが指定されていません。"
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
-#: lxc/image.go:453
+#: lxc/image.go:474
 msgid "Only https:// is supported for remote image import."
 msgstr "リモートイメージのインポートは https:// のみをサポートします。"
 
@@ -504,24 +514,19 @@ msgstr "リモートイメージのインポートは https:// のみをサポ
 msgid "Options:"
 msgstr "オプション:"
 
-#: lxc/image.go:573
-#, c-format
-msgid "Output is in %s"
-msgstr "%s に出力されます"
-
 #: lxc/exec.go:57
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)"
 
-#: lxc/list.go:503
+#: lxc/list.go:502
 msgid "PERSISTENT"
 msgstr ""
 
-#: lxc/list.go:418
+#: lxc/list.go:417
 msgid "PID"
 msgstr ""
 
-#: lxc/list.go:419
+#: lxc/list.go:418
 msgid "PROFILES"
 msgstr ""
 
@@ -529,7 +534,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:677 lxc/remote.go:380
+#: lxc/image.go:831 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
@@ -570,7 +575,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:759
+#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:914
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -618,7 +623,7 @@ msgstr "新しいコンテナに適用するプロファイル"
 msgid "Profiles: %s"
 msgstr "プロファイル: %s"
 
-#: lxc/image.go:379
+#: lxc/image.go:407
 msgid "Properties:"
 msgstr "プロパティ:"
 
@@ -626,7 +631,7 @@ msgstr "プロパティ:"
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
-#: lxc/image.go:362
+#: lxc/image.go:390
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -659,20 +664,20 @@ msgstr "リソース:"
 msgid "Restart containers."
 msgstr "コンテナを作成中"
 
-#: lxc/init.go:227
+#: lxc/init.go:251
 #, c-format
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:680
+#: lxc/image.go:834
 msgid "SIZE"
 msgstr ""
 
-#: lxc/list.go:420
+#: lxc/list.go:419
 msgid "SNAPSHOTS"
 msgstr ""
 
-#: lxc/list.go:421
+#: lxc/list.go:420
 msgid "STATE"
 msgstr ""
 
@@ -721,7 +726,7 @@ msgstr "コンテナログの最後の 100 行を表示しますか?"
 msgid "Show the expanded configuration"
 msgstr "拡張した設定を表示するかどうか"
 
-#: lxc/image.go:360
+#: lxc/image.go:388
 #, c-format
 msgid "Size: %.2fMB"
 msgstr "サイズ: %.2fMB"
@@ -735,7 +740,7 @@ msgstr "スナップショット:"
 msgid "Some containers failed to %s"
 msgstr "コンテナの停止に失敗しました!"
 
-#: lxc/image.go:393
+#: lxc/image.go:421
 msgid "Source:"
 msgstr "取得元:"
 
@@ -744,7 +749,7 @@ msgstr "取得元:"
 msgid "Start containers."
 msgstr "コンテナの不正なURL %s"
 
-#: lxc/launch.go:134
+#: lxc/launch.go:59
 #, c-format
 msgid "Starting %s"
 msgstr "%s を起動中"
@@ -763,7 +768,7 @@ msgstr "コンテナの停止に失敗しました!"
 msgid "Stop the container if currently running"
 msgstr "実行中の場合、コンテナを停止します"
 
-#: lxc/publish.go:121
+#: lxc/publish.go:134
 msgid "Stopping container failed!"
 msgstr "コンテナの停止に失敗しました!"
 
@@ -785,7 +790,7 @@ msgstr "Swap (現在値)"
 msgid "Swap (peak)"
 msgstr "Swap (ピーク)"
 
-#: lxc/list.go:422
+#: lxc/list.go:421
 msgid "TYPE"
 msgstr ""
 
@@ -806,7 +811,7 @@ msgstr ""
 msgid "The device doesn't exist"
 msgstr "デバイスが存在しません"
 
-#: lxc/init.go:284
+#: lxc/init.go:304
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -836,7 +841,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "コンテナを強制停止するまでの時間"
 
-#: lxc/image.go:363
+#: lxc/image.go:391
 msgid "Timestamps:"
 msgstr "タイムスタンプ:"
 
@@ -847,12 +852,12 @@ msgstr ""
 "だ\n"
 "さい。"
 
-#: lxc/image.go:455
+#: lxc/image.go:499
 #, fuzzy, c-format
 msgid "Transferring image: %s"
 msgstr "イメージを転送中: %d%%"
 
-#: lxc/action.go:106 lxc/launch.go:147
+#: lxc/action.go:106 lxc/launch.go:77
 #, c-format
 msgid "Try `lxc info --show-log %s` for more info"
 msgstr "更に情報を得るために `lxc info --show-log` を実行してみてください"
@@ -865,7 +870,7 @@ msgstr "タイプ: ephemeral"
 msgid "Type: persistent"
 msgstr "タイプ: persistent"
 
-#: lxc/image.go:681
+#: lxc/image.go:835
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -881,7 +886,7 @@ msgstr ""
 msgid "Unable to read remote TLS certificate"
 msgstr "リモートの TLS 証明書を読めません"
 
-#: lxc/image.go:368
+#: lxc/image.go:396
 #, c-format
 msgid "Uploaded: %s"
 msgstr "アップロード日時: %s"
@@ -1059,7 +1064,7 @@ msgid ""
 "Help page for the LXD client."
 msgstr ""
 
-#: lxc/image.go:61
+#: lxc/image.go:63
 #, fuzzy
 msgid ""
 "Usage: lxc image <subcommand> [options]\n"
@@ -1237,7 +1242,7 @@ msgstr ""
 "例:\n"
 "lxc init ubuntu u1"
 
-#: lxc/launch.go:24
+#: lxc/launch.go:20
 #, fuzzy
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
@@ -1264,7 +1269,7 @@ msgstr ""
 "例:\n"
 "lxc launch ubuntu:16.04 u1"
 
-#: lxc/list.go:47
+#: lxc/list.go:46
 #, fuzzy
 msgid ""
 "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
@@ -1403,7 +1408,7 @@ msgstr ""
 "例:\n"
 "lxc monitor --type=logging"
 
-#: lxc/move.go:17
+#: lxc/move.go:20
 #, fuzzy
 msgid ""
 "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
@@ -1705,11 +1710,6 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
-#: lxc/launch.go:118
-msgid "bad number of things scanned from image, container or snapshot"
-msgstr ""
-"イメージ、コンテナ、スナップショットのいずれかからスキャンされた数が不正"
-
 #: lxc/copy.go:108
 msgid "can't copy to the same container name"
 msgstr "同じコンテナ名へはコピーできません"
@@ -1722,17 +1722,17 @@ msgstr "デフォルトのリモートは削除できません"
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:211 lxc/init.go:216 lxc/launch.go:99 lxc/launch.go:105
+#: lxc/init.go:273
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
 "した"
 
-#: lxc/image.go:354
+#: lxc/image.go:382
 msgid "disabled"
 msgstr "無効"
 
-#: lxc/image.go:356
+#: lxc/image.go:384
 msgid "enabled"
 msgstr "有効"
 
@@ -1746,11 +1746,7 @@ msgstr "エラー: %v"
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/launch.go:123
-msgid "got bad version"
-msgstr "不正なバージョンを得ました"
-
-#: lxc/image.go:349 lxc/image.go:657
+#: lxc/image.go:377 lxc/image.go:811
 msgid "no"
 msgstr ""
 
@@ -1804,7 +1800,7 @@ msgstr "%s に取得しました"
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:45 lxc/image.go:351 lxc/image.go:661
+#: lxc/delete.go:45 lxc/image.go:379 lxc/image.go:815
 msgid "yes"
 msgstr ""
 
@@ -1812,6 +1808,16 @@ msgstr ""
 msgid "you must specify a source container name"
 msgstr "コピー元のコンテナ名を指定してください"
 
+#~ msgid "Output is in %s"
+#~ msgstr "%s に出力されます"
+
+#~ msgid "bad number of things scanned from image, container or snapshot"
+#~ msgstr ""
+#~ "イメージ、コンテナ、スナップショットのいずれかからスキャンされた数が不正"
+
+#~ msgid "got bad version"
+#~ msgstr "不正なバージョンを得ました"
+
 #~ msgid "bad result type from action"
 #~ msgstr "アクションからの結果タイプが不正です"
 
@@ -2030,10 +2036,6 @@ msgstr "コピー元のコンテナ名を指定してください"
 #~ msgid "Profile %s removed from %s"
 #~ msgstr "プロファイル %s が %s から削除されました"
 
-#, fuzzy
-#~ msgid "Bad image property: %s"
-#~ msgstr "(不正なイメージプロパティ形式: %s\n"
-
 #~ msgid "Cannot change profile name"
 #~ msgstr "プロファイル名を変更できません"
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 738be0c99..7c7baffb4 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-09-24 02:25-0400\n"
+        "POT-Creation-Date: 2017-09-25 10:46-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -36,7 +36,7 @@ msgid   "### This is a yaml representation of the configuration.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:51
+#: lxc/image.go:53
 msgid   "### This is a yaml representation of the image properties.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -65,7 +65,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:654
+#: lxc/image.go:808
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -78,15 +78,15 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:675 lxc/image.go:704
+#: lxc/image.go:829 lxc/image.go:858
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:679
+#: lxc/image.go:833
 msgid   "ARCH"
 msgstr  ""
 
-#: lxc/list.go:415
+#: lxc/list.go:414
 msgid   "ARCHITECTURE"
 msgstr  ""
 
@@ -99,20 +99,25 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:383
+#: lxc/image.go:411
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:361 lxc/info.go:104
+#: lxc/image.go:389 lxc/info.go:104
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:391
+#: lxc/image.go:419
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
 
+#: lxc/image.go:493
+#, c-format
+msgid   "Bad property: %s"
+msgstr  ""
+
 #: lxc/info.go:186
 msgid   "Bytes received"
 msgstr  ""
@@ -125,7 +130,7 @@ msgstr  ""
 msgid   "COMMON NAME"
 msgstr  ""
 
-#: lxc/list.go:416
+#: lxc/list.go:415
 msgid   "CREATED AT"
 msgstr  ""
 
@@ -152,7 +157,7 @@ msgstr  ""
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
-#: lxc/list.go:110 lxc/list.go:111
+#: lxc/list.go:109 lxc/list.go:110
 msgid   "Columns"
 msgstr  ""
 
@@ -164,7 +169,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:758 lxc/profile.go:233
+#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:913 lxc/profile.go:233
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -177,21 +182,21 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/init.go:221
+#: lxc/init.go:278
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
 
-#: lxc/publish.go:167 lxc/publish.go:182
+#: lxc/publish.go:247
 #, c-format
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
 
-#: lxc/image.go:133
+#: lxc/image.go:135
 msgid   "Copy aliases from source"
 msgstr  ""
 
-#: lxc/image.go:277
+#: lxc/image.go:307
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
@@ -200,21 +205,21 @@ msgstr  ""
 msgid   "Could not create server cert dir"
 msgstr  ""
 
-#: lxc/image.go:366 lxc/info.go:106
+#: lxc/image.go:394 lxc/info.go:106
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:186 lxc/launch.go:126
+#: lxc/init.go:188
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
 
-#: lxc/init.go:184
+#: lxc/init.go:186
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:678 lxc/image.go:706
+#: lxc/image.go:832 lxc/image.go:860
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -232,7 +237,7 @@ msgstr  ""
 msgid   "Disk usage:"
 msgstr  ""
 
-#: lxc/list.go:501
+#: lxc/list.go:500
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -264,16 +269,21 @@ msgstr  ""
 msgid   "Event type to listen for"
 msgstr  ""
 
-#: lxc/image.go:370
+#: lxc/image.go:398
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:372
+#: lxc/image.go:400
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/config.go:326 lxc/image.go:676 lxc/image.go:705
+#: lxc/image.go:677
+#, c-format
+msgid   "Exporting the image: %s"
+msgstr  ""
+
+#: lxc/config.go:326 lxc/image.go:830 lxc/image.go:859
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -287,11 +297,11 @@ msgstr  ""
 msgid   "Failed to generate 'lxc.1': %v"
 msgstr  ""
 
-#: lxc/list.go:113
+#: lxc/list.go:112
 msgid   "Fast mode (same as --columns=nsacPt)"
 msgstr  ""
 
-#: lxc/image.go:359
+#: lxc/image.go:387
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -308,7 +318,7 @@ msgstr  ""
 msgid   "Force using the local unix socket"
 msgstr  ""
 
-#: lxc/list.go:112
+#: lxc/list.go:111
 msgid   "Format (table|json)"
 msgstr  ""
 
@@ -316,11 +326,11 @@ msgstr  ""
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
-#: lxc/list.go:413
+#: lxc/list.go:412
 msgid   "IPV4"
 msgstr  ""
 
-#: lxc/list.go:414
+#: lxc/list.go:413
 msgid   "IPV6"
 msgstr  ""
 
@@ -340,18 +350,17 @@ msgstr  ""
 msgid   "Ignore the container state (only for start)"
 msgstr  ""
 
-#: lxc/image.go:280
+#: lxc/image.go:321
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:450 lxc/image.go:462
-#, c-format
-msgid   "Image imported with fingerprint: %s"
+#: lxc/image.go:727
+msgid   "Image exported successfully!"
 msgstr  ""
 
-#: lxc/image.go:447
+#: lxc/image.go:551
 #, c-format
-msgid   "Importing the image: %s"
+msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
 #: lxc/remote.go:136
@@ -381,7 +390,7 @@ msgstr  ""
 msgid   "Ips:"
 msgstr  ""
 
-#: lxc/image.go:134
+#: lxc/image.go:136
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
@@ -389,12 +398,12 @@ msgstr  ""
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/image.go:375
+#: lxc/image.go:403
 #, c-format
 msgid   "Last used: %s"
 msgstr  ""
 
-#: lxc/image.go:377
+#: lxc/image.go:405
 msgid   "Last used: never"
 msgstr  ""
 
@@ -402,7 +411,7 @@ msgstr  ""
 msgid   "Log:"
 msgstr  ""
 
-#: lxc/image.go:132
+#: lxc/image.go:134
 msgid   "Make image public"
 msgstr  ""
 
@@ -444,7 +453,7 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:417 lxc/remote.go:377
+#: lxc/list.go:416 lxc/remote.go:377
 msgid   "NAME"
 msgstr  ""
 
@@ -461,7 +470,7 @@ msgstr  ""
 msgid   "Network usage:"
 msgstr  ""
 
-#: lxc/image.go:135 lxc/publish.go:35
+#: lxc/image.go:137 lxc/publish.go:35
 msgid   "New alias to define at target"
 msgstr  ""
 
@@ -477,7 +486,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:453
+#: lxc/image.go:474
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -485,24 +494,19 @@ msgstr  ""
 msgid   "Options:"
 msgstr  ""
 
-#: lxc/image.go:573
-#, c-format
-msgid   "Output is in %s"
-msgstr  ""
-
 #: lxc/exec.go:57
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:503
+#: lxc/list.go:502
 msgid   "PERSISTENT"
 msgstr  ""
 
-#: lxc/list.go:418
+#: lxc/list.go:417
 msgid   "PID"
 msgstr  ""
 
-#: lxc/list.go:419
+#: lxc/list.go:418
 msgid   "PROFILES"
 msgstr  ""
 
@@ -510,7 +514,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:677 lxc/remote.go:380
+#: lxc/image.go:831 lxc/remote.go:380
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -547,7 +551,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:759
+#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:914
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -592,7 +596,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:379
+#: lxc/image.go:407
 msgid   "Properties:"
 msgstr  ""
 
@@ -600,7 +604,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:362
+#: lxc/image.go:390
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -631,20 +635,20 @@ msgstr  ""
 msgid   "Restart containers."
 msgstr  ""
 
-#: lxc/init.go:227
+#: lxc/init.go:251
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:680
+#: lxc/image.go:834
 msgid   "SIZE"
 msgstr  ""
 
-#: lxc/list.go:420
+#: lxc/list.go:419
 msgid   "SNAPSHOTS"
 msgstr  ""
 
-#: lxc/list.go:421
+#: lxc/list.go:420
 msgid   "STATE"
 msgstr  ""
 
@@ -692,7 +696,7 @@ msgstr  ""
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:360
+#: lxc/image.go:388
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -706,7 +710,7 @@ msgstr  ""
 msgid   "Some containers failed to %s"
 msgstr  ""
 
-#: lxc/image.go:393
+#: lxc/image.go:421
 msgid   "Source:"
 msgstr  ""
 
@@ -714,7 +718,7 @@ msgstr  ""
 msgid   "Start containers."
 msgstr  ""
 
-#: lxc/launch.go:134
+#: lxc/launch.go:59
 #, c-format
 msgid   "Starting %s"
 msgstr  ""
@@ -732,7 +736,7 @@ msgstr  ""
 msgid   "Stop the container if currently running"
 msgstr  ""
 
-#: lxc/publish.go:121
+#: lxc/publish.go:134
 msgid   "Stopping container failed!"
 msgstr  ""
 
@@ -753,7 +757,7 @@ msgstr  ""
 msgid   "Swap (peak)"
 msgstr  ""
 
-#: lxc/list.go:422
+#: lxc/list.go:421
 msgid   "TYPE"
 msgstr  ""
 
@@ -769,7 +773,7 @@ msgstr  ""
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:284
+#: lxc/init.go:304
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -793,7 +797,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:363
+#: lxc/image.go:391
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -801,12 +805,12 @@ msgstr  ""
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:455
+#: lxc/image.go:499
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
 
-#: lxc/action.go:106 lxc/launch.go:147
+#: lxc/action.go:106 lxc/launch.go:77
 #, c-format
 msgid   "Try `lxc info --show-log %s` for more info"
 msgstr  ""
@@ -819,7 +823,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:681
+#: lxc/image.go:835
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -835,7 +839,7 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:368
+#: lxc/image.go:396
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -979,7 +983,7 @@ msgid   "Usage: lxc help [--all]\n"
         "Help page for the LXD client."
 msgstr  ""
 
-#: lxc/image.go:61
+#: lxc/image.go:63
 msgid   "Usage: lxc image <subcommand> [options]\n"
         "\n"
         "Manipulate container images.\n"
@@ -1073,7 +1077,7 @@ msgid   "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e]
         "    lxc init ubuntu:16.04 u1"
 msgstr  ""
 
-#: lxc/launch.go:24
+#: lxc/launch.go:20
 msgid   "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
         "\n"
         "Create and start containers from images.\n"
@@ -1085,7 +1089,7 @@ msgid   "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-
         "    lxc launch ubuntu:16.04 u1"
 msgstr  ""
 
-#: lxc/list.go:47
+#: lxc/list.go:46
 msgid   "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] [--fast]\n"
         "\n"
         "List the existing containers.\n"
@@ -1167,7 +1171,7 @@ msgid   "Usage: lxc monitor [<remote>:] [--type=TYPE...]\n"
         "    Only show log message."
 msgstr  ""
 
-#: lxc/move.go:17
+#: lxc/move.go:20
 msgid   "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]]\n"
         "\n"
         "Move containers within or in between LXD instances.\n"
@@ -1342,10 +1346,6 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/launch.go:118
-msgid   "bad number of things scanned from image, container or snapshot"
-msgstr  ""
-
 #: lxc/copy.go:108
 msgid   "can't copy to the same container name"
 msgstr  ""
@@ -1358,15 +1358,15 @@ msgstr  ""
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:211 lxc/init.go:216 lxc/launch.go:99 lxc/launch.go:105
+#: lxc/init.go:273
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:354
+#: lxc/image.go:382
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:356
+#: lxc/image.go:384
 msgid   "enabled"
 msgstr  ""
 
@@ -1380,11 +1380,7 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/launch.go:123
-msgid   "got bad version"
-msgstr  ""
-
-#: lxc/image.go:349 lxc/image.go:657
+#: lxc/image.go:377 lxc/image.go:811
 msgid   "no"
 msgstr  ""
 
@@ -1438,7 +1434,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:351 lxc/image.go:661
+#: lxc/delete.go:45 lxc/image.go:379 lxc/image.go:815
 msgid   "yes"
 msgstr  ""
 

From 858b7f35cc5ebb2da29dbfe608f34ea483c633f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 15 Jun 2017 17:58:06 -0400
Subject: [PATCH 0991/1193] lxc/copy: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/copy.go | 222 ++++++++++++++++++++++++------------------------------------
 1 file changed, 88 insertions(+), 134 deletions(-)

diff --git a/lxc/copy.go b/lxc/copy.go
index 531945c15..e08e4d94e 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -4,10 +4,9 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/i18n"
 )
@@ -33,197 +32,151 @@ func (c *copyCmd) flags() {
 }
 
 func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string, destResource string, keepVolatile bool, ephemeral int) error {
+	// Parse the source
 	sourceRemote, sourceName, err := conf.ParseRemote(sourceResource)
 	if err != nil {
 		return err
 	}
 
+	// Parse the destination
 	destRemote, destName, err := conf.ParseRemote(destResource)
 	if err != nil {
 		return err
 	}
 
+	// Make sure we have a container or snapshot name
 	if sourceName == "" {
-		return fmt.Errorf(i18n.G("you must specify a source container name"))
+		return fmt.Errorf(i18n.G("You must specify a source container name"))
 	}
 
+	// If no destination name was provided, use the same as the source
 	if destName == "" {
 		destName = sourceName
 	}
 
-	source, err := lxd.NewClient(conf.Legacy(), sourceRemote)
+	// Connect to the source host
+	source, err := conf.GetContainerServer(sourceRemote)
 	if err != nil {
 		return err
 	}
 
-	var status struct {
-		Architecture string
-		Devices      map[string]map[string]string
-		Config       map[string]string
-		Profiles     []string
-	}
-
-	// TODO: presumably we want to do this for copying snapshots too? We
-	// need to think a bit more about how we track the baseImage in the
-	// face of LVM and snapshots in general; this will probably make more
-	// sense once that work is done.
-	baseImage := ""
-
-	if !shared.IsSnapshot(sourceName) {
-		result, err := source.ContainerInfo(sourceName)
+	// Connect to the destination host
+	var dest lxd.ContainerServer
+	if sourceRemote == destRemote {
+		// Source and destination are the same
+		dest = source
+	} else {
+		// Destination is different, connect to it
+		dest, err = conf.GetContainerServer(destRemote)
 		if err != nil {
 			return err
 		}
+	}
 
-		status.Architecture = result.Architecture
-		status.Devices = result.Devices
-		status.Config = result.Config
-		status.Profiles = result.Profiles
+	var op *lxd.RemoteOperation
+	if shared.IsSnapshot(sourceName) {
+		// Prepare the container creation request
+		args := lxd.ContainerSnapshotCopyArgs{
+			Name: destName,
+		}
 
-	} else {
-		result, err := source.SnapshotInfo(sourceName)
+		// Copy of a snapshot into a new container
+		srcFields := strings.SplitN(sourceName, shared.SnapshotDelimiter, 2)
+		entry, _, err := source.GetContainerSnapshot(srcFields[0], srcFields[1])
 		if err != nil {
 			return err
 		}
 
-		status.Architecture = result.Architecture
-		status.Devices = result.Devices
-		status.Config = result.Config
-		status.Profiles = result.Profiles
-	}
+		// Allow overriding the ephemeral status
+		if ephemeral == 1 {
+			entry.Ephemeral = true
+		} else if ephemeral == 0 {
+			entry.Ephemeral = false
+		}
 
-	baseImage = status.Config["volatile.base_image"]
+		// Strip the volatile keys if requested
+		if !keepVolatile {
+			for k := range entry.Config {
+				if k == "volatile.base_image" {
+					continue
+				}
 
-	if !keepVolatile {
-		for k := range status.Config {
-			if strings.HasPrefix(k, "volatile") {
-				delete(status.Config, k)
+				if strings.HasPrefix(k, "volatile") {
+					delete(entry.Config, k)
+				}
 			}
 		}
-	}
-
-	// Do a local copy if the remotes are the same, otherwise do a migration
-	if sourceRemote == destRemote {
-		if sourceName == destName {
-			return fmt.Errorf(i18n.G("can't copy to the same container name"))
-		}
 
-		cp, err := source.LocalCopy(sourceName, destName, status.Config, status.Profiles, ephemeral == 1)
+		// Do the actual copy
+		op, err = dest.CopyContainerSnapshot(source, *entry, &args)
 		if err != nil {
 			return err
 		}
+	} else {
+		// Prepare the container creation request
+		args := lxd.ContainerCopyArgs{
+			Name: destName,
+		}
 
-		return source.WaitForSuccess(cp.Operation)
-	}
-
-	dest, err := lxd.NewClient(conf.Legacy(), destRemote)
-	if err != nil {
-		return err
-	}
-
-	sourceProfs := shared.NewStringSet(status.Profiles)
-	destProfs, err := dest.ListProfiles()
-	if err != nil {
-		return err
-	}
-
-	if !sourceProfs.IsSubset(shared.NewStringSet(destProfs)) {
-		return fmt.Errorf(i18n.G("not all the profiles from the source exist on the target"))
-	}
-
-	if ephemeral == -1 {
-		ct, err := source.ContainerInfo(sourceName)
+		// Copy of a container into a new container
+		entry, _, err := source.GetContainer(sourceName)
 		if err != nil {
 			return err
 		}
 
-		if ct.Ephemeral {
-			ephemeral = 1
-		} else {
-			ephemeral = 0
+		// Allow overriding the ephemeral status
+		if ephemeral == 1 {
+			entry.Ephemeral = true
+		} else if ephemeral == 0 {
+			entry.Ephemeral = false
 		}
-	}
 
-	sourceWSResponse, err := source.GetMigrationSourceWS(sourceName)
-	if err != nil {
-		return err
-	}
-
-	secrets := map[string]string{}
+		// Strip the volatile keys if requested
+		if !keepVolatile {
+			for k := range entry.Config {
+				if k == "volatile.base_image" {
+					continue
+				}
 
-	op, err := sourceWSResponse.MetadataAsOperation()
-	if err != nil {
-		return err
-	}
+				if strings.HasPrefix(k, "volatile") {
+					delete(entry.Config, k)
+				}
+			}
+		}
 
-	for k, v := range op.Metadata {
-		secrets[k] = v.(string)
+		// Do the actual copy
+		op, err = dest.CopyContainer(source, *entry, &args)
+		if err != nil {
+			return err
+		}
 	}
 
-	addresses, err := source.Addresses()
+	// Wait for the copy to complete
+	err = op.Wait()
 	if err != nil {
 		return err
 	}
 
-	/* Since we're trying a bunch of different network ports that
-	 * may be invalid, we can get "bad handshake" errors when the
-	 * websocket code tries to connect. If the first error is a
-	 * real error, but the subsequent errors are only network
-	 * errors, we should try to report the first real error. Of
-	 * course, if all the errors are websocket errors, let's just
-	 * report that.
-	 */
-	waitchan := make(chan map[int]error, 2)
-	wait := func(cli *lxd.Client, op string, ch chan map[int]error, senderid int) {
-		ch <- map[int]error{senderid: cli.WaitForSuccess(op)}
-	}
-
-	var migrationErrFromClient error
-	for _, addr := range addresses {
-		var migration *api.Response
-
-		sourceWSUrl := "https://" + addr + sourceWSResponse.Operation
-		migration, migrationErrFromClient = dest.MigrateFrom(destName, sourceWSUrl, source.Certificate, secrets, status.Architecture, status.Config, status.Devices, status.Profiles, baseImage, ephemeral == 1)
-		if migrationErrFromClient != nil {
-			continue
-		}
-
-		destOpId := 0
-		go wait(dest, migration.Operation, waitchan, destOpId)
-		sourceOpId := 1
-		go wait(source, sourceWSResponse.Operation, waitchan, sourceOpId)
-
-		var sourceOpErr error
-		var destOpErr error
-		for i := 0; i < cap(waitchan); i++ {
-			tmp := <-waitchan
-			err, ok := tmp[sourceOpId]
-			if ok {
-				sourceOpErr = err
-			} else {
-				destOpErr = err
-			}
-		}
-
-		if destOpErr != nil {
-			continue
+	// If choosing a random name, show it to the user
+	if destResource == "" {
+		// Get the succesful operation data
+		opInfo, err := op.GetTarget()
+		if err != nil {
+			return err
 		}
 
-		if sourceOpErr != nil {
-			return sourceOpErr
+		// Extract the list of affected containers
+		containers, ok := opInfo.Resources["containers"]
+		if !ok || len(containers) != 1 {
+			return fmt.Errorf(i18n.G("Failed to get the new container name"))
 		}
 
-		return nil
-	}
-
-	// Check for an error at the source
-	sourceOp, sourceErr := source.GetOperation(sourceWSResponse.Operation)
-	if sourceErr == nil && sourceOp.Err != "" {
-		return fmt.Errorf(i18n.G("Migration failed on source host: %s"), sourceOp.Err)
+		// Extract the name of the container
+		fields := strings.Split(containers[0], "/")
+		fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
 	}
 
-	// Return the error from destination
-	return fmt.Errorf(i18n.G("Migration failed on target host: %s"), migrationErrFromClient)
+	return nil
 }
 
 func (c *copyCmd) run(conf *config.Config, args []string) error {
@@ -231,6 +184,7 @@ func (c *copyCmd) run(conf *config.Config, args []string) error {
 		return errArgs
 	}
 
+	// For copies, default to non-ephemeral and allow override (move uses -1)
 	ephem := 0
 	if c.ephem {
 		ephem = 1

From cfe2691d361fd7a6e1fce0e794c23d22c75681f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Jun 2017 17:32:46 -0400
Subject: [PATCH 0992/1193] lxc/config: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config.go | 369 ++++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 243 insertions(+), 126 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index 5702ed42e..ad4367582 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"crypto/x509"
+	"encoding/base64"
 	"encoding/pem"
 	"fmt"
 	"io/ioutil"
@@ -13,7 +14,7 @@ import (
 	"github.com/olekukonko/tablewriter"
 	"gopkg.in/yaml.v2"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -136,12 +137,12 @@ func (c *configCmd) doSet(conf *config.Config, args []string, unset bool) error
 	}
 
 	// [[lxc config]] set dakara:c1 limits.memory 200000
-	remote, container, err := conf.ParseRemote(args[1])
+	remote, name, err := conf.ParseRemote(args[1])
 	if err != nil {
 		return err
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -157,19 +158,28 @@ func (c *configCmd) doSet(conf *config.Config, args []string, unset bool) error
 		value = string(buf[:])
 	}
 
-	if unset {
-		st, err := d.ContainerInfo(container)
-		if err != nil {
-			return err
-		}
+	container, etag, err := d.GetContainer(name)
+	if err != nil {
+		return err
+	}
 
-		_, ok := st.Config[key]
+	if unset {
+		_, ok := container.Config[key]
 		if !ok {
-			return fmt.Errorf(i18n.G("Can't unset key '%s', it's not currently set."), key)
+			return fmt.Errorf(i18n.G("Can't unset key '%s', it's not currently set"), key)
 		}
+
+		delete(container.Config, key)
+	} else {
+		container.Config[key] = value
+	}
+
+	op, err := d.UpdateContainer(name, container.Writable(), etag)
+	if err != nil {
+		return err
 	}
 
-	return d.SetContainerConfig(container, key, value)
+	return op.Wait()
 }
 
 func (c *configCmd) run(conf *config.Config, args []string) error {
@@ -186,23 +196,23 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 
 		// Deal with local server
 		if len(args) == 2 {
-			c, err := lxd.NewClient(conf.Legacy(), conf.DefaultRemote)
+			c, err := conf.GetContainerServer(conf.DefaultRemote)
 			if err != nil {
 				return err
 			}
 
-			ss, err := c.ServerStatus()
+			server, etag, err := c.GetServer()
 			if err != nil {
 				return err
 			}
 
-			_, ok := ss.Config[args[1]]
+			_, ok := server.Config[args[1]]
 			if !ok {
 				return fmt.Errorf(i18n.G("Can't unset key '%s', it's not currently set."), args[1])
 			}
 
-			_, err = c.SetServerConfig(args[1], "")
-			return err
+			delete(server.Config, args[1])
+			return c.UpdateServer(server.Writable(), etag)
 		}
 
 		// Deal with remote server
@@ -212,23 +222,23 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 		}
 
 		if container == "" {
-			c, err := lxd.NewClient(conf.Legacy(), remote)
+			c, err := conf.GetContainerServer(remote)
 			if err != nil {
 				return err
 			}
 
-			ss, err := c.ServerStatus()
+			server, etag, err := c.GetServer()
 			if err != nil {
 				return err
 			}
 
-			_, ok := ss.Config[args[1]]
+			_, ok := server.Config[args[2]]
 			if !ok {
 				return fmt.Errorf(i18n.G("Can't unset key '%s', it's not currently set."), args[1])
 			}
 
-			_, err = c.SetServerConfig(args[2], "")
-			return err
+			delete(server.Config, args[2])
+			return c.UpdateServer(server.Writable(), etag)
 		}
 
 		// Deal with container
@@ -242,13 +252,19 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 
 		// Deal with local server
 		if len(args) == 3 {
-			c, err := lxd.NewClient(conf.Legacy(), conf.DefaultRemote)
+			c, err := conf.GetContainerServer(conf.DefaultRemote)
 			if err != nil {
 				return err
 			}
 
-			_, err = c.SetServerConfig(args[1], args[2])
-			return err
+			server, etag, err := c.GetServer()
+			if err != nil {
+				return err
+			}
+
+			server.Config[args[1]] = args[2]
+
+			return c.UpdateServer(server.Writable(), etag)
 		}
 
 		// Deal with remote server
@@ -258,13 +274,19 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 		}
 
 		if container == "" {
-			c, err := lxd.NewClient(conf.Legacy(), remote)
+			c, err := conf.GetContainerServer(remote)
 			if err != nil {
 				return err
 			}
 
-			_, err = c.SetServerConfig(args[2], args[3])
-			return err
+			server, etag, err := c.GetServer()
+			if err != nil {
+				return err
+			}
+
+			server.Config[args[2]] = args[3]
+
+			return c.UpdateServer(server.Writable(), etag)
 		}
 
 		// Deal with container
@@ -288,12 +310,12 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 				remote = conf.DefaultRemote
 			}
 
-			d, err := lxd.NewClient(conf.Legacy(), remote)
+			d, err := conf.GetContainerServer(remote)
 			if err != nil {
 				return err
 			}
 
-			trust, err := d.CertificateList()
+			trust, err := d.GetCertificates()
 			if err != nil {
 				return err
 			}
@@ -346,19 +368,24 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 				remote = conf.DefaultRemote
 			}
 
-			d, err := lxd.NewClient(conf.Legacy(), remote)
+			d, err := conf.GetContainerServer(remote)
 			if err != nil {
 				return err
 			}
 
 			fname := args[len(args)-1]
-			cert, err := shared.ReadCert(fname)
+			x509Cert, err := shared.ReadCert(fname)
 			if err != nil {
 				return err
 			}
-
 			name, _ := shared.SplitExt(fname)
-			return d.CertificateAdd(cert, name)
+
+			cert := api.CertificatesPost{}
+			cert.Certificate = base64.StdEncoding.EncodeToString(x509Cert.Raw)
+			cert.Name = name
+			cert.Type = "client"
+
+			return d.CreateCertificate(cert)
 		case "remove":
 			var remote string
 			if len(args) < 3 {
@@ -373,12 +400,12 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 				remote = conf.DefaultRemote
 			}
 
-			d, err := lxd.NewClient(conf.Legacy(), remote)
+			d, err := conf.GetContainerServer(remote)
 			if err != nil {
 				return err
 			}
 
-			return d.CertificateRemove(args[len(args)-1])
+			return d.DeleteCertificate(args[len(args)-1])
 		default:
 			return errArgs
 		}
@@ -394,7 +421,7 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 			}
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
@@ -402,7 +429,7 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 		var data []byte
 
 		if len(args) == 1 || container == "" {
-			server, err := d.ServerStatus()
+			server, _, err := d.GetServer()
 			if err != nil {
 				return err
 			}
@@ -415,7 +442,9 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 		} else {
 			var brief api.ContainerPut
 			if shared.IsSnapshot(container) {
-				snap, err := d.SnapshotInfo(container)
+				fields := strings.Split(container, shared.SnapshotDelimiter)
+
+				snap, _, err := d.GetContainerSnapshot(fields[0], fields[1])
 				if err != nil {
 					return err
 				}
@@ -435,7 +464,7 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 					}
 				}
 			} else {
-				container, err := d.ContainerInfo(container)
+				container, _, err := d.GetContainer(container)
 				if err != nil {
 					return err
 				}
@@ -474,19 +503,19 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 			key = args[2]
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
 
 		if container != "" {
-			resp, err := d.ContainerInfo(container)
+			resp, _, err := d.GetContainer(container)
 			if err != nil {
 				return err
 			}
 			fmt.Println(resp.Config[key])
 		} else {
-			resp, err := d.ServerStatus()
+			resp, _, err := d.GetServer()
 			if err != nil {
 				return err
 			}
@@ -543,7 +572,7 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 			}
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
@@ -561,7 +590,7 @@ func (c *configCmd) run(conf *config.Config, args []string) error {
 	return errArgs
 }
 
-func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error {
+func (c *configCmd) doContainerConfigEdit(client lxd.ContainerServer, cont string) error {
 	// If stdin isn't a terminal, read text from it
 	if !termios.IsTerminal(int(syscall.Stdin)) {
 		contents, err := ioutil.ReadAll(os.Stdin)
@@ -574,11 +603,17 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 		if err != nil {
 			return err
 		}
-		return client.UpdateContainerConfig(cont, newdata)
+
+		op, err := client.UpdateContainer(cont, newdata, "")
+		if err != nil {
+			return err
+		}
+
+		return op.Wait()
 	}
 
 	// Extract the current value
-	container, err := client.ContainerInfo(cont)
+	container, etag, err := client.GetContainer(cont)
 	if err != nil {
 		return err
 	}
@@ -600,7 +635,11 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 		newdata := api.ContainerPut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
-			err = client.UpdateContainerConfig(cont, newdata)
+			var op *lxd.Operation
+			op, err = client.UpdateContainer(cont, newdata, etag)
+			if err == nil {
+				err = op.Wait()
+			}
 		}
 
 		// Respawn the editor
@@ -621,10 +660,11 @@ func (c *configCmd) doContainerConfigEdit(client *lxd.Client, cont string) error
 		}
 		break
 	}
+
 	return nil
 }
 
-func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
+func (c *configCmd) doDaemonConfigEdit(client lxd.ContainerServer) error {
 	// If stdin isn't a terminal, read text from it
 	if !termios.IsTerminal(int(syscall.Stdin)) {
 		contents, err := ioutil.ReadAll(os.Stdin)
@@ -638,12 +678,11 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 			return err
 		}
 
-		_, err = client.UpdateServerConfig(newdata)
-		return err
+		return client.UpdateServer(newdata, "")
 	}
 
 	// Extract the current value
-	server, err := client.ServerStatus()
+	server, etag, err := client.GetServer()
 	if err != nil {
 		return err
 	}
@@ -665,7 +704,7 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 		newdata := api.ServerPut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
-			_, err = client.UpdateServerConfig(newdata)
+			err = client.UpdateServer(newdata, etag)
 		}
 
 		// Respawn the editor
@@ -686,6 +725,7 @@ func (c *configCmd) doDaemonConfigEdit(client *lxd.Client) error {
 		}
 		break
 	}
+
 	return nil
 }
 
@@ -699,36 +739,69 @@ func (c *configCmd) deviceAdd(conf *config.Config, which string, args []string)
 		return err
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
 
 	devname := args[3]
-	devtype := args[4]
-	var props []string
+	device := map[string]string{}
+	device["type"] = args[4]
 	if len(args) > 5 {
-		props = args[5:]
-	} else {
-		props = []string{}
+		for _, prop := range args[5:] {
+			results := strings.SplitN(prop, "=", 2)
+			if len(results) != 2 {
+				return fmt.Errorf("No value found in %q", prop)
+			}
+			k := results[0]
+			v := results[1]
+			device[k] = v
+		}
 	}
 
-	var resp *api.Response
 	if which == "profile" {
-		resp, err = client.ProfileDeviceAdd(name, devname, devtype, props)
+		profile, etag, err := client.GetProfile(name)
+		if err != nil {
+			return err
+		}
+
+		_, ok := profile.Devices[devname]
+		if ok {
+			return fmt.Errorf(i18n.G("The device already exists"))
+		}
+
+		profile.Devices[devname] = device
+
+		err = client.UpdateProfile(name, profile.Writable(), etag)
+		if err != nil {
+			return err
+		}
 	} else {
-		resp, err = client.ContainerDeviceAdd(name, devname, devtype, props)
-	}
-	if err != nil {
-		return err
-	}
-	if which != "profile" {
-		err = client.WaitForSuccess(resp.Operation)
-	}
-	if err == nil {
-		fmt.Printf(i18n.G("Device %s added to %s")+"\n", devname, name)
+		container, etag, err := client.GetContainer(name)
+		if err != nil {
+			return err
+		}
+
+		_, ok := container.Devices[devname]
+		if ok {
+			return fmt.Errorf(i18n.G("The device already exists"))
+		}
+
+		container.Devices[devname] = device
+
+		op, err := client.UpdateContainer(name, container.Writable(), etag)
+		if err != nil {
+			return err
+		}
+
+		err = op.Wait()
+		if err != nil {
+			return err
+		}
 	}
-	return err
+
+	fmt.Printf(i18n.G("Device %s added to %s")+"\n", devname, name)
+	return nil
 }
 
 func (c *configCmd) deviceGet(conf *config.Config, which string, args []string) error {
@@ -741,7 +814,7 @@ func (c *configCmd) deviceGet(conf *config.Config, which string, args []string)
 		return err
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -750,24 +823,24 @@ func (c *configCmd) deviceGet(conf *config.Config, which string, args []string)
 	key := args[4]
 
 	if which == "profile" {
-		st, err := client.ProfileConfig(name)
+		profile, _, err := client.GetProfile(name)
 		if err != nil {
 			return err
 		}
 
-		dev, ok := st.Devices[devname]
+		dev, ok := profile.Devices[devname]
 		if !ok {
 			return fmt.Errorf(i18n.G("The device doesn't exist"))
 		}
 
 		fmt.Println(dev[key])
 	} else {
-		st, err := client.ContainerInfo(name)
+		container, _, err := client.GetContainer(name)
 		if err != nil {
 			return err
 		}
 
-		dev, ok := st.Devices[devname]
+		dev, ok := container.Devices[devname]
 		if !ok {
 			return fmt.Errorf(i18n.G("The device doesn't exist"))
 		}
@@ -788,7 +861,7 @@ func (c *configCmd) deviceSet(conf *config.Config, which string, args []string)
 		return err
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -798,44 +871,49 @@ func (c *configCmd) deviceSet(conf *config.Config, which string, args []string)
 	value := args[5]
 
 	if which == "profile" {
-		st, err := client.ProfileConfig(name)
+		profile, etag, err := client.GetProfile(name)
 		if err != nil {
 			return err
 		}
 
-		dev, ok := st.Devices[devname]
+		dev, ok := profile.Devices[devname]
 		if !ok {
 			return fmt.Errorf(i18n.G("The device doesn't exist"))
 		}
 
 		dev[key] = value
-		st.Devices[devname] = dev
+		profile.Devices[devname] = dev
 
-		err = client.PutProfile(name, st.Writable())
+		err = client.UpdateProfile(name, profile.Writable(), etag)
 		if err != nil {
 			return err
 		}
 	} else {
-		st, err := client.ContainerInfo(name)
+		container, etag, err := client.GetContainer(name)
 		if err != nil {
 			return err
 		}
 
-		dev, ok := st.Devices[devname]
+		dev, ok := container.Devices[devname]
 		if !ok {
 			return fmt.Errorf(i18n.G("The device doesn't exist"))
 		}
 
 		dev[key] = value
-		st.Devices[devname] = dev
+		container.Devices[devname] = dev
 
-		err = client.UpdateContainerConfig(name, st.Writable())
+		op, err := client.UpdateContainer(name, container.Writable(), etag)
+		if err != nil {
+			return err
+		}
+
+		err = op.Wait()
 		if err != nil {
 			return err
 		}
 	}
 
-	return err
+	return nil
 }
 
 func (c *configCmd) deviceUnset(conf *config.Config, which string, args []string) error {
@@ -848,7 +926,7 @@ func (c *configCmd) deviceUnset(conf *config.Config, which string, args []string
 		return err
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -857,44 +935,47 @@ func (c *configCmd) deviceUnset(conf *config.Config, which string, args []string
 	key := args[4]
 
 	if which == "profile" {
-		st, err := client.ProfileConfig(name)
+		profile, etag, err := client.GetProfile(name)
 		if err != nil {
 			return err
 		}
 
-		dev, ok := st.Devices[devname]
+		dev, ok := profile.Devices[devname]
 		if !ok {
 			return fmt.Errorf(i18n.G("The device doesn't exist"))
 		}
-
 		delete(dev, key)
-		st.Devices[devname] = dev
+		profile.Devices[devname] = dev
 
-		err = client.PutProfile(name, st.Writable())
+		err = client.UpdateProfile(name, profile.Writable(), etag)
 		if err != nil {
 			return err
 		}
 	} else {
-		st, err := client.ContainerInfo(name)
+		container, etag, err := client.GetContainer(name)
 		if err != nil {
 			return err
 		}
 
-		dev, ok := st.Devices[devname]
+		dev, ok := container.Devices[devname]
 		if !ok {
 			return fmt.Errorf(i18n.G("The device doesn't exist"))
 		}
-
 		delete(dev, key)
-		st.Devices[devname] = dev
+		container.Devices[devname] = dev
+
+		op, err := client.UpdateContainer(name, container.Writable(), etag)
+		if err != nil {
+			return err
+		}
 
-		err = client.UpdateContainerConfig(name, st.Writable())
+		err = op.Wait()
 		if err != nil {
 			return err
 		}
 	}
 
-	return err
+	return nil
 }
 
 func (c *configCmd) deviceRm(conf *config.Config, which string, args []string) error {
@@ -907,28 +988,54 @@ func (c *configCmd) deviceRm(conf *config.Config, which string, args []string) e
 		return err
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
 
 	devname := args[3]
-	var resp *api.Response
+
 	if which == "profile" {
-		resp, err = client.ProfileDeviceDelete(name, devname)
+		profile, etag, err := client.GetProfile(name)
+		if err != nil {
+			return err
+		}
+
+		_, ok := profile.Devices[devname]
+		if !ok {
+			return fmt.Errorf(i18n.G("The device doesn't exist"))
+		}
+		delete(profile.Devices, devname)
+
+		err = client.UpdateProfile(name, profile.Writable(), etag)
+		if err != nil {
+			return err
+		}
 	} else {
-		resp, err = client.ContainerDeviceDelete(name, devname)
-	}
-	if err != nil {
-		return err
-	}
-	if which != "profile" {
-		err = client.WaitForSuccess(resp.Operation)
-	}
-	if err == nil {
-		fmt.Printf(i18n.G("Device %s removed from %s")+"\n", devname, name)
+		container, etag, err := client.GetContainer(name)
+		if err != nil {
+			return err
+		}
+
+		_, ok := container.Devices[devname]
+		if !ok {
+			return fmt.Errorf(i18n.G("The device doesn't exist"))
+		}
+		delete(container.Devices, devname)
+
+		op, err := client.UpdateContainer(name, container.Writable(), etag)
+		if err != nil {
+			return err
+		}
+
+		err = op.Wait()
+		if err != nil {
+			return err
+		}
 	}
-	return err
+
+	fmt.Printf(i18n.G("Device %s removed from %s")+"\n", devname, name)
+	return nil
 }
 
 func (c *configCmd) deviceList(conf *config.Config, which string, args []string) error {
@@ -941,22 +1048,33 @@ func (c *configCmd) deviceList(conf *config.Config, which string, args []string)
 		return err
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
 
-	var resp []string
+	var devices []string
 	if which == "profile" {
-		resp, err = client.ProfileListDevices(name)
+		profile, _, err := client.GetProfile(name)
+		if err != nil {
+			return err
+		}
+
+		for k := range profile.Devices {
+			devices = append(devices, k)
+		}
 	} else {
-		resp, err = client.ContainerListDevices(name)
-	}
-	if err != nil {
-		return err
+		container, _, err := client.GetContainer(name)
+		if err != nil {
+			return err
+		}
+
+		for k := range container.Devices {
+			devices = append(devices, k)
+		}
 	}
-	fmt.Printf("%s\n", strings.Join(resp, "\n"))
 
+	fmt.Printf("%s\n", strings.Join(devices, "\n"))
 	return nil
 }
 
@@ -970,26 +1088,26 @@ func (c *configCmd) deviceShow(conf *config.Config, which string, args []string)
 		return err
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
 
 	var devices map[string]map[string]string
 	if which == "profile" {
-		resp, err := client.ProfileConfig(name)
+		profile, _, err := client.GetProfile(name)
 		if err != nil {
 			return err
 		}
 
-		devices = resp.Devices
+		devices = profile.Devices
 	} else {
-		resp, err := client.ContainerInfo(name)
+		container, _, err := client.GetContainer(name)
 		if err != nil {
 			return err
 		}
 
-		devices = resp.Devices
+		devices = container.Devices
 	}
 
 	data, err := yaml.Marshal(&devices)
@@ -998,6 +1116,5 @@ func (c *configCmd) deviceShow(conf *config.Config, which string, args []string)
 	}
 
 	fmt.Printf(string(data))
-
 	return nil
 }

From 0068fdad614d8a36501481b60f7d4175da59d86c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Jun 2017 18:11:47 -0400
Subject: [PATCH 0993/1193] lxc/profile: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/profile.go | 113 +++++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 73 insertions(+), 40 deletions(-)

diff --git a/lxc/profile.go b/lxc/profile.go
index 7812f361c..88cff5f68 100644
--- a/lxc/profile.go
+++ b/lxc/profile.go
@@ -9,7 +9,7 @@ import (
 
 	"gopkg.in/yaml.v2"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -139,7 +139,7 @@ func (c *profileCmd) run(conf *config.Config, args []string) error {
 		return err
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -179,15 +179,18 @@ func (c *profileCmd) run(conf *config.Config, args []string) error {
 	}
 }
 
-func (c *profileCmd) doProfileCreate(client *lxd.Client, p string) error {
-	err := client.ProfileCreate(p)
+func (c *profileCmd) doProfileCreate(client lxd.ContainerServer, p string) error {
+	profile := api.ProfilesPost{}
+	profile.Name = p
+
+	err := client.CreateProfile(profile)
 	if err == nil {
 		fmt.Printf(i18n.G("Profile %s created")+"\n", p)
 	}
 	return err
 }
 
-func (c *profileCmd) doProfileEdit(client *lxd.Client, p string) error {
+func (c *profileCmd) doProfileEdit(client lxd.ContainerServer, p string) error {
 	// If stdin isn't a terminal, read text from it
 	if !termios.IsTerminal(int(syscall.Stdin)) {
 		contents, err := ioutil.ReadAll(os.Stdin)
@@ -200,11 +203,12 @@ func (c *profileCmd) doProfileEdit(client *lxd.Client, p string) error {
 		if err != nil {
 			return err
 		}
-		return client.PutProfile(p, newdata)
+
+		return client.UpdateProfile(p, newdata, "")
 	}
 
 	// Extract the current value
-	profile, err := client.ProfileConfig(p)
+	profile, etag, err := client.GetProfile(p)
 	if err != nil {
 		return err
 	}
@@ -225,7 +229,7 @@ func (c *profileCmd) doProfileEdit(client *lxd.Client, p string) error {
 		newdata := api.ProfilePut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
-			err = client.PutProfile(p, newdata)
+			err = client.UpdateProfile(p, newdata, etag)
 		}
 
 		// Respawn the editor
@@ -249,33 +253,48 @@ func (c *profileCmd) doProfileEdit(client *lxd.Client, p string) error {
 	return nil
 }
 
-func (c *profileCmd) doProfileDelete(client *lxd.Client, p string) error {
-	err := client.ProfileDelete(p)
-	if err == nil {
-		fmt.Printf(i18n.G("Profile %s deleted")+"\n", p)
+func (c *profileCmd) doProfileDelete(client lxd.ContainerServer, p string) error {
+	err := client.DeleteProfile(p)
+	if err != nil {
+		return err
 	}
-	return err
+
+	fmt.Printf(i18n.G("Profile %s deleted")+"\n", p)
+	return nil
 }
 
-func (c *profileCmd) doProfileApply(client *lxd.Client, d string, p string) error {
-	resp, err := client.ApplyProfile(d, p)
+func (c *profileCmd) doProfileApply(client lxd.ContainerServer, d string, p string) error {
+	container, etag, err := client.GetContainer(d)
 	if err != nil {
 		return err
 	}
 
-	err = client.WaitForSuccess(resp.Operation)
-	if err == nil {
-		if p == "" {
-			p = i18n.G("(none)")
-		}
-		fmt.Printf(i18n.G("Profile %s applied to %s")+"\n", p, d)
+	if p != "" {
+		container.Profiles = strings.Split(p, ",")
+	} else {
+		container.Profiles = nil
 	}
 
-	return err
+	op, err := client.UpdateContainer(d, container.Writable(), etag)
+	if err != nil {
+		return err
+	}
+
+	err = op.Wait()
+	if err != nil {
+		return err
+	}
+
+	if p == "" {
+		p = i18n.G("(none)")
+	}
+	fmt.Printf(i18n.G("Profiles %s applied to %s")+"\n", p, d)
+
+	return nil
 }
 
-func (c *profileCmd) doProfileShow(client *lxd.Client, p string) error {
-	profile, err := client.ProfileConfig(p)
+func (c *profileCmd) doProfileShow(client lxd.ContainerServer, p string) error {
+	profile, _, err := client.GetProfile(p)
 	if err != nil {
 		return err
 	}
@@ -290,7 +309,7 @@ func (c *profileCmd) doProfileShow(client *lxd.Client, p string) error {
 	return nil
 }
 
-func (c *profileCmd) doProfileCopy(conf *config.Config, client *lxd.Client, p string, args []string) error {
+func (c *profileCmd) doProfileCopy(conf *config.Config, client lxd.ContainerServer, p string, args []string) error {
 	if len(args) != 1 {
 		return errArgs
 	}
@@ -304,12 +323,22 @@ func (c *profileCmd) doProfileCopy(conf *config.Config, client *lxd.Client, p st
 		newname = p
 	}
 
-	dest, err := lxd.NewClient(conf.Legacy(), remote)
+	dest, err := conf.GetContainerServer(remote)
+	if err != nil {
+		return err
+	}
+
+	profile, _, err := client.GetProfile(p)
 	if err != nil {
 		return err
 	}
 
-	return client.ProfileCopy(p, newname, dest)
+	newProfile := api.ProfilesPost{
+		ProfilePut: profile.Writable(),
+		Name:       newname,
+	}
+
+	return dest.CreateProfile(newProfile)
 }
 
 func (c *profileCmd) doProfileDevice(conf *config.Config, args []string) error {
@@ -342,25 +371,22 @@ func (c *profileCmd) doProfileDevice(conf *config.Config, args []string) error {
 	}
 }
 
-func (c *profileCmd) doProfileGet(client *lxd.Client, p string, args []string) error {
+func (c *profileCmd) doProfileGet(client lxd.ContainerServer, p string, args []string) error {
 	// we shifted @args so so it should read "<key>"
 	if len(args) != 1 {
 		return errArgs
 	}
 
-	resp, err := client.GetProfileConfig(p)
+	profile, _, err := client.GetProfile(p)
 	if err != nil {
 		return err
 	}
-	for k, v := range resp {
-		if k == args[0] {
-			fmt.Printf("%s\n", v)
-		}
-	}
+
+	fmt.Printf("%s\n", profile.Config[args[0]])
 	return nil
 }
 
-func (c *profileCmd) doProfileSet(client *lxd.Client, p string, args []string) error {
+func (c *profileCmd) doProfileSet(client lxd.ContainerServer, p string, args []string) error {
 	// we shifted @args so so it should read "<key> [<value>]"
 	if len(args) < 1 {
 		return errArgs
@@ -382,11 +408,17 @@ func (c *profileCmd) doProfileSet(client *lxd.Client, p string, args []string) e
 		value = string(buf[:])
 	}
 
-	err := client.SetProfileConfigItem(p, key, value)
-	return err
+	profile, etag, err := client.GetProfile(p)
+	if err != nil {
+		return err
+	}
+
+	profile.Config[key] = value
+
+	return client.UpdateProfile(p, profile.Writable(), etag)
 }
 
-func (c *profileCmd) doProfileUnset(client *lxd.Client, p string, args []string) error {
+func (c *profileCmd) doProfileUnset(client lxd.ContainerServer, p string, args []string) error {
 	// we shifted @args so so it should read "<key> [<value>]"
 	if len(args) != 1 {
 		return errArgs
@@ -412,15 +444,16 @@ func (c *profileCmd) doProfileList(conf *config.Config, args []string) error {
 		remote = conf.DefaultRemote
 	}
 
-	client, err := lxd.NewClient(conf.Legacy(), remote)
+	client, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
 
-	profiles, err := client.ListProfiles()
+	profiles, err := client.GetProfileNames()
 	if err != nil {
 		return err
 	}
+
 	fmt.Printf("%s\n", strings.Join(profiles, "\n"))
 	return nil
 }

From 0de827e8929dbf0bab9788e8cfb81a8875daf9f6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Jun 2017 23:48:44 -0400
Subject: [PATCH 0994/1193] lxc/file: Port to new client library
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/file.go | 22 +++++++++++++++-------
 1 file changed, 15 insertions(+), 7 deletions(-)

diff --git a/lxc/file.go b/lxc/file.go
index 12c637c4e..4d48e66d1 100644
--- a/lxc/file.go
+++ b/lxc/file.go
@@ -11,7 +11,7 @@ import (
 	"strings"
 	"syscall"
 
-	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
@@ -76,7 +76,7 @@ func (c *fileCmd) push(conf *config.Config, send_file_perms bool, args []string)
 		return err
 	}
 
-	d, err := lxd.NewClient(conf.Legacy(), remote)
+	d, err := conf.GetContainerServer(remote)
 	if err != nil {
 		return err
 	}
@@ -141,6 +141,13 @@ func (c *fileCmd) push(conf *config.Config, send_file_perms bool, args []string)
 			fpath = path.Join(fpath, path.Base(f.Name()))
 		}
 
+		args := lxd.ContainerFileArgs{
+			Content: f,
+			UID:     -1,
+			GID:     -1,
+			Mode:    -1,
+		}
+
 		if send_file_perms {
 			if c.mode == "" || c.uid == -1 || c.gid == -1 {
 				fMode, fUid, fGid, err := c.getOwner(f)
@@ -161,11 +168,12 @@ func (c *fileCmd) push(conf *config.Config, send_file_perms bool, args []string)
 				}
 			}
 
-			err = d.PushFile(container, fpath, gid, uid, fmt.Sprintf("%04o", mode.Perm()), f)
-		} else {
-			err = d.PushFile(container, fpath, -1, -1, "", f)
+			args.UID = int64(uid)
+			args.GID = int64(gid)
+			args.Mode = int(mode.Perm())
 		}
 
+		err = d.CreateContainerFile(container, fpath, args)
 		if err != nil {
 			return err
 		}
@@ -216,12 +224,12 @@ func (c *fileCmd) pull(conf *config.Config, args []string) error {
 			return err
 		}
 
-		d, err := lxd.NewClient(conf.Legacy(), remote)
+		d, err := conf.GetContainerServer(remote)
 		if err != nil {
 			return err
 		}
 
-		_, _, _, buf, err := d.PullFile(container, pathSpec[1])
+		buf, _, err := d.GetContainerFile(container, pathSpec[1])
 		if err != nil {
 			return err
 		}

From b97b8d17ccd9425c05eb9ac73e451c641d10812f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 16 Jun 2017 23:50:31 -0400
Subject: [PATCH 0995/1193] Revert "config: Implement temporary Legacy
 function"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This reverts commit 713cb1b0ce913040ce7cea1de6cc82d595b6e826.

This was only needed during the port and can be now reverted.
Keeping around so that the individual commits can still be built which
would make bisection easier.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config/legacy.go | 31 -------------------------------
 1 file changed, 31 deletions(-)
 delete mode 100644 lxc/config/legacy.go

diff --git a/lxc/config/legacy.go b/lxc/config/legacy.go
deleted file mode 100644
index d286f1f03..000000000
--- a/lxc/config/legacy.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package config
-
-import (
-	"github.com/lxc/lxd"
-)
-
-// Legacy returns a legacy *lxd.Config
-func (c *Config) Legacy() *lxd.Config {
-	conf := &lxd.Config{
-		DefaultRemote: c.DefaultRemote,
-		Aliases:       c.Aliases,
-		ConfigDir:     c.ConfigDir,
-	}
-
-	remotes := map[string]lxd.RemoteConfig{}
-
-	for k, v := range c.Remotes {
-		remote := lxd.RemoteConfig{
-			Addr:     v.Addr,
-			Public:   v.Public,
-			Protocol: v.Protocol,
-			Static:   v.Static,
-		}
-
-		remotes[k] = remote
-	}
-
-	conf.Remotes = remotes
-
-	return conf
-}

From 449c46ad0764e0cb0dadd68d19a521d3122ca503 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 26 Jun 2017 18:57:05 -0400
Subject: [PATCH 0996/1193] lxd/images: Always expand fingerprint
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This fixes an issue where partial fingerprints weren't expanded for
private image copies. LXD will now allow querying the image with a
partial fingerprint using the secret string, LXD will then use that data
to get the full fingerprint and proceeed with the download using that.

Closes #3424

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go | 13 ++++++++++---
 lxd/images.go        | 24 ++++++++++++------------
 2 files changed, 22 insertions(+), 15 deletions(-)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 167e9717e..6fc5ed7da 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -338,11 +338,18 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		if info == nil {
 			if secret != "" {
 				info, _, err = remote.GetPrivateImage(fp, secret)
+				if err != nil {
+					return nil, err
+				}
+
+				// Expand the fingerprint now and mark alias string to match
+				fp = info.Fingerprint
+				alias = info.Fingerprint
 			} else {
 				info, _, err = remote.GetImage(fp)
-			}
-			if err != nil {
-				return nil, err
+				if err != nil {
+					return nil, err
+				}
 			}
 		}
 
diff --git a/lxd/images.go b/lxd/images.go
index 238715c52..73e0f3f79 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1038,15 +1038,15 @@ func imageGet(d *Daemon, r *http.Request) Response {
 	public := !d.isTrustedClient(r)
 	secret := r.FormValue("secret")
 
-	if public == true && imageValidSecret(fingerprint, secret) == true {
-		public = false
-	}
-
-	info, response := doImageGet(d, fingerprint, public)
+	info, response := doImageGet(d, fingerprint, false)
 	if response != nil {
 		return response
 	}
 
+	if !info.Public && public && !imageValidSecret(info.Fingerprint, secret) {
+		return NotFound
+	}
+
 	return SyncResponse(true, info)
 }
 
@@ -1226,15 +1226,15 @@ func imageExport(d *Daemon, r *http.Request) Response {
 	public := !d.isTrustedClient(r)
 	secret := r.FormValue("secret")
 
-	if public == true && imageValidSecret(fingerprint, secret) == true {
-		public = false
-	}
-
-	_, imgInfo, err := dbImageGet(d.db, fingerprint, public, false)
+	_, imgInfo, err := dbImageGet(d.db, fingerprint, false, false)
 	if err != nil {
 		return SmartError(err)
 	}
 
+	if !imgInfo.Public && public && !imageValidSecret(imgInfo.Fingerprint, secret) {
+		return NotFound
+	}
+
 	imagePath := shared.VarPath("images", imgInfo.Fingerprint)
 	rootfsPath := imagePath + ".rootfs"
 
@@ -1276,7 +1276,7 @@ func imageExport(d *Daemon, r *http.Request) Response {
 
 func imageSecret(d *Daemon, r *http.Request) Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
-	_, _, err := dbImageGet(d.db, fingerprint, false, false)
+	_, imgInfo, err := dbImageGet(d.db, fingerprint, false, false)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1291,7 +1291,7 @@ func imageSecret(d *Daemon, r *http.Request) Response {
 	meta["secret"] = secret
 
 	resources := map[string][]string{}
-	resources["images"] = []string{fingerprint}
+	resources["images"] = []string{imgInfo.Fingerprint}
 
 	op, err := operationCreate(operationClassToken, resources, meta, nil, nil, nil)
 	if err != nil {

From 9d3872cbd8c8198336ec09872b85605fc335415f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 28 Jun 2017 00:22:36 -0400
Subject: [PATCH 0997/1193] lxc/publish: Fix fingerprint printing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/publish.go | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/lxc/publish.go b/lxc/publish.go
index 5c494fe2f..ca154e799 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -167,8 +167,6 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 		properties[entry[0]] = entry[1]
 	}
 
-	var fp string
-
 	// We should only set the properties field if there actually are any.
 	// Otherwise we will only delete any existing properties on publish.
 	// This is something which only direct callers of the API are allowed to
@@ -244,7 +242,7 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 		}
 	}
 
-	fmt.Printf(i18n.G("Container published with fingerprint: %s")+"\n", fp)
+	fmt.Printf(i18n.G("Container published with fingerprint: %s")+"\n", fingerprint)
 
 	return nil
 }

From 8185c8245c0e01a9187247121be80952963718e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 28 Jun 2017 14:47:05 -0400
Subject: [PATCH 0998/1193] Fix failure to launch containers with random names
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/init.go          | 3 ++-
 test/suites/basic.sh | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/lxc/init.go b/lxc/init.go
index a60f74120..0c962fd6a 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -275,7 +275,8 @@ func (c *initCmd) create(conf *config.Config, args []string) (lxd.ContainerServe
 
 	if len(containers) == 1 && name == "" {
 		fields := strings.Split(containers[0], "/")
-		fmt.Printf(i18n.G("Container name is: %s")+"\n", fields[len(fields)-1])
+		name = fields[len(fields)-1]
+		fmt.Printf(i18n.G("Container name is: %s")+"\n", name)
 	}
 
 	return d, name, nil
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 5075054dd..b5a76ecee 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -173,9 +173,9 @@ test_basic_usage() {
   [ ! -d "${LXD_DIR}/snapshots/bar" ]
 
   # Test randomly named container creation
-  lxc init testimage
+  lxc launch testimage
   RDNAME=$(lxc list | tail -n2 | grep ^\| | awk '{print $2}')
-  lxc delete "${RDNAME}"
+  lxc delete -f "${RDNAME}"
 
   # Test "nonetype" container creation
   wait_for "${LXD_ADDR}" my_curl -X POST "https://${LXD_ADDR}/1.0/containers" \

From ee1ab8dd9901cfdfe2be99ab124982a26bca53ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 28 Jun 2017 15:41:25 -0400
Subject: [PATCH 0999/1193] client: Fix handling of public LXD remote
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Public LXD remotes don't advertise their bound addresses, but they are
still reachable using the URL that was used for the remote.

Closes #3464

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd.go         | 8 ++++----
 test/suites/remote.sh | 5 +++++
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/client/lxd.go b/client/lxd.go
index df1c6dbf9..742ad2fc6 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -36,11 +36,11 @@ func (r *ProtocolLXD) GetConnectionInfo() (*ConnectionInfo, error) {
 	info.Protocol = "lxd"
 
 	urls := []string{}
-	if len(r.server.Environment.Addresses) > 0 {
-		if r.httpProtocol == "https" {
-			urls = append(urls, r.httpHost)
-		}
+	if r.httpProtocol == "https" {
+		urls = append(urls, r.httpHost)
+	}
 
+	if len(r.server.Environment.Addresses) > 0 {
 		for _, addr := range r.server.Environment.Addresses {
 			url := fmt.Sprintf("https://%s", addr)
 			if !shared.StringInSlice(url, urls) {
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index 01cef366b..457cd5e34 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -129,7 +129,12 @@ test_remote_usage() {
   lxc_remote image show lxd2:bar | grep -q "public: true"
   ! lxc_remote image show bar
   lxc_remote delete pub
+
+  # test spawn from public server
+  lxc_remote remote add lxd2-public "${LXD2_ADDR}" --public --accept-certificate
+  lxc_remote init lxd2-public:bar pub
   lxc_remote image delete lxd2:bar
+  lxc_remote delete pub
 
   # Double launch to test if the image downloads only once.
   lxc_remote init localhost:testimage lxd2:c1 &

From 34b735d0df5c72e29afb5c6c14de674c62fbc3ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 29 Jun 2017 13:00:41 -0400
Subject: [PATCH 1000/1193] cancel: Fix crash if no canceler is setup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/cancel/canceler.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/cancel/canceler.go b/shared/cancel/canceler.go
index 7e8685197..79f60dbf9 100644
--- a/shared/cancel/canceler.go
+++ b/shared/cancel/canceler.go
@@ -34,7 +34,7 @@ func CancelableDownload(c *Canceler, client *http.Client, req *http.Request) (*h
 		}
 
 		select {
-		case <-c.chCancel:
+		case <-chCancel:
 			if transport, ok := client.Transport.(*http.Transport); ok {
 				transport.CancelRequest(req)
 			}

From 3cb4d420bbe500346113d7a559100d3428717b15 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 30 Jun 2017 15:44:47 -0400
Subject: [PATCH 1001/1193] lxc/config: Removal of multiple devices at once
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Launchpad: https://bugs.launchpad.net/ubuntu/+source/lxd/+bug/1690299
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config.go | 26 ++++++++++++++------------
 1 file changed, 14 insertions(+), 12 deletions(-)

diff --git a/lxc/config.go b/lxc/config.go
index ad4367582..2535fa906 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -99,7 +99,7 @@ lxc config device list [<remote>:]<container>
 lxc config device show [<remote>:]<container>
     Show full device details for container.
 
-lxc config device remove [<remote>:]<container> <name>
+lxc config device remove [<remote>:]<container> <name>...
     Remove device from container.
 
 *Client trust store management*
@@ -993,19 +993,19 @@ func (c *configCmd) deviceRm(conf *config.Config, which string, args []string) e
 		return err
 	}
 
-	devname := args[3]
-
 	if which == "profile" {
 		profile, etag, err := client.GetProfile(name)
 		if err != nil {
 			return err
 		}
 
-		_, ok := profile.Devices[devname]
-		if !ok {
-			return fmt.Errorf(i18n.G("The device doesn't exist"))
+		for _, devname := range args[3:] {
+			_, ok := profile.Devices[devname]
+			if !ok {
+				return fmt.Errorf(i18n.G("The device doesn't exist"))
+			}
+			delete(profile.Devices, devname)
 		}
-		delete(profile.Devices, devname)
 
 		err = client.UpdateProfile(name, profile.Writable(), etag)
 		if err != nil {
@@ -1017,11 +1017,13 @@ func (c *configCmd) deviceRm(conf *config.Config, which string, args []string) e
 			return err
 		}
 
-		_, ok := container.Devices[devname]
-		if !ok {
-			return fmt.Errorf(i18n.G("The device doesn't exist"))
+		for _, devname := range args[3:] {
+			_, ok := container.Devices[devname]
+			if !ok {
+				return fmt.Errorf(i18n.G("The device doesn't exist"))
+			}
+			delete(container.Devices, devname)
 		}
-		delete(container.Devices, devname)
 
 		op, err := client.UpdateContainer(name, container.Writable(), etag)
 		if err != nil {
@@ -1034,7 +1036,7 @@ func (c *configCmd) deviceRm(conf *config.Config, which string, args []string) e
 		}
 	}
 
-	fmt.Printf(i18n.G("Device %s removed from %s")+"\n", devname, name)
+	fmt.Printf(i18n.G("Device %s removed from %s")+"\n", strings.Join(args[3:], ", "), name)
 	return nil
 }
 

From e0f8f1b21934868d72b297b4df1d06fe24f64c45 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 30 Jun 2017 17:02:21 -0400
Subject: [PATCH 1002/1193] client: Commonize error handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Some functions that were doing direct HTTP queries weren't using the
centralized error handling and would lead to low level errors being
reported to the user rather than the more useful higher level errors.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd.go            | 50 ++++++++++++++++++++++++++----------------------
 client/lxd_containers.go | 15 +++++++++++----
 client/lxd_images.go     | 23 ++++++----------------
 3 files changed, 44 insertions(+), 44 deletions(-)

diff --git a/client/lxd.go b/client/lxd.go
index 742ad2fc6..a6e071308 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -74,6 +74,32 @@ func (r *ProtocolLXD) RawWebsocket(path string) (*websocket.Conn, error) {
 }
 
 // Internal functions
+func (r *ProtocolLXD) parseResponse(resp *http.Response) (*api.Response, string, error) {
+	// Get the ETag
+	etag := resp.Header.Get("ETag")
+
+	// Decode the response
+	decoder := json.NewDecoder(resp.Body)
+	response := api.Response{}
+
+	err := decoder.Decode(&response)
+	if err != nil {
+		// Check the return value for a cleaner error
+		if resp.StatusCode != http.StatusOK {
+			return nil, "", fmt.Errorf("Failed to fetch %s: %s", resp.Request.URL.String(), resp.Status)
+		}
+
+		return nil, "", err
+	}
+
+	// Handle errors
+	if response.Type == api.ErrorResponse {
+		return nil, "", fmt.Errorf(response.Error)
+	}
+
+	return &response, etag, nil
+}
+
 func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag string) (*api.Response, string, error) {
 	var req *http.Request
 	var err error
@@ -130,29 +156,7 @@ func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag
 	}
 	defer resp.Body.Close()
 
-	// Get the ETag
-	etag := resp.Header.Get("ETag")
-
-	// Decode the response
-	decoder := json.NewDecoder(resp.Body)
-	response := api.Response{}
-
-	err = decoder.Decode(&response)
-	if err != nil {
-		// Check the return value for a cleaner error
-		if resp.StatusCode != http.StatusOK {
-			return nil, "", fmt.Errorf("Failed to fetch %s: %s", url, resp.Status)
-		}
-
-		return nil, "", err
-	}
-
-	// Handle errors
-	if response.Type == api.ErrorResponse {
-		return nil, "", fmt.Errorf(response.Error)
-	}
-
-	return &response, etag, nil
+	return r.parseResponse(resp)
 }
 
 func (r *ProtocolLXD) query(method string, path string, data interface{}, ETag string) (*api.Response, string, error) {
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index c3786914b..e8d03a144 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -490,7 +490,10 @@ func (r *ProtocolLXD) GetContainerFile(containerName string, path string) (io.Re
 
 	// Check the return value for a cleaner error
 	if resp.StatusCode != http.StatusOK {
-		return nil, nil, fmt.Errorf("Failed to fetch %s: %s", url, resp.Status)
+		_, _, err := r.parseResponse(resp)
+		if err != nil {
+			return nil, nil, err
+		}
 	}
 
 	// Parse the headers
@@ -581,8 +584,9 @@ func (r *ProtocolLXD) CreateContainerFile(containerName string, path string, arg
 	}
 
 	// Check the return value for a cleaner error
-	if resp.StatusCode != http.StatusOK {
-		return fmt.Errorf("Failed to upload to %s: %s", url, resp.Status)
+	_, _, err = r.parseResponse(resp)
+	if err != nil {
+		return err
 	}
 
 	return nil
@@ -856,7 +860,10 @@ func (r *ProtocolLXD) GetContainerLogfile(name string, filename string) (io.Read
 
 	// Check the return value for a cleaner error
 	if resp.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("Failed to fetch %s: %s", url, resp.Status)
+		_, _, err := r.parseResponse(resp)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	return resp.Body, err
diff --git a/client/lxd_images.go b/client/lxd_images.go
index edc7a4530..e17b1d29e 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -2,7 +2,6 @@ package lxd
 
 import (
 	"crypto/sha256"
-	"encoding/json"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -125,7 +124,10 @@ func (r *ProtocolLXD) GetPrivateImageFile(fingerprint string, secret string, req
 	defer response.Body.Close()
 
 	if response.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("Unable to fetch %s: %s", url, response.Status)
+		_, _, err := r.parseResponse(response)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	ctype, ctypeParams, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
@@ -406,25 +408,12 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
 	}
 	defer resp.Body.Close()
 
-	// Decode the response
-	decoder := json.NewDecoder(resp.Body)
-	response := api.Response{}
-
-	err = decoder.Decode(&response)
+	// Handle errors
+	response, _, err := r.parseResponse(resp)
 	if err != nil {
-		// Check the return value for a cleaner error
-		if resp.StatusCode != http.StatusOK {
-			return nil, fmt.Errorf("Failed to fetch %s: %s", reqURL, resp.Status)
-		}
-
 		return nil, err
 	}
 
-	// Handle errors
-	if response.Type == api.ErrorResponse {
-		return nil, fmt.Errorf(response.Error)
-	}
-
 	// Get to the operation
 	respOperation, err := response.MetadataAsOperation()
 	if err != nil {

From f6790f20ede9648a6a2b5d5bca06bdafadb8ebfe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Jul 2017 01:33:31 -0400
Subject: [PATCH 1003/1193] config: Try to be clever about ":" in snapshots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

If we detect a ":" in the provided string and can't find any remote with
that name and we detect a "/" in the resource name, then assume we're
dealing with a snapshot containing ":" and select the default remote.

Launchpad: https://bugs.launchpad.net/ubuntu/+source/lxd/+bug/1694855
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config/remote.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxc/config/remote.go b/lxc/config/remote.go
index 36796cbdb..c9eaf1e75 100644
--- a/lxc/config/remote.go
+++ b/lxc/config/remote.go
@@ -21,11 +21,16 @@ type Remote struct {
 func (c *Config) ParseRemote(raw string) (string, string, error) {
 	result := strings.SplitN(raw, ":", 2)
 	if len(result) == 1 {
-		return c.DefaultRemote, result[0], nil
+		return c.DefaultRemote, raw, nil
 	}
 
 	_, ok := c.Remotes[result[0]]
 	if !ok {
+		// Attempt to play nice with snapshots containing ":"
+		if shared.IsSnapshot(raw) && strings.Contains(result[0], "/") {
+			return c.DefaultRemote, raw, nil
+		}
+
 		return "", "", fmt.Errorf("The remote \"%s\" doesn't exist", result[0])
 	}
 

From e52fbcfa23f6ffbbccd455a35736d33f3eadc3c9 Mon Sep 17 00:00:00 2001
From: Denis Pynkin <denis.pynkin at collabora.com>
Date: Sat, 1 Jul 2017 20:32:06 +0300
Subject: [PATCH 1004/1193] Fix readonly mode for directory mount

Added remount of binded mounts with readonly mode if flag "readonly" is "true".

Signed-off-by: Denis Pynkin <denis.pynkin at collabora.com>
---
 lxd/devices.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/lxd/devices.go b/lxd/devices.go
index 4600f7f3d..49d092490 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -530,6 +530,14 @@ func deviceMountDisk(srcPath string, dstPath string, readonly bool, recursive bo
 		return fmt.Errorf("Unable to mount %s at %s: %s", srcPath, dstPath, err)
 	}
 
+	// Remount bind mounts in readonly mode if requested
+	if readonly == true && flags&syscall.MS_BIND == syscall.MS_BIND {
+		flags = syscall.MS_RDONLY | syscall.MS_BIND | syscall.MS_REMOUNT
+		if err = syscall.Mount("", dstPath, fstype, uintptr(flags), ""); err != nil {
+			return fmt.Errorf("Unable to mount %s in readonly mode: %s", dstPath, err)
+		}
+	}
+
 	flags = syscall.MS_REC | syscall.MS_SLAVE
 	if err = syscall.Mount("", dstPath, "", uintptr(flags), ""); err != nil {
 		return fmt.Errorf("unable to make mount %s private: %s", dstPath, err)

From 2d755c8eeb0db68a2f4e5e665bac8eb47df90859 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Jul 2017 15:58:51 -0400
Subject: [PATCH 1005/1193] tests: Add a test for read-only disks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/config.sh | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/suites/config.sh b/test/suites/config.sh
index 300fbc053..d02b40d2f 100644
--- a/test/suites/config.sh
+++ b/test/suites/config.sh
@@ -136,6 +136,7 @@ test_config_profiles() {
 
   # test live-adding a nic
   lxc start foo
+  lxc exec foo -- cat /proc/self/mountinfo | grep -q "/mnt1.*ro,"
   ! lxc config show foo | grep -q "raw.lxc"
   lxc config show foo --expanded | grep -q "raw.lxc"
   ! lxc config show foo | grep -v "volatile.eth0" | grep -q "eth0"
@@ -150,6 +151,7 @@ test_config_profiles() {
   mkdir "${TEST_DIR}/mnt2"
   touch "${TEST_DIR}/mnt2/hosts"
   lxc config device add foo mnt2 disk source="${TEST_DIR}/mnt2" path=/mnt2 readonly=true
+  lxc exec foo -- cat /proc/self/mountinfo | grep -q "/mnt2.*ro,"
   lxc exec foo -- ls /mnt2/hosts
   lxc stop foo --force
   lxc start foo

From 4b80961c154611db302280407de3b7e8cdcd901d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Jul 2017 15:49:53 -0400
Subject: [PATCH 1006/1193] shared/cancel: Fix return value ordering
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/util.go            | 2 +-
 lxd/daemon_images.go      | 2 +-
 shared/cancel/canceler.go | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/client/util.go b/client/util.go
index 1a166787d..de5073628 100644
--- a/client/util.go
+++ b/client/util.go
@@ -97,7 +97,7 @@ func downloadFileSha256(httpClient *http.Client, useragent string, progress func
 	}
 
 	// Perform the request
-	r, err, doneCh := cancel.CancelableDownload(canceler, httpClient, req)
+	r, doneCh, err := cancel.CancelableDownload(canceler, httpClient, req)
 	if err != nil {
 		return -1, err
 	}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 6fc5ed7da..92f6db7c6 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -393,7 +393,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		req.Header.Set("User-Agent", version.UserAgent)
 
 		// Make the request
-		raw, err, doneCh := cancel.CancelableDownload(canceler, httpClient, req)
+		raw, doneCh, err := cancel.CancelableDownload(canceler, httpClient, req)
 		defer close(doneCh)
 		if err != nil {
 			return nil, err
diff --git a/shared/cancel/canceler.go b/shared/cancel/canceler.go
index 79f60dbf9..c978c6936 100644
--- a/shared/cancel/canceler.go
+++ b/shared/cancel/canceler.go
@@ -24,7 +24,7 @@ func (c *Canceler) Cancel() error {
 	return nil
 }
 
-func CancelableDownload(c *Canceler, client *http.Client, req *http.Request) (*http.Response, error, chan bool) {
+func CancelableDownload(c *Canceler, client *http.Client, req *http.Request) (*http.Response, chan bool, error) {
 	chDone := make(chan bool)
 
 	go func() {
@@ -47,5 +47,5 @@ func CancelableDownload(c *Canceler, client *http.Client, req *http.Request) (*h
 	}()
 
 	resp, err := client.Do(req)
-	return resp, err, chDone
+	return resp, chDone, err
 }

From 18759f07dcdb0024d8385787f57618d8bdcfd611 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Jul 2017 15:53:14 -0400
Subject: [PATCH 1007/1193] client: Allow canceling image download from LXDs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_images.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index e17b1d29e..0ba289f34 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -14,6 +14,7 @@ import (
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/cancel"
 	"github.com/lxc/lxd/shared/ioprogress"
 )
 
@@ -117,11 +118,12 @@ func (r *ProtocolLXD) GetPrivateImageFile(fingerprint string, secret string, req
 	}
 
 	// Start the request
-	response, err := r.http.Do(request)
+	response, doneCh, err := cancel.CancelableDownload(req.Canceler, r.http, request)
 	if err != nil {
 		return nil, err
 	}
 	defer response.Body.Close()
+	defer close(doneCh)
 
 	if response.StatusCode != http.StatusOK {
 		_, _, err := r.parseResponse(response)

From da7c8abd57b3ba8d5a18563786f3ab69407cb91f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Jul 2017 16:26:28 -0400
Subject: [PATCH 1008/1193] shared/cancel: Use request Cancel channel
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/cancel/canceler.go | 21 +++++++--------------
 1 file changed, 7 insertions(+), 14 deletions(-)

diff --git a/shared/cancel/canceler.go b/shared/cancel/canceler.go
index c978c6936..83c58992f 100644
--- a/shared/cancel/canceler.go
+++ b/shared/cancel/canceler.go
@@ -7,7 +7,7 @@ import (
 
 // A struct to track canceleation
 type Canceler struct {
-	chCancel chan bool
+	chCancel chan struct{}
 }
 
 func (c *Canceler) Cancelable() bool {
@@ -26,21 +26,14 @@ func (c *Canceler) Cancel() error {
 
 func CancelableDownload(c *Canceler, client *http.Client, req *http.Request) (*http.Response, chan bool, error) {
 	chDone := make(chan bool)
+	chCancel := make(chan struct{})
+	if c != nil {
+		c.chCancel = chCancel
+	}
+	req.Cancel = chCancel
 
 	go func() {
-		chCancel := make(chan bool)
-		if c != nil {
-			c.chCancel = chCancel
-		}
-
-		select {
-		case <-chCancel:
-			if transport, ok := client.Transport.(*http.Transport); ok {
-				transport.CancelRequest(req)
-			}
-		case <-chDone:
-		}
-
+		<-chDone
 		if c != nil {
 			c.chCancel = nil
 		}

From 403b387200f1e26393d4a3a4dc5fcdbbff614ace Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Jul 2017 15:41:52 -0400
Subject: [PATCH 1009/1193] client: Add CancelTarget to RemoteOperation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/operations.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/client/operations.go b/client/operations.go
index 7bf1cbd60..0c0db3808 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -267,6 +267,15 @@ func (op *RemoteOperation) AddHandler(function func(api.Operation)) (*EventTarge
 	return target, nil
 }
 
+// CancelTarget attempts to cancel the target operation
+func (op *RemoteOperation) CancelTarget() error {
+	if op.targetOp == nil {
+		return fmt.Errorf("No associated target operation")
+	}
+
+	return op.targetOp.Cancel()
+}
+
 // GetTarget returns the target operation
 func (op *RemoteOperation) GetTarget() (*api.Operation, error) {
 	if op.targetOp == nil {

From 41fb6476f227de65fcd685407e3f5440d1755c6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 25 Sep 2017 14:33:34 -0400
Subject: [PATCH 1010/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 124 ++++++++++++++++++++++++++++++-------------------------------
 po/fr.po   | 118 +++++++++++++++++++++++++++-------------------------------
 po/ja.po   | 124 ++++++++++++++++++++++++++++++-------------------------------
 po/lxd.pot | 111 ++++++++++++++++++++++++++----------------------------
 4 files changed, 232 insertions(+), 245 deletions(-)

diff --git a/po/de.po b/po/de.po
index c85bac4e3..03cadd140 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-25 10:46-0400\n"
+"POT-Creation-Date: 2017-09-25 14:33-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -16,7 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/config.go:38
+#: lxc/config.go:39
 #, fuzzy
 msgid ""
 "### This is a yaml representation of the configuration.\n"
@@ -121,7 +121,7 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 
-#: lxc/profile.go:269
+#: lxc/profile.go:289
 msgid "(none)"
 msgstr ""
 
@@ -174,7 +174,7 @@ msgstr ""
 msgid "Bytes sent"
 msgstr ""
 
-#: lxc/config.go:327
+#: lxc/config.go:349
 msgid "COMMON NAME"
 msgstr ""
 
@@ -182,17 +182,22 @@ msgstr ""
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:155
+#: lxc/config.go:156
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr ""
 
-#: lxc/config.go:168 lxc/config.go:201 lxc/config.go:227
+#: lxc/config.go:169
+#, c-format
+msgid "Can't unset key '%s', it's not currently set"
+msgstr ""
+
+#: lxc/config.go:211 lxc/config.go:237
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:409
+#: lxc/profile.go:441
 msgid "Cannot provide container name to list"
 msgstr ""
 
@@ -218,7 +223,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:913 lxc/profile.go:233
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:913 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
@@ -231,12 +236,12 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:278
+#: lxc/copy.go:176 lxc/init.go:279
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:247
+#: lxc/publish.go:245
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
@@ -273,12 +278,12 @@ msgstr "kann nicht zum selben Container Namen kopieren"
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/config.go:729
+#: lxc/config.go:803
 #, fuzzy, c-format
 msgid "Device %s added to %s"
 msgstr "Gerät %s wurde zu %s hinzugefügt\n"
 
-#: lxc/config.go:929
+#: lxc/config.go:1039
 #, fuzzy, c-format
 msgid "Device %s removed from %s"
 msgstr "Gerät %s wurde von %s entfernt\n"
@@ -291,7 +296,7 @@ msgstr ""
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:329
+#: lxc/config.go:351
 msgid "EXPIRY DATE"
 msgstr ""
 
@@ -313,7 +318,7 @@ msgstr ""
 msgid "Environment:"
 msgstr ""
 
-#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:138 lxc/init.go:139
 msgid "Ephemeral container"
 msgstr "Flüchtiger Container"
 
@@ -335,7 +340,7 @@ msgstr ""
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:326 lxc/image.go:830 lxc/image.go:859
+#: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -349,6 +354,11 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
+#: lxc/copy.go:171
+#, fuzzy
+msgid "Failed to get the new container name"
+msgstr "kann nicht zum selben Container Namen kopieren"
+
 #: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
@@ -388,7 +398,7 @@ msgstr ""
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:328
+#: lxc/config.go:350
 msgid "ISSUE DATE"
 msgstr ""
 
@@ -424,7 +434,7 @@ msgstr "Abbild mit Fingerabdruck %s importiert\n"
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
 
-#: lxc/config.go:307
+#: lxc/config.go:329
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "Akzeptiere Zertifikat"
@@ -433,7 +443,7 @@ msgstr "Akzeptiere Zertifikat"
 msgid "Invalid configuration key"
 msgstr ""
 
-#: lxc/file.go:211
+#: lxc/file.go:219
 #, c-format
 msgid "Invalid source %s"
 msgstr "Ungültige Quelle %s"
@@ -489,21 +499,11 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/copy.go:222
-#, c-format
-msgid "Migration failed on source host: %s"
-msgstr ""
-
-#: lxc/copy.go:226
-#, c-format
-msgid "Migration failed on target host: %s"
-msgstr ""
-
 #: lxc/utils.go:197
 msgid "Missing summary."
 msgstr "Fehlende Zusammenfassung."
 
-#: lxc/file.go:199
+#: lxc/file.go:207
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "Mehr als eine Datei herunterzuladen, aber das Ziel ist kein Verzeichnis"
@@ -534,12 +534,12 @@ msgstr ""
 msgid "New alias to define at target"
 msgstr ""
 
-#: lxc/config.go:338
+#: lxc/config.go:360
 #, fuzzy
 msgid "No certificate provided to add"
 msgstr "Kein Zertifikat zum hinzufügen bereitgestellt"
 
-#: lxc/config.go:365
+#: lxc/config.go:392
 msgid "No fingerprint specified."
 msgstr "Kein Fingerabdruck angegeben."
 
@@ -611,11 +611,11 @@ msgstr ""
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/profile.go:234
+#: lxc/profile.go:238
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:914
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:914
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -636,17 +636,12 @@ msgstr ""
 msgid "Processes: %d"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:271
-#, fuzzy, c-format
-msgid "Profile %s applied to %s"
-msgstr "Profil %s wurde auf %s angewandt\n"
-
-#: lxc/profile.go:185
+#: lxc/profile.go:188
 #, fuzzy, c-format
 msgid "Profile %s created"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/profile.go:255
+#: lxc/profile.go:262
 #, fuzzy, c-format
 msgid "Profile %s deleted"
 msgstr "Profil %s gelöscht\n"
@@ -656,6 +651,11 @@ msgstr "Profil %s gelöscht\n"
 msgid "Profile to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
+#: lxc/profile.go:291
+#, fuzzy, c-format
+msgid "Profiles %s applied to %s"
+msgstr "Profil %s wurde auf %s angewandt\n"
+
 #: lxc/info.go:115
 #, fuzzy, c-format
 msgid "Profiles: %s"
@@ -760,7 +760,7 @@ msgstr ""
 msgid "Show the container's last 100 log lines?"
 msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 
-#: lxc/config.go:34
+#: lxc/config.go:35
 msgid "Show the expanded configuration"
 msgstr ""
 
@@ -842,13 +842,18 @@ msgid ""
 "restarted."
 msgstr ""
 
-#: lxc/config.go:760 lxc/config.go:772 lxc/config.go:808 lxc/config.go:826
-#: lxc/config.go:867 lxc/config.go:885
+#: lxc/config.go:770 lxc/config.go:787
+#, fuzzy
+msgid "The device already exists"
+msgstr "entfernte Instanz %s existiert bereits"
+
+#: lxc/config.go:833 lxc/config.go:845 lxc/config.go:881 lxc/config.go:899
+#: lxc/config.go:945 lxc/config.go:962 lxc/config.go:1005 lxc/config.go:1023
 #, fuzzy
 msgid "The device doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/init.go:304
+#: lxc/init.go:305
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -937,7 +942,7 @@ msgstr ""
 "Benutzung: lxc [Unterbefehl] [Optionen]\n"
 "Verfügbare Befehle:\n"
 
-#: lxc/config.go:59
+#: lxc/config.go:60
 msgid ""
 "Usage: lxc config <subcommand> [options]\n"
 "\n"
@@ -981,7 +986,7 @@ msgid ""
 "lxc config device show [<remote>:]<container>\n"
 "    Show full device details for container.\n"
 "\n"
-"lxc config device remove [<remote>:]<container> <name>\n"
+"lxc config device remove [<remote>:]<container> <name>...\n"
 "    Remove device from container.\n"
 "\n"
 "*Client trust store management*\n"
@@ -1014,7 +1019,7 @@ msgid ""
 "    Will set the server's trust password to blah."
 msgstr ""
 
-#: lxc/copy.go:24
+#: lxc/copy.go:23
 msgid ""
 "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
 "[--ephemeral|e]\n"
@@ -1581,14 +1586,15 @@ msgstr "Zustand des laufenden Containers sichern oder nicht"
 msgid "YES"
 msgstr ""
 
+#: lxc/copy.go:49
+#, fuzzy
+msgid "You must specify a source container name"
+msgstr "der Name des Ursprung Containers muss angegeben werden"
+
 #: lxc/main.go:58
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/copy.go:108
-msgid "can't copy to the same container name"
-msgstr "kann nicht zum selben Container Namen kopieren"
-
 #: lxc/remote.go:341
 msgid "can't remove the default remote"
 msgstr ""
@@ -1623,10 +1629,6 @@ msgstr "Fehler: unbekannter Befehl: %s\n"
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:131
-msgid "not all the profiles from the source exist on the target"
-msgstr "nicht alle Profile der Quelle sind am Ziel vorhanden."
-
 #: lxc/remote.go:206
 #, fuzzy
 msgid "ok (y/n)?"
@@ -1678,9 +1680,11 @@ msgstr "falsche Anzahl an Parametern für Unterbefehl"
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:47
-msgid "you must specify a source container name"
-msgstr "der Name des Ursprung Containers muss angegeben werden"
+#~ msgid "can't copy to the same container name"
+#~ msgstr "kann nicht zum selben Container Namen kopieren"
+
+#~ msgid "not all the profiles from the source exist on the target"
+#~ msgstr "nicht alle Profile der Quelle sind am Ziel vorhanden."
 
 #~ msgid "bad number of things scanned from image, container or snapshot"
 #~ msgstr ""
@@ -2027,10 +2031,6 @@ msgstr "der Name des Ursprung Containers muss angegeben werden"
 #~ msgstr "Falsche Version in Profil URL"
 
 #, fuzzy
-#~ msgid "device already exists"
-#~ msgstr "entfernte Instanz %s existiert bereits"
-
-#, fuzzy
 #~ msgid "error."
 #~ msgstr "Fehler: %v\n"
 
diff --git a/po/fr.po b/po/fr.po
index 309df4e21..3e9ce7a77 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-25 10:46-0400\n"
+"POT-Creation-Date: 2017-09-25 14:33-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -16,7 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/config.go:38
+#: lxc/config.go:39
 msgid ""
 "### This is a yaml representation of the configuration.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -78,7 +78,7 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 
-#: lxc/profile.go:269
+#: lxc/profile.go:289
 msgid "(none)"
 msgstr ""
 
@@ -130,7 +130,7 @@ msgstr ""
 msgid "Bytes sent"
 msgstr ""
 
-#: lxc/config.go:327
+#: lxc/config.go:349
 msgid "COMMON NAME"
 msgstr ""
 
@@ -138,17 +138,22 @@ msgstr ""
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:155
+#: lxc/config.go:156
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr ""
 
-#: lxc/config.go:168 lxc/config.go:201 lxc/config.go:227
+#: lxc/config.go:169
+#, c-format
+msgid "Can't unset key '%s', it's not currently set"
+msgstr ""
+
+#: lxc/config.go:211 lxc/config.go:237
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr ""
 
-#: lxc/profile.go:409
+#: lxc/profile.go:441
 msgid "Cannot provide container name to list"
 msgstr ""
 
@@ -173,7 +178,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:913 lxc/profile.go:233
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:913 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
@@ -186,12 +191,12 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/init.go:278
+#: lxc/copy.go:176 lxc/init.go:279
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:247
+#: lxc/publish.go:245
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -227,12 +232,12 @@ msgstr ""
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/config.go:729
+#: lxc/config.go:803
 #, c-format
 msgid "Device %s added to %s"
 msgstr ""
 
-#: lxc/config.go:929
+#: lxc/config.go:1039
 #, c-format
 msgid "Device %s removed from %s"
 msgstr ""
@@ -245,7 +250,7 @@ msgstr ""
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:329
+#: lxc/config.go:351
 msgid "EXPIRY DATE"
 msgstr ""
 
@@ -267,7 +272,7 @@ msgstr ""
 msgid "Environment:"
 msgstr ""
 
-#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:138 lxc/init.go:139
 msgid "Ephemeral container"
 msgstr ""
 
@@ -289,7 +294,7 @@ msgstr ""
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:326 lxc/image.go:830 lxc/image.go:859
+#: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -303,6 +308,10 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
+#: lxc/copy.go:171
+msgid "Failed to get the new container name"
+msgstr ""
+
 #: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
@@ -342,7 +351,7 @@ msgstr ""
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:328
+#: lxc/config.go:350
 msgid "ISSUE DATE"
 msgstr ""
 
@@ -378,7 +387,7 @@ msgstr "Empreinte du certificat: % x\n"
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
 
-#: lxc/config.go:307
+#: lxc/config.go:329
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "Gérer la configuration.\n"
@@ -388,7 +397,7 @@ msgstr "Gérer la configuration.\n"
 msgid "Invalid configuration key"
 msgstr "Gérer la configuration.\n"
 
-#: lxc/file.go:211
+#: lxc/file.go:219
 #, c-format
 msgid "Invalid source %s"
 msgstr "Source invalide %s"
@@ -443,21 +452,11 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/copy.go:222
-#, c-format
-msgid "Migration failed on source host: %s"
-msgstr ""
-
-#: lxc/copy.go:226
-#, c-format
-msgid "Migration failed on target host: %s"
-msgstr ""
-
 #: lxc/utils.go:197
 msgid "Missing summary."
 msgstr "Sommaire manquant."
 
-#: lxc/file.go:199
+#: lxc/file.go:207
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "Plusieurs fichiers à télécharger mais la destination n'est pas un dossier"
@@ -487,12 +486,12 @@ msgstr ""
 msgid "New alias to define at target"
 msgstr ""
 
-#: lxc/config.go:338
+#: lxc/config.go:360
 #, fuzzy
 msgid "No certificate provided to add"
 msgstr "Un certificat n'a pas été fournis"
 
-#: lxc/config.go:365
+#: lxc/config.go:392
 msgid "No fingerprint specified."
 msgstr "Aucune empreinte n'a été spécifié."
 
@@ -565,11 +564,11 @@ msgstr ""
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/profile.go:234
+#: lxc/profile.go:238
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:914
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:914
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -590,17 +589,12 @@ msgstr ""
 msgid "Processes: %d"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/profile.go:271
-#, fuzzy, c-format
-msgid "Profile %s applied to %s"
-msgstr "Mauvaise URL pour le conteneur %s"
-
-#: lxc/profile.go:185
+#: lxc/profile.go:188
 #, c-format
 msgid "Profile %s created"
 msgstr ""
 
-#: lxc/profile.go:255
+#: lxc/profile.go:262
 #, c-format
 msgid "Profile %s deleted"
 msgstr ""
@@ -609,6 +603,11 @@ msgstr ""
 msgid "Profile to apply to the new container"
 msgstr ""
 
+#: lxc/profile.go:291
+#, fuzzy, c-format
+msgid "Profiles %s applied to %s"
+msgstr "Mauvaise URL pour le conteneur %s"
+
 #: lxc/info.go:115
 #, fuzzy, c-format
 msgid "Profiles: %s"
@@ -711,7 +710,7 @@ msgstr ""
 msgid "Show the container's last 100 log lines?"
 msgstr ""
 
-#: lxc/config.go:34
+#: lxc/config.go:35
 msgid "Show the expanded configuration"
 msgstr ""
 
@@ -793,13 +792,18 @@ msgid ""
 "restarted."
 msgstr ""
 
-#: lxc/config.go:760 lxc/config.go:772 lxc/config.go:808 lxc/config.go:826
-#: lxc/config.go:867 lxc/config.go:885
+#: lxc/config.go:770 lxc/config.go:787
+#, fuzzy
+msgid "The device already exists"
+msgstr "le serveur distant %s existe déjà"
+
+#: lxc/config.go:833 lxc/config.go:845 lxc/config.go:881 lxc/config.go:899
+#: lxc/config.go:945 lxc/config.go:962 lxc/config.go:1005 lxc/config.go:1023
 #, fuzzy
 msgid "The device doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/init.go:304
+#: lxc/init.go:305
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -887,7 +891,7 @@ msgstr ""
 "Utilisation: lxc [sous commande] [options]\n"
 "Comande disponibles:\n"
 
-#: lxc/config.go:59
+#: lxc/config.go:60
 msgid ""
 "Usage: lxc config <subcommand> [options]\n"
 "\n"
@@ -931,7 +935,7 @@ msgid ""
 "lxc config device show [<remote>:]<container>\n"
 "    Show full device details for container.\n"
 "\n"
-"lxc config device remove [<remote>:]<container> <name>\n"
+"lxc config device remove [<remote>:]<container> <name>...\n"
 "    Remove device from container.\n"
 "\n"
 "*Client trust store management*\n"
@@ -964,7 +968,7 @@ msgid ""
 "    Will set the server's trust password to blah."
 msgstr ""
 
-#: lxc/copy.go:24
+#: lxc/copy.go:23
 msgid ""
 "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
 "[--ephemeral|e]\n"
@@ -1431,12 +1435,12 @@ msgstr ""
 msgid "YES"
 msgstr ""
 
-#: lxc/main.go:58
-msgid "`lxc config profile` is deprecated, please use `lxc profile`"
+#: lxc/copy.go:49
+msgid "You must specify a source container name"
 msgstr ""
 
-#: lxc/copy.go:108
-msgid "can't copy to the same container name"
+#: lxc/main.go:58
+msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
 #: lxc/remote.go:341
@@ -1474,10 +1478,6 @@ msgstr "erreur: comande inconnue: %s\n"
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:131
-msgid "not all the profiles from the source exist on the target"
-msgstr ""
-
 #: lxc/remote.go:206
 #, fuzzy
 msgid "ok (y/n)?"
@@ -1529,10 +1529,6 @@ msgstr "nombre d'argument incorrect pour la sous-comande"
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:47
-msgid "you must specify a source container name"
-msgstr ""
-
 #, fuzzy
 #~ msgid "bad number of things scanned from image, container or snapshot"
 #~ msgstr "nombre de propriété invalide pour la ressource"
@@ -1601,10 +1597,6 @@ msgstr ""
 #~ msgstr "version invalide dans l'URL du conteneur"
 
 #, fuzzy
-#~ msgid "device already exists"
-#~ msgstr "le serveur distant %s existe déjà"
-
-#, fuzzy
 #~ msgid "error."
 #~ msgstr "erreur: %v\n"
 
diff --git a/po/ja.po b/po/ja.po
index 7c262b406..38ccf9a74 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-25 10:46-0400\n"
+"POT-Creation-Date: 2017-09-25 14:33-0400\n"
 "PO-Revision-Date: 2016-04-26 14:31+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -16,7 +16,7 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/config.go:38
+#: lxc/config.go:39
 msgid ""
 "### This is a yaml representation of the configuration.\n"
 "### Any line starting with a '# will be ignored.\n"
@@ -77,7 +77,7 @@ msgstr ""
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' はスナップショットの名前には使用できません。"
 
-#: lxc/profile.go:269
+#: lxc/profile.go:289
 msgid "(none)"
 msgstr ""
 
@@ -129,7 +129,7 @@ msgstr "受信バイト数"
 msgid "Bytes sent"
 msgstr "送信バイト数"
 
-#: lxc/config.go:327
+#: lxc/config.go:349
 msgid "COMMON NAME"
 msgstr ""
 
@@ -137,17 +137,22 @@ msgstr ""
 msgid "CREATED AT"
 msgstr ""
 
-#: lxc/config.go:155
+#: lxc/config.go:156
 #, c-format
 msgid "Can't read from stdin: %s"
 msgstr "標準入力から読み込めません: %s"
 
-#: lxc/config.go:168 lxc/config.go:201 lxc/config.go:227
+#: lxc/config.go:169
+#, fuzzy, c-format
+msgid "Can't unset key '%s', it's not currently set"
+msgstr "キー '%s' が指定されていないので削除できません。"
+
+#: lxc/config.go:211 lxc/config.go:237
 #, c-format
 msgid "Can't unset key '%s', it's not currently set."
 msgstr "キー '%s' が指定されていないので削除できません。"
 
-#: lxc/profile.go:409
+#: lxc/profile.go:441
 msgid "Cannot provide container name to list"
 msgstr "コンテナ名を取得できません"
 
@@ -172,7 +177,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:913 lxc/profile.go:233
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:913 lxc/profile.go:237
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
@@ -185,12 +190,12 @@ msgstr "接続が拒否されました。LXDが実行されていますか?"
 msgid "Container name is mandatory"
 msgstr "コンテナ名を指定する必要があります"
 
-#: lxc/init.go:278
+#: lxc/copy.go:176 lxc/init.go:279
 #, c-format
 msgid "Container name is: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/publish.go:247
+#: lxc/publish.go:245
 #, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "コンテナは以下のフィンガープリントで publish されます: %s"
@@ -226,12 +231,12 @@ msgstr "コンテナを作成中"
 msgid "DESCRIPTION"
 msgstr ""
 
-#: lxc/config.go:729
+#: lxc/config.go:803
 #, c-format
 msgid "Device %s added to %s"
 msgstr "デバイス %s が %s に追加されました"
 
-#: lxc/config.go:929
+#: lxc/config.go:1039
 #, c-format
 msgid "Device %s removed from %s"
 msgstr "デバイス %s が %s から削除されました"
@@ -245,7 +250,7 @@ msgstr "  ディスク使用量:"
 msgid "EPHEMERAL"
 msgstr ""
 
-#: lxc/config.go:329
+#: lxc/config.go:351
 msgid "EXPIRY DATE"
 msgstr ""
 
@@ -268,7 +273,7 @@ msgstr "環境変数を HOME=/home/foo の形式で指定します"
 msgid "Environment:"
 msgstr "環境変数:"
 
-#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:138 lxc/init.go:139
 msgid "Ephemeral container"
 msgstr "Ephemeral コンテナ"
 
@@ -290,7 +295,7 @@ msgstr "失効日時: 失効しない"
 msgid "Exporting the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/config.go:326 lxc/image.go:830 lxc/image.go:859
+#: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -304,6 +309,11 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
+#: lxc/copy.go:171
+#, fuzzy
+msgid "Failed to get the new container name"
+msgstr "新しいコンテナに適用するプロファイル"
+
 #: lxc/list.go:112
 #, fuzzy
 msgid "Fast mode (same as --columns=nsacPt)"
@@ -345,7 +355,7 @@ msgstr ""
 msgid "IPV6"
 msgstr ""
 
-#: lxc/config.go:328
+#: lxc/config.go:350
 msgid "ISSUE DATE"
 msgstr ""
 
@@ -383,7 +393,7 @@ msgstr "イメージは以下のフィンガープリントでインポートさ
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr "不正な URL スキーム \"%s\" (\"%s\" 内)"
 
-#: lxc/config.go:307
+#: lxc/config.go:329
 #, fuzzy
 msgid "Invalid certificate"
 msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
@@ -392,7 +402,7 @@ msgstr "証明書のフィンガープリントの確認なしで証明書を受
 msgid "Invalid configuration key"
 msgstr "正しくない設定項目 (key) です"
 
-#: lxc/file.go:211
+#: lxc/file.go:219
 #, c-format
 msgid "Invalid source %s"
 msgstr "不正なソース %s"
@@ -448,21 +458,11 @@ msgstr "メモリ (ピーク)"
 msgid "Memory usage:"
 msgstr "  メモリ消費量:"
 
-#: lxc/copy.go:222
-#, c-format
-msgid "Migration failed on source host: %s"
-msgstr ""
-
-#: lxc/copy.go:226
-#, c-format
-msgid "Migration failed on target host: %s"
-msgstr ""
-
 #: lxc/utils.go:197
 msgid "Missing summary."
 msgstr "サマリーはありません。"
 
-#: lxc/file.go:199
+#: lxc/file.go:207
 msgid "More than one file to download, but target is not a directory"
 msgstr ""
 "ダウンロード対象のファイルが複数ありますが、コピー先がディレクトリではありま"
@@ -494,11 +494,11 @@ msgstr "  ネットワーク使用状況:"
 msgid "New alias to define at target"
 msgstr "新しいエイリアスを定義する"
 
-#: lxc/config.go:338
+#: lxc/config.go:360
 msgid "No certificate provided to add"
 msgstr "追加すべき証明書が提供されていません"
 
-#: lxc/config.go:365
+#: lxc/config.go:392
 msgid "No fingerprint specified."
 msgstr "フィンガープリントが指定されていません。"
 
@@ -571,11 +571,11 @@ msgstr "アクセスが拒否されました。lxd グループに所属して
 msgid "Pid: %d"
 msgstr ""
 
-#: lxc/profile.go:234
+#: lxc/profile.go:238
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:914
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:914
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -599,17 +599,12 @@ msgstr "詳細情報を表示します。"
 msgid "Processes: %d"
 msgstr "プロセス数: %d"
 
-#: lxc/profile.go:271
-#, fuzzy, c-format
-msgid "Profile %s applied to %s"
-msgstr "プロファイル %s が %s に追加されました"
-
-#: lxc/profile.go:185
+#: lxc/profile.go:188
 #, c-format
 msgid "Profile %s created"
 msgstr "プロファイル %s を作成しました"
 
-#: lxc/profile.go:255
+#: lxc/profile.go:262
 #, c-format
 msgid "Profile %s deleted"
 msgstr "プロファイル %s を削除しました"
@@ -618,6 +613,11 @@ msgstr "プロファイル %s を削除しました"
 msgid "Profile to apply to the new container"
 msgstr "新しいコンテナに適用するプロファイル"
 
+#: lxc/profile.go:291
+#, fuzzy, c-format
+msgid "Profiles %s applied to %s"
+msgstr "プロファイル %s が %s に追加されました"
+
 #: lxc/info.go:115
 #, c-format
 msgid "Profiles: %s"
@@ -721,7 +721,7 @@ msgstr ""
 msgid "Show the container's last 100 log lines?"
 msgstr "コンテナログの最後の 100 行を表示しますか?"
 
-#: lxc/config.go:34
+#: lxc/config.go:35
 #, fuzzy
 msgid "Show the expanded configuration"
 msgstr "拡張した設定を表示するかどうか"
@@ -806,12 +806,17 @@ msgstr ""
 "コンテナは現在実行中です。停止して、再起動するために --force を使用してくだ\n"
 "さい。"
 
-#: lxc/config.go:760 lxc/config.go:772 lxc/config.go:808 lxc/config.go:826
-#: lxc/config.go:867 lxc/config.go:885
+#: lxc/config.go:770 lxc/config.go:787
+#, fuzzy
+msgid "The device already exists"
+msgstr "リモート %s は既に存在します"
+
+#: lxc/config.go:833 lxc/config.go:845 lxc/config.go:881 lxc/config.go:899
+#: lxc/config.go:945 lxc/config.go:962 lxc/config.go:1005 lxc/config.go:1023
 msgid "The device doesn't exist"
 msgstr "デバイスが存在しません"
 
-#: lxc/init.go:304
+#: lxc/init.go:305
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -904,7 +909,7 @@ msgstr ""
 msgid "Usage: lxc <command> [options]"
 msgstr "使い方: lxc [サブコマンド] [オプション]"
 
-#: lxc/config.go:59
+#: lxc/config.go:60
 msgid ""
 "Usage: lxc config <subcommand> [options]\n"
 "\n"
@@ -948,7 +953,7 @@ msgid ""
 "lxc config device show [<remote>:]<container>\n"
 "    Show full device details for container.\n"
 "\n"
-"lxc config device remove [<remote>:]<container> <name>\n"
+"lxc config device remove [<remote>:]<container> <name>...\n"
 "    Remove device from container.\n"
 "\n"
 "*Client trust store management*\n"
@@ -981,7 +986,7 @@ msgid ""
 "    Will set the server's trust password to blah."
 msgstr ""
 
-#: lxc/copy.go:24
+#: lxc/copy.go:23
 msgid ""
 "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
 "[--ephemeral|e]\n"
@@ -1706,14 +1711,15 @@ msgstr "コンテナの稼動状態のスナップショットを取得するか
 msgid "YES"
 msgstr ""
 
+#: lxc/copy.go:49
+#, fuzzy
+msgid "You must specify a source container name"
+msgstr "コピー元のコンテナ名を指定してください"
+
 #: lxc/main.go:58
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
-#: lxc/copy.go:108
-msgid "can't copy to the same container name"
-msgstr "同じコンテナ名へはコピーできません"
-
 #: lxc/remote.go:341
 msgid "can't remove the default remote"
 msgstr "デフォルトのリモートは削除できません"
@@ -1750,10 +1756,6 @@ msgstr "エラー: 未知のコマンド: %s"
 msgid "no"
 msgstr ""
 
-#: lxc/copy.go:131
-msgid "not all the profiles from the source exist on the target"
-msgstr "コピー元の全てのプロファイルがターゲットに存在しません"
-
 #: lxc/remote.go:206
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
@@ -1804,9 +1806,11 @@ msgstr "サブコマンドの引数の数が正しくありません"
 msgid "yes"
 msgstr ""
 
-#: lxc/copy.go:47
-msgid "you must specify a source container name"
-msgstr "コピー元のコンテナ名を指定してください"
+#~ msgid "can't copy to the same container name"
+#~ msgstr "同じコンテナ名へはコピーできません"
+
+#~ msgid "not all the profiles from the source exist on the target"
+#~ msgstr "コピー元の全てのプロファイルがターゲットに存在しません"
 
 #~ msgid "Output is in %s"
 #~ msgstr "%s に出力されます"
@@ -2069,10 +2073,6 @@ msgstr "コピー元のコンテナ名を指定してください"
 #~ msgstr "プロファイルURL内のバージョンが不正"
 
 #, fuzzy
-#~ msgid "device already exists"
-#~ msgstr "リモート %s は既に存在します"
-
-#, fuzzy
 #~ msgid "error."
 #~ msgstr "エラー: %v\n"
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 7c7baffb4..f02a08d31 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-09-25 10:46-0400\n"
+        "POT-Creation-Date: 2017-09-25 14:33-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -16,7 +16,7 @@ msgstr  "Project-Id-Version: lxd\n"
         "Content-Type: text/plain; charset=CHARSET\n"
         "Content-Transfer-Encoding: 8bit\n"
 
-#: lxc/config.go:38
+#: lxc/config.go:39
 msgid   "### This is a yaml representation of the configuration.\n"
         "### Any line starting with a '# will be ignored.\n"
         "###\n"
@@ -74,7 +74,7 @@ msgstr  ""
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
 
-#: lxc/profile.go:269
+#: lxc/profile.go:289
 msgid   "(none)"
 msgstr  ""
 
@@ -126,7 +126,7 @@ msgstr  ""
 msgid   "Bytes sent"
 msgstr  ""
 
-#: lxc/config.go:327
+#: lxc/config.go:349
 msgid   "COMMON NAME"
 msgstr  ""
 
@@ -134,17 +134,22 @@ msgstr  ""
 msgid   "CREATED AT"
 msgstr  ""
 
-#: lxc/config.go:155
+#: lxc/config.go:156
 #, c-format
 msgid   "Can't read from stdin: %s"
 msgstr  ""
 
-#: lxc/config.go:168 lxc/config.go:201 lxc/config.go:227
+#: lxc/config.go:169
+#, c-format
+msgid   "Can't unset key '%s', it's not currently set"
+msgstr  ""
+
+#: lxc/config.go:211 lxc/config.go:237
 #, c-format
 msgid   "Can't unset key '%s', it's not currently set."
 msgstr  ""
 
-#: lxc/profile.go:409
+#: lxc/profile.go:441
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
@@ -169,7 +174,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:608 lxc/config.go:673 lxc/image.go:913 lxc/profile.go:233
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:913 lxc/profile.go:237
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -182,12 +187,12 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/init.go:278
+#: lxc/copy.go:176 lxc/init.go:279
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
 
-#: lxc/publish.go:247
+#: lxc/publish.go:245
 #, c-format
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
@@ -223,12 +228,12 @@ msgstr  ""
 msgid   "DESCRIPTION"
 msgstr  ""
 
-#: lxc/config.go:729
+#: lxc/config.go:803
 #, c-format
 msgid   "Device %s added to %s"
 msgstr  ""
 
-#: lxc/config.go:929
+#: lxc/config.go:1039
 #, c-format
 msgid   "Device %s removed from %s"
 msgstr  ""
@@ -241,7 +246,7 @@ msgstr  ""
 msgid   "EPHEMERAL"
 msgstr  ""
 
-#: lxc/config.go:329
+#: lxc/config.go:351
 msgid   "EXPIRY DATE"
 msgstr  ""
 
@@ -261,7 +266,7 @@ msgstr  ""
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:31 lxc/copy.go:32 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:138 lxc/init.go:139
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -283,7 +288,7 @@ msgstr  ""
 msgid   "Exporting the image: %s"
 msgstr  ""
 
-#: lxc/config.go:326 lxc/image.go:830 lxc/image.go:859
+#: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -297,6 +302,10 @@ msgstr  ""
 msgid   "Failed to generate 'lxc.1': %v"
 msgstr  ""
 
+#: lxc/copy.go:171
+msgid   "Failed to get the new container name"
+msgstr  ""
+
 #: lxc/list.go:112
 msgid   "Fast mode (same as --columns=nsacPt)"
 msgstr  ""
@@ -334,7 +343,7 @@ msgstr  ""
 msgid   "IPV6"
 msgstr  ""
 
-#: lxc/config.go:328
+#: lxc/config.go:350
 msgid   "ISSUE DATE"
 msgstr  ""
 
@@ -368,7 +377,7 @@ msgstr  ""
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
 
-#: lxc/config.go:307
+#: lxc/config.go:329
 msgid   "Invalid certificate"
 msgstr  ""
 
@@ -376,7 +385,7 @@ msgstr  ""
 msgid   "Invalid configuration key"
 msgstr  ""
 
-#: lxc/file.go:211
+#: lxc/file.go:219
 #, c-format
 msgid   "Invalid source %s"
 msgstr  ""
@@ -431,21 +440,11 @@ msgstr  ""
 msgid   "Memory usage:"
 msgstr  ""
 
-#: lxc/copy.go:222
-#, c-format
-msgid   "Migration failed on source host: %s"
-msgstr  ""
-
-#: lxc/copy.go:226
-#, c-format
-msgid   "Migration failed on target host: %s"
-msgstr  ""
-
 #: lxc/utils.go:197
 msgid   "Missing summary."
 msgstr  ""
 
-#: lxc/file.go:199
+#: lxc/file.go:207
 msgid   "More than one file to download, but target is not a directory"
 msgstr  ""
 
@@ -474,11 +473,11 @@ msgstr  ""
 msgid   "New alias to define at target"
 msgstr  ""
 
-#: lxc/config.go:338
+#: lxc/config.go:360
 msgid   "No certificate provided to add"
 msgstr  ""
 
-#: lxc/config.go:365
+#: lxc/config.go:392
 msgid   "No fingerprint specified."
 msgstr  ""
 
@@ -547,11 +546,11 @@ msgstr  ""
 msgid   "Pid: %d"
 msgstr  ""
 
-#: lxc/profile.go:234
+#: lxc/profile.go:238
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:609 lxc/config.go:674 lxc/image.go:914
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:914
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -572,17 +571,12 @@ msgstr  ""
 msgid   "Processes: %d"
 msgstr  ""
 
-#: lxc/profile.go:271
-#, c-format
-msgid   "Profile %s applied to %s"
-msgstr  ""
-
-#: lxc/profile.go:185
+#: lxc/profile.go:188
 #, c-format
 msgid   "Profile %s created"
 msgstr  ""
 
-#: lxc/profile.go:255
+#: lxc/profile.go:262
 #, c-format
 msgid   "Profile %s deleted"
 msgstr  ""
@@ -591,6 +585,11 @@ msgstr  ""
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
+#: lxc/profile.go:291
+#, c-format
+msgid   "Profiles %s applied to %s"
+msgstr  ""
+
 #: lxc/info.go:115
 #, c-format
 msgid   "Profiles: %s"
@@ -692,7 +691,7 @@ msgstr  ""
 msgid   "Show the container's last 100 log lines?"
 msgstr  ""
 
-#: lxc/config.go:34
+#: lxc/config.go:35
 msgid   "Show the expanded configuration"
 msgstr  ""
 
@@ -769,11 +768,15 @@ msgstr  ""
 msgid   "The container is currently running. Use --force to have it stopped and restarted."
 msgstr  ""
 
-#: lxc/config.go:760 lxc/config.go:772 lxc/config.go:808 lxc/config.go:826 lxc/config.go:867 lxc/config.go:885
+#: lxc/config.go:770 lxc/config.go:787
+msgid   "The device already exists"
+msgstr  ""
+
+#: lxc/config.go:833 lxc/config.go:845 lxc/config.go:881 lxc/config.go:899 lxc/config.go:945 lxc/config.go:962 lxc/config.go:1005 lxc/config.go:1023
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:304
+#: lxc/init.go:305
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -855,7 +858,7 @@ msgstr  ""
 msgid   "Usage: lxc <command> [options]"
 msgstr  ""
 
-#: lxc/config.go:59
+#: lxc/config.go:60
 msgid   "Usage: lxc config <subcommand> [options]\n"
         "\n"
         "Change container or server configuration options.\n"
@@ -897,7 +900,7 @@ msgid   "Usage: lxc config <subcommand> [options]\n"
         "lxc config device show [<remote>:]<container>\n"
         "    Show full device details for container.\n"
         "\n"
-        "lxc config device remove [<remote>:]<container> <name>\n"
+        "lxc config device remove [<remote>:]<container> <name>...\n"
         "    Remove device from container.\n"
         "\n"
         "*Client trust store management*\n"
@@ -929,7 +932,7 @@ msgid   "Usage: lxc config <subcommand> [options]\n"
         "    Will set the server's trust password to blah."
 msgstr  ""
 
-#: lxc/copy.go:24
+#: lxc/copy.go:23
 msgid   "Usage: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e]\n"
         "\n"
         "Copy containers within or in between LXD instances."
@@ -1342,12 +1345,12 @@ msgstr  ""
 msgid   "YES"
 msgstr  ""
 
-#: lxc/main.go:58
-msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
+#: lxc/copy.go:49
+msgid   "You must specify a source container name"
 msgstr  ""
 
-#: lxc/copy.go:108
-msgid   "can't copy to the same container name"
+#: lxc/main.go:58
+msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
 #: lxc/remote.go:341
@@ -1384,10 +1387,6 @@ msgstr  ""
 msgid   "no"
 msgstr  ""
 
-#: lxc/copy.go:131
-msgid   "not all the profiles from the source exist on the target"
-msgstr  ""
-
 #: lxc/remote.go:206
 msgid   "ok (y/n)?"
 msgstr  ""
@@ -1438,7 +1437,3 @@ msgstr  ""
 msgid   "yes"
 msgstr  ""
 
-#: lxc/copy.go:47
-msgid   "you must specify a source container name"
-msgstr  ""
-

From 210f69bf007da15f15d4a858f4d1302a8dfd2c07 Mon Sep 17 00:00:00 2001
From: KATOH Yasufumi <karma at jazz.email.ne.jp>
Date: Thu, 28 Sep 2017 19:09:28 +0900
Subject: [PATCH 1011/1193] i18n: Update Japanese translation (for stable-2.0)

Signed-off-by: KATOH Yasufumi <karma at jazz.email.ne.jp>
---
 po/ja.po | 603 +++++++++++++++++++++++++++++++++++++++------------------------
 1 file changed, 372 insertions(+), 231 deletions(-)

diff --git a/po/ja.po b/po/ja.po
index 38ccf9a74..1176c79e4 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -8,7 +8,7 @@ msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
 "POT-Creation-Date: 2017-09-25 14:33-0400\n"
-"PO-Revision-Date: 2016-04-26 14:31+0900\n"
+"PO-Revision-Date: 2017-09-28 18:58+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
 "Language: \n"
@@ -117,9 +117,9 @@ msgid "Auto update: %s"
 msgstr "自動更新: %s"
 
 #: lxc/image.go:493
-#, fuzzy, c-format
+#, c-format
 msgid "Bad property: %s"
-msgstr "(不正なイメージプロパティ形式: %s\n"
+msgstr "不正なプロパティ: %s\n"
 
 #: lxc/info.go:186
 msgid "Bytes received"
@@ -143,9 +143,9 @@ msgid "Can't read from stdin: %s"
 msgstr "標準入力から読み込めません: %s"
 
 #: lxc/config.go:169
-#, fuzzy, c-format
+#, c-format
 msgid "Can't unset key '%s', it's not currently set"
-msgstr "キー '%s' が指定されていないので削除できません。"
+msgstr "キー '%s' が設定されていないので削除できません。"
 
 #: lxc/config.go:211 lxc/config.go:237
 #, c-format
@@ -157,9 +157,9 @@ msgid "Cannot provide container name to list"
 msgstr "コンテナ名を取得できません"
 
 #: lxc/remote.go:205
-#, fuzzy, c-format
+#, c-format
 msgid "Certificate fingerprint: %s"
-msgstr "証明書のフィンガープリント: %x"
+msgstr "証明書のフィンガープリント: %s"
 
 #: lxc/remote.go:293
 msgid "Client certificate stored at server: "
@@ -171,7 +171,7 @@ msgstr "カラムレイアウト"
 
 #: lxc/help.go:53
 msgid "Commands:"
-msgstr ""
+msgstr "コマンド:"
 
 #: lxc/init.go:134 lxc/init.go:135
 msgid "Config key/value to apply to the new container"
@@ -242,9 +242,8 @@ msgid "Device %s removed from %s"
 msgstr "デバイス %s が %s から削除されました"
 
 #: lxc/info.go:154
-#, fuzzy
 msgid "Disk usage:"
-msgstr "  ディスク使用量:"
+msgstr "ディスク使用量:"
 
 #: lxc/list.go:500
 msgid "EPHEMERAL"
@@ -255,17 +254,14 @@ msgid "EXPIRY DATE"
 msgstr ""
 
 #: lxc/main.go:47
-#, fuzzy
 msgid "Enable debug mode"
-msgstr "デバッグモードを有効にします。"
+msgstr "デバッグモードを有効にします"
 
 #: lxc/main.go:46
-#, fuzzy
 msgid "Enable verbose mode"
-msgstr "詳細モードを有効にします。"
+msgstr "詳細モードを有効にします"
 
 #: lxc/exec.go:56
-#, fuzzy
 msgid "Environment variable to set (e.g. HOME=/home/foo)"
 msgstr "環境変数を HOME=/home/foo の形式で指定します"
 
@@ -291,9 +287,9 @@ msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
 #: lxc/image.go:677
-#, fuzzy, c-format
+#, c-format
 msgid "Exporting the image: %s"
-msgstr "イメージのコピー中: %s"
+msgstr "イメージのエクスポート中: %s"
 
 #: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
 msgid "FINGERPRINT"
@@ -302,20 +298,18 @@ msgstr ""
 #: lxc/manpage.go:62
 #, c-format
 msgid "Failed to generate 'lxc.%s.1': %v"
-msgstr ""
+msgstr "'lxc.%s.1' の生成が失敗しました: %v"
 
 #: lxc/manpage.go:55
 #, c-format
 msgid "Failed to generate 'lxc.1': %v"
-msgstr ""
+msgstr "'lxc.1' の生成が失敗しました: %v"
 
 #: lxc/copy.go:171
-#, fuzzy
 msgid "Failed to get the new container name"
-msgstr "新しいコンテナに適用するプロファイル"
+msgstr "コピー先のコンテナ名が取得できませんでした"
 
 #: lxc/list.go:112
-#, fuzzy
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
@@ -325,23 +319,20 @@ msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
 
 #: lxc/action.go:46 lxc/action.go:47
-#, fuzzy
 msgid "Force the container to shutdown"
 msgstr "コンテナを強制シャットダウンします。"
 
 #: lxc/delete.go:34 lxc/delete.go:35
-#, fuzzy
 msgid "Force the removal of stopped containers"
 msgstr "停止したコンテナを強制的に削除します。"
 
 #: lxc/main.go:48
-#, fuzzy
 msgid "Force using the local unix socket"
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
 #: lxc/list.go:111
 msgid "Format (table|json)"
-msgstr ""
+msgstr "フォーマット (table|json)"
 
 #: lxc/main.go:139
 msgid "Generating a client certificate. This may take a minute..."
@@ -365,12 +356,10 @@ msgid ""
 msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要があります"
 
 #: lxc/main.go:49
-#, fuzzy
 msgid "Ignore aliases when determining what command to run"
 msgstr "どのコマンドを実行するか決める際にエイリアスを無視します。"
 
 #: lxc/action.go:50
-#, fuzzy
 msgid "Ignore the container state (only for start)"
 msgstr "コンテナの状態を無視します (startのみ)。"
 
@@ -379,9 +368,8 @@ msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
 #: lxc/image.go:727
-#, fuzzy
 msgid "Image exported successfully!"
-msgstr "イメージのコピーが成功しました!"
+msgstr "イメージのエクスポートに成功しました!"
 
 #: lxc/image.go:551
 #, c-format
@@ -394,9 +382,8 @@ msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr "不正な URL スキーム \"%s\" (\"%s\" 内)"
 
 #: lxc/config.go:329
-#, fuzzy
 msgid "Invalid certificate"
-msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
+msgstr "不正な証明書"
 
 #: lxc/init.go:31 lxc/init.go:36
 msgid "Invalid configuration key"
@@ -425,13 +412,13 @@ msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
 #: lxc/image.go:403
-#, fuzzy, c-format
+#, c-format
 msgid "Last used: %s"
-msgstr "状態: %s"
+msgstr "最終使用: %s"
 
 #: lxc/image.go:405
 msgid "Last used: never"
-msgstr ""
+msgstr "最終使用: 未使用"
 
 #: lxc/info.go:239
 msgid "Log:"
@@ -454,9 +441,8 @@ msgid "Memory (peak)"
 msgstr "メモリ (ピーク)"
 
 #: lxc/info.go:177
-#, fuzzy
 msgid "Memory usage:"
-msgstr "  メモリ消費量:"
+msgstr "メモリ消費量:"
 
 #: lxc/utils.go:197
 msgid "Missing summary."
@@ -486,9 +472,8 @@ msgid "Name: %s"
 msgstr "コンテナ名: %s"
 
 #: lxc/info.go:194
-#, fuzzy
 msgid "Network usage:"
-msgstr "  ネットワーク使用状況:"
+msgstr "ネットワーク使用状況:"
 
 #: lxc/image.go:137 lxc/publish.go:35
 msgid "New alias to define at target"
@@ -547,22 +532,18 @@ msgid "Packets sent"
 msgstr "送信パケット"
 
 #: lxc/help.go:78
-#, fuzzy
 msgid "Path to an alternate client configuration directory"
 msgstr "別のクライアント用設定ディレクトリ"
 
 #: lxc/help.go:79
-#, fuzzy
 msgid "Path to an alternate server directory"
 msgstr "別のサーバ用設定ディレクトリ"
 
 #: lxc/main.go:209
-#, fuzzy
 msgid "Pause containers."
-msgstr "コンテナの不正なURL %s"
+msgstr "コンテナを一時停止します。"
 
 #: lxc/main.go:37
-#, fuzzy
 msgid "Permission denied, are you in the lxd group?"
 msgstr "アクセスが拒否されました。lxd グループに所属していますか?"
 
@@ -580,19 +561,16 @@ msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
 #: lxc/help.go:73
-#, fuzzy
 msgid "Print debug information"
 msgstr "デバッグ情報を表示します。"
 
 #: lxc/help.go:72
-#, fuzzy
 msgid "Print less common commands"
-msgstr "全てのコマンドを表示します (主なコマンドだけではなく)。"
+msgstr "全てのコマンドを表示します"
 
 #: lxc/help.go:74
-#, fuzzy
 msgid "Print verbose information"
-msgstr "詳細情報を表示します。"
+msgstr "詳細情報を表示します"
 
 #: lxc/info.go:141
 #, c-format
@@ -614,7 +592,7 @@ msgid "Profile to apply to the new container"
 msgstr "新しいコンテナに適用するプロファイル"
 
 #: lxc/profile.go:291
-#, fuzzy, c-format
+#, c-format
 msgid "Profiles %s applied to %s"
 msgstr "プロファイル %s が %s に追加されました"
 
@@ -634,16 +612,16 @@ msgstr "Public なイメージサーバとして設定します"
 #: lxc/image.go:390
 #, c-format
 msgid "Public: %s"
-msgstr ""
+msgstr "パブリック: %s"
 
 #: lxc/remote.go:68
 msgid "Remote admin password"
 msgstr "リモートの管理者パスワード"
 
 #: lxc/info.go:101
-#, fuzzy, c-format
+#, c-format
 msgid "Remote: %s"
-msgstr "コンテナ名: %s"
+msgstr "リモート名: %s"
 
 #: lxc/delete.go:42
 #, c-format
@@ -651,18 +629,16 @@ msgid "Remove %s (yes/no): "
 msgstr "%s を消去しますか (yes/no): "
 
 #: lxc/delete.go:36 lxc/delete.go:37
-#, fuzzy
 msgid "Require user confirmation"
-msgstr "ユーザの確認を要求する。"
+msgstr "ユーザの確認を要求する"
 
 #: lxc/info.go:138
 msgid "Resources:"
 msgstr "リソース:"
 
 #: lxc/main.go:217
-#, fuzzy
 msgid "Restart containers."
-msgstr "コンテナを作成中"
+msgstr "コンテナを再起動します。"
 
 #: lxc/init.go:251
 #, c-format
@@ -715,16 +691,15 @@ msgstr "全てコマンドを表示します (主なコマンドだけではな
 
 #: lxc/help.go:75
 msgid "Show client version"
-msgstr ""
+msgstr "クライアントのバージョンを表示します"
 
 #: lxc/info.go:39
 msgid "Show the container's last 100 log lines?"
 msgstr "コンテナログの最後の 100 行を表示しますか?"
 
 #: lxc/config.go:35
-#, fuzzy
 msgid "Show the expanded configuration"
-msgstr "拡張した設定を表示するかどうか"
+msgstr "拡張した設定を表示する"
 
 #: lxc/image.go:388
 #, c-format
@@ -736,18 +711,17 @@ msgid "Snapshots:"
 msgstr "スナップショット:"
 
 #: lxc/action.go:142
-#, fuzzy, c-format
+#, c-format
 msgid "Some containers failed to %s"
-msgstr "コンテナの停止に失敗しました!"
+msgstr "一部のコンテナで %s が失敗しました"
 
 #: lxc/image.go:421
 msgid "Source:"
 msgstr "取得元:"
 
 #: lxc/main.go:227
-#, fuzzy
 msgid "Start containers."
-msgstr "コンテナの不正なURL %s"
+msgstr "コンテナを起動します。"
 
 #: lxc/launch.go:59
 #, c-format
@@ -760,9 +734,8 @@ msgid "Status: %s"
 msgstr "状態: %s"
 
 #: lxc/main.go:233
-#, fuzzy
 msgid "Stop containers."
-msgstr "コンテナの停止に失敗しました!"
+msgstr "コンテナを停止します。"
 
 #: lxc/publish.go:36 lxc/publish.go:37
 msgid "Stop the container if currently running"
@@ -773,14 +746,13 @@ msgid "Stopping container failed!"
 msgstr "コンテナの停止に失敗しました!"
 
 #: lxc/delete.go:121
-#, fuzzy, c-format
+#, c-format
 msgid "Stopping the container failed: %s"
-msgstr "コンテナの停止に失敗しました!"
+msgstr "コンテナの停止に失敗しました: %s"
 
 #: lxc/action.go:49
-#, fuzzy
 msgid "Store the container state (only for stop)"
-msgstr "コンテナの状態を保存します (stopのみ)。"
+msgstr "コンテナの状態を保存します (stopのみ)"
 
 #: lxc/info.go:169
 msgid "Swap (current)"
@@ -807,9 +779,8 @@ msgstr ""
 "さい。"
 
 #: lxc/config.go:770 lxc/config.go:787
-#, fuzzy
 msgid "The device already exists"
-msgstr "リモート %s は既に存在します"
+msgstr "デバイス %s は既に存在します"
 
 #: lxc/config.go:833 lxc/config.go:845 lxc/config.go:881 lxc/config.go:899
 #: lxc/config.go:945 lxc/config.go:962 lxc/config.go:1005 lxc/config.go:1023
@@ -824,7 +795,7 @@ msgstr ""
 
 #: lxc/action.go:34
 msgid "The opposite of \"lxc pause\" is \"lxc start\"."
-msgstr ""
+msgstr "\"lxc pause\" の反対のコマンドは \"lxc start\" です。"
 
 #: lxc/publish.go:72
 msgid "There is no \"image name\".  Did you want an alias?"
@@ -840,9 +811,12 @@ msgid ""
 "All of LXD's features can be driven through the various commands below.\n"
 "For help with any of those, simply call them with --help."
 msgstr ""
+"これは LXD のコマンドクライアントです。\n"
+"\n"
+"LXD の機能のすべてが、以下の色々なコマンドから操作できます。\n"
+"コマンドのヘルプは、--help をコマンドに付けて実行するだけです。"
 
 #: lxc/action.go:45
-#, fuzzy
 msgid "Time to wait for the container before killing it"
 msgstr "コンテナを強制停止するまでの時間"
 
@@ -858,9 +832,9 @@ msgstr ""
 "さい。"
 
 #: lxc/image.go:499
-#, fuzzy, c-format
+#, c-format
 msgid "Transferring image: %s"
-msgstr "イメージを転送中: %d%%"
+msgstr "イメージを転送中: %s"
 
 #: lxc/action.go:106 lxc/launch.go:77
 #, c-format
@@ -885,7 +859,7 @@ msgstr ""
 
 #: lxc/manpage.go:36
 msgid "Unable to find help2man."
-msgstr ""
+msgstr "help2man が見つかりません。"
 
 #: lxc/remote.go:96
 msgid "Unable to read remote TLS certificate"
@@ -903,11 +877,13 @@ msgid ""
 "\n"
 "%s%s"
 msgstr ""
+"使い方: lxc %s [<remote>:]<container> [[<remote>:]<container>...]\n"
+"\n"
+"%s%s"
 
 #: lxc/help.go:45
-#, fuzzy
 msgid "Usage: lxc <command> [options]"
-msgstr "使い方: lxc [サブコマンド] [オプション]"
+msgstr "使い方: lxc <command> [options]"
 
 #: lxc/config.go:60
 msgid ""
@@ -985,6 +961,77 @@ msgid ""
 "lxc config set core.trust_password blah\n"
 "    Will set the server's trust password to blah."
 msgstr ""
+"使い方: lxc config <subcommand> [options]\n"
+"\n"
+"コンテナもしくはサーバの設定オプションを変更します。\n"
+"\n"
+"*コンテナの設定*\n"
+"\n"
+"lxc config get [<remote>:][container] <key>\n"
+"    コンテナもしくはサーバの設定項目の値を取得します。\n"
+"\n"
+"lxc config set [<remote>:][container] <key> <value>\n"
+"    コンテナもしくはサーバの設定項目に値を設定します。\n"
+"\n"
+"lxc config unset [<remote>:][container] <key>\n"
+"    コンテナもしくはサーバの設定項目を削除します。\n"
+"\n"
+"lxc config show [<remote>:][container] [--expanded]\n"
+"    コンテナもしくはサーバの設定を表示します。\n"
+"\n"
+"lxc config edit [<remote>:][container]\n"
+"    外部エディタを起動するか、標準入力から読み込むことにより、設定を編集します。\n"
+"\n"
+"*デバイスの管理*\n"
+"\n"
+"lxc config device add [<remote>:]<container> <device> <type> [key=value...]\n"
+"    コンテナにデバイスを追加します。\n"
+"\n"
+"lxc config device get [<remote>:]<container> <device> <key>\n"
+"    デバイスのプロパティを取得します。\n"
+"\n"
+"lxc config device set [<remote>:]<container> <device> <key> <value>\n"
+"    デバイスのプロパティを設定します。\n"
+"\n"
+"lxc config device unset [<remote>:]<container> <device> <key>\n"
+"    デバイスのプロパティを削除します。\n"
+"\n"
+"lxc config device list [<remote>:]<container>\n"
+"    コンテナのデバイスを一覧表示します。\n"
+"\n"
+"lxc config device show [<remote>:]<container>\n"
+"    全てのデバイスの詳細を表示します。\n"
+"\n"
+"lxc config device remove [<remote>:]<container> <name>...\n"
+"    コンテナからデバイスを削除します。\n"
+"\n"
+"*クライアントの証明書ストアの管理*\n"
+"\n"
+"lxc config trust list [<remote>:]\n"
+"    信頼する証明書を全て表示します。\n"
+"\n"
+"lxc config trust add [<remote>:] <certfile.crt>\n"
+"    certfile.crt を信頼するホストに追加します。\n"
+"\n"
+"lxc config trust remove [<remote>:] [hostname|fingerprint]\n"
+"    信頼するホストから証明書を消去します。\n"
+"\n"
+"*例*\n"
+"\n"
+"cat config.yaml | lxc config edit <container>\n"
+"    config.yaml から読み込んで、コンテナの設定を更新します。\n"
+"\n"
+"lxc config device add [<remote>:]container1 <device-name> disk source=/share/c1 path=opt\n"
+"    ホストの /share/c1 をコンテナ内の /opt にマウントします。\n"
+"\n"
+"lxc config set [<remote>:]<container> limits.cpu 2\n"
+"    コンテナの CPU 制限値を \"2\" に設定する。\n"
+"\n"
+"lxc config set core.https_address [::]:8443\n"
+"    IPv4 と IPv6 のポート 8443 で Listen するよう設定する。\n"
+"\n"
+"lxc config set core.trust_password blah\n"
+"    サーバのパスワードを blah に設定する。"
 
 #: lxc/copy.go:23
 msgid ""
@@ -993,25 +1040,22 @@ msgid ""
 "\n"
 "Copy containers within or in between LXD instances."
 msgstr ""
+"使い方: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e]\n"
+"\n"
+"LXDインスタンス内もしくはLXDインスタンス間でコンテナをコピーします。"
 
 #: lxc/delete.go:27
-#, fuzzy
 msgid ""
 "Usage: lxc delete [<remote>:]<container>[/<snapshot>] "
 "[[<remote>:]<container>[/<snapshot>]...]\n"
 "\n"
 "Delete containers and snapshots."
 msgstr ""
-"コンテナもしくはコンテナのスナップショットを消去します。\n"
-"\n"
-"lxc delete [remote:]<container>[/<snapshot>] [remote:]"
-"[<container>[<snapshot>]...]\n"
+"使い方: lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]\n"
 "\n"
-"付属するデータ (設定、スナップショット、...) と一緒にコンテナもしくはコンテ\n"
-"ナのスナップショットを消去します。"
+"コンテナとコンテナのスナップショットを消去します。"
 
 #: lxc/exec.go:47
-#, fuzzy
 msgid ""
 "Usage: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-"
 "interactive] [--env KEY=VALUE...] [--] <command line>\n"
@@ -1021,13 +1065,12 @@ msgid ""
 "Mode defaults to non-interactive, interactive mode is selected if both stdin "
 "AND stdout are terminals (stderr is ignored)."
 msgstr ""
-"指定したコマンドをコンテナ内で実行します。\n"
+"使い方: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>\n"
 "\n"
-"lxc exec [remote:]container [--mode=auto|interactive|non-interactive] [--env "
-"EDITOR=/usr/bin/vim]... <command>\n"
+"指定したコマンドをコンテナ内で実行します。\n"
 "\n"
-"デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナ\n"
-"ルの場合は interactive モードが選択されます (標準エラー出力は無視されます)。"
+"デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナル\n"
+"の場合は interactive モードが選択されます (標準エラー出力は無視されます)。"
 
 #: lxc/file.go:33
 msgid ""
@@ -1054,6 +1097,25 @@ msgid ""
 "   To pull /etc/hosts from the container and write it to the current "
 "directory."
 msgstr ""
+"使い方: lxc file <subcommand> [options]\n"
+"\n"
+"コンテナ内のファイルを管理します。\n"
+"\n"
+"lxc file pull [<remote>:]<container>/<path> [[<remote>:]<container>/<path>...] <target path>\n"
+"    コンテナからファイルを取得します。\n"
+"\n"
+"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source path>...] [<remote>:]<container>/<path>\n"
+"    コンテナにファイルを設置します。\n"
+"\n"
+"lxc file edit [<remote>:]<container>/<path>\n"
+"    デフォルトのテキストエディタを使用してコンテナ内のファイルを編集します。\n"
+"\n"
+"*例*\n"
+"lxc file push /etc/hosts foo/etc/hosts\n"
+"   /etc/hosts を \"foo\" コンテナ内に設置します。\n"
+"\n"
+"lxc file pull foo/etc/hosts .\n"
+"   コンテナから /etc/hosts を取得し、カレントディレクトリに置きます。"
 
 #: lxc/finger.go:15
 msgid ""
@@ -1061,6 +1123,9 @@ msgid ""
 "\n"
 "Check if the LXD server is alive."
 msgstr ""
+"使い方: lxc finger [<remote>:]\n"
+"\n"
+"LXD サーバが動作しているかチェックします。"
 
 #: lxc/help.go:22
 msgid ""
@@ -1068,9 +1133,11 @@ msgid ""
 "\n"
 "Help page for the LXD client."
 msgstr ""
+"使い方: lxc help [--all]\n"
+"\n"
+"LXD クライアントのヘルプを表示します。"
 
 #: lxc/image.go:63
-#, fuzzy
 msgid ""
 "Usage: lxc image <subcommand> [options]\n"
 "\n"
@@ -1144,6 +1211,8 @@ msgid ""
 "    List the aliases. Filters may be part of the image hash or part of the "
 "image alias name."
 msgstr ""
+"使い方: lxc image <subcommand> [options]\n"
+"\n"
 "コンテナイメージを操作します。\n"
 "\n"
 "LXD では、コンテナはイメージから作られます。このイメージは、既存のコンテナ\n"
@@ -1159,53 +1228,58 @@ msgstr ""
 "る場合は) エイリアスで参照できます。\n"
 "\n"
 "\n"
-"lxc image import <tarball> [rootfs tarball|URL] [remote:] [--public] [--"
-"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
-"alias=ALIAS].. [prop=value]\n"
+"lxc image import <tarball> [<rootfs tarball>|<URL>] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS].. [prop=value]\n"
 "    イメージの tarball (複数も可能) を LXD のイメージストアにインポートしま\n"
 "    す。\n"
 "\n"
-"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
-"[--public] [--auto-update]\n"
+"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update]\n"
 "    ネットワーク経由である LXD デーモンから他の LXD デーモンへイメージを\n"
 "    コピーします。\n"
 "\n"
 "    auto-update フラグは、サーバがイメージを最新に保つように指示します。イ\n"
 "    メージのソースがエイリアスであり、public である必要があります。\n"
 "\n"
-"lxc image delete [remote:]<image>\n"
-"    LXD のイメージストアからイメージを削除します。\n"
+"lxc image delete [remote:]<image> [[<remote>:]<image>...]\n"
+"    LXD のイメージストアから (ひとつ以上の) イメージを削除します。\n"
 "\n"
-"lxc image export [remote:]<image>\n"
+"lxc image export [<remote>:]<image> [target]\n"
 "    LXD のイメージストアから配布可能な tarball としてイメージをエクスポート\n"
 "    します。\n"
 "\n"
-"lxc image info [remote:]<image>\n"
+"    出力先の指定はオプションで、デフォルトでは現在のディレクトリです。\n"
+"    出力先は存在するディレクトリ、ファイル名、標準出力を示す \\\"-\\\" のい\n"
+"    ずれかです。分割イメージをエクスポートする際は、出力先はディレクト\n"
+"    リでなければなりません。出力先がディレクトリの場合、データベースで\n"
+"    見つかったイメージ名 (分割イメージの部分それぞれの名前) をエクスポー\n"
+"    トするイメージに使用します。出力先がファイルの場合 (ディレクトリで\n"
+"    も標準出力でもない場合)、イメージを圧縮するアルゴリズムにもとづい\n"
+"    た適切な拡張子が対象のファイル名に追加されます。\n"
+"\n"
+"lxc image info [<remote>:]<image>\n"
 "    指定したイメージについてのすべての情報を表示します。\n"
 "\n"
-"lxc image list [remote:] [filter]\n"
+"lxc image list [<remote>:] [filter]\n"
 "    LXD のイメージストア内のイメージを一覧表示します。プロパティでフィルタ\n"
 "    を行う場合は、フィルタは <key>=<value> の形になります。フィルタはイメー\n"
 "    ジハッシュの一部やイメージエイリアス名の一部も指定できます。\n"
 "\n"
-"lxc image show [remote:]<image>\n"
+"lxc image show [<remote>:]<image>\n"
 "    ユーザが変更できるプロパティの YAML 形式の出力を行います。\n"
 "\n"
-"lxc image edit [remote:]<image>\n"
+"lxc image edit [<remote>:]<image>\n"
 "    外部エディタまたは標準入力からの読み込みにより、イメージを編集します。\n"
 "    例: lxc image edit <image> # エディタの起動\n"
 "        cat image.yml | lxc image edit <image> # image.yml から読み込み\n"
 "\n"
-"lxc image alias create [remote:]<alias> <fingerprint>\n"
+"lxc image alias create [<remote>:]<alias> <fingerprint>\n"
 "    既存のイメージに新たにエイリアスを作成します。\n"
 "\n"
-"lxc image alias delete [remote:]<alias>\n"
+"lxc image alias delete [<remote>:]<alias>\n"
 "    エイリアスを削除します。\n"
 "\n"
-"lxc image alias list [remote:] [filter]\n"
-"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリア"
-"ス\n"
-"    名の一部をフィルタとして指定できます。\n"
+"lxc image alias list [<remote>:] [filter]\n"
+"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリアス\n"
+"    名の一部をフィルタとして指定できます。"
 
 #: lxc/info.go:26
 msgid ""
@@ -1219,9 +1293,17 @@ msgid ""
 "lxc info [<remote>:]\n"
 "    For LXD server information."
 msgstr ""
+"使い方: lxc info [<remote>:][<container>] [--show-log]\n"
+"\n"
+"コンテナもしくはサーバの情報を表示します。\n"
+"\n"
+"lxc info [<remote>:]<container> [--show-log]\n"
+"    コンテナの情報を表示します。\n"
+"\n"
+"lxc info [<remote>:]\n"
+"    LXD サーバの情報を表示します。"
 
 #: lxc/init.go:74
-#, fuzzy
 msgid ""
 "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
 "profile|-p <profile>...] [--config|-c <key=value>...]\n"
@@ -1236,8 +1318,7 @@ msgid ""
 msgstr ""
 "指定したイメージからコンテナを初期化します。\n"
 "\n"
-"lxc init [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
+"lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
 "\n"
 "指定したイメージとコンテナ名を使ってコンテナを初期化します。\n"
 "\n"
@@ -1248,7 +1329,6 @@ msgstr ""
 "lxc init ubuntu u1"
 
 #: lxc/launch.go:20
-#, fuzzy
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
 "profile|-p <profile>...] [--config|-c <key=value>...]\n"
@@ -1263,8 +1343,7 @@ msgid ""
 msgstr ""
 "指定したイメージからコンテナを起動します。\n"
 "\n"
-"lxc launch [remote:]<image> [remote:][<name>] [--ephemeral|-e] [--profile|-p "
-"<profile>...] [--config|-c <key=value>...]\n"
+"lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
 "\n"
 "指定したイメージと名前を使ってコンテナを起動します。\n"
 "\n"
@@ -1275,7 +1354,6 @@ msgstr ""
 "lxc launch ubuntu:16.04 u1"
 
 #: lxc/list.go:46
-#, fuzzy
 msgid ""
 "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
 "[--fast]\n"
@@ -1343,41 +1421,69 @@ msgid ""
 "    Shows a list of containers using the \"NAME\", \"STATE\", \"IPV4\", "
 "\"IPV6\" columns."
 msgstr ""
-"利用可能なリソースを一覧表示します。\n"
-"\n"
-"lxc list [resource] [filters] [--format table|json] [-c columns] [--fast]\n"
-"\n"
-"フィルタの指定:\n"
-"* 単一の \"web\" のようなキーワードを指定すると、名前が \"web\" ではじまるコ"
-"ンテ\n"
-"  ナが一覧表示されます。\n"
-"* コンテナ名の正規表現 (例: .*web.*01$)\n"
-"* 設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することが"
-"で\n"
-"  きます:\n"
-" * \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定され"
-"ている\n"
-"  コンテナをすべて一覧表示します。\n"
-" * \"u.blah=abc\" は上記と同じ意味になります。\n"
-" * \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
-" * \"s.privilaged=1\" は上記と同じ意味になります。\n"
-" * 設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:."
-"*)\n"
-"\n"
-"表のカラムの指定:\n"
-"* 4 - IPv4 アドレス\n"
-"* 6 - IPv6 アドレス\n"
-"* a - アーキテクチャ\n"
-"* c - 作成日\n"
-"* n - 名前\n"
-"* p - コンテナの init プロセスの pid\n"
-"* P - プロファイル\n"
-"* s - 状態\n"
-"* S - スナップショットの数\n"
-"* t - タイプ (persistent or ephemeral)\n"
+"lxc list [<remote>:] [filters] [--format table|json] [-c columns] [--fast]\n"
+"\n"
+"コンテナを一覧表示します。\n"
 "\n"
 "デフォルトのカラムレイアウト: ns46tS\n"
-"Fast モードのカラムレイアウト: nsacPt"
+"Fast モードのカラムレイアウト: nsacPt\n"
+"\n"
+"*フィルタ*\n"
+"単一の \"web\" のようなキーワードを指定すると、名前が \"web\" ではじまるコ\n"
+"ンテナが一覧表示されます。\n"
+"\n"
+"コンテナ名の正規表現 (例: .*web.*01$)。\n"
+"\n"
+"設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することがで\n"
+"きます:\n"
+" - \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定され\n"
+"   ているコンテナをすべて一覧表示します。\n"
+"\n"
+" - \"u.blah=abc\" は上記と同じ意味になります。\n"
+"\n"
+" - \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
+"\n"
+" - \"s.privilaged=1\" は上記と同じ意味になります。\n"
+"\n"
+"設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:.*)\n"
+"\n"
+"*カラム*\n"
+"\n"
+"-c オプションには、表形式で表示する際にどのコンテナの属性を表示するか\n"
+"を指定するコンマ区切りのリストを指定します。\n"
+"\n"
+"カラムの指定は、定義済みの短縮形 (以下を参照) もしくは (拡張された)\n"
+"設定キーのどちらかを指定します。\n"
+"\n"
+"連続する短縮形の間のコンマは省略できます。\n"
+"\n"
+"短縮形で指定できる文字:\\n\"\n"
+"\n"
+"    4 - IPv4 アドレス\n"
+"\n"
+"    6 - IPv6 アドレス\n"
+"\n"
+"    a - アーキテクチャ\n"
+"\n"
+"    c - 作成日\n"
+"\n"
+"    l - 最終使用日\n"
+"\n"
+"    n - 名前\n"
+"\n"
+"    p - コンテナの init プロセスの pid\n"
+"\n"
+"    P - プロファイル\n"
+"\n"
+"    s - 状態\n"
+"\n"
+"    S - スナップショットの数\n"
+"\n"
+"    t - タイプ (persistent or ephemeral)\n"
+"\n"
+"*例*\n"
+"lxc list -c ns46\n"
+"    コンテナの \"NAME\"、\"STATE\"、\"IPV4\"、\"IPV6\" を使って一覧表示します。"
 
 #: lxc/manpage.go:20
 msgid ""
@@ -1385,9 +1491,11 @@ msgid ""
 "\n"
 "Generate all the LXD manpages."
 msgstr ""
+"使い方: lxc manpage <directory>\n"
+"\n"
+"LXD の manpage をすべて生成します。"
 
 #: lxc/monitor.go:41
-#, fuzzy
 msgid ""
 "Usage: lxc monitor [<remote>:] [--type=TYPE...]\n"
 "\n"
@@ -1403,18 +1511,19 @@ msgid ""
 msgstr ""
 "LXD サーバの動作をモニタリングします。\n"
 "\n"
-"lxc monitor [remote:] [--type=TYPE...]\n"
+"使い方: lxc monitor [<remote>:] [--type=TYPE...]\n"
 "\n"
-"指定した LXD サーバのモニタリングインターフェースに接続します。\n"
+"ローカルまたはリモートの LXD サーバの動作をモニタリングします。\n"
 "\n"
 "デフォルトでは全てのタイプをモニタリングします。\n"
+"\n"
 "--type により、モニタリングするタイプを指定できます。\n"
 "\n"
 "例:\n"
-"lxc monitor --type=logging"
+"lxc monitor --type=logging\n"
+"    ログメッセージのみ表示します。"
 
 #: lxc/move.go:20
-#, fuzzy
 msgid ""
 "Usage: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/"
 "<snapshot>]]\n"
@@ -1431,17 +1540,21 @@ msgid ""
 "lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
 "    Rename a snapshot."
 msgstr ""
+"使い方: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]]\n"
+"\n"
 "LXD ホスト内、もしくは LXD ホスト間でコンテナを移動します。\n"
 "\n"
-"lxc move [remote:]<source container> [remote:]<destination container>\n"
+"lxc move [<remote>:]<source container> [<remote>:][<destination container>]\n"
 "    2 つのホスト間でコンテナを移動します。コピー先の名前が元と違う場合は\n"
 "    同時にリネームされます。\n"
 "\n"
 "lxc move <old name> <new name>\n"
 "    ローカルのコンテナをリネームします。\n"
+"\n"
+"lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
+"    スナップショットをリネームします。"
 
 #: lxc/profile.go:49
-#, fuzzy
 msgid ""
 "Usage: lxc profile <subcommand> [options]\n"
 "\n"
@@ -1516,71 +1629,97 @@ msgid ""
 "lxc profile apply foo ''\n"
 "    Remove all profile from \"foo\""
 msgstr ""
+"使い方: lxc profile <subcommand> [options]\n"
+"\n"
 "設定プロファイルを管理します。\n"
 "\n"
-"lxc profile list [filters]\n"
+"*プロファイル設定*\n"
+"lxc profile list [<remote>:]\n"
 "    利用可能なプロファイルを一覧します。\n"
-"lxc profile show <profile>\n"
+"\n"
+"lxc profile show [<remote>:]<profile>\n"
 "    プロファイルの詳細を表示します。\n"
-"lxc profile create <profile>\n"
+"\n"
+"lxc profile create [<remote>:]<profile>\n"
 "    プロファイルを作成します。\n"
-"lxc profile copy <profile> <remote>\n"
-"    プロファイルを remote にコピーします。\n"
-"lxc profile get <profile> <key>\n"
+"\n"
+"lxc profile copy [<remote>:]<profile> [<remote>:]<profile>\n"
+"    プロファイルをコピーします。\n"
+"\n"
+"lxc profile get [<remote>:]<profile> <key>\n"
 "    プロファイルの設定を取得します。\n"
-"lxc profile set <profile> <key> <value>\n"
+"\n"
+"lxc profile set [<remote>:]<profile> <key> <value>\n"
 "    プロファイルの設定を設定します。\n"
-"lxc profile delete <profile>\n"
+"\n"
+"lxc profile unset [<remote>:]<profile> <key>\n"
+"    プロファイルから設定項目を削除します。\n"
+"\n"
+"lxc profile delete [<remote>:]<profile>\n"
 "    プロファイルを削除します。\n"
-"lxc profile edit <profile>\n"
+"\n"
+"lxc profile edit [<remote>:]<profile>\n"
 "    プロファイルを編集します。外部エディタもしくはSTDINから読み込みます。\n"
-"    例: lxc profile edit <profile> # エディタの起動\n"
-"        cat profile.yml | lxc profile edit <profile> # profile.yml から読み込"
-"み\n"
-"\n"
-"lxc profile apply <container> <profiles>\n"
-"    プロファイルのコンマ区切りのリストをコンテナに順番に適用します。\n"
-"    このコマンドで指定したプロファイルだけが対象のコンテナに適用されます。\n"
-"    例: lxc profile apply foo default,bar # defaultとbarを適用\n"
-"        lxc profile apply foo default # defaultだけを有効化\n"
-"        lxc profile apply '' # 一切のプロファイルを適用しない\n"
-"        lxc profile apply bar,default # defaultを2番目に適用\n"
-"lxc profile apply-add <container> <profile>\n"
-"lxc profile apply-remove <container> <profile>\n"
-"\n"
-"デバイス:\n"
-"lxc profile device list <profile>\n"
+"\n"
+"*プロファイルの割り当て*\n"
+"lxc profile apply [<remote>:]<container> <profiles>\n"
+"    コンテナの現在のプロファイルを指定したプロファイルで置き換えます。\n"
+"\n"
+"lxc profile add [<remote>:]<container> <profile>\n"
+"    コンテナにプロファイルを追加します。\n"
+"\n"
+"lxc profile remove [<remote>:]<container> <profile>\n"
+"    コンテナからプロファイルを削除します。\n"
+"\n"
+"*デバイス管理*\n"
+"lxc profile device list [<remote>:]<profile>\n"
 "    指定したプロファイル内のデバイスを一覧表示します\n"
-"lxc profile device show <profile>\n"
+"\n"
+"lxc profile device show [<remote>:]<profile>\n"
 "    指定したプロファイル内の全デバイスの詳細を表示します\n"
-"lxc profile device remove <profile> <name>\n"
+"\n"
+"lxc profile device remove [<remote>:]<profile> <name>\n"
 "    プロファイルからデバイスを削除します\n"
-"lxc profile device get <[remote:]profile> <name> <key>\n"
+"\n"
+"lxc profile device get [<remote>:]<profile> <name> <key>\n"
 "    デバイスプロパティを取得します\n"
-"lxc profile device set <[remote:]profile> <name> <key> <value>\n"
+"\n"
+"lxc profile device set [<remote>:]<profile> <name> <key> <value>\n"
 "    デバイスプロパティを設定します\n"
+"\n"
 "lxc profile device unset <[remote:]profile> <name> <key>\n"
 "    デバイスプロパティを削除します\n"
-"lxc profile device add <profile name> <device name> <device type> "
-"[key=value]...\n"
+"\n"
+"lxc profile device add [<remote>:]<profile> <device> <type> [key=value...]\n"
 "    ディスクやNICのようなプロファイルデバイスを指定したプロファイルを使って\n"
-"    コンテナに追加します。"
+"    コンテナに追加します。\n"
+"\n"
+"*例*\n"
+"cat profile.yaml | lxc profile edit <profile>\n"
+"    profile.yaml の内容でプロファイルを更新します。\n"
+"\n"
+"lxc profile apply foo default,bar\n"
+"    \"default\" と \"bar\" をプロファイル \"foo\" に設定します。\n"
+"\n"
+"lxc profile apply foo default\n"
+"    \"foo\" プロファイルを \"default\" プロファイルの内容のみにリセットし\n"
+"    ます。\n"
+"\n"
+"lxc profile apply foo ''\n"
+"    \"foo\" からすべてのプロファイルを削除します。"
 
 #: lxc/publish.go:27
-#, fuzzy
 msgid ""
 "Usage: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--"
 "alias=ALIAS...] [prop-key=prop-value...]\n"
 "\n"
 "Publish containers as images."
 msgstr ""
-"イメージとしてコンテナを publish します。\n"
+"使い方: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]\n"
 "\n"
-"lxc publish [remote:]container [remote:] [--alias=ALIAS]... [prop-key=prop-"
-"value]..."
+"イメージとしてコンテナを publish します。"
 
 #: lxc/remote.go:39
-#, fuzzy
 msgid ""
 "Usage: lxc remote <subcommand> [options]\n"
 "\n"
@@ -1608,27 +1747,32 @@ msgid ""
 "lxc remote get-default\n"
 "    Print the default remote."
 msgstr ""
-"リモートの LXD サーバを管理します。\n"
+"使い方: lxc remote <subcommand> [options]\n"
+"\n"
+"リモートの LXD サーバのリストを管理します。\n"
+"\n"
+"lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD] [--public] [--protocol=PROTOCOL]\n"
+"    <url> をリモートホスト <remote> として追加します。\n"
+"\n"
+"lxc remote remove <remote>\n"
+"    リモートホスト <remote> を削除します。\n"
 "\n"
-"lxc remote add <name> <url>\n"
-"    [--accept-certificate] [--password=PASSWORD]\n"
-"    [--public] [--protocol=PROTOCOL]\n"
-"    <url> をリモートホスト <name> として追加します。\n"
-"lxc remote remove <name>\n"
-"    リモートホスト <name> を削除します。\n"
 "lxc remote list\n"
-"    登録済みのリモートホストを全て一覧表示します。\n"
-"lxc remote rename <old> <new>\n"
-"    リモートホストの名前を <old> から <new> に変更します。\n"
-"lxc remote set-url <name> <url>\n"
-"    <name> の url を <url> に更新します。\n"
-"lxc remote set-default <name>\n"
-"    <name> をデフォルトのリモートホストに設定します。\n"
+"    リモートホストを全て一覧表示します。\n"
+"\n"
+"lxc remote rename <old name> <new name>\n"
+"    リモートホストの名前を <old name> から <new name> に変更します。\n"
+"\n"
+"lxc remote set-url <remote> <url>\n"
+"    <remote> の url を <url> に更新します。\n"
+"\n"
+"lxc remote set-default <remote>\n"
+"    <remote> をデフォルトのリモートホストに設定します。\n"
+"\n"
 "lxc remote get-default\n"
 "    デフォルトに設定されているリモートホストを表示します。"
 
 #: lxc/restore.go:22
-#, fuzzy
 msgid ""
 "Usage: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
 "\n"
@@ -1643,19 +1787,20 @@ msgid ""
 "lxc restore u1 snap0\n"
 "    Restore the snapshot."
 msgstr ""
-"リソースの現在の状態をスナップショット時点の状態に設定します。\n"
+"使い方: lxc restore [<remote>:]<container> <snapshot> [--stateful]\n"
 "\n"
-"lxc restore [remote:]<container> <snapshot name> [--stateful]\n"
+"スナップショットからコンテナをリストアします。\n"
 "\n"
-"スナップショットからコンテナをリストアします (オプションで実行状態もリスト\n"
-"アします。詳しくはスナップショットのヘルプをご覧ください)。\n"
+"--stateful を指定すると、実行状態もリストアされます。\n"
 "\n"
-"例:\n"
-"lxc snapshot u1 snap0 # スナップショットの作成\n"
-"lxc restore u1 snap0 # スナップショットからリストア"
+"*例*\n"
+"lxc snapshot u1 snap0\n"
+"    スナップショットを作成します。\n"
+"\n"
+"lxc restore u1 snap0\n"
+"    スナップショットからリストアします。"
 
 #: lxc/snapshot.go:22
-#, fuzzy
 msgid ""
 "Usage: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
 "\n"
@@ -1668,29 +1813,26 @@ msgid ""
 "lxc snapshot u1 snap0\n"
 "    Create a snapshot of \"u1\" called \"snap0\"."
 msgstr ""
-"コンテナの読み取り専用のスナップショットを作成します。\n"
+"使い方: lxc snapshot [<remote>:]<container> <snapshot name> [--stateful]\n"
 "\n"
-"lxc snapshot [remote:]<source> <snapshot name> [--stateful]\n"
+"コンテナのスナップショットを作成します。\n"
 "\n"
-"コンテナのスナップショットを作成します (オプションでコンテナのメモリ状態を\n"
-"含めて)。--statefulが指定された場合、LXDはコンテナプロセスのメモリ状態、TCP\n"
-"接続などの実行状態を、あとでlxc restoreを使ってリストアできるように、コンテ\n"
-"ナのチェックポイントを取得しようとします (しかし、タイムアウトウィンドウが\n"
-"expireしたあとのTCP接続のように正常にリストアできないものもあります)\n"
+"--stateful を指定すると、LXD はプロセスのメモリ状態、TCP 接続などを含\n"
+"む、コンテナの実行状態をチェックポイントしようとします。\n"
 "\n"
-"例:\n"
-"lxc snapshot u1 snap0"
+"*例*\n"
+"lxc snapshot u1 snap0\n"
+"    \"u1\" のスナップショットを \"snap0\" という名前で作成します。"
 
 #: lxc/version.go:18
-#, fuzzy
 msgid ""
 "Usage: lxc version\n"
 "\n"
 "Print the version number of this client tool."
 msgstr ""
-"お使いのクライアントのバージョン番号を表示します。\n"
+"使い方: lxc version\n"
 "\n"
-"lxc version"
+"お使いのクライアントのバージョン番号を表示します。"
 
 #: lxc/delete.go:46
 msgid "User aborted delete operation."
@@ -1712,7 +1854,6 @@ msgid "YES"
 msgstr ""
 
 #: lxc/copy.go:49
-#, fuzzy
 msgid "You must specify a source container name"
 msgstr "コピー元のコンテナ名を指定してください"
 

From e1e5c152b23e2b367063060fe751b1d0c2a48262 Mon Sep 17 00:00:00 2001
From: KATOH Yasufumi <karma at jazz.email.ne.jp>
Date: Thu, 28 Sep 2017 21:57:37 +0900
Subject: [PATCH 1012/1193] i18n: Tweak and fix Japanese translation

Signed-off-by: KATOH Yasufumi <karma at jazz.email.ne.jp>
---
 po/ja.po | 34 +++++++++++-----------------------
 1 file changed, 11 insertions(+), 23 deletions(-)

diff --git a/po/ja.po b/po/ja.po
index 1176c79e4..f3967c03f 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -8,7 +8,7 @@ msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
 "POT-Creation-Date: 2017-09-25 14:33-0400\n"
-"PO-Revision-Date: 2017-09-28 18:58+0900\n"
+"PO-Revision-Date: 2017-09-28 21:49+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
 "Language: \n"
@@ -1316,9 +1316,7 @@ msgid ""
 "Examples:\n"
 "    lxc init ubuntu:16.04 u1"
 msgstr ""
-"指定したイメージからコンテナを初期化します。\n"
-"\n"
-"lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"使い方: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
 "\n"
 "指定したイメージとコンテナ名を使ってコンテナを初期化します。\n"
 "\n"
@@ -1326,7 +1324,7 @@ msgstr ""
 "\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n"
 "\n"
 "例:\n"
-"lxc init ubuntu u1"
+"    lxc init ubuntu u1"
 
 #: lxc/launch.go:20
 msgid ""
@@ -1341,9 +1339,7 @@ msgid ""
 "Examples:\n"
 "    lxc launch ubuntu:16.04 u1"
 msgstr ""
-"指定したイメージからコンテナを起動します。\n"
-"\n"
-"lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"使い方: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
 "\n"
 "指定したイメージと名前を使ってコンテナを起動します。\n"
 "\n"
@@ -1351,7 +1347,7 @@ msgstr ""
 "\"-p\" のように引数なしで -p を使うとプロファイルなしとなります。\n"
 "\n"
 "例:\n"
-"lxc launch ubuntu:16.04 u1"
+"    lxc launch ubuntu:16.04 u1"
 
 #: lxc/list.go:46
 msgid ""
@@ -1421,7 +1417,7 @@ msgid ""
 "    Shows a list of containers using the \"NAME\", \"STATE\", \"IPV4\", "
 "\"IPV6\" columns."
 msgstr ""
-"lxc list [<remote>:] [filters] [--format table|json] [-c columns] [--fast]\n"
+"使い方: lxc list [<remote>:] [filters] [--format table|json] [-c columns] [--fast]\n"
 "\n"
 "コンテナを一覧表示します。\n"
 "\n"
@@ -1436,14 +1432,14 @@ msgstr ""
 "\n"
 "設定項目のキーと値。キーの名前空間は一意に識別できる場合は短縮することがで\n"
 "きます:\n"
-" - \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定され\n"
-"   ているコンテナをすべて一覧表示します。\n"
+"    - \"user.blah=abc\" は \"blah\" という user プロパティが \"abc\" に設定\n"
+"    されているコンテナをすべて一覧表示します。\n"
 "\n"
-" - \"u.blah=abc\" は上記と同じ意味になります。\n"
+"    - \"u.blah=abc\" は上記と同じ意味になります。\n"
 "\n"
-" - \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
+"    - \"security.privileged=1\" は特権コンテナをすべて一覧表示します。\n"
 "\n"
-" - \"s.privilaged=1\" は上記と同じ意味になります。\n"
+"    - \"s.privilaged=1\" は上記と同じ意味になります。\n"
 "\n"
 "設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:.*)\n"
 "\n"
@@ -1509,8 +1505,6 @@ msgid ""
 "lxc monitor --type=logging\n"
 "    Only show log message."
 msgstr ""
-"LXD サーバの動作をモニタリングします。\n"
-"\n"
 "使い方: lxc monitor [<remote>:] [--type=TYPE...]\n"
 "\n"
 "ローカルまたはリモートの LXD サーバの動作をモニタリングします。\n"
@@ -1665,12 +1659,6 @@ msgstr ""
 "lxc profile apply [<remote>:]<container> <profiles>\n"
 "    コンテナの現在のプロファイルを指定したプロファイルで置き換えます。\n"
 "\n"
-"lxc profile add [<remote>:]<container> <profile>\n"
-"    コンテナにプロファイルを追加します。\n"
-"\n"
-"lxc profile remove [<remote>:]<container> <profile>\n"
-"    コンテナからプロファイルを削除します。\n"
-"\n"
 "*デバイス管理*\n"
 "lxc profile device list [<remote>:]<profile>\n"
 "    指定したプロファイル内のデバイスを一覧表示します\n"

From 40f0f638fa533795061387ccdd5f00b5a2c3fa6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 28 Jun 2017 00:48:40 -0400
Subject: [PATCH 1013/1193] Add a new security.idmap.base key
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3451

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/api-extensions.md |  5 +++++
 doc/containers.md     |  1 +
 doc/userns-idmap.md   |  4 ++++
 lxd/api_1.0.go        |  1 +
 lxd/container.go      |  2 ++
 lxd/container_lxc.go  | 50 +++++++++++++++++++++++++++++++-------------------
 test/suites/idmap.sh  | 10 ++++++++++
 7 files changed, 54 insertions(+), 19 deletions(-)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 811c382d3..f03bae372 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -9,3 +9,8 @@ looking at the api\_extensions field in GET /1.0/.
 ## id\_map
 Enables setting the `security.idmap.isolated` and `security.idmap.isolated`,
 `security.idmap.size`, and `raw.id_map` fields.
+
+## id\_map\_base
+This introduces a new security.idmap.base allowing the user to skip the
+map auto-selection process for isolated containers and specify what host
+uid/gid to use as the base.
diff --git a/doc/containers.md b/doc/containers.md
index 0ad45d0e6..a9f3a00ec 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -39,6 +39,7 @@ linux.kernel\_modules       | string    | -             | yes           | Comma
 raw.apparmor                | blob      | -             | yes           | Apparmor profile entries to be appended to the generated profile
 raw.lxc                     | blob      | -             | no            | Raw LXC configuration to be appended to the generated one
 raw.idmap                   | blob      | -             | no            | Raw idmap configuration (e.g. "both 1000 1000")
+security.idmap.base         | integer   | -             | no            | The base host ID to use for the allocation (overrides auto-detection)
 security.idmap.isolated     | boolean   | false         | no            | Use an idmap for this container that is unique among containers with isolated set.
 security.idmap.size         | integer   | -             | no            | The size of the idmap to use
 security.nesting            | boolean   | false         | yes           | Support running lxd (nested) inside the container
diff --git a/doc/userns-idmap.md b/doc/userns-idmap.md
index 4e83610f7..cdd62ca53 100644
--- a/doc/userns-idmap.md
+++ b/doc/userns-idmap.md
@@ -57,6 +57,10 @@ size. Isolated containers without this property set default to a id range of
 size 65536; this allows for POSIX compliance and a "nobody" user inside the
 container.
 
+To select a specific map, the `security.idmap.base` key will let you
+override the auto-detection mechanism and tell LXD what host uid/gid you
+want to use as the base for the container.
+
 These properties require a container reboot to take effect.
 
 # Custom idmaps
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 41c9c5ac9..de4545f07 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -59,6 +59,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		 */
 		APIExtensions: []string{
 			"id_map",
+			"id_map_base",
 		},
 		APIStatus:  "stable",
 		APIVersion: version.APIVersion,
diff --git a/lxd/container.go b/lxd/container.go
index 46f8d5df1..93bb49da1 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -184,6 +184,8 @@ func containerValidConfigKey(key string, value string) error {
 		return isBool(key, value)
 	case "security.nesting":
 		return isBool(key, value)
+	case "security.idmap.base":
+		return isUint32(key, value)
 	case "security.idmap.size":
 		return isUint32(key, value)
 	case "security.idmap.isolated":
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 44f69c38d..121ce1bd9 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -271,6 +271,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 			d,
 			args.Name,
 			c.expandedConfig["security.idmap.isolated"],
+			c.expandedConfig["security.idmap.base"],
 			c.expandedConfig["security.idmap.size"],
 			c.expandedConfig["raw.idmap"],
 		)
@@ -559,7 +560,7 @@ func parseRawIdmap(value string) ([]shared.IdmapEntry, error) {
 	return ret.Idmap, nil
 }
 
-func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize string, rawIdmap string) (*shared.IdmapSet, int64, error) {
+func findIdmap(daemon *Daemon, cName string, isolatedStr string, configBase string, configSize string, rawIdmap string) (*shared.IdmapSet, int64, error) {
 	isolated := false
 	if shared.IsTrue(isolatedStr) {
 		isolated = true
@@ -581,6 +582,33 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 		return &newIdmapset, 0, nil
 	}
 
+	size, err := idmapSize(daemon, isolatedStr, configSize)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	mkIdmap := func(offset int64, size int64) *shared.IdmapSet {
+		set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+			{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
+			{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
+		}}
+
+		for _, ent := range rawMaps {
+			set.AddSafe(ent)
+		}
+
+		return set
+	}
+
+	if configBase != "" {
+		offset, err := strconv.ParseInt(configBase, 10, 64)
+		if err != nil {
+			return nil, 0, err
+		}
+
+		return mkIdmap(offset, size), offset, nil
+	}
+
 	idmapLock.Lock()
 	defer idmapLock.Unlock()
 
@@ -590,10 +618,6 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 	}
 
 	offset := daemon.IdmapSet.Idmap[0].Hostid + 65536
-	size, err := idmapSize(daemon, isolatedStr, configSize)
-	if err != nil {
-		return nil, 0, err
-	}
 
 	mapentries := shared.ByHostid{}
 	for _, name := range cs {
@@ -633,19 +657,6 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configSize stri
 
 	sort.Sort(mapentries)
 
-	mkIdmap := func(offset int64, size int64) *shared.IdmapSet {
-		set := &shared.IdmapSet{Idmap: []shared.IdmapEntry{
-			{Isuid: true, Nsid: 0, Hostid: offset, Maprange: size},
-			{Isgid: true, Nsid: 0, Hostid: offset, Maprange: size},
-		}}
-
-		for _, ent := range rawMaps {
-			set.AddSafe(ent)
-		}
-
-		return set
-	}
-
 	for i := range mapentries {
 		if i == 0 {
 			if mapentries[0].Hostid < offset+size {
@@ -2738,7 +2749,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 		}
 	}
 
-	if shared.StringInSlice("security.idmap.isolated", changedConfig) || shared.StringInSlice("security.idmap.size", changedConfig) || shared.StringInSlice("raw.idmap", changedConfig) || shared.StringInSlice("security.privileged", changedConfig) {
+	if shared.StringInSlice("security.idmap.isolated", changedConfig) || shared.StringInSlice("security.idmap.base", changedConfig) || shared.StringInSlice("security.idmap.size", changedConfig) || shared.StringInSlice("raw.idmap", changedConfig) || shared.StringInSlice("security.privileged", changedConfig) {
 		var idmap *shared.IdmapSet
 		base := int64(0)
 		if !c.IsPrivileged() {
@@ -2747,6 +2758,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 				c.daemon,
 				c.Name(),
 				c.expandedConfig["security.idmap.isolated"],
+				c.expandedConfig["security.idmap.base"],
 				c.expandedConfig["security.idmap.size"],
 				c.expandedConfig["raw.idmap"],
 			)
diff --git a/test/suites/idmap.sh b/test/suites/idmap.sh
index 88b3f2232..f5581745b 100644
--- a/test/suites/idmap.sh
+++ b/test/suites/idmap.sh
@@ -67,7 +67,17 @@ test_idmap() {
   [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $3}')" = "100000" ]
   [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $3}')" = "100000" ]
 
+  # Test using a custom base
+  lxc config set idmap security.idmap.base $((UID_BASE+12345))
+  lxc config set idmap security.idmap.size 110000
+  lxc restart idmap --force
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $2}')" = "$((UID_BASE+12345))" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $2}')" = "$((GID_BASE+12345))" ]
+  [ "$(lxc exec idmap -- cat /proc/self/uid_map | awk '{print $3}')" = "110000" ]
+  [ "$(lxc exec idmap -- cat /proc/self/gid_map | awk '{print $3}')" = "110000" ]
+
   # Switch back to full LXD range
+  lxc config unset idmap security.idmap.base
   lxc config unset idmap security.idmap.isolated
   lxc config unset idmap security.idmap.size
   lxc restart idmap --force

From 7c88b008b7f01972b2b366430ae9ac0ed743caf3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 2 Oct 2017 14:20:13 -0400
Subject: [PATCH 1014/1193] Temporary workaround for log15 API breakage
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

log15 broke their v2 API earlier today, this change should get things
building again until upstream log15 fixes the API breakage.

Related: https://github.com/inconshreveable/log15/issues/139

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/logging/log_posix.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/shared/logging/log_posix.go b/shared/logging/log_posix.go
index ffc55da5d..5ada8081e 100644
--- a/shared/logging/log_posix.go
+++ b/shared/logging/log_posix.go
@@ -3,6 +3,8 @@
 package logging
 
 import (
+	slog "log/syslog"
+
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
@@ -13,11 +15,11 @@ func getSystemHandler(syslog string, debug bool, format log.Format) log.Handler
 		if !debug {
 			return log.LvlFilterHandler(
 				log.LvlInfo,
-				log.Must.SyslogHandler(syslog, format),
+				log.Must.SyslogHandler(slog.LOG_INFO, syslog, format),
 			)
 		}
 
-		return log.Must.SyslogHandler(syslog, format)
+		return log.Must.SyslogHandler(slog.LOG_INFO, syslog, format)
 	}
 
 	return nil

From 820d58218d96d746a62dcb1b22230d930a7e11ce Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Fri, 30 Jun 2017 11:25:10 +0200
Subject: [PATCH 1015/1193] Extend/rework security-related documentation.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 doc/debugging.md                               |  2 +-
 doc/{lxd-ssl-authentication.md => security.md} | 49 ++++++++++++--------------
 2 files changed, 24 insertions(+), 27 deletions(-)
 rename doc/{lxd-ssl-authentication.md => security.md} (70%)

diff --git a/doc/debugging.md b/doc/debugging.md
index 27bd20be3..6f1fbea20 100644
--- a/doc/debugging.md
+++ b/doc/debugging.md
@@ -38,7 +38,7 @@ See [rest-api.md](rest-api.md) for available API.
 
 ### REST API through HTTPS
 
-[HTTPS connection to LXD](lxd-ssl-authentication.md) requires valid
+[HTTPS connection to LXD](security.md) requires valid
 client certificate, generated in `~/.config/lxc/client.crt` on
 first `lxc remote add`. This certificate should be passed to
 connection tools for authentication and encryption.
diff --git a/doc/lxd-ssl-authentication.md b/doc/security.md
similarity index 70%
rename from doc/lxd-ssl-authentication.md
rename to doc/security.md
index f648b8e75..0d058ca1e 100644
--- a/doc/lxd-ssl-authentication.md
+++ b/doc/security.md
@@ -19,10 +19,13 @@ they're launched. The server will use that for all https connections to
 the LXD socket and the client will use its certificate as a client
 certificate for any client-server communication.
 
+To cause certificates to be regenerated, simply remove the old ones. On the
+next connection a new certificate will be generated.
+
 # Adding a remote with a default setup
-In the default setup, when the user adds a new server with "lxc remote
-add", the server will be contacted over HTTPs, its certificate
-downloaded and the fingerprint will be shown to the user.
+In the default setup, when the user adds a new server with `lxc remote add`,
+the server will be contacted over HTTPs, its certificate downloaded and the
+fingerprint will be shown to the user.
 
 The user will then be asked to confirm that this is indeed the server's
 fingerprint which they can manually check by connecting to or asking
@@ -43,29 +46,12 @@ a TXT record, then if the domain is signed by DNSSEC, the client will
 automatically accept the fingerprint if it matches that in the DNS
 record.
 
-# Adding a remote with a PKI based setup
-In the PKI setup, a system administrator is managing a central PKI, that
-PKI then issues client certificates for all the lxc clients and server
-certificates for all the LXD daemons.
-
-Those certificates and keys are manually put in place on the various
-machines, replacing the automatically generated ones.
-
-The CA certificate is also added to all machines.
-A CRL may also accompany the CA certificate.
+# Managing trusted clients
+The list of certificates trusted by a LXD server can be obtained with `lxc
+config trust list`.
 
-In that mode, any connection to a LXD daemon will be done using the
-preseeded CA certificate. If the server certificate isn't signed by the
-CA, or if it has been revoked, the connection will simply go through the
-normal authentication mechanism.
-
-If the server certificate is valid and signed by the CA, then the
-connection continues without prompting the user for the certificate.
-
-After that, the user must enter the trust password for that server, if
-it matches, the client certificate is added to the server's trust store
-and the client can now connect to the server without having to provide
-any additional credentials.
+To revoke trust to a client its certificate can be removed with `lxc config
+trust remove FINGERPRINT`.
 
 # Password prompt
 To establish a new trust relationship, a password must be set on the
@@ -73,7 +59,7 @@ server and send by the client when adding itself.
 
 A remote add operation should therefore go like this:
  1. Call GET /1.0
- 2. If we're not in a PKI setup ask the user to confirm the fingerprint.
+ 2. Ask the user to confirm the fingerprint.
  3. Look at the dict we received back from the server. If "auth" is
     "untrusted", ask the user for the server's password and do a POST to
     /1.0/certificates, then call /1.0 again to check that we're indeed
@@ -104,3 +90,14 @@ trusted.
 
 This happens if another trusted client or the local server administrator
 removed the trust entry on the server.
+
+
+# Production setup
+For production setup, it's recommended that `core.trust_password` is unset
+after all clients have been added.  This prevents brute-force attacks trying to
+guess the password.
+
+Furthermore, `core.https_address` should be set to the single address where the
+server should be available (rather than any address on the host), and firewall
+rules should be set to only allow access to the LXD port from authorized
+hosts/subnets.

From 1603c82921e9104a28ba49c9827dff0102f39921 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Jul 2017 15:53:50 -0400
Subject: [PATCH 1016/1193] lxc: Add plumbing for operation cancelation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/utils.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/lxc/utils.go b/lxc/utils.go
index 383862191..3378b8706 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -5,8 +5,10 @@ import (
 	"net"
 	"net/url"
 	"os"
+	"os/signal"
 	"strings"
 	"syscall"
+	"time"
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared/api"
@@ -18,6 +20,7 @@ type ProgressRenderer struct {
 	Format string
 
 	maxLength int
+	wait      time.Time
 }
 
 func (p *ProgressRenderer) Done(msg string) {
@@ -36,6 +39,11 @@ func (p *ProgressRenderer) Done(msg string) {
 }
 
 func (p *ProgressRenderer) Update(status string) {
+	timeout := p.wait.Sub(time.Now())
+	if timeout.Seconds() > 0 {
+		time.Sleep(timeout)
+	}
+
 	msg := "%s"
 	if p.Format != "" {
 		msg = p.Format
@@ -52,6 +60,19 @@ func (p *ProgressRenderer) Update(status string) {
 	fmt.Print(msg)
 }
 
+func (p *ProgressRenderer) Warn(status string, timeout time.Duration) {
+	p.wait = time.Now().Add(timeout)
+	msg := fmt.Sprintf("\r%s", status)
+
+	if len(msg) > p.maxLength {
+		p.maxLength = len(msg)
+	} else {
+		fmt.Printf("\r%s", strings.Repeat(" ", p.maxLength))
+	}
+
+	fmt.Print(msg)
+}
+
 func (p *ProgressRenderer) UpdateProgress(progress lxd.ProgressData) {
 	p.Update(progress.Text)
 }
@@ -231,3 +252,40 @@ func getLocalErr(err error) error {
 
 	return nil
 }
+
+// Wait for an operation and cancel it on SIGINT/SIGTERM
+func cancelableWait(op *lxd.RemoteOperation, progress *ProgressRenderer) error {
+	// Signal handling
+	chSignal := make(chan os.Signal)
+	signal.Notify(chSignal, os.Interrupt)
+
+	// Operation handling
+	chOperation := make(chan error)
+	go func() {
+		chOperation <- op.Wait()
+		close(chOperation)
+	}()
+
+	count := 0
+	for {
+		select {
+		case err := <-chOperation:
+			return err
+		case <-chSignal:
+			err := op.CancelTarget()
+			if err == nil {
+				return fmt.Errorf(i18n.G("Remote operation canceled by user"))
+			} else {
+				count++
+
+				if count == 3 {
+					return fmt.Errorf(i18n.G("User signaled us three times, exiting. The remote operation will keep running."))
+				}
+
+				if progress != nil {
+					progress.Warn(fmt.Sprintf(i18n.G("%v (interrupt two more times to force)"), err), time.Second*5)
+				}
+			}
+		}
+	}
+}

From e3c2fbe9f75948f351cff1efa49cc2ba13864dc6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 2 Jul 2017 02:23:09 -0400
Subject: [PATCH 1017/1193] client: Fix race condition in operation handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/operations.go | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/client/operations.go b/client/operations.go
index 0c0db3808..f278510e3 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -175,6 +175,10 @@ func (op *Operation) setupListener() error {
 	})
 	if err != nil {
 		op.listener.Disconnect()
+		op.listener = nil
+		close(op.chActive)
+		close(chReady)
+
 		return err
 	}
 
@@ -182,14 +186,22 @@ func (op *Operation) setupListener() error {
 	err = op.Refresh()
 	if err != nil {
 		op.listener.Disconnect()
+		op.listener = nil
+		close(op.chActive)
+		close(chReady)
+
 		return err
 	}
 
 	// Check if not done already
 	if op.StatusCode.IsFinal() {
 		op.listener.Disconnect()
+		op.listener = nil
 		close(op.chActive)
 
+		op.handlerReady = true
+		close(chReady)
+
 		if op.Err != "" {
 			return fmt.Errorf(op.Err)
 		}

From 6097a262d8d478f5d7d7efae30cbf893c01dcf0e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 2 Jul 2017 01:56:41 -0400
Subject: [PATCH 1018/1193] lxc/copy: Report progress data
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/copy.go | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/lxc/copy.go b/lxc/copy.go
index e08e4d94e..e34d49dc3 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -151,11 +151,21 @@ func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string, dest
 		}
 	}
 
+	// Watch the background operation
+	progress := ProgressRenderer{Format: i18n.G("Transfering container: %s")}
+	_, err = op.AddHandler(progress.UpdateOp)
+	if err != nil {
+		progress.Done("")
+		return err
+	}
+
 	// Wait for the copy to complete
 	err = op.Wait()
 	if err != nil {
+		progress.Done("")
 		return err
 	}
+	progress.Done("")
 
 	// If choosing a random name, show it to the user
 	if destResource == "" {

From f04c590fb3c22923612e476a6a6dc1ae54b3fd12 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 2 Jul 2017 18:14:10 -0400
Subject: [PATCH 1019/1193] Better handle errors in memory reporting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3482

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 39 ++++++++++++++++++---------------------
 1 file changed, 18 insertions(+), 21 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 121ce1bd9..36a847ee1 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4242,39 +4242,36 @@ func (c *containerLXC) memoryState() api.ContainerStateMemory {
 
 	// Memory in bytes
 	value, err := c.CGroupGet("memory.usage_in_bytes")
-	valueInt, err := strconv.ParseInt(value, 10, 64)
-	if err != nil {
-		valueInt = -1
+	valueInt, err1 := strconv.ParseInt(value, 10, 64)
+	if err == nil && err1 == nil {
+		memory.Usage = valueInt
 	}
-	memory.Usage = valueInt
 
 	// Memory peak in bytes
 	value, err = c.CGroupGet("memory.max_usage_in_bytes")
-	valueInt, err = strconv.ParseInt(value, 10, 64)
-	if err != nil {
-		valueInt = -1
+	valueInt, err1 = strconv.ParseInt(value, 10, 64)
+	if err == nil && err1 == nil {
+		memory.UsagePeak = valueInt
 	}
 
-	memory.UsagePeak = valueInt
-
 	if cgSwapAccounting {
 		// Swap in bytes
-		value, err := c.CGroupGet("memory.memsw.usage_in_bytes")
-		valueInt, err := strconv.ParseInt(value, 10, 64)
-		if err != nil {
-			valueInt = -1
+		if memory.Usage > 0 {
+			value, err := c.CGroupGet("memory.memsw.usage_in_bytes")
+			valueInt, err1 := strconv.ParseInt(value, 10, 64)
+			if err == nil && err1 == nil {
+				memory.SwapUsage = valueInt - memory.Usage
+			}
 		}
 
-		memory.SwapUsage = valueInt - memory.Usage
-
 		// Swap peak in bytes
-		value, err = c.CGroupGet("memory.memsw.max_usage_in_bytes")
-		valueInt, err = strconv.ParseInt(value, 10, 64)
-		if err != nil {
-			valueInt = -1
+		if memory.UsagePeak > 0 {
+			value, err = c.CGroupGet("memory.memsw.max_usage_in_bytes")
+			valueInt, err1 = strconv.ParseInt(value, 10, 64)
+			if err == nil && err1 == nil {
+				memory.SwapUsagePeak = valueInt - memory.UsagePeak
+			}
 		}
-
-		memory.SwapUsagePeak = valueInt - memory.UsagePeak
 	}
 
 	return memory

From ee51d28c1ee3195d683da10484ef564ffa0a97ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 3 Jul 2017 01:25:39 -0400
Subject: [PATCH 1020/1193] client: Don't live migrate stopped containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_containers.go | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index e8d03a144..c2362423e 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -203,7 +203,6 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 		ContainerPut: container.Writable(),
 	}
 	req.Source.BaseImage = container.Config["volatile.base_image"]
-	req.Source.Live = container.StatusCode == api.Running
 
 	// Process the copy arguments
 	if args != nil {
@@ -221,6 +220,10 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 		req.Source.ContainerOnly = args.ContainerOnly
 	}
 
+	if req.Source.Live {
+		req.Source.Live = container.StatusCode == api.Running
+	}
+
 	// Optimization for the local copy case
 	if r == source {
 		// Local copy source fields

From fcfaa7c52e86d94ae16d7c3ad57940dffcc1f7e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 2 Jul 2017 01:57:01 -0400
Subject: [PATCH 1021/1193] client: Implement push and relay container copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go     |   6 +
 client/lxd_containers.go | 359 ++++++++++++++++++++++++++++++++++++++++++++---
 shared/network.go        |  54 +++++++
 3 files changed, 403 insertions(+), 16 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index 410c4f726..d146c1ddf 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -240,12 +240,18 @@ type ContainerCopyArgs struct {
 
 	// If set, only the container will copied, its snapshots won't
 	ContainerOnly bool
+
+	// The transfer mode, can be "pull" (default), "push" or "relay"
+	Mode string
 }
 
 // The ContainerSnapshotCopyArgs struct is used to pass additional options during container copy
 type ContainerSnapshotCopyArgs struct {
 	// If set, the container will be renamed on copy
 	Name string
+
+	// The transfer mode, can be "pull" (default), "push" or "relay"
+	Mode string
 }
 
 // The ContainerExecArgs struct is used to pass additional options during container exec
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index c2362423e..4e2b5c8b0 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -207,8 +207,28 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 	// Process the copy arguments
 	if args != nil {
 		// Sanity checks
-		if args.ContainerOnly && !r.HasExtension("container_only_migration") {
-			return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+		if args.ContainerOnly {
+			if !r.HasExtension("container_only_migration") {
+				return nil, fmt.Errorf("The target server is missing the required \"container_only_migration\" API extension")
+			}
+
+			if !source.HasExtension("container_only_migration") {
+				return nil, fmt.Errorf("The source server is missing the required \"container_only_migration\" API extension")
+			}
+		}
+
+		if shared.StringInSlice(args.Mode, []string{"push", "relay"}) {
+			if !r.HasExtension("container_push") {
+				return nil, fmt.Errorf("The target server is missing the required \"container_push\" API extension")
+			}
+
+			if !source.HasExtension("container_push") {
+				return nil, fmt.Errorf("The source server is missing the required \"container_push\" API extension")
+			}
+		}
+
+		if args.Mode == "push" && !source.HasExtension("container_push_target") {
+			return nil, fmt.Errorf("The source server is missing the required \"container_push_target\" API extension")
 		}
 
 		// Allow overriding the target name
@@ -250,19 +270,51 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 		return &rop, nil
 	}
 
-	// Get source server connection information
-	info, err := source.GetConnectionInfo()
-	if err != nil {
-		return nil, err
-	}
-
-	// Get a source operation
+	// Source request
 	sourceReq := api.ContainerPost{
 		Migration:     true,
 		Live:          req.Source.Live,
 		ContainerOnly: req.Source.ContainerOnly,
 	}
 
+	// Push mode migration
+	if args != nil && args.Mode == "push" {
+		// Get target server connection information
+		info, err := r.GetConnectionInfo()
+		if err != nil {
+			return nil, err
+		}
+
+		// Create the container
+		req.Source.Type = "migration"
+		req.Source.Mode = "push"
+
+		op, err := r.CreateContainer(req)
+		if err != nil {
+			return nil, err
+		}
+
+		targetSecrets := map[string]string{}
+		for k, v := range op.Metadata {
+			targetSecrets[k] = v.(string)
+		}
+
+		// Prepare the source request
+		target := api.ContainerPostTarget{}
+		target.Operation = op.ID
+		target.Websockets = targetSecrets
+		target.Certificate = info.Certificate
+		sourceReq.Target = &target
+
+		return r.tryMigrateContainer(source, container.Name, sourceReq, info.Addresses)
+	}
+
+	// Get source server connection information
+	info, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, err
+	}
+
 	op, err := source.MigrateContainer(container.Name, sourceReq)
 	if err != nil {
 		return nil, err
@@ -273,7 +325,71 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 		sourceSecrets[k] = v.(string)
 	}
 
-	// Migration source fields
+	// Relay mode migration
+	if args != nil && args.Mode == "relay" {
+		// Push copy source fields
+		req.Source.Type = "migration"
+		req.Source.Mode = "push"
+
+		// Start the process
+		targetOp, err := r.CreateContainer(req)
+		if err != nil {
+			return nil, err
+		}
+
+		// Extract the websockets
+		targetSecrets := map[string]string{}
+		for k, v := range targetOp.Metadata {
+			targetSecrets[k] = v.(string)
+		}
+
+		// Launch the relay
+		dones := []chan bool{}
+		conns := []*websocket.Conn{}
+
+		for name := range sourceSecrets {
+			sourceConn, err := source.GetOperationWebsocket(op.ID, sourceSecrets[name])
+			if err != nil {
+				return nil, err
+			}
+
+			targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets[name])
+			if err != nil {
+				return nil, err
+			}
+
+			conns = append(conns, sourceConn)
+			conns = append(conns, targetConn)
+			dones = append(dones, shared.WebsocketProxy(sourceConn, targetConn))
+		}
+
+		// Wait for everything to be done
+		go func() {
+			for _, chDone := range dones {
+				<-chDone
+			}
+
+			for _, conn := range conns {
+				conn.Close()
+			}
+		}()
+
+		// Prepare a tracking operation
+		rop := RemoteOperation{
+			targetOp: targetOp,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Pull mode migration
 	req.Source.Type = "migration"
 	req.Source.Mode = "pull"
 	req.Source.Operation = op.ID
@@ -310,6 +426,56 @@ func (r *ProtocolLXD) RenameContainer(name string, container api.ContainerPost)
 	return op, nil
 }
 
+func (r *ProtocolLXD) tryMigrateContainer(source ContainerServer, name string, req api.ContainerPost, urls []string) (*RemoteOperation, error) {
+	if len(urls) == 0 {
+		return nil, fmt.Errorf("The target server isn't listening on the network")
+	}
+
+	rop := RemoteOperation{
+		chDone: make(chan bool),
+	}
+
+	operation := req.Target.Operation
+
+	// Forward targetOp to remote op
+	go func() {
+		success := false
+		errors := []string{}
+		for _, serverURL := range urls {
+			req.Target.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, operation)
+
+			op, err := source.MigrateContainer(name, req)
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			rop.targetOp = op
+
+			for _, handler := range rop.handlers {
+				rop.targetOp.AddHandler(handler)
+			}
+
+			err = rop.targetOp.Wait()
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			success = true
+			break
+		}
+
+		if !success {
+			rop.err = fmt.Errorf("Failed container migration:\n - %s", strings.Join(errors, "\n - "))
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
 // MigrateContainer requests that LXD prepares for a container migration
 func (r *ProtocolLXD) MigrateContainer(name string, container api.ContainerPost) (*Operation, error) {
 	if container.ContainerOnly {
@@ -689,6 +855,21 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 
 	// Process the copy arguments
 	if args != nil {
+		// Sanity checks
+		if shared.StringInSlice(args.Mode, []string{"push", "relay"}) {
+			if !r.HasExtension("container_push") {
+				return nil, fmt.Errorf("The target server is missing the required \"container_push\" API extension")
+			}
+
+			if !source.HasExtension("container_push") {
+				return nil, fmt.Errorf("The source server is missing the required \"container_push\" API extension")
+			}
+		}
+
+		if args.Mode == "push" && !source.HasExtension("container_push_target") {
+			return nil, fmt.Errorf("The source server is missing the required \"container_push_target\" API extension")
+		}
+
 		// Allow overriding the target name
 		if args.Name != "" {
 			req.Name = args.Name
@@ -721,17 +902,49 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 		return &rop, nil
 	}
 
+	// Source request
+	sourceReq := api.ContainerSnapshotPost{
+		Migration: true,
+	}
+
+	// Push mode migration
+	if args != nil && args.Mode == "push" {
+		// Get target server connection information
+		info, err := r.GetConnectionInfo()
+		if err != nil {
+			return nil, err
+		}
+
+		// Create the container
+		req.Source.Type = "migration"
+		req.Source.Mode = "push"
+
+		op, err := r.CreateContainer(req)
+		if err != nil {
+			return nil, err
+		}
+
+		targetSecrets := map[string]string{}
+		for k, v := range op.Metadata {
+			targetSecrets[k] = v.(string)
+		}
+
+		// Prepare the source request
+		target := api.ContainerPostTarget{}
+		target.Operation = op.ID
+		target.Websockets = targetSecrets
+		target.Certificate = info.Certificate
+		sourceReq.Target = &target
+
+		return r.tryMigrateContainerSnapshot(source, cName, sName, sourceReq, info.Addresses)
+	}
+
 	// Get source server connection information
 	info, err := source.GetConnectionInfo()
 	if err != nil {
 		return nil, err
 	}
 
-	// Get a source operation
-	sourceReq := api.ContainerSnapshotPost{
-		Migration: true,
-	}
-
 	op, err := source.MigrateContainerSnapshot(cName, sName, sourceReq)
 	if err != nil {
 		return nil, err
@@ -742,7 +955,71 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 		sourceSecrets[k] = v.(string)
 	}
 
-	// Migration source fields
+	// Relay mode migration
+	if args != nil && args.Mode == "relay" {
+		// Push copy source fields
+		req.Source.Type = "migration"
+		req.Source.Mode = "push"
+
+		// Start the process
+		targetOp, err := r.CreateContainer(req)
+		if err != nil {
+			return nil, err
+		}
+
+		// Extract the websockets
+		targetSecrets := map[string]string{}
+		for k, v := range targetOp.Metadata {
+			targetSecrets[k] = v.(string)
+		}
+
+		// Launch the relay
+		dones := []chan bool{}
+		conns := []*websocket.Conn{}
+
+		for name := range sourceSecrets {
+			sourceConn, err := source.GetOperationWebsocket(op.ID, sourceSecrets[name])
+			if err != nil {
+				return nil, err
+			}
+
+			targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets[name])
+			if err != nil {
+				return nil, err
+			}
+
+			conns = append(conns, sourceConn)
+			conns = append(conns, targetConn)
+			dones = append(dones, shared.WebsocketProxy(sourceConn, targetConn))
+		}
+
+		// Wait for everything to be done
+		go func() {
+			for _, chDone := range dones {
+				<-chDone
+			}
+
+			for _, conn := range conns {
+				conn.Close()
+			}
+		}()
+
+		// Prepare a tracking operation
+		rop := RemoteOperation{
+			targetOp: targetOp,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Pull mode migration
 	req.Source.Type = "migration"
 	req.Source.Mode = "pull"
 	req.Source.Operation = op.ID
@@ -768,6 +1045,56 @@ func (r *ProtocolLXD) RenameContainerSnapshot(containerName string, name string,
 	return op, nil
 }
 
+func (r *ProtocolLXD) tryMigrateContainerSnapshot(source ContainerServer, containerName string, name string, req api.ContainerSnapshotPost, urls []string) (*RemoteOperation, error) {
+	if len(urls) == 0 {
+		return nil, fmt.Errorf("The target server isn't listening on the network")
+	}
+
+	rop := RemoteOperation{
+		chDone: make(chan bool),
+	}
+
+	operation := req.Target.Operation
+
+	// Forward targetOp to remote op
+	go func() {
+		success := false
+		errors := []string{}
+		for _, serverURL := range urls {
+			req.Target.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, operation)
+
+			op, err := source.MigrateContainerSnapshot(containerName, name, req)
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			rop.targetOp = op
+
+			for _, handler := range rop.handlers {
+				rop.targetOp.AddHandler(handler)
+			}
+
+			err = rop.targetOp.Wait()
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			success = true
+			break
+		}
+
+		if !success {
+			rop.err = fmt.Errorf("Failed container migration:\n - %s", strings.Join(errors, "\n - "))
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
 // MigrateContainerSnapshot requests that LXD prepares for a snapshot migration
 func (r *ProtocolLXD) MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (*Operation, error) {
 	// Sanity check
diff --git a/shared/network.go b/shared/network.go
index 51f1d796a..4cf7af81b 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -230,6 +230,60 @@ func WebsocketRecvStream(w io.Writer, conn *websocket.Conn) chan bool {
 	return ch
 }
 
+func WebsocketProxy(source *websocket.Conn, target *websocket.Conn) chan bool {
+	forward := func(in *websocket.Conn, out *websocket.Conn, ch chan bool) {
+		for {
+			mt, r, err := in.NextReader()
+			if mt == websocket.CloseMessage {
+				logger.Debugf("Got close message for reader")
+				break
+			}
+
+			if mt == websocket.TextMessage {
+				logger.Debugf("got message barrier")
+				break
+			}
+
+			if err != nil {
+				logger.Debugf("Got error getting next reader %s", err)
+				break
+			}
+
+			w, err := out.NextWriter(websocket.BinaryMessage)
+			if err != nil {
+				logger.Debugf("Got error getting next writer %s", err)
+				break
+			}
+
+			_, err = io.Copy(w, r)
+			w.Close()
+			if err != nil {
+				logger.Debugf("Got err writing %s", err)
+				break
+			}
+		}
+		out.WriteMessage(websocket.TextMessage, []byte{})
+
+		ch <- true
+	}
+
+	chSend := make(chan bool)
+	go forward(source, target, chSend)
+
+	chRecv := make(chan bool)
+	go forward(target, source, chRecv)
+
+	ch := make(chan bool)
+	go func() {
+		<-chSend
+		<-chRecv
+
+		ch <- true
+	}()
+
+	return ch
+}
+
 func defaultReader(conn *websocket.Conn, r io.ReadCloser, readDone chan<- bool) {
 	/* For now, we don't need to adjust buffer sizes in
 	* WebsocketMirror, since it's used for interactive things like

From b953b9448ea6f34df76fc45491fc7cf4110e5c85 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Mon, 3 Jul 2017 11:10:52 +0200
Subject: [PATCH 1022/1193] Fix file push/pull with names containing spaces.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 client/lxd_containers.go | 19 +++++++++++++++----
 shared/util.go           | 16 ++++++++++++++++
 shared/util_test.go      | 10 ++++++++++
 3 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 4e2b5c8b0..e42cb4564 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -640,8 +640,14 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 // GetContainerFile retrieves the provided path from the container
 func (r *ProtocolLXD) GetContainerFile(containerName string, path string) (io.ReadCloser, *ContainerFileResponse, error) {
 	// Prepare the HTTP request
-	url := fmt.Sprintf("%s/1.0/containers/%s/files?path=%s", r.httpHost, containerName, path)
-	req, err := http.NewRequest("GET", url, nil)
+	requestURL, err := shared.URLEncode(
+		fmt.Sprintf("%s/1.0/containers/%s/files", r.httpHost, containerName),
+		map[string]string{"path": path})
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := http.NewRequest("GET", requestURL, nil)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -714,8 +720,13 @@ func (r *ProtocolLXD) CreateContainerFile(containerName string, path string, arg
 	}
 
 	// Prepare the HTTP request
-	url := fmt.Sprintf("%s/1.0/containers/%s/files?path=%s", r.httpHost, containerName, path)
-	req, err := http.NewRequest("POST", url, args.Content)
+	requestURL, err := shared.URLEncode(
+		fmt.Sprintf("%s/1.0/containers/%s/files", r.httpHost, containerName),
+		map[string]string{"path": path})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest("POST", requestURL, args.Content)
 	if err != nil {
 		return err
 	}
diff --git a/shared/util.go b/shared/util.go
index eb19caf91..d5fd7783c 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -11,6 +11,7 @@ import (
 	"io"
 	"io/ioutil"
 	"net/http"
+	"net/url"
 	"os"
 	"os/exec"
 	"path"
@@ -27,6 +28,21 @@ import (
 const SnapshotDelimiter = "/"
 const DefaultPort = "8443"
 
+// URLEncode encodes a path and query parameters to a URL.
+func URLEncode(path string, query map[string]string) (string, error) {
+	u, err := url.Parse(path)
+	if err != nil {
+		return "", err
+	}
+
+	params := url.Values{}
+	for key, value := range query {
+		params.Add(key, value)
+	}
+	u.RawQuery = params.Encode()
+	return u.String(), nil
+}
+
 // AddSlash adds a slash to the end of paths if they don't already have one.
 // This can be useful for rsyncing things, since rsync has behavior present on
 // the presence or absence of a trailing slash.
diff --git a/shared/util_test.go b/shared/util_test.go
index 458c8fca9..331573749 100644
--- a/shared/util_test.go
+++ b/shared/util_test.go
@@ -9,6 +9,16 @@ import (
 	"testing"
 )
 
+func TestURLEncode(t *testing.T) {
+	url, _ := URLEncode(
+		"/path/with spaces",
+		map[string]string{"param": "with spaces", "other": "without"})
+	expected := "/path/with%20spaces?other=without&param=with+spaces"
+	if url != expected {
+		t.Error(fmt.Errorf("'%s' != '%s'", url, expected))
+	}
+}
+
 func TestFileCopy(t *testing.T) {
 	helloWorld := []byte("hello world\n")
 	source, err := ioutil.TempFile("", "")

From 70b853ae18303f7869b7f013bbd32762598be916 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 2 Jul 2017 20:42:33 -0400
Subject: [PATCH 1023/1193] api: Implement complete push migration
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/api/container.go          | 12 ++++++++++++
 shared/api/container_snapshot.go |  5 +++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/shared/api/container.go b/shared/api/container.go
index a300a0731..58cac56cf 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -25,6 +25,18 @@ type ContainerPost struct {
 
 	// API extension: container_only_migration
 	ContainerOnly bool `json:"container_only" yaml:"container_only"`
+
+	// API extension: container_push_target
+	Target *ContainerPostTarget `json:"target" yaml:"target"`
+}
+
+// ContainerPostTarget represents the migration target host and operation
+//
+// API extension: container_push_target
+type ContainerPostTarget struct {
+	Certificate string            `json:"certificate" yaml:"certificate"`
+	Operation   string            `json:"operation,omitempty" yaml:"operation,omitempty"`
+	Websockets  map[string]string `json:"secrets,omitempty" yaml:"secrets,omitempty"`
 }
 
 // ContainerPut represents the modifiable fields of a LXD container
diff --git a/shared/api/container_snapshot.go b/shared/api/container_snapshot.go
index 17e5d3a8d..3f9b0fd6c 100644
--- a/shared/api/container_snapshot.go
+++ b/shared/api/container_snapshot.go
@@ -12,8 +12,9 @@ type ContainerSnapshotsPost struct {
 
 // ContainerSnapshotPost represents the fields required to rename/move a LXD container snapshot
 type ContainerSnapshotPost struct {
-	Name      string `json:"name" yaml:"name"`
-	Migration bool   `json:"migration" yaml:"migration"`
+	Name      string               `json:"name" yaml:"name"`
+	Migration bool                 `json:"migration" yaml:"migration"`
+	Target    *ContainerPostTarget `json:"target" yaml:"target"`
 }
 
 // ContainerSnapshot represents a LXD conainer snapshot

From c37844035325aa50d2d9e78ad22b3731f51ff4c7 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Mon, 3 Jul 2017 16:04:17 +0200
Subject: [PATCH 1024/1193] Error if --columns and --fast are used together in
 lxc list

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxc/list.go          | 7 ++++++-
 test/suites/basic.sh | 3 +++
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/lxc/list.go b/lxc/list.go
index af5e23b4b..58ef16c3b 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -422,7 +422,12 @@ func (c *listCmd) run(conf *config.Config, args []string) error {
 	}
 
 	if c.fast {
-		c.chosenColumnRunes = "nsacPt"
+		if c.chosenColumnRunes != "ns46tS" {
+			// --columns was specified too
+			return fmt.Errorf("Can't specify --fast with --columns")
+		} else {
+			c.chosenColumnRunes = "nsacPt"
+		}
 	}
 
 	columns := []column{}
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index b5a76ecee..1009bb130 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -96,6 +96,9 @@ test_basic_usage() {
   # Test list json format
   lxc list --format json | jq '.[]|select(.name="foo")' | grep '"name": "foo"'
 
+  # Test list with --columns and --fast
+  ! lxc list --columns=nsp --fast
+
   # Test container rename
   lxc move foo bar
   lxc list | grep -v foo

From cc7dde8062fc684ee754b0fa31657ecaaa3d249a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 3 Jul 2017 13:52:42 -0400
Subject: [PATCH 1025/1193] Fix typos
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/copy.go      | 4 ++--
 lxd/main_init.go | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/lxc/copy.go b/lxc/copy.go
index e34d49dc3..0b1e4722d 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -152,7 +152,7 @@ func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string, dest
 	}
 
 	// Watch the background operation
-	progress := ProgressRenderer{Format: i18n.G("Transfering container: %s")}
+	progress := ProgressRenderer{Format: i18n.G("Transferring container: %s")}
 	_, err = op.AddHandler(progress.UpdateOp)
 	if err != nil {
 		progress.Done("")
@@ -169,7 +169,7 @@ func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string, dest
 
 	// If choosing a random name, show it to the user
 	if destResource == "" {
-		// Get the succesful operation data
+		// Get the successful operation data
 		opInfo, err := op.GetTarget()
 		if err != nil {
 			return err
diff --git a/lxd/main_init.go b/lxd/main_init.go
index e752c9533..a7ffbf5b4 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -352,7 +352,7 @@ func (cmd *CmdInit) initConfig(client lxd.ContainerServer, config map[string]int
 		return nil, err
 	}
 
-	// Updating the server was sucessful, so return the reverter function
+	// Updating the server was successful, so return the reverter function
 	// in case it's needed later.
 	return reverter, nil
 }

From 7fef71a28278392579b0616c9a912297108c6616 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 3 Jul 2017 17:32:26 -0400
Subject: [PATCH 1026/1193] README: Seriously rework the content
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 README.md | 328 ++++++++++++++++++++------------------------------------------
 1 file changed, 106 insertions(+), 222 deletions(-)

diff --git a/README.md b/README.md
index 97dea4882..fce6613f4 100644
--- a/README.md
+++ b/README.md
@@ -1,120 +1,55 @@
+[![LXD](https://linuxcontainers.org/static/img/containers.png)](https://linuxcontainers.org/lxd)
 # LXD
+LXD is a next generation system container manager.  
+It offers a user experience similar to virtual machines but using Linux containers instead.
 
-REST API, command line tool and OpenStack integration plugin for LXC.
+It's image based with pre-made images available for a [wide number of Linux distributions](https://images.linuxcontainers.org)  
+and is built around a very powerful, yet pretty simple, REST API.
 
-LXD is pronounced lex-dee.
+To get a better idea of what LXD is and what it does, you can [try it online](https://linuxcontainers.org/lxd/try-it/)!  
+Then if you want to run it locally, take a look at our [getting started guide](https://linuxcontainers.org/lxd/getting-started-cli/).
 
-To easily see what LXD is about, you can [try it online](https://linuxcontainers.org/lxd/try-it).
+Release announcements can be found here: https://linuxcontainers.org/lxd/news/  
+And the release tarballs here: https://linuxcontainers.org/lxd/downloads/
 
 ## Status
-
-* GoDoc: [![GoDoc](https://godoc.org/github.com/lxc/lxd/client?status.svg)](https://godoc.org/github.com/lxc/lxd/client)
-* Jenkins (Linux): [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit/)
-* Travis (macOS): [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=master)](https://travis-ci.org/lxc/lxd/)
-* AppVeyor (Windows): [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/master?svg=true)](https://ci.appveyor.com/project/lxc/lxd/)
-* Weblate (translations): [![Translation status](https://hosted.weblate.org/widgets/linux-containers/-/svg-badge.svg)](https://hosted.weblate.org/projects/linux-containers/lxd/)
-
-
-## Getting started with LXD
-
-Since LXD development is happening at such a rapid pace, we only provide daily
-builds right now. They're available via:
-
-    sudo add-apt-repository ppa:ubuntu-lxc/lxd-git-master && sudo apt-get update
-    sudo apt-get install lxd
-
-Because group membership is only applied at login, you then either need to
-close and re-open your user session or use the "newgrp lxd" command in the
-shell you're going to interact with lxd from.
-
-    newgrp lxd
-
-After you've got LXD installed and a session with the right permissions, you
-can take your [first steps](#first-steps).
-
-#### Getting started with LXD on Windows
-
-LXD server is not available on Windows, but it is possible to use
-[`lxc` client](https://ci.appveyor.com/project/lxc/lxd/branch/master/artifacts)
-with
-[some limitations](https://github.com/lxc/lxd/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20windows)
-to control remote containers.
-
-
-## Using the REST API
-The LXD REST API can be used locally via unauthenticated Unix socket or remotely via SSL encapsulated TCP.
-
-#### via Unix socket
-```bash
-curl --unix-socket /var/lib/lxd/unix.socket \
-    -H "Content-Type: application/json" \
-    -X POST \
-    -d @hello-ubuntu.json \
-    lxd/1.0/containers
-```
-
-#### via TCP
-TCP requires some additional configuration and is not enabled by default.
-```bash
-lxc config set core.https_address "[::]:8443"
-```
-```bash
-curl -k -L \
-    --cert ~/.config/lxc/client.crt \
-    --key ~/.config/lxc/client.key \
-    -H "Content-Type: application/json" \
-    -X POST \
-    -d @hello-ubuntu.json \
-    "https://127.0.0.1:8443/1.0/containers"
-```
-#### JSON payload
-The `hello-ubuntu.json` file referenced above could contain something like:
-```json
-{
-    "name":"some-ubuntu",
-    "ephemeral":true,
-    "config":{
-        "limits.cpu":"2"
-    },
-    "source": {
-        "type":"image",
-        "mode":"pull",
-        "protocol":"simplestreams",
-        "server":"https://cloud-images.ubuntu.com/releases",
-        "alias":"14.04"
-    }
-}
-```
-
-## Building from source
-
+Type            | Service               | Status
+---             | ---                   | ---
+CI (Linux)      | Jenkins               | [![Build Status](https://jenkins.linuxcontainers.org/job/lxd-github-commit-stable-2.0/badge/icon)](https://jenkins.linuxcontainers.org/job/lxd-github-commit-stable-2.0/)
+CI (macOS)      | Travis                | [![Build Status](https://travis-ci.org/lxc/lxd.svg?branch=stable-2.0)](https://travis-ci.org/lxc/lxd/)
+CI (Windows)    | AppVeyor              | [![Build Status](https://ci.appveyor.com/api/projects/status/rb4141dsi2xm3n0x/branch/stable-2.0?svg=true)](https://ci.appveyor.com/project/lxc/lxd/)
+Documentation   | Godoc                 | [![GoDoc](https://godoc.org/github.com/lxc/lxd/client?status.svg)](https://godoc.org/github.com/lxc/lxd/client)
+Static analysis | GoReport              | [![Go Report Card](https://goreportcard.com/badge/github.com/lxc/lxd)](https://goreportcard.com/report/github.com/lxc/lxd)
+Translations    | Weblate               | [![Translation status](https://hosted.weblate.org/widgets/linux-containers/-/svg-badge.svg)](https://hosted.weblate.org/projects/linux-containers/lxd/)
+Project status  | CII Best Practices    | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1086/badge)](https://bestpractices.coreinfrastructure.org/projects/1086)
+
+## Installing LXD from packages
+Instructions on installing LXD for a wide variety of Linux distributions and operating systems [can be found on our website](https://linuxcontainers.org/lxd/getting-started-cli/).
+
+## Installing LXD from source
 We recommend having the latest versions of liblxc (>= 2.0.0 required) and CRIU
 (>= 1.7 recommended) available for LXD development. Additionally, LXD requires
-Golang 1.5 or later to work. All the right versions dependencies are available
-via the LXD PPA:
+Golang 1.5 or later to work. On ubuntu, you can get those with:
 
-    sudo apt-get install software-properties-common
-    sudo add-apt-repository ppa:ubuntu-lxc/lxd-git-master
-    sudo apt-get update
-    sudo apt-get install acl git golang liblxc1 lxc-dev make pkg-config rsync squashfs-tools tar xz-utils
+    sudo apt update
+    sudo apt install acl dnsmasq-base git golang liblxc1 lxc-dev libacl1-dev make pkg-config rsync squashfs-tools tar xz-utils
 
-There are a few storage backends for LXD besides the default "directory"
-backend. Installing these tools adds a bit to initramfs and may slow down your
+There are a few storage backends for LXD besides the default "directory" backend.
+Installing these tools adds a bit to initramfs and may slow down your
 host boot, but are needed if you'd like to use a particular backend:
 
-    sudo apt-get install lvm2 thin-provisioning-tools
-    sudo apt-get install btrfs-tools
+    sudo apt install lvm2 thin-provisioning-tools
+    sudo apt install btrfs-tools
 
 To run the testsuite, you'll also need:
 
-    sudo apt-get install curl gettext jq sqlite3 uuid-runtime bzr
+    sudo apt install curl gettext jq sqlite3 uuid-runtime bzr
 
 
 ### Building the tools
-
 LXD consists of two binaries, a client called `lxc` and a server called `lxd`.
-These live in the source tree in the `lxc/` and `lxd/` dirs, respectively. To
-get the code, set up your go environment:
+These live in the source tree in the `lxc/` and `lxd/` dirs, respectively.
+To get the code, set up your go environment:
 
     mkdir -p ~/go
     export GOPATH=~/go
@@ -129,7 +64,6 @@ And then download it as usual:
 and `lxc` a command line client to that daemon.
 
 ### Machine Setup
-
 You'll need sub{u,g}ids for root, so that LXD can create the unprivileged
 containers:
 
@@ -140,59 +74,29 @@ group to talk to LXD; you can create your own group if you want):
 
     sudo -E $GOPATH/bin/lxd --group sudo
 
-## First steps
-
-LXD has two parts, the daemon (the `lxd` binary), and the client (the `lxc`
-binary). Now that the daemon is all configured and running (either via the
-packaging or via the from-source instructions above), you can create a container:
-
-    $GOPATH/bin/lxc launch ubuntu:14.04
-
-Alternatively, you can also use a remote LXD host as a source of images.
-One comes pre-configured in LXD, called "images" (images.linuxcontainers.org)
-
-    $GOPATH/bin/lxc launch images:centos/7/amd64 centos
+## Getting started with LXD
+Now that you have LXD running on your system you can read the [getting started guide](https://linuxcontainers.org/lxd/getting-started-cli/) or go through more examples and configurations in [our documentation](https://github.com/lxc/lxd/tree/master/doc).
 
 ## Bug reports
-
-Bug reports can be filed at https://github.com/lxc/lxd/issues/new
+Bug reports can be filed at: https://github.com/lxc/lxd/issues/new
 
 ## Contributing
-
-Fixes and new features are greatly appreciated but please read our
-[contributing guidelines](CONTRIBUTING.md) first.
-
-Contributions to this project should be sent as pull requests on github.
-
-## Hacking
-
-Sometimes it is useful to view the raw response that LXD sends; you can do
-this by:
-
-    lxc config set core.trust_password foo
-    lxc remote add local 127.0.0.1:8443
-    wget --no-check-certificate https://127.0.0.1:8443/1.0 --certificate=$HOME/.config/lxc/client.crt --private-key=$HOME/.config/lxc/client.key -O - -q
-
-## Upgrading
-
-The `lxd` and `lxc` (`lxd-client`) binaries should be upgraded at the same time with:
-
-    apt-get update
-    apt-get install lxd lxd-client
+Fixes and new features are greatly appreciated but please read our [contributing guidelines](CONTRIBUTING.md) first.
 
 ## Support and discussions
+### Forum
+A discussion forum is available at: https://discuss.linuxcontainers.org
 
+### Mailing-lists
 We use the LXC mailing-lists for developer and user discussions, you can
 find and subscribe to those at: https://lists.linuxcontainers.org
 
+### IRC
 If you prefer live discussions, some of us also hang out in
 [#lxcontainers](http://webchat.freenode.net/?channels=#lxcontainers) on irc.freenode.net.
 
-
 ## FAQ
-
 #### How to enable LXD server for remote access?
-
 By default LXD server is not accessible from the networks as it only listens
 on a local unix socket. You can make LXD available from the network by specifying
 additional addresses to listen to. This is done with the `core.https_address`
@@ -209,7 +113,6 @@ the `config set` command on the server:
     lxc config set core.https_address 192.168.1.15
 
 #### When I do a `lxc remote add` over https, it asks for a password?
-
 By default, LXD has no password for security reasons, so you can't do a remote
 add this way. In order to set a password, do:
 
@@ -224,88 +127,26 @@ certificate from `.config/lxc/client.crt` to the server and adding it with:
     lxc config trust add client.crt
 
 
-#### How do I configure alternative storage backends for LXD?
-
-LXD supports various storage backends; below are instructions on how to
-configure some of them. By default, we use a simple directory backed storage
-mechanism, but we recommend using ZFS for best results.
-
-###### ZFS
-
-First, you need to install the ZFS tooling. On Wily and above this is just:
-
-    sudo apt-get install zfsutils-linux
-
-ZFS has many different ways to procure a zpool, which is what you need to feed
-LXD. For example, if you have an extra block device laying around, you can
-just:
-
-    sudo zpool create lxd /dev/sdc6 -m none
-
-However, if you want to test things out on a laptop or don't have an extra disk
-laying around, ZFS has its own loopback driver and can be used directly on a
-(sparse) file. To do this, first create the sparse file:
-
-    sudo truncate -s 100G /var/lib/lxd.img
-
-then,
-
-    sudo zpool create lxd /var/lib/lxd.img -m none
-
-Finally, whichever method you used to create your zpool, you need to tell LXD
-to use it:
-
-    lxc config set storage.zfs_pool_name lxd
-
-###### BTRFS
+#### How do I configure LXD storage?
+LXD supports btrfs, directory, lvm and zfs based storage.
 
-The setup for btrfs is fairly simple, just mount /var/lib/lxd (or whatever your
-chosen `LXD_DIR` is) as a btrfs filesystem before you start LXD, and you're
-good to go. First install the btrfs userspace tools,
+First make sure you have the relevant tools for your filesystem of
+choice installed on the machine (btrfs-progs, lvm2 or zfsutils-linux).
 
-    sudo apt-get install btrfs-tools
+You can get a basic configuration done with:
 
-Now, you need to create a btrfs filesystem. If you don't have an extra disk
-laying around, you'll have to create your own loopback device manually:
+    lxd init
 
-    sudo truncate -s 100G /var/lib/lxd.img
-    sudo losetup /dev/loop0 /var/lib/lxd.img
+"lxd init" supports both directory based storage and ZFS.
 
-Once you've got a loopback device (or an actual device), you can create the
-btrfs filesystem and mount it:
-
-    sudo mkfs.btrfs /dev/loop0 # or your real device
-    sudo mount /dev/loop0 /var/lib/lxd
-
-###### LVM
-
-To set up LVM, the instructions are similar to the above. First, install the
-userspace tools:
-
-    sudo apt-get install lvm2 thin-provisioning-tools
-
-Then, if you have a block device laying around:
-
-    sudo pvcreate /dev/sdc6
-    sudo vgcreate lxd /dev/sdc6
-    lxc config set storage.lvm_vg_name lxd
-
-Alternatively, if you want to try it via a loopback device, there is a script
-provided in
-[/scripts/lxd-setup-lvm-storage](https://raw.githubusercontent.com/lxc/lxd/master/scripts/lxd-setup-lvm-storage)
-which will do it for you. It can be run via:
-
-    sudo apt-get install lvm2
-    ./scripts/lxd-setup-lvm-storage -s 10G
-
-And it has a --destroy argument to clean up the bits as well:
-
-    ./scripts/lxd-setup-lvm-storage --destroy
+"btrfs" is automatically setup if /var/lib/lxd is stored on btrfs.
 
+For LVM, you can set the "storage.lvm\_vg\_name" key to a valid LVM volume group.
 
+For production environments, you should be using block backed storage
+instead both for performance and reliability reasons.
 
 #### How can I live migrate a container using LXD?
-
 Live migration requires a tool installed on both hosts called
 [CRIU](http://criu.org), which is available in Ubuntu via:
 
@@ -322,8 +163,7 @@ experimental stages and may not work for all workloads. Please report bugs on
 lxc-devel, and we can escalate to CRIU lists as necessary.
 
 #### Can I bind mount my home directory in a container?
-
-Yes. The easiest way to do that is using a privileged container:
+Yes. The easiest way to do that is using a privileged container to avoid file ownership issues:
 
 1.a) create a container.
 
@@ -337,18 +177,62 @@ Yes. The easiest way to do that is using a privileged container:
     lxc config device add privilegedContainerName shareName disk source=/home/$USER path=/home/ubuntu
 
 #### How can I run docker inside a LXD container?
+In order to run Docker inside a LXD container the `security.nesting` property of the container should be set to `true`.  
 
-To run docker inside a lxd container, you must be running a kernel with cgroup
-namespaces (Ubuntu 4.4 kernel or newer, or upstream 4.6 or newer), and must
-apply the docker profile to your container.
+    lxc config set <container> security.nesting true
 
-    lxc launch ubuntu:xenial my-docker-host -p default -p docker
+Note that LXD containers cannot load kernel modules, so depending on your Docker configuration  
+you may need to have the needed extra kernel modules loaded by the host.
 
-Note that the docker profile does not provide a network interface, so the
-common case will want to compose the default and docker profiles.
+You can do so by setting a comma seperate list of kernel modules that your container needs with:
 
-Also note that Docker coming from [upstream](https://apt.dockerproject.org/repo) doesn't currently run as is inside the lxd container. Look at issue [#2621](https://github.com/lxc/lxd/issues/2621) for more details. You need to download the docker coming from Ubuntu (docker.io package) to get this working. So once you are in the lxd container run
+    lxc config set <container> linux.kernel_modules <modules>
 
-    sudo apt-get install -y docker.io runc containerd
+We have also received some reports that creating a "/.dockerenv" file in your container  
+can help Docker ignore some errors it's getting due to running in a nested environment.
+
+## Hacking on LXD
+### Directly using the REST API
+The LXD REST API can be used locally via unauthenticated Unix socket or remotely via SSL encapsulated TCP.
 
-The container must be using the Ubuntu 1.10.2-0ubuntu4 or newer docker package.
+#### Via Unix socket
+```bash
+curl --unix-socket /var/lib/lxd/unix.socket \
+    -H "Content-Type: application/json" \
+    -X POST \
+    -d @hello-ubuntu.json \
+    lxd/1.0/containers
+```
+
+#### Via TCP
+TCP requires some additional configuration and is not enabled by default.
+```bash
+lxc config set core.https_address "[::]:8443"
+```
+```bash
+curl -k -L \
+    --cert ~/.config/lxc/client.crt \
+    --key ~/.config/lxc/client.key \
+    -H "Content-Type: application/json" \
+    -X POST \
+    -d @hello-ubuntu.json \
+    "https://127.0.0.1:8443/1.0/containers"
+```
+#### JSON payload
+The `hello-ubuntu.json` file referenced above could contain something like:
+```json
+{
+    "name":"some-ubuntu",
+    "ephemeral":true,
+    "config":{
+        "limits.cpu":"2"
+    },
+    "source": {
+        "type":"image",
+        "mode":"pull",
+        "protocol":"simplestreams",
+        "server":"https://cloud-images.ubuntu.com/releases",
+        "alias":"14.04"
+    }
+}
+```

From 0e372e88b3bc28829d6a4a6dcb43721b1c2c618a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 3 Jul 2017 22:06:51 -0400
Subject: [PATCH 1027/1193] lxd/images: Clear error for image not found
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 92f6db7c6..7c1135381 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -191,6 +191,8 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			fp = matches[0]
 		} else if len(matches) > 1 {
 			return nil, fmt.Errorf("Provided partial image fingerprint matches more than one image")
+		} else {
+			return nil, fmt.Errorf("The requested image couldn't be found.")
 		}
 	} else if protocol == "lxd" {
 		// Setup LXD client

From 7dc97aabda7adc0c95c6ada680aeb93dc47a6780 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 3 Jul 2017 22:43:01 -0400
Subject: [PATCH 1028/1193] client: Properly handle remote disconnections
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/operations.go | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/client/operations.go b/client/operations.go
index f278510e3..03f7f2c69 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -182,6 +182,32 @@ func (op *Operation) setupListener() error {
 		return err
 	}
 
+	// Monitor event listener
+	go func() {
+		<-chReady
+
+		// We don't want concurrency while accessing the listener
+		op.handlerLock.Lock()
+
+		// Check if we're done already (because of another event)
+		if op.listener == nil {
+			op.handlerLock.Unlock()
+			return
+		}
+		op.handlerLock.Unlock()
+
+		// Wait for the listener or operation to be done
+		select {
+		case <-op.listener.chActive:
+			op.handlerLock.Lock()
+			op.Err = fmt.Sprintf("%v", op.listener.err)
+			close(op.chActive)
+			op.handlerLock.Unlock()
+		case <-op.chActive:
+			return
+		}
+	}()
+
 	// And do a manual refresh to avoid races
 	err = op.Refresh()
 	if err != nil {

From 7b4528ba04f95f656656e3fbd5fc70fbfbf64176 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 3 Jul 2017 23:05:46 -0400
Subject: [PATCH 1029/1193] client: Fix crash in operation handler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/operations.go | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/client/operations.go b/client/operations.go
index 03f7f2c69..90544ff96 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -190,7 +190,8 @@ func (op *Operation) setupListener() error {
 		op.handlerLock.Lock()
 
 		// Check if we're done already (because of another event)
-		if op.listener == nil {
+		listener := op.listener
+		if listener == nil {
 			op.handlerLock.Unlock()
 			return
 		}
@@ -198,10 +199,12 @@ func (op *Operation) setupListener() error {
 
 		// Wait for the listener or operation to be done
 		select {
-		case <-op.listener.chActive:
+		case <-listener.chActive:
 			op.handlerLock.Lock()
-			op.Err = fmt.Sprintf("%v", op.listener.err)
-			close(op.chActive)
+			if op.listener != nil {
+				op.Err = fmt.Sprintf("%v", listener.err)
+				close(op.chActive)
+			}
 			op.handlerLock.Unlock()
 		case <-op.chActive:
 			return

From 731e6a7c1b7099aaad81da3ff14995d91c6bdd6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Jul 2017 00:24:17 -0400
Subject: [PATCH 1030/1193] client: Improve migration relay code
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This removes code duplication by adding a new function for migration
proxying and makes it more robust by validating things before starting
the proxy.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd_containers.go | 147 +++++++++++++++++++++++++++++------------------
 1 file changed, 91 insertions(+), 56 deletions(-)

diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index e42cb4564..8a6a373de 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -344,36 +344,11 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 		}
 
 		// Launch the relay
-		dones := []chan bool{}
-		conns := []*websocket.Conn{}
-
-		for name := range sourceSecrets {
-			sourceConn, err := source.GetOperationWebsocket(op.ID, sourceSecrets[name])
-			if err != nil {
-				return nil, err
-			}
-
-			targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets[name])
-			if err != nil {
-				return nil, err
-			}
-
-			conns = append(conns, sourceConn)
-			conns = append(conns, targetConn)
-			dones = append(dones, shared.WebsocketProxy(sourceConn, targetConn))
+		err = r.proxyMigration(targetOp, targetSecrets, source, op, sourceSecrets)
+		if err != nil {
+			return nil, err
 		}
 
-		// Wait for everything to be done
-		go func() {
-			for _, chDone := range dones {
-				<-chDone
-			}
-
-			for _, conn := range conns {
-				conn.Close()
-			}
-		}()
-
 		// Prepare a tracking operation
 		rop := RemoteOperation{
 			targetOp: targetOp,
@@ -399,6 +374,91 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 	return r.tryCreateContainer(req, info.Addresses)
 }
 
+func (r *ProtocolLXD) proxyMigration(targetOp *Operation, targetSecrets map[string]string, source ContainerServer, sourceOp *Operation, sourceSecrets map[string]string) error {
+	// Sanity checks
+	for n := range targetSecrets {
+		_, ok := sourceSecrets[n]
+		if !ok {
+			return fmt.Errorf("Migration target expects the \"%s\" socket but source isn't providing it", n)
+		}
+	}
+
+	if targetSecrets["control"] == "" {
+		return fmt.Errorf("Migration target didn't setup the required \"control\" socket")
+	}
+
+	// Struct used to hold everything together
+	type proxy struct {
+		done       chan bool
+		sourceConn *websocket.Conn
+		targetConn *websocket.Conn
+	}
+
+	proxies := map[string]*proxy{}
+
+	// Connect the control socket
+	sourceConn, err := source.GetOperationWebsocket(sourceOp.ID, sourceSecrets["control"])
+	if err != nil {
+		return err
+	}
+
+	targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets["control"])
+	if err != nil {
+		return err
+	}
+
+	proxies["control"] = &proxy{
+		done:       shared.WebsocketProxy(sourceConn, targetConn),
+		sourceConn: sourceConn,
+		targetConn: targetConn,
+	}
+
+	// Connect the data sockets
+	for name := range sourceSecrets {
+		if name == "control" {
+			continue
+		}
+
+		// Handle resets (used for multiple objects)
+		sourceConn, err := source.GetOperationWebsocket(sourceOp.ID, sourceSecrets[name])
+		if err != nil {
+			break
+		}
+
+		targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets[name])
+		if err != nil {
+			break
+		}
+
+		proxies[name] = &proxy{
+			sourceConn: sourceConn,
+			targetConn: targetConn,
+			done:       shared.WebsocketProxy(sourceConn, targetConn),
+		}
+	}
+
+	// Cleanup once everything is done
+	go func() {
+		// Wait for control socket
+		<-proxies["control"].done
+		proxies["control"].sourceConn.Close()
+		proxies["control"].targetConn.Close()
+
+		// Then deal with the others
+		for name, proxy := range proxies {
+			if name == "control" {
+				continue
+			}
+
+			<-proxy.done
+			proxy.sourceConn.Close()
+			proxy.targetConn.Close()
+		}
+	}()
+
+	return nil
+}
+
 // UpdateContainer updates the container definition
 func (r *ProtocolLXD) UpdateContainer(name string, container api.ContainerPut, ETag string) (*Operation, error) {
 	// Send the request
@@ -985,36 +1045,11 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 		}
 
 		// Launch the relay
-		dones := []chan bool{}
-		conns := []*websocket.Conn{}
-
-		for name := range sourceSecrets {
-			sourceConn, err := source.GetOperationWebsocket(op.ID, sourceSecrets[name])
-			if err != nil {
-				return nil, err
-			}
-
-			targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets[name])
-			if err != nil {
-				return nil, err
-			}
-
-			conns = append(conns, sourceConn)
-			conns = append(conns, targetConn)
-			dones = append(dones, shared.WebsocketProxy(sourceConn, targetConn))
+		err = r.proxyMigration(targetOp, targetSecrets, source, op, sourceSecrets)
+		if err != nil {
+			return nil, err
 		}
 
-		// Wait for everything to be done
-		go func() {
-			for _, chDone := range dones {
-				<-chDone
-			}
-
-			for _, conn := range conns {
-				conn.Close()
-			}
-		}()
-
 		// Prepare a tracking operation
 		rop := RemoteOperation{
 			targetOp: targetOp,

From 77f3811caf2c477c5e862e2c19c39ec9e2ac1e00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Jul 2017 00:25:41 -0400
Subject: [PATCH 1031/1193] shared: Websocket proxy should proxy everything
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/network.go | 25 ++++++++-----------------
 1 file changed, 8 insertions(+), 17 deletions(-)

diff --git a/shared/network.go b/shared/network.go
index 4cf7af81b..a2ee54740 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -234,35 +234,21 @@ func WebsocketProxy(source *websocket.Conn, target *websocket.Conn) chan bool {
 	forward := func(in *websocket.Conn, out *websocket.Conn, ch chan bool) {
 		for {
 			mt, r, err := in.NextReader()
-			if mt == websocket.CloseMessage {
-				logger.Debugf("Got close message for reader")
-				break
-			}
-
-			if mt == websocket.TextMessage {
-				logger.Debugf("got message barrier")
-				break
-			}
-
 			if err != nil {
-				logger.Debugf("Got error getting next reader %s", err)
 				break
 			}
 
-			w, err := out.NextWriter(websocket.BinaryMessage)
+			w, err := out.NextWriter(mt)
 			if err != nil {
-				logger.Debugf("Got error getting next writer %s", err)
 				break
 			}
 
 			_, err = io.Copy(w, r)
 			w.Close()
 			if err != nil {
-				logger.Debugf("Got err writing %s", err)
 				break
 			}
 		}
-		out.WriteMessage(websocket.TextMessage, []byte{})
 
 		ch <- true
 	}
@@ -275,8 +261,13 @@ func WebsocketProxy(source *websocket.Conn, target *websocket.Conn) chan bool {
 
 	ch := make(chan bool)
 	go func() {
-		<-chSend
-		<-chRecv
+		select {
+		case <-chSend:
+		case <-chRecv:
+		}
+
+		source.Close()
+		target.Close()
 
 		ch <- true
 	}()

From 1f588ab407a85a40d63c52f229c977c57daf2f1d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Jul 2017 16:19:08 -0400
Subject: [PATCH 1032/1193] shared: Use custom error type for RunCommand
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3502

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/util.go | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/shared/util.go b/shared/util.go
index d5fd7783c..45dd4f3fc 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -763,10 +763,23 @@ func GetByteSizeString(input int64, precision uint) string {
 	return fmt.Sprintf("%.*fEB", precision, value)
 }
 
+type RunError struct {
+	msg string
+	Err error
+}
+
+func (e RunError) Error() string {
+	return e.msg
+}
+
 func RunCommand(name string, arg ...string) (string, error) {
 	output, err := exec.Command(name, arg...).CombinedOutput()
 	if err != nil {
-		return string(output), fmt.Errorf("Failed to run: %s %s: %s", name, strings.Join(arg, " "), strings.TrimSpace(string(output)))
+		err := RunError{
+			msg: fmt.Sprintf("Failed to run: %s %s: %s", name, strings.Join(arg, " "), strings.TrimSpace(string(output))),
+			Err: err,
+		}
+		return string(output), err
 	}
 
 	return string(output), nil

From 87a423683e9f4230dba82392f547f0466b2e7121 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Jul 2017 18:15:29 -0400
Subject: [PATCH 1033/1193] tests: Validate that the right busybox is present
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/main.sh | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/test/main.sh b/test/main.sh
index 169e8339c..8a7a3222f 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -236,6 +236,16 @@ ensure_import_testimage() {
     if [ -e "${LXD_TEST_IMAGE:-}" ]; then
       lxc image import "${LXD_TEST_IMAGE}" --alias testimage
     else
+      if [ ! -e "/bin/busybox" ]; then
+        echo "Please install busybox (busybox-static) or set LXD_TEST_IMAGE"
+        exit 1
+      fi
+
+      if ldd /bin/busybox >/dev/null 2>&1; then
+        echo "The testsuite requires /bin/busybox to be a static binary"
+        exit 1
+      fi
+
       deps/import-busybox --alias testimage
     fi
   fi

From 26344bf0a16214fd389de1b16f97a08f9a52db19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 6 Jul 2017 18:28:08 -0400
Subject: [PATCH 1034/1193] tests: Skip apparmor tests when no kernel support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/basic.sh | 44 ++++++++++++++++++++++++--------------------
 1 file changed, 24 insertions(+), 20 deletions(-)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 1009bb130..0bae78621 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -298,29 +298,33 @@ test_basic_usage() {
   # cleanup
   lxc delete foo -f
 
-  # check that an apparmor profile is created for this container, that it is
-  # unloaded on stop, and that it is deleted when the container is deleted
-  lxc launch testimage lxd-apparmor-test
-
-  MAJOR=0
-  MINOR=0
-  if [ -f /sys/kernel/security/apparmor/features/domain/version ]; then
-    MAJOR=$(awk -F. '{print $1}' < /sys/kernel/security/apparmor/features/domain/version)
-    MINOR=$(awk -F. '{print $2}' < /sys/kernel/security/apparmor/features/domain/version)
-  fi
+  if [ -e /sys/module/apparmor/ ]; then
+    # check that an apparmor profile is created for this container, that it is
+    # unloaded on stop, and that it is deleted when the container is deleted
+    lxc launch testimage lxd-apparmor-test
+
+    MAJOR=0
+    MINOR=0
+    if [ -f /sys/kernel/security/apparmor/features/domain/version ]; then
+      MAJOR=$(awk -F. '{print $1}' < /sys/kernel/security/apparmor/features/domain/version)
+      MINOR=$(awk -F. '{print $2}' < /sys/kernel/security/apparmor/features/domain/version)
+    fi
 
-  if [ "${MAJOR}" -gt "1" ] || ([ "${MAJOR}" = "1" ] && [ "${MINOR}" -ge "2" ]); then
-    aa_namespace="lxd-lxd-apparmor-test_<$(echo "${LXD_DIR}" | sed -e 's/\//-/g' -e 's/^.//')>"
-    aa-status | grep ":${aa_namespace}://unconfined"
-    lxc stop lxd-apparmor-test --force
-    ! aa-status | grep -q ":${aa_namespace}:"
+    if [ "${MAJOR}" -gt "1" ] || ([ "${MAJOR}" = "1" ] && [ "${MINOR}" -ge "2" ]); then
+      aa_namespace="lxd-lxd-apparmor-test_<$(echo "${LXD_DIR}" | sed -e 's/\//-/g' -e 's/^.//')>"
+      aa-status | grep ":${aa_namespace}://unconfined"
+      lxc stop lxd-apparmor-test --force
+      ! aa-status | grep -q ":${aa_namespace}:"
+    else
+      aa-status | grep "lxd-lxd-apparmor-test_<${LXD_DIR}>"
+      lxc stop lxd-apparmor-test --force
+      ! aa-status | grep -q "lxd-lxd-apparmor-test_<${LXD_DIR}>"
+    fi
+    lxc delete lxd-apparmor-test
+    [ ! -f "${LXD_DIR}/security/apparmor/profiles/lxd-lxd-apparmor-test" ]
   else
-    aa-status | grep "lxd-lxd-apparmor-test_<${LXD_DIR}>"
-    lxc stop lxd-apparmor-test --force
-    ! aa-status | grep -q "lxd-lxd-apparmor-test_<${LXD_DIR}>"
+    echo "==> SKIP: apparmor tests (missing kernel support)"
   fi
-  lxc delete lxd-apparmor-test
-  [ ! -f "${LXD_DIR}/security/apparmor/profiles/lxd-lxd-apparmor-test" ]
 
   # make sure that privileged containers are not world-readable
   lxc profile create unconfined

From acb43e1ccc4d8f02c5a29651b6f0219c439458f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 7 Jul 2017 01:04:43 -0400
Subject: [PATCH 1035/1193] lxc/utils: Avoid potential progress race condition
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

It's possible that we get a callback just after the operation completed,
so make sure that once we called Done() no more printing can happen.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/utils.go | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/lxc/utils.go b/lxc/utils.go
index 3378b8706..2eb5a7577 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -21,9 +21,12 @@ type ProgressRenderer struct {
 
 	maxLength int
 	wait      time.Time
+	done      bool
 }
 
 func (p *ProgressRenderer) Done(msg string) {
+	p.done = true
+
 	if msg != "" {
 		msg += "\n"
 	}
@@ -39,6 +42,10 @@ func (p *ProgressRenderer) Done(msg string) {
 }
 
 func (p *ProgressRenderer) Update(status string) {
+	if p.done {
+		return
+	}
+
 	timeout := p.wait.Sub(time.Now())
 	if timeout.Seconds() > 0 {
 		time.Sleep(timeout)

From feb703e54d13af6ddc75313c8e0b232f930f8304 Mon Sep 17 00:00:00 2001
From: Fedor Dikarev <yatheo at yandex-team.ru>
Date: Fri, 7 Jul 2017 16:43:10 +0300
Subject: [PATCH 1036/1193] Fix spaces, commas, quotes, brackets where needed

Signed-off-by: Fedor Dikarev fedor.dikarev at gmail.com
---
 doc/rest-api.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 07bda2004..c95f5a231 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -488,7 +488,7 @@ Input (using a remote container, sent over the migration websocket):
                    "base-image": "<fingerprint>",                                       # Optional, the base image the container was created from
                    "secrets": {"control": "my-secret-string",                           # Secrets to use when talking to the migration source
                                "criu":    "my-other-secret",
-                               "fs":      "my third secret"},
+                               "fs":      "my third secret"}
     }
 
 Input (using a local container):
@@ -611,6 +611,7 @@ Input (simple rename):
     }
 
 Input (migration across lxd instances):
+
     {
         "migration": true
     }

From 2a81741fa7e821760d2bc5ce7fa05f670e4c4082 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 7 Jul 2017 15:43:32 -0400
Subject: [PATCH 1037/1193] lxc/exec: Fix signal handler for Windows
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3496

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/exec_windows.go | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/lxc/exec_windows.go b/lxc/exec_windows.go
index 105219e29..67078df6e 100644
--- a/lxc/exec_windows.go
+++ b/lxc/exec_windows.go
@@ -5,6 +5,8 @@ package main
 import (
 	"io"
 	"os"
+	"os/signal"
+	"syscall"
 
 	"github.com/gorilla/websocket"
 	"github.com/mattn/go-colorable"
@@ -32,11 +34,24 @@ func (c *execCmd) getTERM() (string, bool) {
 }
 
 func (c *execCmd) controlSocketHandler(control *websocket.Conn) {
-	// TODO: figure out what the equivalent of signal.SIGWINCH is on
-	// windows and use that; for now if you resize your terminal it just
-	// won't work quite correctly.
-	err := c.sendTermSize(control)
-	if err != nil {
-		logger.Debugf("error setting term size %s", err)
+	ch := make(chan os.Signal, 10)
+	signal.Notify(ch, os.Interrupt)
+
+	closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
+	defer control.WriteMessage(websocket.CloseMessage, closeMsg)
+
+	for {
+		sig := <-ch
+		switch sig {
+		case os.Interrupt:
+			logger.Debugf("Received '%s signal', forwarding to executing program.", sig)
+			err := c.forwardSignal(control, syscall.SIGINT)
+			if err != nil {
+				logger.Debugf("Failed to forward signal '%s'.", syscall.SIGINT)
+				return
+			}
+		default:
+			break
+		}
 	}
 }

From dce8dbbab42a069adeba436769236fe049864074 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 7 Jul 2017 16:39:32 -0400
Subject: [PATCH 1038/1193] tests: More apparmor presence checking
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/config.sh | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/test/suites/config.sh b/test/suites/config.sh
index d02b40d2f..0042a7978 100644
--- a/test/suites/config.sh
+++ b/test/suites/config.sh
@@ -197,7 +197,9 @@ test_config_profiles() {
   lxc profile apply foo onenic,unconfined
   lxc start foo
 
-  lxc exec foo -- cat /proc/self/attr/current | grep unconfined
+  if [ -e /sys/module/apparmor ]; then
+    lxc exec foo -- cat /proc/self/attr/current | grep unconfined
+  fi
   lxc exec foo -- ls /sys/class/net | grep eth0
 
   lxc stop foo --force

From 99b8bddbe06bb8fc11a063a1b208145b47c33eda Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Fri, 7 Jul 2017 19:57:08 +0300
Subject: [PATCH 1039/1193] ISSUE_TEMPLATE.md: Fix lxd.log location

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 .github/ISSUE_TEMPLATE.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 0d5599981..dd96d36de 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -23,8 +23,8 @@ A brief description of what failed or what could be improved.
 
 # Information to attach
 
- - [ ] any relevant kernel output (dmesg)
- - [ ] container log (lxc info NAME --show-log)
- - [ ] main daemon log (/var/log/lxd.log)
+ - [ ] any relevant kernel output (`dmesg`)
+ - [ ] container log (`lxc info NAME --show-log`)
+ - [ ] main daemon log (`cat /var/log/lxd/lxd.log`)
  - [ ] output of the client with --debug
  - [ ] output of the daemon with --debug

From 31c740e9cf44ed5ba1c8fd1f47c2c59708ede0ef Mon Sep 17 00:00:00 2001
From: Elvis Espinal <elvisespinal at neoscloudllc.com>
Date: Mon, 10 Jul 2017 07:53:12 -0400
Subject: [PATCH 1040/1193] Fix help to provide sample that actually works

Using 1 doesn't display privileged containers but if we use true it does.

Signed-off-by: Elvis Espinal <elvisespinal at neoscloudllc.com>
---
 lxc/list.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index 58ef16c3b..aabd2c2a1 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -61,9 +61,9 @@ A key/value pair referring to a configuration item. For those, the namespace can
 
     - "u.blah=abc" will do the same
 
-    - "security.privileged=1" will list all privileged containers
+    - "security.privileged=true" will list all privileged containers
 
-    - "s.privileged=1" will do the same
+    - "s.privileged=true" will do the same
 
 A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*).
 

From 5a734e3af1f74756d7ed040cb0d137ed27978405 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 11:40:18 +0200
Subject: [PATCH 1041/1193] liblxc: handle pre 2.1 liblxc

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 43 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 36a847ee1..7d7fa742e 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -105,6 +105,49 @@ func lxcSetConfigItem(c *lxc.Container, key string, value string) error {
 		return fmt.Errorf("Uninitialized go-lxc struct")
 	}
 
+	if !lxc.VersionAtLeast(2, 1, 0) {
+		switch key {
+		case "lxc.uts.name":
+			key = "lxc.utsname"
+		case "lxc.pty.max":
+			key = "lxc.pts"
+		case "lxc.tty.dir":
+			key = "lxc.devttydir"
+		case "lxc.tty.max":
+			key = "lxc.tty"
+		case "lxc.apparmor.profile":
+			key = "lxc.aa_profile"
+		case "lxc.apparmor.allow_incomplete":
+			key = "lxc.aa_allow_incomplete"
+		case "lxc.selinux.context":
+			key = "lxc.se_context"
+		case "lxc.mount.fstab":
+			key = "lxc.mount"
+		case "lxc.console.path":
+			key = "lxc.console"
+		case "lxc.seccomp.profile":
+			key = "lxc.seccomp"
+		case "lxc.signal.halt":
+			key = "lxc.haltsignal"
+		case "lxc.signal.reboot":
+			key = "lxc.rebootsignal"
+		case "lxc.signal.stop":
+			key = "lxc.stopsignal"
+		case "lxc.log.syslog":
+			key = "lxc.syslog"
+		case "lxc.log.level":
+			key = "lxc.loglevel"
+		case "lxc.log.file":
+			key = "lxc.logfile"
+		case "lxc.init.cmd":
+			key = "lxc.init_cmd"
+		case "lxc.init.uid":
+			key = "lxc.init_uid"
+		case "lxc.init.gid":
+			key = "lxc.init_gid"
+		}
+	}
+
 	err := c.SetConfigItem(key, value)
 	if err != nil {
 		return fmt.Errorf("Failed to set LXC config: %s=%s", key, value)

From a970dd3d73d4bc9f53345f851cc40b273217fdbe Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:37:26 +0200
Subject: [PATCH 1042/1193] liblxc 2.1: lxc.rootfs -> lxc.rootfs.path

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 22 +++++++++++++++-------
 1 file changed, 15 insertions(+), 7 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 7d7fa742e..e3ea4845d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1338,17 +1338,25 @@ func (c *containerLXC) initLXC() error {
 
 			// Deal with a rootfs
 			if tgtPath == "" {
-				// Set the rootfs backend type if supported (must happen before any other lxc.rootfs)
-				err := lxcSetConfigItem(cc, "lxc.rootfs.backend", "dir")
-				if err == nil {
-					value := cc.ConfigItem("lxc.rootfs.backend")
-					if len(value) == 0 || value[0] != "dir" {
-						lxcSetConfigItem(cc, "lxc.rootfs.backend", "")
+				if !lxc.VersionAtLeast(2, 1, 0) {
+					// Set the rootfs backend type if supported (must happen before any other lxc.rootfs)
+					err := lxcSetConfigItem(cc, "lxc.rootfs.backend", "dir")
+					if err == nil {
+						value := cc.ConfigItem("lxc.rootfs.backend")
+						if len(value) == 0 || value[0] != "dir" {
+							lxcSetConfigItem(cc, "lxc.rootfs.backend", "")
+						}
 					}
 				}
 
 				// Set the rootfs path
-				err = lxcSetConfigItem(cc, "lxc.rootfs", c.RootfsPath())
+				if lxc.VersionAtLeast(2, 1, 0) {
+					rootfsPath := fmt.Sprintf("dir:%s", c.RootfsPath())
+					err = lxcSetConfigItem(cc, "lxc.rootfs.path", rootfsPath)
+				} else {
+					rootfsPath := c.RootfsPath()
+					err = lxcSetConfigItem(cc, "lxc.rootfs", rootfsPath)
+				}
 				if err != nil {
 					return err
 				}

From 811adbf233f99c6d95cb530dcadca1f735816876 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:41:00 +0200
Subject: [PATCH 1043/1193] liblxc: ipv{4,6} --> ipv{4,6}.address

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index e3ea4845d..b97c846c6 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -199,11 +199,17 @@ func lxcValidConfig(rawLxc string) error {
 
 		if strings.HasPrefix(key, networkKeyPrefix) {
 			fields := strings.Split(key, ".")
-			if len(fields) == 4 && shared.StringInSlice(fields[3], []string{"ipv4", "ipv6"}) {
+
+			allowedIPKeys := []string{"ipv4.address", "ipv6.address"}
+			if !lxc.VersionAtLeast(2, 1, 0) {
+				allowedIPKeys = []string{"ipv4", "ipv6"}
+			}
+
+			if len(fields) == 4 && shared.StringInSlice(fields[3], allowedIPKeys) {
 				continue
 			}
 
-			if len(fields) == 5 && shared.StringInSlice(fields[3], []string{"ipv4", "ipv6"}) && fields[4] == "gateway" {
+			if len(fields) == 5 && shared.StringInSlice(fields[3], allowedIPKeys) && fields[4] == "gateway" {
 				continue
 			}
 

From b11bbc6da413c040a760dd5d96d048a5fb612ac5 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:42:53 +0200
Subject: [PATCH 1044/1193] liblxc: lxc.tty --> lxc.tty.max

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b97c846c6..ef424d3a2 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -955,7 +955,7 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup the console
-	err = lxcSetConfigItem(cc, "lxc.tty", "0")
+	err = lxcSetConfigItem(cc, "lxc.tty.max", "0")
 	if err != nil {
 		return err
 	}

From 3721eb82ebe563a47c7c5dab47fff8001834ad98 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:43:56 +0200
Subject: [PATCH 1045/1193] liblxc: lxc.pts --> lxc.pty.max

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ef424d3a2..b30cadc09 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -807,7 +807,7 @@ func (c *containerLXC) initLXC() error {
 		return err
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.pts", "1024")
+	err = lxcSetConfigItem(cc, "lxc.pty.max", "1024")
 	if err != nil {
 		return err
 	}

From ebd6f1699568ede32c9cbe4b4b776b328a907506 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:45:10 +0200
Subject: [PATCH 1046/1193] liblxc: lxc.seccomp --> lxc.seccomp.profile

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b30cadc09..ee6c46aeb 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1005,7 +1005,7 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup Seccomp
-	err = lxcSetConfigItem(cc, "lxc.seccomp", SeccompProfilePath(c))
+	err = lxcSetConfigItem(cc, "lxc.seccomp.profile", SeccompProfilePath(c))
 	if err != nil {
 		return err
 	}

From d85f0179db62f3e859a7cb38d0d3035450809e3f Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:47:02 +0200
Subject: [PATCH 1047/1193] liblxc: lxc.loglevel --> lxc.log.level

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ee6c46aeb..842351d5f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -924,7 +924,7 @@ func (c *containerLXC) initLXC() error {
 		logLevel = "info"
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.loglevel", logLevel)
+	err = lxcSetConfigItem(cc, "lxc.log.level", logLevel)
 	if err != nil {
 		return err
 	}

From af2ee5ca532c9b4b822bfdfff954bdc5c0efa1ba Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:49:11 +0200
Subject: [PATCH 1048/1193] liblxc: lxc.aa_profile --> lxc.apparmor.profile

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 842351d5f..0fbe573f5 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -978,7 +978,7 @@ func (c *containerLXC) initLXC() error {
 			// If confined but otherwise able to use AppArmor, use our own profile
 			curProfile := aaProfile()
 			curProfile = strings.TrimSuffix(curProfile, " (enforce)")
-			err = lxcSetConfigItem(cc, "lxc.aa_profile", curProfile)
+			err := lxcSetConfigItem(cc, "lxc.apparmor.profile", curProfile)
 			if err != nil {
 				return err
 			}
@@ -997,7 +997,7 @@ func (c *containerLXC) initLXC() error {
 				profile = fmt.Sprintf("%s//&:%s:", profile, AANamespace(c))
 			}
 
-			err := lxcSetConfigItem(cc, "lxc.aa_profile", profile)
+			err := lxcSetConfigItem(cc, "lxc.apparmor.profile", profile)
 			if err != nil {
 				return err
 			}

From 036dd1e9a76602f2235523df23ec45ddd0913f33 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:50:25 +0200
Subject: [PATCH 1049/1193] liblxc: lxc.utsname --> lxc.uts.name

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0fbe573f5..1dfe694ea 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -961,7 +961,7 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup the hostname
-	err = lxcSetConfigItem(cc, "lxc.utsname", c.Name())
+	err = lxcSetConfigItem(cc, "lxc.uts.name", c.Name())
 	if err != nil {
 		return err
 	}

From d6f000d6b253b8907e5376eee201f25b186b44a7 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:52:41 +0200
Subject: [PATCH 1050/1193] liblxc: lxc.logfile --> lxc.log.file

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 1dfe694ea..94adb2a7b 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -180,7 +180,7 @@ func lxcValidConfig(rawLxc string) error {
 		key := strings.ToLower(strings.Trim(membs[0], " \t"))
 
 		// Blacklist some keys
-		if key == "lxc.logfile" {
+		if key == "lxc.logfile" || key == "lxc.log.file" {
 			return fmt.Errorf("Setting lxc.logfile is not allowed")
 		}
 

From a9e41bd9b1c0a5546cbb4946f3f96f505199f2a9 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Tue, 11 Jul 2017 00:53:18 +0200
Subject: [PATCH 1051/1193] liblxc: lxc.syslog --> lxc.log.syslog

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 94adb2a7b..4d853f951 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -184,7 +184,7 @@ func lxcValidConfig(rawLxc string) error {
 			return fmt.Errorf("Setting lxc.logfile is not allowed")
 		}
 
-		if key == "lxc.syslog" {
+		if key == "lxc.syslog" || key == "lxc.log.syslog" {
 			return fmt.Errorf("Setting lxc.syslog is not allowed")
 		}
 

From bcb0e1cb62d29336660c70b2fe64587f43691d60 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 11 Jul 2017 13:09:24 -0400
Subject: [PATCH 1052/1193] Show underlying error when container delete fails
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 4d853f951..cbe87336f 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -2465,7 +2465,7 @@ func (c *containerLXC) Delete() error {
 		// Delete the container from disk
 		if shared.PathExists(c.Path()) {
 			if err := c.storage.ContainerDelete(c); err != nil {
-				logger.Error("Failed deleting container storage", ctxMap)
+				logger.Error("Failed deleting container storage", log.Ctx{"name": c.Name(), "err": err})
 				return err
 			}
 		}
@@ -2473,7 +2473,7 @@ func (c *containerLXC) Delete() error {
 
 	// Remove the database record
 	if err := dbContainerRemove(c.daemon.db, c.Name()); err != nil {
-		logger.Error("Failed deleting container entry", ctxMap)
+		logger.Error("Failed deleting container entry", log.Ctx{"name": c.Name(), "err": err})
 		return err
 	}
 

From 31dfb652c91f14ddbc3dd7524ad5d3e4baaefa5f Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 12 Jul 2017 10:06:24 +0200
Subject: [PATCH 1053/1193] lxc/image: always use long fingerprint in exported
 filenames.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/images.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 73e0f3f79..a40ea48f7 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1242,7 +1242,7 @@ func imageExport(d *Daemon, r *http.Request) Response {
 	if err != nil {
 		ext = ""
 	}
-	filename := fmt.Sprintf("%s%s", fingerprint, ext)
+	filename := fmt.Sprintf("%s%s", imgInfo.Fingerprint, ext)
 
 	if shared.PathExists(rootfsPath) {
 		files := make([]fileResponseEntry, 2)
@@ -1257,7 +1257,7 @@ func imageExport(d *Daemon, r *http.Request) Response {
 		if err != nil {
 			ext = ""
 		}
-		filename = fmt.Sprintf("%s%s", fingerprint, ext)
+		filename = fmt.Sprintf("%s%s", imgInfo.Fingerprint, ext)
 
 		files[1].identifier = "rootfs"
 		files[1].path = rootfsPath

From 0dfdbdbd2bc3e802c0216d57a1f28245879c9c0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 15 Jul 2017 18:27:47 +0200
Subject: [PATCH 1054/1193] Improve "lxc image list" filter handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3555

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxc/image.go b/lxc/image.go
index e34890af6..2e8d3a7ac 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -577,10 +577,15 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 					return err
 				}
 			} else {
-				remote, _, err = conf.ParseRemote(args[1])
+				var filter string
+				remote, filter, err = conf.ParseRemote(args[1])
 				if err != nil {
 					return err
 				}
+
+				if filter != "" {
+					filters = append(filters, filter)
+				}
 			}
 		} else {
 			remote, _, err = conf.ParseRemote("")

From f8b419fafd73e860d7cad4f61442a8b0d94e22b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 18 Jul 2017 14:21:43 +0200
Subject: [PATCH 1055/1193] Fix "lxc image copy" not recording the source
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 2e8d3a7ac..19de94473 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -273,13 +273,26 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		// Check if an alias
-		fingerprint := c.dereferenceAlias(d, inName)
+		// Optimisation for simplestreams
+		var imgInfo *api.Image
+		image := inName
+		if conf.Remotes[remote].Protocol == "simplestreams" {
+			imgInfo = &api.Image{}
+			imgInfo.Fingerprint = image
+			imgInfo.Public = true
+		} else {
 
-		// Get the image
-		image, _, err := d.GetImage(fingerprint)
-		if err != nil {
-			return err
+			// Attempt to resolve an image alias
+			alias, _, err := d.GetImageAlias(image)
+			if err == nil {
+				image = alias.Target
+			}
+
+			// Get the image info
+			imgInfo, _, err = d.GetImage(image)
+			if err != nil {
+				return err
+			}
 		}
 
 		// Setup the copy arguments
@@ -298,7 +311,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		}
 
 		// Do the copy
-		op, err := dest.CopyImage(d, *image, &args)
+		op, err := dest.CopyImage(d, *imgInfo, &args)
 		if err != nil {
 			return err
 		}

From 3282213082efab4413671f0fe408675cfac484fe Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 18 Jul 2017 15:30:45 +0200
Subject: [PATCH 1056/1193] Fix image refresh when fingerprint is passed.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/images.go | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index a40ea48f7..78afff860 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -836,7 +836,7 @@ func autoUpdateImages(d *Daemon) {
 	for _, fingerprint := range images {
 		id, info, err := dbImageGet(d.db, fingerprint, false, true)
 		if err != nil {
-			logger.Error("Error loading image", log.Ctx{"err": err, "fp": fingerprint})
+			logger.Error("Error loading image", log.Ctx{"err": err, "fp": info.Fingerprint})
 			continue
 		}
 
@@ -844,7 +844,7 @@ func autoUpdateImages(d *Daemon) {
 			continue
 		}
 
-		autoUpdateImage(d, nil, fingerprint, id, info)
+		autoUpdateImage(d, nil, id, info)
 	}
 
 	logger.Infof("Done updating images")
@@ -852,7 +852,8 @@ func autoUpdateImages(d *Daemon) {
 
 // Update a single image.  The operation can be nil, if no progress tracking is needed.
 // Returns whether the image has been updated.
-func autoUpdateImage(d *Daemon, op *operation, fingerprint string, id int, info *api.Image) error {
+func autoUpdateImage(d *Daemon, op *operation, id int, info *api.Image) error {
+	fingerprint := info.Fingerprint
 	_, source, err := dbImageSourceGet(d.db, id)
 	if err != nil {
 		logger.Error("Error getting source image", log.Ctx{"err": err, "fp": fingerprint})

From d106e27c7da591d3ab080e5d91961f83782ced89 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 19 Jul 2017 12:32:37 +0200
Subject: [PATCH 1057/1193] Don't access the returned struct in case of error

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/images.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/images.go b/lxd/images.go
index 78afff860..1f4a35792 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -836,7 +836,7 @@ func autoUpdateImages(d *Daemon) {
 	for _, fingerprint := range images {
 		id, info, err := dbImageGet(d.db, fingerprint, false, true)
 		if err != nil {
-			logger.Error("Error loading image", log.Ctx{"err": err, "fp": info.Fingerprint})
+			logger.Error("Error loading image", log.Ctx{"err": err, "fp": fingerprint})
 			continue
 		}
 

From ba0ad20d534e27d7b362bb86a2602b02a7a62dc7 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Mon, 17 Jul 2017 15:17:24 +0200
Subject: [PATCH 1058/1193] Update image aliases when they already exist

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxc/image.go         | 61 ++++++++++++++++++++++++++--------------------------
 lxc/publish.go       |  8 ++++---
 lxc/utils.go         | 51 +++++++++++++++++++++++++++++++++++++++++++
 lxc/utils_test.go    | 37 +++++++++++++++++++++++++++++++
 test/suites/basic.sh | 11 ++++++++++
 test/suites/image.sh | 12 +++++++++++
 6 files changed, 147 insertions(+), 33 deletions(-)
 create mode 100644 lxc/utils_test.go

diff --git a/lxc/image.go b/lxc/image.go
index 19de94473..0fd84fd72 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -222,8 +222,7 @@ func (c *imageCmd) doImageAlias(conf *config.Config, args []string) error {
 			return err
 		}
 
-		err = d.DeleteImageAlias(alias)
-		return err
+		return d.DeleteImageAlias(alias)
 	}
 	return errArgs
 }
@@ -273,16 +272,10 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		// Optimisation for simplestreams
+		// Attempt to resolve an image alias
 		var imgInfo *api.Image
 		image := inName
-		if conf.Remotes[remote].Protocol == "simplestreams" {
-			imgInfo = &api.Image{}
-			imgInfo.Fingerprint = image
-			imgInfo.Public = true
-		} else {
-
-			// Attempt to resolve an image alias
+		if c.copyAliases {
 			alias, _, err := d.GetImageAlias(image)
 			if err == nil {
 				image = alias.Target
@@ -293,21 +286,17 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			if err != nil {
 				return err
 			}
-		}
-
-		// Setup the copy arguments
-		aliases := []api.ImageAlias{}
-		for _, entry := range c.addAliases {
-			alias := api.ImageAlias{}
-			alias.Name = entry
-			aliases = append(aliases, alias)
+		} else {
+			// Don't fetch full image info if we don't need aliases (since it's
+			// an expensive operation)
+			imgInfo = &api.Image{}
+			imgInfo.Fingerprint = image
+			imgInfo.Public = true
 		}
 
 		args := lxd.ImageCopyArgs{
-			Aliases:     aliases,
-			AutoUpdate:  c.autoUpdate,
-			CopyAliases: c.copyAliases,
-			Public:      c.publicImage,
+			AutoUpdate: c.autoUpdate,
+			Public:     c.publicImage,
 		}
 
 		// Do the copy
@@ -332,7 +321,20 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		}
 
 		progress.Done(i18n.G("Image copied successfully!"))
-		return nil
+
+		// Ensure aliases
+		aliases := make([]api.ImageAlias, len(c.addAliases))
+		for i, entry := range c.addAliases {
+			aliases[i].Name = entry
+		}
+		if c.copyAliases {
+			// Also add the original aliases
+			for _, alias := range imgInfo.Aliases {
+				aliases = append(aliases, alias)
+			}
+		}
+		err = ensureImageAliases(dest, aliases, image)
+		return err
 
 	case "delete":
 		/* delete [<remote>:]<image> [<image>...] */
@@ -564,17 +566,16 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
 
 		// Add the aliases
-		for _, entry := range c.addAliases {
-			alias := api.ImageAliasesPost{}
-			alias.Name = entry
-			alias.Target = fingerprint
-
-			err = d.CreateImageAlias(alias)
+		if len(c.addAliases) > 0 {
+			aliases := make([]api.ImageAlias, len(c.addAliases))
+			for i, entry := range c.addAliases {
+				aliases[i].Name = entry
+			}
+			err = ensureImageAliases(d, aliases, fingerprint)
 			if err != nil {
 				return err
 			}
 		}
-
 		return nil
 
 	case "list":
diff --git a/lxc/publish.go b/lxc/publish.go
index ca154e799..216bc511e 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -197,7 +197,6 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 	}
 
 	if cRemote == iRemote {
-		req.Aliases = aliases
 		req.Public = c.makePublic
 	}
 
@@ -226,8 +225,7 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 
 		// Image copy arguments
 		args := lxd.ImageCopyArgs{
-			Aliases: aliases,
-			Public:  c.makePublic,
+			Public: c.makePublic,
 		}
 
 		// Copy the image to the destination host
@@ -242,6 +240,10 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 		}
 	}
 
+	err = ensureImageAliases(d, aliases, fingerprint)
+	if err != nil {
+		return err
+	}
 	fmt.Printf(i18n.G("Container published with fingerprint: %s")+"\n", fingerprint)
 
 	return nil
diff --git a/lxc/utils.go b/lxc/utils.go
index 2eb5a7577..c4181d87e 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -6,6 +6,7 @@ import (
 	"net/url"
 	"os"
 	"os/signal"
+	"sort"
 	"strings"
 	"syscall"
 	"time"
@@ -296,3 +297,53 @@ func cancelableWait(op *lxd.RemoteOperation, progress *ProgressRenderer) error {
 		}
 	}
 }
+
+// Create the specified image alises, updating those that already exist
+func ensureImageAliases(client lxd.ContainerServer, aliases []api.ImageAlias, fingerprint string) error {
+	if len(aliases) == 0 {
+		return nil
+	}
+
+	names := make([]string, len(aliases))
+	for i, alias := range aliases {
+		names[i] = alias.Name
+	}
+	sort.Strings(names)
+
+	resp, err := client.GetImageAliases()
+	if err != nil {
+		return err
+	}
+
+	// Delete existing aliases that match provided ones
+	for _, alias := range GetExistingAliases(names, resp) {
+		err := client.DeleteImageAlias(alias.Name)
+		if err != nil {
+			fmt.Println(i18n.G("Failed to remove alias %s"), alias.Name)
+		}
+	}
+	// Create new aliases
+	for _, alias := range aliases {
+		aliasPost := api.ImageAliasesPost{}
+		aliasPost.Name = alias.Name
+		aliasPost.Target = fingerprint
+		err := client.CreateImageAlias(aliasPost)
+		if err != nil {
+			fmt.Println(i18n.G("Failed to create alias %s"), alias.Name)
+		}
+	}
+	return nil
+}
+
+// GetExistingAliases returns the intersection between a list of aliases and all the existing ones.
+func GetExistingAliases(aliases []string, allAliases []api.ImageAliasesEntry) []api.ImageAliasesEntry {
+	existing := []api.ImageAliasesEntry{}
+	for _, alias := range allAliases {
+		name := alias.Name
+		pos := sort.SearchStrings(aliases, name)
+		if pos < len(aliases) && aliases[pos] == name {
+			existing = append(existing, alias)
+		}
+	}
+	return existing
+}
diff --git a/lxc/utils_test.go b/lxc/utils_test.go
new file mode 100644
index 000000000..7763b2c85
--- /dev/null
+++ b/lxc/utils_test.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+
+	"github.com/lxc/lxd/shared/api"
+)
+
+type utilsTestSuite struct {
+	suite.Suite
+}
+
+func TestUtilsTestSuite(t *testing.T) {
+	suite.Run(t, new(utilsTestSuite))
+}
+
+func (s *utilsTestSuite) TestGetExistingAliases() {
+	images := []api.ImageAliasesEntry{
+		{Name: "foo"},
+		{Name: "bar"},
+		{Name: "baz"},
+	}
+	aliases := GetExistingAliases([]string{"bar", "foo", "other"}, images)
+	s.Exactly([]api.ImageAliasesEntry{images[0], images[1]}, aliases)
+}
+
+func (s *utilsTestSuite) TestGetExistingAliasesEmpty() {
+	images := []api.ImageAliasesEntry{
+		{Name: "foo"},
+		{Name: "bar"},
+		{Name: "baz"},
+	}
+	aliases := GetExistingAliases([]string{"other1", "other2"}, images)
+	s.Exactly([]api.ImageAliasesEntry{}, aliases)
+}
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 0bae78621..d3915799c 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -120,6 +120,17 @@ test_basic_usage() {
   curl -k -s --cert "${LXD_CONF}/client3.crt" --key "${LXD_CONF}/client3.key" -X GET "https://${LXD_ADDR}/1.0/images" | grep "/1.0/images/" && false
   lxc image delete foo-image
 
+  # Test container publish with existing alias
+  lxc publish bar --alias=foo-image --alias=foo-image2
+  lxc launch testimage baz
+  # change the container filesystem so the resulting image is different
+  lxc exec baz touch /somefile
+  lxc stop baz
+  # publishing another image with same alias doesn't fail
+  lxc publish baz --alias=foo-image
+  lxc delete baz
+  lxc image delete foo-image foo-image2
+
   # Test privileged container publish
   lxc profile create priv
   lxc profile set priv security.privileged true
diff --git a/test/suites/image.sh b/test/suites/image.sh
index e3b9f9b79..313ab1490 100644
--- a/test/suites/image.sh
+++ b/test/suites/image.sh
@@ -38,3 +38,15 @@ test_image_expiry() {
   lxc_remote remote remove l2
   kill_lxd "$LXD2_DIR"
 }
+
+test_image_import_existing_alias() {
+    ensure_import_testimage
+    lxc init testimage c
+    lxc publish c --alias newimage --alias image2
+    lxc delete c
+    lxc image export testimage testimage.file
+    lxc image delete testimage
+    # the image can be imported with an existing alias
+    lxc image import testimage.file --alias newimage
+    lxc image delete newimage image2
+}

From cd6ac7da2cf9db4b94dad0a7ff757c2c1d588cf7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 21 Jul 2017 14:40:45 +0200
Subject: [PATCH 1059/1193] lxc: Cross-platform HOME handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3573

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/lxc/main.go b/lxc/main.go
index b22c9feb9..5a798553a 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"os/user"
 	"path"
 	"path/filepath"
 	"strings"
@@ -48,9 +49,16 @@ func run() error {
 	forceLocal := gnuflag.Bool("force-local", false, i18n.G("Force using the local unix socket"))
 	noAlias := gnuflag.Bool("no-alias", false, i18n.G("Ignore aliases when determining what command to run"))
 
-	configDir := "$HOME/.config/lxc"
+	var configDir string
 	if os.Getenv("LXD_CONF") != "" {
 		configDir = os.Getenv("LXD_CONF")
+	} else {
+		user, err := user.Current()
+		if err != nil {
+			return err
+		}
+
+		configDir = path.Join(user.HomeDir, ".config", "lxc")
 	}
 	configPath = os.ExpandEnv(path.Join(configDir, "config.yml"))
 

From de2475793cd87dc647f5d7a6a6907a2c343a1fe7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 22 Jul 2017 09:55:49 +0200
Subject: [PATCH 1060/1193] lxc: Respect HOME if set
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/main.go | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lxc/main.go b/lxc/main.go
index 5a798553a..59b3a3803 100644
--- a/lxc/main.go
+++ b/lxc/main.go
@@ -52,6 +52,8 @@ func run() error {
 	var configDir string
 	if os.Getenv("LXD_CONF") != "" {
 		configDir = os.Getenv("LXD_CONF")
+	} else if os.Getenv("HOME") != "" {
+		configDir = path.Join(os.Getenv("HOME"), ".config", "lxc")
 	} else {
 		user, err := user.Current()
 		if err != nil {

From 164f74fdf336c4d38a4c2350efdeab9188fd69df Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 21 Jul 2017 15:58:29 +0200
Subject: [PATCH 1061/1193] api: migration: state{ful,less} snapshot migration

Closes #3570.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 client/interfaces.go             |  4 ++++
 client/lxd_containers.go         | 13 ++++++++++++-
 shared/api/container_snapshot.go |  3 +++
 3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index d146c1ddf..7e0273010 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -252,6 +252,10 @@ type ContainerSnapshotCopyArgs struct {
 
 	// The transfer mode, can be "pull" (default), "push" or "relay"
 	Mode string
+
+	// API extension: container_snapshot_stateful_migration
+	// If set, the container running state will be transferred (live migration)
+	Live bool
 }
 
 // The ContainerExecArgs struct is used to pass additional options during container exec
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 8a6a373de..1d916e836 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -919,9 +919,16 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 			Devices:      snapshot.Devices,
 			Ephemeral:    snapshot.Ephemeral,
 			Profiles:     snapshot.Profiles,
-			Stateful:     snapshot.Stateful,
 		},
 	}
+
+	if snapshot.Stateful && args.Live {
+		if !r.HasExtension("container_snapshot_stateful_migration") {
+			return nil, fmt.Errorf("The server is missing the required \"container_snapshot_stateful_migration\" API extension")
+		}
+		req.ContainerPut.Stateful = snapshot.Stateful
+		req.Source.Live = args.Live
+	}
 	req.Source.BaseImage = snapshot.Config["volatile.base_image"]
 
 	// Process the copy arguments
@@ -976,6 +983,10 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 	// Source request
 	sourceReq := api.ContainerSnapshotPost{
 		Migration: true,
+		Name:      args.Name,
+	}
+	if snapshot.Stateful && args.Live {
+		sourceReq.Live = args.Live
 	}
 
 	// Push mode migration
diff --git a/shared/api/container_snapshot.go b/shared/api/container_snapshot.go
index 3f9b0fd6c..4ff42c9a1 100644
--- a/shared/api/container_snapshot.go
+++ b/shared/api/container_snapshot.go
@@ -15,6 +15,9 @@ type ContainerSnapshotPost struct {
 	Name      string               `json:"name" yaml:"name"`
 	Migration bool                 `json:"migration" yaml:"migration"`
 	Target    *ContainerPostTarget `json:"target" yaml:"target"`
+
+	// API extension: container_snapshot_stateful_migration
+	Live bool `json:"live,omitempty" yaml:"live,omitempty"`
 }
 
 // ContainerSnapshot represents a LXD conainer snapshot

From 34b3c2bca0d30512698a07164a8cabc8eb918a7e Mon Sep 17 00:00:00 2001
From: anatoly techtonik <techtonik at gmail.com>
Date: Wed, 12 Jul 2017 14:06:34 +0300
Subject: [PATCH 1062/1193] doc: Update containers.md

Link to full descriptions and fill missing USB and GPU.

Signed-off-by: anatoly techtonik <techtonik at gmail.com>
---
 doc/containers.md | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/doc/containers.md b/doc/containers.md
index a9f3a00ec..01ac2722c 100644
--- a/doc/containers.md
+++ b/doc/containers.md
@@ -124,13 +124,13 @@ Device entries are added through:
 ### Device types
 LXD supports the following device types:
 
-ID (database)   | Name          | Description
-:--             | :--           | :--
-0               | none          | Inheritance blocker
-1               | nic           | Network interface
-2               | disk          | Mountpoint inside the container
-3               | unix-char     | Unix character device
-4               | unix-block    | Unix block device
+ID (database)   | Name                              | Description
+:--             | :--                               | :--
+0               | [none](#type-none)                | Inheritance blocker
+1               | [nic](#type-nic)                  | Network interface
+2               | [disk](#type-disk)                | Mountpoint inside the container
+3               | [unix-char](#type-unix-char)      | Unix character device
+4               | [unix-block](#type-unix-block)    | Unix block device
 
 ### Type: none
 A none type device doesn't have any property and doesn't create anything inside the container.

From 6f20cfd6b1bce563fd0b1a9e0e83ec125ad55776 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Mon, 24 Jul 2017 15:13:51 +0200
Subject: [PATCH 1063/1193] util linux: guess size when sysconf() returns -1

Closes #3581.

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 shared/util_linux.go | 42 ++++++++++++++++++++++++++++++++++++------
 1 file changed, 36 insertions(+), 6 deletions(-)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index b8aefe63c..3c971f26b 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -291,6 +291,10 @@ func UserId(name string) (int, error) {
 	var result *C.struct_passwd
 
 	bufSize := C.size_t(C.sysconf(C._SC_GETPW_R_SIZE_MAX))
+	if bufSize < 0 {
+		bufSize = 4096
+	}
+
 	buf := C.malloc(bufSize)
 	if buf == nil {
 		return -1, fmt.Errorf("allocation failed")
@@ -300,13 +304,24 @@ func UserId(name string) (int, error) {
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
-	rv := C.getpwnam_r(cname,
+again:
+	rv, errno := C.getpwnam_r(cname,
 		&pw,
 		(*C.char)(buf),
 		bufSize,
 		&result)
-
-	if rv != 0 {
+	if rv < 0 {
+		// OOM killer will take care of us if we end up doing this too
+		// often.
+		if errno == syscall.ERANGE {
+			bufSize *= 2
+			tmp := C.realloc(buf, bufSize)
+			if tmp == nil {
+				return -1, fmt.Errorf("allocation failed")
+			}
+			buf = tmp
+			goto again
+		}
 		return -1, fmt.Errorf("failed user lookup: %s", syscall.Errno(rv))
 	}
 
@@ -323,6 +338,10 @@ func GroupId(name string) (int, error) {
 	var result *C.struct_group
 
 	bufSize := C.size_t(C.sysconf(C._SC_GETGR_R_SIZE_MAX))
+	if bufSize < 0 {
+		bufSize = 4096
+	}
+
 	buf := C.malloc(bufSize)
 	if buf == nil {
 		return -1, fmt.Errorf("allocation failed")
@@ -332,13 +351,24 @@ func GroupId(name string) (int, error) {
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
 
-	rv := C.getgrnam_r(cname,
+again:
+	rv, errno := C.getgrnam_r(cname,
 		&grp,
 		(*C.char)(buf),
 		bufSize,
 		&result)
-
-	if rv != 0 {
+	if rv < 0 {
+		// OOM killer will take care of us if we end up doing this too
+		// often.
+		if errno == syscall.ERANGE {
+			bufSize *= 2
+			tmp := C.realloc(buf, bufSize)
+			if tmp == nil {
+				return -1, fmt.Errorf("allocation failed")
+			}
+			buf = tmp
+			goto again
+		}
 		return -1, fmt.Errorf("failed group lookup: %s", syscall.Errno(rv))
 	}
 

From 218e5a2d8802a34e1ad2e0b80cad4305a4487c1d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 24 Jul 2017 17:44:29 -0400
Subject: [PATCH 1064/1193] lxc: Properly record alias source on copy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3586

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/lxc/image.go b/lxc/image.go
index 0fd84fd72..64d18cb96 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -294,6 +294,11 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			imgInfo.Public = true
 		}
 
+		if imgInfo.Public && imgInfo.Fingerprint != inName && !strings.HasPrefix(imgInfo.Fingerprint, inName) {
+			// If dealing with an alias, set the imgInfo fingerprint to match
+			imgInfo.Fingerprint = inName
+		}
+
 		args := lxd.ImageCopyArgs{
 			AutoUpdate: c.autoUpdate,
 			Public:     c.publicImage,

From 3887e00d9498f152cacbceb3972dfd00befd27b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 24 Jul 2017 17:49:57 -0400
Subject: [PATCH 1065/1193] lxc/utils: Println doesn't do format strings
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/utils.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/utils.go b/lxc/utils.go
index c4181d87e..1a29baa54 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -319,7 +319,7 @@ func ensureImageAliases(client lxd.ContainerServer, aliases []api.ImageAlias, fi
 	for _, alias := range GetExistingAliases(names, resp) {
 		err := client.DeleteImageAlias(alias.Name)
 		if err != nil {
-			fmt.Println(i18n.G("Failed to remove alias %s"), alias.Name)
+			fmt.Println(fmt.Sprintf(i18n.G("Failed to remove alias %s"), alias.Name))
 		}
 	}
 	// Create new aliases
@@ -329,7 +329,7 @@ func ensureImageAliases(client lxd.ContainerServer, aliases []api.ImageAlias, fi
 		aliasPost.Target = fingerprint
 		err := client.CreateImageAlias(aliasPost)
 		if err != nil {
-			fmt.Println(i18n.G("Failed to create alias %s"), alias.Name)
+			fmt.Println(fmt.Sprintf(i18n.G("Failed to create alias %s"), alias.Name))
 		}
 	}
 	return nil

From c4193fa8a526eabb7e7780bfa7854cd263e43f39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 24 Jul 2017 23:41:52 -0400
Subject: [PATCH 1066/1193] client: Allow specifying base http client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3580

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go | 11 +++++++----
 client/util.go       | 18 ++++++++++--------
 2 files changed, 17 insertions(+), 12 deletions(-)

diff --git a/client/connection.go b/client/connection.go
index 02e0e6aa6..ae13332d3 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -29,6 +29,9 @@ type ConnectionArgs struct {
 
 	// Custom proxy
 	Proxy func(*http.Request) (*url.URL, error)
+
+	// Custom HTTP Client (used as base for the connection)
+	HTTPClient *http.Client
 }
 
 // ConnectLXD lets you connect to a remote LXD daemon over HTTPs.
@@ -55,7 +58,7 @@ func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 	}
 
 	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
+	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
 	if err != nil {
 		return nil, err
 	}
@@ -100,7 +103,7 @@ func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error)
 	}
 
 	// Setup the HTTP client
-	httpClient, err := unixHTTPClient(path)
+	httpClient, err := unixHTTPClient(args.HTTPClient, path)
 	if err != nil {
 		return nil, err
 	}
@@ -138,7 +141,7 @@ func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 	}
 
 	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
+	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
 	if err != nil {
 		return nil, err
 	}
@@ -172,7 +175,7 @@ func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error)
 	}
 
 	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
+	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
 	if err != nil {
 		return nil, err
 	}
diff --git a/client/util.go b/client/util.go
index de5073628..3649a0a83 100644
--- a/client/util.go
+++ b/client/util.go
@@ -13,7 +13,7 @@ import (
 	"github.com/lxc/lxd/shared/ioprogress"
 )
 
-func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsCA string, tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
+func tlsHTTPClient(client *http.Client, tlsClientCert string, tlsClientKey string, tlsCA string, tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
 	// Get the TLS configuration
 	tlsConfig, err := shared.GetTLSConfigMem(tlsClientCert, tlsClientKey, tlsCA, tlsServerCert)
 	if err != nil {
@@ -34,9 +34,10 @@ func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsCA string, tlsS
 	}
 
 	// Define the http client
-	client := http.Client{
-		Transport: transport,
+	if client == nil {
+		client = &http.Client{}
 	}
+	client.Transport = transport
 
 	// Setup redirect policy
 	client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
@@ -46,10 +47,10 @@ func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsCA string, tlsS
 		return nil
 	}
 
-	return &client, nil
+	return client, nil
 }
 
-func unixHTTPClient(path string) (*http.Client, error) {
+func unixHTTPClient(client *http.Client, path string) (*http.Client, error) {
 	// Setup a Unix socket dialer
 	unixDial := func(network, addr string) (net.Conn, error) {
 		raddr, err := net.ResolveUnixAddr("unix", path)
@@ -67,9 +68,10 @@ func unixHTTPClient(path string) (*http.Client, error) {
 	}
 
 	// Define the http client
-	client := http.Client{
-		Transport: transport,
+	if client == nil {
+		client = &http.Client{}
 	}
+	client.Transport = transport
 
 	// Setup redirect policy
 	client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
@@ -79,7 +81,7 @@ func unixHTTPClient(path string) (*http.Client, error) {
 		return nil
 	}
 
-	return &client, nil
+	return client, nil
 }
 
 func downloadFileSha256(httpClient *http.Client, useragent string, progress func(progress ProgressData), canceler *cancel.Canceler, filename string, url string, hash string, target io.WriteSeeker) (int64, error) {

From efe43ffbc2d0bf1ae0d976aebd2b2a637e026063 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 24 Jul 2017 23:52:46 -0400
Subject: [PATCH 1067/1193] client: Make it possible to retrieve HTTP client
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3580

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go    |  2 ++
 client/lxd.go           |  9 +++++++++
 client/simplestreams.go | 10 ++++++++++
 3 files changed, 21 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index 7e0273010..de3a4e074 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -2,6 +2,7 @@ package lxd
 
 import (
 	"io"
+	"net/http"
 
 	"github.com/gorilla/websocket"
 
@@ -12,6 +13,7 @@ import (
 // The Server type represents a generic read-only server.
 type Server interface {
 	GetConnectionInfo() (info *ConnectionInfo, err error)
+	GetHTTPClient() (client *http.Client, err error)
 }
 
 // The ImageServer type represents a read-only image server.
diff --git a/client/lxd.go b/client/lxd.go
index a6e071308..3473e317c 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -53,6 +53,15 @@ func (r *ProtocolLXD) GetConnectionInfo() (*ConnectionInfo, error) {
 	return &info, nil
 }
 
+// GetHTTPClient returns the http client used for the connection. This can be used to set custom http options.
+func (r *ProtocolLXD) GetHTTPClient() (*http.Client, error) {
+	if r.http == nil {
+		return nil, fmt.Errorf("HTTP client isn't set, bad connection")
+	}
+
+	return r.http, nil
+}
+
 // RawQuery allows directly querying the LXD API
 //
 // This should only be used by internal LXD tools.
diff --git a/client/simplestreams.go b/client/simplestreams.go
index 79fef42ae..68fa55b8b 100644
--- a/client/simplestreams.go
+++ b/client/simplestreams.go
@@ -1,6 +1,7 @@
 package lxd
 
 import (
+	"fmt"
 	"net/http"
 
 	"github.com/lxc/lxd/shared/simplestreams"
@@ -25,3 +26,12 @@ func (r *ProtocolSimpleStreams) GetConnectionInfo() (*ConnectionInfo, error) {
 
 	return &info, nil
 }
+
+// GetHTTPClient returns the http client used for the connection. This can be used to set custom http options.
+func (r *ProtocolSimpleStreams) GetHTTPClient() (*http.Client, error) {
+	if r.http == nil {
+		return nil, fmt.Errorf("HTTP client isn't set, bad connection")
+	}
+
+	return r.http, nil
+}

From ea5148f4dd3a5cdb708ed4f686a183c8e1ca1a4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 25 Jul 2017 00:23:12 -0400
Subject: [PATCH 1068/1193] doc: Document the exec control API
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3574

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/rest-api.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index c95f5a231..8fd46bae6 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -674,6 +674,20 @@ Depending on the state of the interactive flag, one or three different
 websocket/secret pairs will be returned, which are valid for connecting to this
 operations /websocket endpoint.
 
+
+The control websocket can be used to send out-of-band messages during an exec session.
+This is currently used for window size changes and for forwarding of signals.
+
+Control (window size change):
+
+    {
+        "command": "window-resize",
+        "args": {
+            "width": "80",
+            "height": "50"
+        }
+    }
+
 Return (with wait-for-websocket=true and interactive=false):
 
     {

From bdd08a6d7c7ec6f0b55563589c0016809a191961 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 13 Jul 2017 10:41:36 +0200
Subject: [PATCH 1069/1193] api: Add API for editing containers metadata.yaml
 and template files

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 client/interfaces.go     |  3 +++
 client/lxd_containers.go | 32 ++++++++++++++++++++++++++++++++
 shared/api/image.go      | 17 +++++++++++++++++
 3 files changed, 52 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index de3a4e074..1260c3e9a 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -88,6 +88,9 @@ type ContainerServer interface {
 	GetContainerLogfile(name string, filename string) (content io.ReadCloser, err error)
 	DeleteContainerLogfile(name string, filename string) (err error)
 
+	GetContainerMetadata(name string) (*api.ImageMetadata, string, error)
+	SetContainerMetadata(name string, metadata api.ImageMetadata, ETag string) error
+
 	// Event handling functions
 	GetEvents() (listener *EventListener, err error)
 
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 1d916e836..d8b2510a0 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -1266,3 +1266,35 @@ func (r *ProtocolLXD) DeleteContainerLogfile(name string, filename string) error
 
 	return nil
 }
+
+// GetContainerMetadata returns container metadata.
+func (r *ProtocolLXD) GetContainerMetadata(name string) (*api.ImageMetadata, string, error) {
+	if !r.HasExtension("container_edit_metadata") {
+		return nil, "", fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	metadata := api.ImageMetadata{}
+
+	url := fmt.Sprintf("/containers/%s/metadata", name)
+	etag, err := r.queryStruct("GET", url, nil, "", &metadata)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &metadata, etag, err
+}
+
+// SetContainerMetadata sets the content of the container metadata file.
+func (r *ProtocolLXD) SetContainerMetadata(name string, metadata api.ImageMetadata, ETag string) error {
+	if !r.HasExtension("container_edit_metadata") {
+		return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	url := fmt.Sprintf("/containers/%s/metadata", name)
+	_, _, err := r.query("PUT", url, metadata, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/shared/api/image.go b/shared/api/image.go
index 814c0981d..3f1834d10 100644
--- a/shared/api/image.go
+++ b/shared/api/image.go
@@ -102,3 +102,20 @@ type ImageAliasesEntry struct {
 
 	Name string `json:"name" yaml:"name"`
 }
+
+// ImageMetadata represents LXD image metadata
+type ImageMetadata struct {
+	Architecture string                            `json:"architecture" yaml:"architecture"`
+	CreationDate int64                             `json:"creation_date" yaml:"creation_date"`
+	ExpiryDate   int64                             `json:"expiry_date" yaml:"expiry_date"`
+	Properties   map[string]string                 `json:"properties" yaml:"properties"`
+	Templates    map[string]*ImageMetadataTemplate `json:"templates" yaml:"templates"`
+}
+
+// ImageMetadataTemplate represents a template entry in image metadata
+type ImageMetadataTemplate struct {
+	When       []string          `json:"when" yaml:"when"`
+	CreateOnly bool              `json:"create_only" yaml:"create_only"`
+	Template   string            `json:"template" yaml:"template"`
+	Properties map[string]string `json:"properties" yaml:"properties"`
+}

From a266e16f1da645f4fb892918d35758232dc4acf5 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Mon, 24 Jul 2017 11:00:08 +0200
Subject: [PATCH 1070/1193] api: Add container template files operations.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 client/interfaces.go     |  6 +++
 client/lxd_containers.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 105 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index 1260c3e9a..ed9b001a3 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -91,6 +91,12 @@ type ContainerServer interface {
 	GetContainerMetadata(name string) (*api.ImageMetadata, string, error)
 	SetContainerMetadata(name string, metadata api.ImageMetadata, ETag string) error
 
+	GetContainerTemplateFiles(containerName string) (templates []string, err error)
+	GetContainerTemplateFile(containerName string, templateName string) (content io.ReadCloser, err error)
+	CreateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
+	UpdateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
+	DeleteContainerTemplateFile(name string, templateName string) (err error)
+
 	// Event handling functions
 	GetEvents() (listener *EventListener, err error)
 
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index d8b2510a0..5a501d1e8 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -1298,3 +1298,102 @@ func (r *ProtocolLXD) SetContainerMetadata(name string, metadata api.ImageMetada
 
 	return nil
 }
+
+// GetContainerTemplateFiles returns the list of names of template files for a container.
+func (r *ProtocolLXD) GetContainerTemplateFiles(containerName string) ([]string, error) {
+	if !r.HasExtension("container_edit_metadata") {
+		return nil, fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	templates := []string{}
+
+	url := fmt.Sprintf("/containers/%s/metadata/templates", containerName)
+	_, err := r.queryStruct("GET", url, nil, "", &templates)
+	if err != nil {
+		return nil, err
+	}
+
+	return templates, nil
+}
+
+// GetContainerTemplateFile returns the content of a template file for a container.
+func (r *ProtocolLXD) GetContainerTemplateFile(containerName string, templateName string) (io.ReadCloser, error) {
+	if !r.HasExtension("container_edit_metadata") {
+		return nil, fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	url := fmt.Sprintf("%s/1.0/containers/%s/metadata/templates?path=%s", r.httpHost, containerName, templateName)
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.http.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := r.parseResponse(resp)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return resp.Body, err
+}
+
+// CreateContainerTemplateFile creates an a template for a container.
+func (r *ProtocolLXD) CreateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) error {
+	return r.setContainerTemplateFile(containerName, templateName, content, "POST")
+}
+
+// UpdateContainerTemplateFile updates the content for a container template file.
+func (r *ProtocolLXD) UpdateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) error {
+	return r.setContainerTemplateFile(containerName, templateName, content, "PUT")
+}
+
+func (r *ProtocolLXD) setContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker, httpMethod string) error {
+	if !r.HasExtension("container_edit_metadata") {
+		return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	url := fmt.Sprintf("%s/1.0/containers/%s/metadata/templates?path=%s", r.httpHost, containerName, templateName)
+	req, err := http.NewRequest(httpMethod, url, content)
+	if err != nil {
+		return err
+	}
+	req.Header.Set("Content-Type", "application/octet-stream")
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.http.Do(req)
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := r.parseResponse(resp)
+		if err != nil {
+			return err
+		}
+	}
+	return err
+}
+
+// DeleteContainerTemplateFile deletes a template file for a container.
+func (r *ProtocolLXD) DeleteContainerTemplateFile(name string, templateName string) error {
+	if !r.HasExtension("container_edit_metadata") {
+		return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+	_, _, err := r.query("DELETE", fmt.Sprintf("/containers/%s/metadata/templates?path=%s", name, templateName), nil, "")
+	return err
+}

From 50c04c7d1314beb9ef46ae9ddcdec01091a74438 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 25 Jul 2017 00:56:08 -0400
Subject: [PATCH 1071/1193] doc: Initial documentation of container env
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #477

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/container-environment.md | 84 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 84 insertions(+)
 create mode 100644 doc/container-environment.md

diff --git a/doc/container-environment.md b/doc/container-environment.md
new file mode 100644
index 000000000..704d2d0ee
--- /dev/null
+++ b/doc/container-environment.md
@@ -0,0 +1,84 @@
+# Container runtime environment
+LXD attempts to present a consistent environment to the container it runs.
+
+The exact environment will differ slightly based on kernel features and  
+user configuration but will otherwise be identical for all containers.
+
+## PID1
+LXD spawns whatever is located at /sbin/init as the initial process of the container (PID 1).
+This binary should act as a proper init system, including handling re-parented processes.
+
+LXD's communication with PID1 in the container is limited to two signals:
+ - SIGINT to trigger a reboot of the container
+ - SIGPWR (or alternatively SIGRTMIN+3) to trigger a clean shutdown of the container
+
+The initial environment of PID1 is blank except for "container=lxc" which can be used  
+by the init system to detect the runtime.
+
+All file descriptors above the default 3 are closed prior to PID1 being spawned.
+
+## Filesystem
+LXD assumes that any image it uses to create a new container from will come with at least:
+ - /dev (empty)
+ - /proc (empty)
+ - /sbin/init (executable)
+ - /sys (empty)
+
+## Devices
+LXD containers have a minimal and ephemeral /dev based on a tmpfs filesystem.
+Since this is a tmpfs and not a devtmpfs, device nodes will only appear if manually created.
+
+The standard set of device nodes will be setup:
+ - /dev/console
+ - /dev/fd
+ - /dev/full
+ - /dev/log
+ - /dev/null
+ - /dev/ptmx
+ - /dev/random
+ - /dev/stdin
+ - /dev/stderr
+ - /dev/stdout
+ - /dev/tty
+ - /dev/urandom
+ - /dev/zero
+
+On top of the standard set of devices, the following are also setup for convenience:
+ - /dev/fuse
+ - /dev/net/tun
+ - /dev/mqueue
+
+## Mounts
+The following mounts are setup by default under LXD:
+ - /proc (proc)
+ - /sys (sysfs)
+ - /sys/fs/cgroup/\* (cgroupfs) (only on kernels lacking cgroup namespace support)
+
+The following paths will also be automatically mounted if present on the host:
+ - /proc/sys/fs/binfmt\_misc
+ - /sys/firmware/efi/efivars
+ - /sys/fs/fuse/connections
+ - /sys/fs/pstore
+ - /sys/kernel/debug
+ - /sys/kernel/security
+
+The reason for passing all of those is legacy init systems which require those  
+to be mounted or be mountabled inside the container.
+
+The majority of those will not be writable (or even readable) from inside an  
+unprivileged container and will be blocked by our AppArmor policy inside privileged containers.
+
+## Network
+LXD containers may have any number of network devices attached to them.
+The naming for those unless overriden by the user is ethX where X is an incrementing number.
+
+## Container to host communication
+LXD sets up a socket at /dev/lxd/sock which root in the container can use to communicate with LXD on the host.
+
+The API is [documented here](dev-lxd.md).
+
+## LXCFS
+If LXCFS is present on the host, it will automatically be setup for the container.
+
+This normally results in a number of /proc files being overriden through bind-mounts.
+On older kernels a virtual version of /sys/fs/cgroup may also be setup by LXCFS.

From fc9e075ba9e34c3114a984ac769e3ccfbd1d59df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 3 Oct 2017 00:05:39 -0400
Subject: [PATCH 1072/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 163 ++++++++++++++++++++++++++------------------
 po/fr.po   | 163 ++++++++++++++++++++++++++------------------
 po/ja.po   | 225 ++++++++++++++++++++++++++++++++++++++-----------------------
 po/lxd.pot | 160 +++++++++++++++++++++++++------------------
 4 files changed, 427 insertions(+), 284 deletions(-)

diff --git a/po/de.po b/po/de.po
index 03cadd140..5d1f6ec95 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-25 14:33-0400\n"
+"POT-Creation-Date: 2017-10-03 00:05-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -111,11 +111,16 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:808
+#: lxc/image.go:832
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
+#: lxc/utils.go:294
+#, c-format
+msgid "%v (interrupt two more times to force)"
+msgstr ""
+
 #: lxc/snapshot.go:53
 #, fuzzy
 msgid "'/' not allowed in snapshot name"
@@ -125,11 +130,11 @@ msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:829 lxc/image.go:858
+#: lxc/image.go:853 lxc/image.go:882
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:833
+#: lxc/image.go:857
 msgid "ARCH"
 msgstr ""
 
@@ -146,22 +151,22 @@ msgstr "Akzeptiere Zertifikat"
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
 
-#: lxc/image.go:411
+#: lxc/image.go:431
 #, fuzzy
 msgid "Aliases:"
 msgstr "Aliasse:\n"
 
-#: lxc/image.go:389 lxc/info.go:104
+#: lxc/image.go:409 lxc/info.go:104
 #, fuzzy, c-format
 msgid "Architecture: %s"
 msgstr "Architektur: %s\n"
 
-#: lxc/image.go:419
+#: lxc/image.go:439
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/image.go:493
+#: lxc/image.go:513
 #, fuzzy, c-format
 msgid "Bad property: %s"
 msgstr "Ungültige Abbild Eigenschaft: %s\n"
@@ -223,12 +228,12 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:913 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:937 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
 
-#: lxc/main.go:35
+#: lxc/main.go:36
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
@@ -236,12 +241,12 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/copy.go:176 lxc/init.go:279
+#: lxc/copy.go:186 lxc/init.go:279
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:245
+#: lxc/publish.go:247
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
@@ -250,7 +255,7 @@ msgstr "Abbild mit Fingerabdruck %s importiert\n"
 msgid "Copy aliases from source"
 msgstr "Kopiere Aliasse von der Quelle"
 
-#: lxc/image.go:307
+#: lxc/image.go:314
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
@@ -259,7 +264,7 @@ msgstr ""
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
-#: lxc/image.go:394 lxc/info.go:106
+#: lxc/image.go:414 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
@@ -274,7 +279,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:832 lxc/image.go:860
+#: lxc/image.go:856 lxc/image.go:884
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -292,7 +297,7 @@ msgstr "Gerät %s wurde von %s entfernt\n"
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:500
+#: lxc/list.go:505
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -300,12 +305,12 @@ msgstr ""
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:47
+#: lxc/main.go:48
 #, fuzzy
 msgid "Enable debug mode"
 msgstr "Aktiviert Debug Modus"
 
-#: lxc/main.go:46
+#: lxc/main.go:47
 #, fuzzy
 msgid "Enable verbose mode"
 msgstr "Aktiviert ausführliche Ausgabe"
@@ -326,24 +331,29 @@ msgstr "Flüchtiger Container"
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:398
+#: lxc/image.go:418
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:400
+#: lxc/image.go:420
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/image.go:677
+#: lxc/image.go:701
 #, c-format
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
+#: lxc/config.go:348 lxc/image.go:854 lxc/image.go:883
 msgid "FINGERPRINT"
 msgstr ""
 
+#: lxc/utils.go:332
+#, c-format
+msgid "Failed to create alias %s"
+msgstr ""
+
 #: lxc/manpage.go:62
 #, c-format
 msgid "Failed to generate 'lxc.%s.1': %v"
@@ -354,16 +364,21 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/copy.go:171
+#: lxc/copy.go:181
 #, fuzzy
 msgid "Failed to get the new container name"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
+#: lxc/utils.go:322
+#, c-format
+msgid "Failed to remove alias %s"
+msgstr ""
+
 #: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:387
+#: lxc/image.go:407
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Fingerabdruck: %s\n"
@@ -377,7 +392,7 @@ msgstr "Herunterfahren des Containers erzwingen."
 msgid "Force the removal of stopped containers"
 msgstr ""
 
-#: lxc/main.go:48
+#: lxc/main.go:49
 msgid "Force using the local unix socket"
 msgstr ""
 
@@ -385,7 +400,7 @@ msgstr ""
 msgid "Format (table|json)"
 msgstr ""
 
-#: lxc/main.go:139
+#: lxc/main.go:149
 #, fuzzy
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Generiere Nutzerzertifikat. Dies kann wenige Minuten dauern...\n"
@@ -402,12 +417,12 @@ msgstr ""
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:147
+#: lxc/main.go:157
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:49
+#: lxc/main.go:50
 msgid "Ignore aliases when determining what command to run"
 msgstr ""
 
@@ -416,15 +431,15 @@ msgstr ""
 msgid "Ignore the container state (only for start)"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/image.go:321
+#: lxc/image.go:328
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:727
+#: lxc/image.go:751
 msgid "Image exported successfully!"
 msgstr ""
 
-#: lxc/image.go:551
+#: lxc/image.go:571
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
@@ -461,16 +476,16 @@ msgstr ""
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:33
+#: lxc/main.go:34
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:403
+#: lxc/image.go:423
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:405
+#: lxc/image.go:425
 msgid "Last used: never"
 msgstr ""
 
@@ -499,7 +514,7 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/utils.go:197
+#: lxc/utils.go:226
 msgid "Missing summary."
 msgstr "Fehlende Zusammenfassung."
 
@@ -547,11 +562,11 @@ msgstr "Kein Fingerabdruck angegeben."
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:474
+#: lxc/image.go:494
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
-#: lxc/help.go:71 lxc/main.go:120 lxc/main.go:172
+#: lxc/help.go:71 lxc/main.go:130 lxc/main.go:182
 msgid "Options:"
 msgstr ""
 
@@ -559,7 +574,7 @@ msgstr ""
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:502
+#: lxc/list.go:507
 msgid "PERSISTENT"
 msgstr ""
 
@@ -575,7 +590,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:831 lxc/remote.go:380
+#: lxc/image.go:855 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
@@ -597,12 +612,12 @@ msgstr "Alternatives config Verzeichnis."
 msgid "Path to an alternate server directory"
 msgstr "Alternatives config Verzeichnis."
 
-#: lxc/main.go:209
+#: lxc/main.go:219
 #, fuzzy
 msgid "Pause containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/main.go:37
+#: lxc/main.go:38
 msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
@@ -615,7 +630,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:914
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:938
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -661,7 +676,7 @@ msgstr "Profil %s wurde auf %s angewandt\n"
 msgid "Profiles: %s"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/image.go:407
+#: lxc/image.go:427
 #, fuzzy
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
@@ -670,7 +685,7 @@ msgstr "Eigenschaften:\n"
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:390
+#: lxc/image.go:410
 #, fuzzy, c-format
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
@@ -679,6 +694,11 @@ msgstr "Öffentlich: %s\n"
 msgid "Remote admin password"
 msgstr "Entferntes Administrator Passwort"
 
+#: lxc/utils.go:285
+#, fuzzy
+msgid "Remote operation canceled by user"
+msgstr "Server Zertifikat vom Benutzer nicht akzeptiert"
+
 #: lxc/info.go:101
 #, c-format
 msgid "Remote: %s"
@@ -697,7 +717,7 @@ msgstr ""
 msgid "Resources:"
 msgstr ""
 
-#: lxc/main.go:217
+#: lxc/main.go:227
 #, fuzzy
 msgid "Restart containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
@@ -707,7 +727,7 @@ msgstr "kann nicht zum selben Container Namen kopieren"
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:834
+#: lxc/image.go:858
 msgid "SIZE"
 msgstr ""
 
@@ -764,7 +784,7 @@ msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:388
+#: lxc/image.go:408
 #, fuzzy, c-format
 msgid "Size: %.2fMB"
 msgstr "Größe: %.2vMB\n"
@@ -778,11 +798,11 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/image.go:421
+#: lxc/image.go:441
 msgid "Source:"
 msgstr ""
 
-#: lxc/main.go:227
+#: lxc/main.go:237
 #, fuzzy
 msgid "Start containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
@@ -797,7 +817,7 @@ msgstr ""
 msgid "Status: %s"
 msgstr ""
 
-#: lxc/main.go:233
+#: lxc/main.go:243
 #, fuzzy
 msgid "Stop containers."
 msgstr "Anhalten des Containers fehlgeschlagen!"
@@ -879,16 +899,21 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Wartezeit bevor der Container gestoppt wird."
 
-#: lxc/image.go:391
+#: lxc/image.go:411
 #, fuzzy
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
 
-#: lxc/main.go:148
+#: lxc/main.go:158
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:499
+#: lxc/copy.go:155
+#, fuzzy, c-format
+msgid "Transferring container: %s"
+msgstr "kann nicht zum selben Container Namen kopieren"
+
+#: lxc/image.go:519
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
@@ -906,7 +931,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:835
+#: lxc/image.go:859
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -922,7 +947,7 @@ msgstr ""
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:396
+#: lxc/image.go:416
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1257,9 +1282,9 @@ msgid ""
 "\n"
 "    - \"u.blah=abc\" will do the same\n"
 "\n"
-"    - \"security.privileged=1\" will list all privileged containers\n"
+"    - \"security.privileged=true\" will list all privileged containers\n"
 "\n"
-"    - \"s.privileged=1\" will do the same\n"
+"    - \"s.privileged=true\" will do the same\n"
 "\n"
 "A regular expression matching a configuration item or its value. (e.g. "
 "volatile.eth0.hwaddr=00:16:3e:.*).\n"
@@ -1570,6 +1595,12 @@ msgstr ""
 msgid "User aborted delete operation."
 msgstr ""
 
+#: lxc/utils.go:290
+msgid ""
+"User signaled us three times, exiting. The remote operation will keep "
+"running."
+msgstr ""
+
 #: lxc/restore.go:38
 msgid ""
 "Whether or not to restore the container's running state from snapshot (if "
@@ -1591,7 +1622,7 @@ msgstr ""
 msgid "You must specify a source container name"
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
-#: lxc/main.go:58
+#: lxc/main.go:68
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
@@ -1607,25 +1638,25 @@ msgstr ""
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
-#: lxc/image.go:382
+#: lxc/image.go:402
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:384
+#: lxc/image.go:404
 msgid "enabled"
 msgstr ""
 
-#: lxc/action.go:134 lxc/main.go:28 lxc/main.go:168
+#: lxc/action.go:134 lxc/main.go:29 lxc/main.go:178
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "Fehler: %v\n"
 
-#: lxc/help.go:37 lxc/main.go:114
+#: lxc/help.go:37 lxc/main.go:124
 #, fuzzy, c-format
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/image.go:377 lxc/image.go:811
+#: lxc/image.go:397 lxc/image.go:835
 msgid "no"
 msgstr ""
 
@@ -1634,7 +1665,7 @@ msgstr ""
 msgid "ok (y/n)?"
 msgstr "OK (y/n)? "
 
-#: lxc/main.go:303 lxc/main.go:307
+#: lxc/main.go:313 lxc/main.go:317
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
@@ -1672,11 +1703,11 @@ msgstr ""
 msgid "taken at %s"
 msgstr ""
 
-#: lxc/main.go:242
+#: lxc/main.go:252
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:45 lxc/image.go:379 lxc/image.go:815
+#: lxc/delete.go:45 lxc/image.go:399 lxc/image.go:839
 msgid "yes"
 msgstr ""
 
diff --git a/po/fr.po b/po/fr.po
index 3e9ce7a77..4e0bad3b0 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-25 14:33-0400\n"
+"POT-Creation-Date: 2017-10-03 00:05-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -68,11 +68,16 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:808
+#: lxc/image.go:832
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
+#: lxc/utils.go:294
+#, c-format
+msgid "%v (interrupt two more times to force)"
+msgstr ""
+
 #: lxc/snapshot.go:53
 #, fuzzy
 msgid "'/' not allowed in snapshot name"
@@ -82,11 +87,11 @@ msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:829 lxc/image.go:858
+#: lxc/image.go:853 lxc/image.go:882
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:833
+#: lxc/image.go:857
 msgid "ARCH"
 msgstr ""
 
@@ -103,21 +108,21 @@ msgstr ""
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
 
-#: lxc/image.go:411
+#: lxc/image.go:431
 msgid "Aliases:"
 msgstr ""
 
-#: lxc/image.go:389 lxc/info.go:104
+#: lxc/image.go:409 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr ""
 
-#: lxc/image.go:419
+#: lxc/image.go:439
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/image.go:493
+#: lxc/image.go:513
 #, fuzzy, c-format
 msgid "Bad property: %s"
 msgstr "(Image invalide: %s\n"
@@ -178,12 +183,12 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:913 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:937 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
 
-#: lxc/main.go:35
+#: lxc/main.go:36
 msgid "Connection refused; is LXD running?"
 msgstr ""
 
@@ -191,12 +196,12 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/copy.go:176 lxc/init.go:279
+#: lxc/copy.go:186 lxc/init.go:279
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
 
-#: lxc/publish.go:245
+#: lxc/publish.go:247
 #, fuzzy, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -205,7 +210,7 @@ msgstr "Empreinte du certificat: % x\n"
 msgid "Copy aliases from source"
 msgstr ""
 
-#: lxc/image.go:307
+#: lxc/image.go:314
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
@@ -214,7 +219,7 @@ msgstr ""
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
-#: lxc/image.go:394 lxc/info.go:106
+#: lxc/image.go:414 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
@@ -228,7 +233,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:832 lxc/image.go:860
+#: lxc/image.go:856 lxc/image.go:884
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -246,7 +251,7 @@ msgstr ""
 msgid "Disk usage:"
 msgstr ""
 
-#: lxc/list.go:500
+#: lxc/list.go:505
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -254,12 +259,12 @@ msgstr ""
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:47
+#: lxc/main.go:48
 #, fuzzy
 msgid "Enable debug mode"
 msgstr "Active le mode de déboguage."
 
-#: lxc/main.go:46
+#: lxc/main.go:47
 #, fuzzy
 msgid "Enable verbose mode"
 msgstr "Active le mode verbeux."
@@ -280,24 +285,29 @@ msgstr ""
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:398
+#: lxc/image.go:418
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:400
+#: lxc/image.go:420
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/image.go:677
+#: lxc/image.go:701
 #, c-format
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
+#: lxc/config.go:348 lxc/image.go:854 lxc/image.go:883
 msgid "FINGERPRINT"
 msgstr ""
 
+#: lxc/utils.go:332
+#, c-format
+msgid "Failed to create alias %s"
+msgstr ""
+
 #: lxc/manpage.go:62
 #, c-format
 msgid "Failed to generate 'lxc.%s.1': %v"
@@ -308,15 +318,20 @@ msgstr ""
 msgid "Failed to generate 'lxc.1': %v"
 msgstr ""
 
-#: lxc/copy.go:171
+#: lxc/copy.go:181
 msgid "Failed to get the new container name"
 msgstr ""
 
+#: lxc/utils.go:322
+#, c-format
+msgid "Failed to remove alias %s"
+msgstr ""
+
 #: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:387
+#: lxc/image.go:407
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -330,7 +345,7 @@ msgstr "Force l'arrêt du conteneur."
 msgid "Force the removal of stopped containers"
 msgstr ""
 
-#: lxc/main.go:48
+#: lxc/main.go:49
 msgid "Force using the local unix socket"
 msgstr ""
 
@@ -338,7 +353,7 @@ msgstr ""
 msgid "Format (table|json)"
 msgstr ""
 
-#: lxc/main.go:139
+#: lxc/main.go:149
 #, fuzzy
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "Géneration d'un certificat client. Ceci peut prendre une minute...\n"
@@ -355,12 +370,12 @@ msgstr ""
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:147
+#: lxc/main.go:157
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr ""
 
-#: lxc/main.go:49
+#: lxc/main.go:50
 msgid "Ignore aliases when determining what command to run"
 msgstr ""
 
@@ -369,15 +384,15 @@ msgstr ""
 msgid "Ignore the container state (only for start)"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/image.go:321
+#: lxc/image.go:328
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:727
+#: lxc/image.go:751
 msgid "Image exported successfully!"
 msgstr ""
 
-#: lxc/image.go:551
+#: lxc/image.go:571
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -415,16 +430,16 @@ msgstr ""
 msgid "Keep the image up to date after initial copy"
 msgstr ""
 
-#: lxc/main.go:33
+#: lxc/main.go:34
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:403
+#: lxc/image.go:423
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:405
+#: lxc/image.go:425
 msgid "Last used: never"
 msgstr ""
 
@@ -452,7 +467,7 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/utils.go:197
+#: lxc/utils.go:226
 msgid "Missing summary."
 msgstr "Sommaire manquant."
 
@@ -499,11 +514,11 @@ msgstr "Aucune empreinte n'a été spécifié."
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:474
+#: lxc/image.go:494
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
-#: lxc/help.go:71 lxc/main.go:120 lxc/main.go:172
+#: lxc/help.go:71 lxc/main.go:130 lxc/main.go:182
 #, fuzzy
 msgid "Options:"
 msgstr "Opération %s"
@@ -512,7 +527,7 @@ msgstr "Opération %s"
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr ""
 
-#: lxc/list.go:502
+#: lxc/list.go:507
 msgid "PERSISTENT"
 msgstr ""
 
@@ -528,7 +543,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:831 lxc/remote.go:380
+#: lxc/image.go:855 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
@@ -550,12 +565,12 @@ msgstr "Dossier de configuration alternatif."
 msgid "Path to an alternate server directory"
 msgstr "Dossier de configuration alternatif."
 
-#: lxc/main.go:209
+#: lxc/main.go:219
 #, fuzzy
 msgid "Pause containers."
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/main.go:37
+#: lxc/main.go:38
 msgid "Permission denied, are you in the lxd group?"
 msgstr ""
 
@@ -568,7 +583,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:914
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:938
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -613,7 +628,7 @@ msgstr "Mauvaise URL pour le conteneur %s"
 msgid "Profiles: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:407
+#: lxc/image.go:427
 msgid "Properties:"
 msgstr ""
 
@@ -621,7 +636,7 @@ msgstr ""
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:390
+#: lxc/image.go:410
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -630,6 +645,11 @@ msgstr ""
 msgid "Remote admin password"
 msgstr ""
 
+#: lxc/utils.go:285
+#, fuzzy
+msgid "Remote operation canceled by user"
+msgstr "Le certificat serveur a été rejeté par l'utilisateur"
+
 #: lxc/info.go:101
 #, c-format
 msgid "Remote: %s"
@@ -648,7 +668,7 @@ msgstr ""
 msgid "Resources:"
 msgstr ""
 
-#: lxc/main.go:217
+#: lxc/main.go:227
 #, fuzzy
 msgid "Restart containers."
 msgstr "Liste de l'information sur les conteneurs.\n"
@@ -658,7 +678,7 @@ msgstr "Liste de l'information sur les conteneurs.\n"
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:834
+#: lxc/image.go:858
 msgid "SIZE"
 msgstr ""
 
@@ -714,7 +734,7 @@ msgstr ""
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:388
+#: lxc/image.go:408
 #, c-format
 msgid "Size: %.2fMB"
 msgstr ""
@@ -728,11 +748,11 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/image.go:421
+#: lxc/image.go:441
 msgid "Source:"
 msgstr ""
 
-#: lxc/main.go:227
+#: lxc/main.go:237
 #, fuzzy
 msgid "Start containers."
 msgstr "Mauvaise URL pour le conteneur %s"
@@ -747,7 +767,7 @@ msgstr ""
 msgid "Status: %s"
 msgstr ""
 
-#: lxc/main.go:233
+#: lxc/main.go:243
 #, fuzzy
 msgid "Stop containers."
 msgstr "L'arrêt du conteneur a échoué!"
@@ -829,15 +849,20 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Temps d'attente avant de tuer le conteneur."
 
-#: lxc/image.go:391
+#: lxc/image.go:411
 msgid "Timestamps:"
 msgstr ""
 
-#: lxc/main.go:148
+#: lxc/main.go:158
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 
-#: lxc/image.go:499
+#: lxc/copy.go:155
+#, fuzzy, c-format
+msgid "Transferring container: %s"
+msgstr "Mauvaise URL pour le conteneur %s"
+
+#: lxc/image.go:519
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
@@ -855,7 +880,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:835
+#: lxc/image.go:859
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -871,7 +896,7 @@ msgstr ""
 msgid "Unable to read remote TLS certificate"
 msgstr ""
 
-#: lxc/image.go:396
+#: lxc/image.go:416
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1175,9 +1200,9 @@ msgid ""
 "\n"
 "    - \"u.blah=abc\" will do the same\n"
 "\n"
-"    - \"security.privileged=1\" will list all privileged containers\n"
+"    - \"security.privileged=true\" will list all privileged containers\n"
 "\n"
-"    - \"s.privileged=1\" will do the same\n"
+"    - \"s.privileged=true\" will do the same\n"
 "\n"
 "A regular expression matching a configuration item or its value. (e.g. "
 "volatile.eth0.hwaddr=00:16:3e:.*).\n"
@@ -1416,6 +1441,12 @@ msgstr "Montre le numéro de version de LXD.\n"
 msgid "User aborted delete operation."
 msgstr ""
 
+#: lxc/utils.go:290
+msgid ""
+"User signaled us three times, exiting. The remote operation will keep "
+"running."
+msgstr ""
+
 #: lxc/restore.go:38
 #, fuzzy
 msgid ""
@@ -1439,7 +1470,7 @@ msgstr ""
 msgid "You must specify a source container name"
 msgstr ""
 
-#: lxc/main.go:58
+#: lxc/main.go:68
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
@@ -1456,25 +1487,25 @@ msgstr ""
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
 
-#: lxc/image.go:382
+#: lxc/image.go:402
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:384
+#: lxc/image.go:404
 msgid "enabled"
 msgstr ""
 
-#: lxc/action.go:134 lxc/main.go:28 lxc/main.go:168
+#: lxc/action.go:134 lxc/main.go:29 lxc/main.go:178
 #, fuzzy, c-format
 msgid "error: %v"
 msgstr "erreur: %v\n"
 
-#: lxc/help.go:37 lxc/main.go:114
+#: lxc/help.go:37 lxc/main.go:124
 #, fuzzy, c-format
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/image.go:377 lxc/image.go:811
+#: lxc/image.go:397 lxc/image.go:835
 msgid "no"
 msgstr ""
 
@@ -1483,7 +1514,7 @@ msgstr ""
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:303 lxc/main.go:307
+#: lxc/main.go:313 lxc/main.go:317
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr ""
@@ -1521,11 +1552,11 @@ msgstr ""
 msgid "taken at %s"
 msgstr ""
 
-#: lxc/main.go:242
+#: lxc/main.go:252
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:45 lxc/image.go:379 lxc/image.go:815
+#: lxc/delete.go:45 lxc/image.go:399 lxc/image.go:839
 msgid "yes"
 msgstr ""
 
diff --git a/po/ja.po b/po/ja.po
index f3967c03f..0c247d004 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-09-25 14:33-0400\n"
+"POT-Creation-Date: 2017-10-03 00:05-0400\n"
 "PO-Revision-Date: 2017-09-28 21:49+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -68,11 +68,16 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:808
+#: lxc/image.go:832
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
+#: lxc/utils.go:294
+#, c-format
+msgid "%v (interrupt two more times to force)"
+msgstr ""
+
 #: lxc/snapshot.go:53
 msgid "'/' not allowed in snapshot name"
 msgstr "'/' はスナップショットの名前には使用できません。"
@@ -81,11 +86,11 @@ msgstr "'/' はスナップショットの名前には使用できません。"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:829 lxc/image.go:858
+#: lxc/image.go:853 lxc/image.go:882
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:833
+#: lxc/image.go:857
 msgid "ARCH"
 msgstr ""
 
@@ -102,21 +107,21 @@ msgstr "証明書のフィンガープリントの確認なしで証明書を受
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
 
-#: lxc/image.go:411
+#: lxc/image.go:431
 msgid "Aliases:"
 msgstr "エイリアス:"
 
-#: lxc/image.go:389 lxc/info.go:104
+#: lxc/image.go:409 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr "アーキテクチャ: %s"
 
-#: lxc/image.go:419
+#: lxc/image.go:439
 #, c-format
 msgid "Auto update: %s"
 msgstr "自動更新: %s"
 
-#: lxc/image.go:493
+#: lxc/image.go:513
 #, c-format
 msgid "Bad property: %s"
 msgstr "不正なプロパティ: %s\n"
@@ -177,12 +182,12 @@ msgstr "コマンド:"
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:913 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:937 lxc/profile.go:237
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
 
-#: lxc/main.go:35
+#: lxc/main.go:36
 msgid "Connection refused; is LXD running?"
 msgstr "接続が拒否されました。LXDが実行されていますか?"
 
@@ -190,12 +195,12 @@ msgstr "接続が拒否されました。LXDが実行されていますか?"
 msgid "Container name is mandatory"
 msgstr "コンテナ名を指定する必要があります"
 
-#: lxc/copy.go:176 lxc/init.go:279
+#: lxc/copy.go:186 lxc/init.go:279
 #, c-format
 msgid "Container name is: %s"
 msgstr "コンテナ名: %s"
 
-#: lxc/publish.go:245
+#: lxc/publish.go:247
 #, c-format
 msgid "Container published with fingerprint: %s"
 msgstr "コンテナは以下のフィンガープリントで publish されます: %s"
@@ -204,7 +209,7 @@ msgstr "コンテナは以下のフィンガープリントで publish されま
 msgid "Copy aliases from source"
 msgstr "ソースからエイリアスをコピーしました"
 
-#: lxc/image.go:307
+#: lxc/image.go:314
 #, c-format
 msgid "Copying the image: %s"
 msgstr "イメージのコピー中: %s"
@@ -213,7 +218,7 @@ msgstr "イメージのコピー中: %s"
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
-#: lxc/image.go:394 lxc/info.go:106
+#: lxc/image.go:414 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr "作成日時: %s"
@@ -227,7 +232,7 @@ msgstr "%s を作成中"
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:832 lxc/image.go:860
+#: lxc/image.go:856 lxc/image.go:884
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -245,7 +250,7 @@ msgstr "デバイス %s が %s から削除されました"
 msgid "Disk usage:"
 msgstr "ディスク使用量:"
 
-#: lxc/list.go:500
+#: lxc/list.go:505
 msgid "EPHEMERAL"
 msgstr ""
 
@@ -253,11 +258,11 @@ msgstr ""
 msgid "EXPIRY DATE"
 msgstr ""
 
-#: lxc/main.go:47
+#: lxc/main.go:48
 msgid "Enable debug mode"
 msgstr "デバッグモードを有効にします"
 
-#: lxc/main.go:46
+#: lxc/main.go:47
 msgid "Enable verbose mode"
 msgstr "詳細モードを有効にします"
 
@@ -277,24 +282,29 @@ msgstr "Ephemeral コンテナ"
 msgid "Event type to listen for"
 msgstr "Listenするイベントタイプ"
 
-#: lxc/image.go:398
+#: lxc/image.go:418
 #, c-format
 msgid "Expires: %s"
 msgstr "失効日時: %s"
 
-#: lxc/image.go:400
+#: lxc/image.go:420
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/image.go:677
+#: lxc/image.go:701
 #, c-format
 msgid "Exporting the image: %s"
 msgstr "イメージのエクスポート中: %s"
 
-#: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
+#: lxc/config.go:348 lxc/image.go:854 lxc/image.go:883
 msgid "FINGERPRINT"
 msgstr ""
 
+#: lxc/utils.go:332
+#, fuzzy, c-format
+msgid "Failed to create alias %s"
+msgstr "'lxc.%s.1' の生成が失敗しました: %v"
+
 #: lxc/manpage.go:62
 #, c-format
 msgid "Failed to generate 'lxc.%s.1': %v"
@@ -305,15 +315,20 @@ msgstr "'lxc.%s.1' の生成が失敗しました: %v"
 msgid "Failed to generate 'lxc.1': %v"
 msgstr "'lxc.1' の生成が失敗しました: %v"
 
-#: lxc/copy.go:171
+#: lxc/copy.go:181
 msgid "Failed to get the new container name"
 msgstr "コピー先のコンテナ名が取得できませんでした"
 
+#: lxc/utils.go:322
+#, c-format
+msgid "Failed to remove alias %s"
+msgstr ""
+
 #: lxc/list.go:112
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
-#: lxc/image.go:387
+#: lxc/image.go:407
 #, c-format
 msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
@@ -326,7 +341,7 @@ msgstr "コンテナを強制シャットダウンします。"
 msgid "Force the removal of stopped containers"
 msgstr "停止したコンテナを強制的に削除します。"
 
-#: lxc/main.go:48
+#: lxc/main.go:49
 msgid "Force using the local unix socket"
 msgstr "強制的にローカルのUNIXソケットを使います。"
 
@@ -334,7 +349,7 @@ msgstr "強制的にローカルのUNIXソケットを使います。"
 msgid "Format (table|json)"
 msgstr "フォーマット (table|json)"
 
-#: lxc/main.go:139
+#: lxc/main.go:149
 msgid "Generating a client certificate. This may take a minute..."
 msgstr "クライアント証明書を生成します。1分ぐらいかかります..."
 
@@ -350,12 +365,12 @@ msgstr ""
 msgid "ISSUE DATE"
 msgstr ""
 
-#: lxc/main.go:147
+#: lxc/main.go:157
 msgid ""
 "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr "初めて LXD を使う場合、sudo lxd init と実行する必要があります"
 
-#: lxc/main.go:49
+#: lxc/main.go:50
 msgid "Ignore aliases when determining what command to run"
 msgstr "どのコマンドを実行するか決める際にエイリアスを無視します。"
 
@@ -363,15 +378,15 @@ msgstr "どのコマンドを実行するか決める際にエイリアスを無
 msgid "Ignore the container state (only for start)"
 msgstr "コンテナの状態を無視します (startのみ)。"
 
-#: lxc/image.go:321
+#: lxc/image.go:328
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:727
+#: lxc/image.go:751
 msgid "Image exported successfully!"
 msgstr "イメージのエクスポートに成功しました!"
 
-#: lxc/image.go:551
+#: lxc/image.go:571
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
@@ -407,16 +422,16 @@ msgstr "IPアドレス:"
 msgid "Keep the image up to date after initial copy"
 msgstr "最初にコピーした後も常にイメージを最新の状態に保つ"
 
-#: lxc/main.go:33
+#: lxc/main.go:34
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
-#: lxc/image.go:403
+#: lxc/image.go:423
 #, c-format
 msgid "Last used: %s"
 msgstr "最終使用: %s"
 
-#: lxc/image.go:405
+#: lxc/image.go:425
 msgid "Last used: never"
 msgstr "最終使用: 未使用"
 
@@ -444,7 +459,7 @@ msgstr "メモリ (ピーク)"
 msgid "Memory usage:"
 msgstr "メモリ消費量:"
 
-#: lxc/utils.go:197
+#: lxc/utils.go:226
 msgid "Missing summary."
 msgstr "サマリーはありません。"
 
@@ -491,11 +506,11 @@ msgstr "フィンガープリントが指定されていません。"
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
-#: lxc/image.go:474
+#: lxc/image.go:494
 msgid "Only https:// is supported for remote image import."
 msgstr "リモートイメージのインポートは https:// のみをサポートします。"
 
-#: lxc/help.go:71 lxc/main.go:120 lxc/main.go:172
+#: lxc/help.go:71 lxc/main.go:130 lxc/main.go:182
 msgid "Options:"
 msgstr "オプション:"
 
@@ -503,7 +518,7 @@ msgstr "オプション:"
 msgid "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr "ターミナルモードを上書きします (auto, interactive, non-interactive)"
 
-#: lxc/list.go:502
+#: lxc/list.go:507
 msgid "PERSISTENT"
 msgstr ""
 
@@ -519,7 +534,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:831 lxc/remote.go:380
+#: lxc/image.go:855 lxc/remote.go:380
 msgid "PUBLIC"
 msgstr ""
 
@@ -539,11 +554,11 @@ msgstr "別のクライアント用設定ディレクトリ"
 msgid "Path to an alternate server directory"
 msgstr "別のサーバ用設定ディレクトリ"
 
-#: lxc/main.go:209
+#: lxc/main.go:219
 msgid "Pause containers."
 msgstr "コンテナを一時停止します。"
 
-#: lxc/main.go:37
+#: lxc/main.go:38
 msgid "Permission denied, are you in the lxd group?"
 msgstr "アクセスが拒否されました。lxd グループに所属していますか?"
 
@@ -556,7 +571,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:914
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:938
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -601,7 +616,7 @@ msgstr "プロファイル %s が %s に追加されました"
 msgid "Profiles: %s"
 msgstr "プロファイル: %s"
 
-#: lxc/image.go:407
+#: lxc/image.go:427
 msgid "Properties:"
 msgstr "プロパティ:"
 
@@ -609,7 +624,7 @@ msgstr "プロパティ:"
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
-#: lxc/image.go:390
+#: lxc/image.go:410
 #, c-format
 msgid "Public: %s"
 msgstr "パブリック: %s"
@@ -618,6 +633,11 @@ msgstr "パブリック: %s"
 msgid "Remote admin password"
 msgstr "リモートの管理者パスワード"
 
+#: lxc/utils.go:285
+#, fuzzy
+msgid "Remote operation canceled by user"
+msgstr "ユーザによりサーバ証明書が拒否されました"
+
 #: lxc/info.go:101
 #, c-format
 msgid "Remote: %s"
@@ -636,7 +656,7 @@ msgstr "ユーザの確認を要求する"
 msgid "Resources:"
 msgstr "リソース:"
 
-#: lxc/main.go:217
+#: lxc/main.go:227
 msgid "Restart containers."
 msgstr "コンテナを再起動します。"
 
@@ -645,7 +665,7 @@ msgstr "コンテナを再起動します。"
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:834
+#: lxc/image.go:858
 msgid "SIZE"
 msgstr ""
 
@@ -701,7 +721,7 @@ msgstr "コンテナログの最後の 100 行を表示しますか?"
 msgid "Show the expanded configuration"
 msgstr "拡張した設定を表示する"
 
-#: lxc/image.go:388
+#: lxc/image.go:408
 #, c-format
 msgid "Size: %.2fMB"
 msgstr "サイズ: %.2fMB"
@@ -715,11 +735,11 @@ msgstr "スナップショット:"
 msgid "Some containers failed to %s"
 msgstr "一部のコンテナで %s が失敗しました"
 
-#: lxc/image.go:421
+#: lxc/image.go:441
 msgid "Source:"
 msgstr "取得元:"
 
-#: lxc/main.go:227
+#: lxc/main.go:237
 msgid "Start containers."
 msgstr "コンテナを起動します。"
 
@@ -733,7 +753,7 @@ msgstr "%s を起動中"
 msgid "Status: %s"
 msgstr "状態: %s"
 
-#: lxc/main.go:233
+#: lxc/main.go:243
 msgid "Stop containers."
 msgstr "コンテナを停止します。"
 
@@ -820,18 +840,23 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "コンテナを強制停止するまでの時間"
 
-#: lxc/image.go:391
+#: lxc/image.go:411
 msgid "Timestamps:"
 msgstr "タイムスタンプ:"
 
-#: lxc/main.go:148
+#: lxc/main.go:158
 msgid "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr ""
 "初めてコンテナを起動するには、\"lxc launch ubuntu:16.04\" と実行してみてく"
 "だ\n"
 "さい。"
 
-#: lxc/image.go:499
+#: lxc/copy.go:155
+#, fuzzy, c-format
+msgid "Transferring container: %s"
+msgstr "イメージを転送中: %s"
+
+#: lxc/image.go:519
 #, c-format
 msgid "Transferring image: %s"
 msgstr "イメージを転送中: %s"
@@ -849,7 +874,7 @@ msgstr "タイプ: ephemeral"
 msgid "Type: persistent"
 msgstr "タイプ: persistent"
 
-#: lxc/image.go:835
+#: lxc/image.go:859
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -865,7 +890,7 @@ msgstr "help2man が見つかりません。"
 msgid "Unable to read remote TLS certificate"
 msgstr "リモートの TLS 証明書を読めません"
 
-#: lxc/image.go:396
+#: lxc/image.go:416
 #, c-format
 msgid "Uploaded: %s"
 msgstr "アップロード日時: %s"
@@ -980,7 +1005,8 @@ msgstr ""
 "    コンテナもしくはサーバの設定を表示します。\n"
 "\n"
 "lxc config edit [<remote>:][container]\n"
-"    外部エディタを起動するか、標準入力から読み込むことにより、設定を編集します。\n"
+"    外部エディタを起動するか、標準入力から読み込むことにより、設定を編集しま"
+"す。\n"
 "\n"
 "*デバイスの管理*\n"
 "\n"
@@ -1021,7 +1047,8 @@ msgstr ""
 "cat config.yaml | lxc config edit <container>\n"
 "    config.yaml から読み込んで、コンテナの設定を更新します。\n"
 "\n"
-"lxc config device add [<remote>:]container1 <device-name> disk source=/share/c1 path=opt\n"
+"lxc config device add [<remote>:]container1 <device-name> disk source=/share/"
+"c1 path=opt\n"
 "    ホストの /share/c1 をコンテナ内の /opt にマウントします。\n"
 "\n"
 "lxc config set [<remote>:]<container> limits.cpu 2\n"
@@ -1040,7 +1067,8 @@ msgid ""
 "\n"
 "Copy containers within or in between LXD instances."
 msgstr ""
-"使い方: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] [--ephemeral|e]\n"
+"使い方: lxc copy [<remote>:]<source>[/<snapshot>] [[<remote>:]<destination>] "
+"[--ephemeral|e]\n"
 "\n"
 "LXDインスタンス内もしくはLXDインスタンス間でコンテナをコピーします。"
 
@@ -1051,7 +1079,8 @@ msgid ""
 "\n"
 "Delete containers and snapshots."
 msgstr ""
-"使い方: lxc delete [<remote>:]<container>[/<snapshot>] [[<remote>:]<container>[/<snapshot>]...]\n"
+"使い方: lxc delete [<remote>:]<container>[/<snapshot>] "
+"[[<remote>:]<container>[/<snapshot>]...]\n"
 "\n"
 "コンテナとコンテナのスナップショットを消去します。"
 
@@ -1065,11 +1094,13 @@ msgid ""
 "Mode defaults to non-interactive, interactive mode is selected if both stdin "
 "AND stdout are terminals (stderr is ignored)."
 msgstr ""
-"使い方: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-interactive] [--env KEY=VALUE...] [--] <command line>\n"
+"使い方: lxc exec [<remote>:]<container> [--mode=auto|interactive|non-"
+"interactive] [--env KEY=VALUE...] [--] <command line>\n"
 "\n"
 "指定したコマンドをコンテナ内で実行します。\n"
 "\n"
-"デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナル\n"
+"デフォルトのモードは non-interactive です。もし標準入出力が両方ともターミナ"
+"ル\n"
 "の場合は interactive モードが選択されます (標準エラー出力は無視されます)。"
 
 #: lxc/file.go:33
@@ -1101,14 +1132,17 @@ msgstr ""
 "\n"
 "コンテナ内のファイルを管理します。\n"
 "\n"
-"lxc file pull [<remote>:]<container>/<path> [[<remote>:]<container>/<path>...] <target path>\n"
+"lxc file pull [<remote>:]<container>/<path> [[<remote>:]<container>/"
+"<path>...] <target path>\n"
 "    コンテナからファイルを取得します。\n"
 "\n"
-"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source path>...] [<remote>:]<container>/<path>\n"
+"lxc file push [--uid=UID] [--gid=GID] [--mode=MODE] <source path> [<source "
+"path>...] [<remote>:]<container>/<path>\n"
 "    コンテナにファイルを設置します。\n"
 "\n"
 "lxc file edit [<remote>:]<container>/<path>\n"
-"    デフォルトのテキストエディタを使用してコンテナ内のファイルを編集します。\n"
+"    デフォルトのテキストエディタを使用してコンテナ内のファイルを編集しま"
+"す。\n"
 "\n"
 "*例*\n"
 "lxc file push /etc/hosts foo/etc/hosts\n"
@@ -1228,11 +1262,14 @@ msgstr ""
 "る場合は) エイリアスで参照できます。\n"
 "\n"
 "\n"
-"lxc image import <tarball> [<rootfs tarball>|<URL>] [remote:] [--public] [--created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--alias=ALIAS].. [prop=value]\n"
+"lxc image import <tarball> [<rootfs tarball>|<URL>] [remote:] [--public] [--"
+"created-at=ISO-8601] [--expires-at=ISO-8601] [--fingerprint=FINGERPRINT] [--"
+"alias=ALIAS].. [prop=value]\n"
 "    イメージの tarball (複数も可能) を LXD のイメージストアにインポートしま\n"
 "    す。\n"
 "\n"
-"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] [--public] [--auto-update]\n"
+"lxc image copy [remote:]<image> <remote>: [--alias=ALIAS].. [--copy-aliases] "
+"[--public] [--auto-update]\n"
 "    ネットワーク経由である LXD デーモンから他の LXD デーモンへイメージを\n"
 "    コピーします。\n"
 "\n"
@@ -1278,7 +1315,8 @@ msgstr ""
 "    エイリアスを削除します。\n"
 "\n"
 "lxc image alias list [<remote>:] [filter]\n"
-"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリアス\n"
+"    エイリアスを一覧表示します。イメージハッシュの一部やイメージのエイリア"
+"ス\n"
 "    名の一部をフィルタとして指定できます。"
 
 #: lxc/info.go:26
@@ -1316,7 +1354,8 @@ msgid ""
 "Examples:\n"
 "    lxc init ubuntu:16.04 u1"
 msgstr ""
-"使い方: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"使い方: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
+"profile|-p <profile>...] [--config|-c <key=value>...]\n"
 "\n"
 "指定したイメージとコンテナ名を使ってコンテナを初期化します。\n"
 "\n"
@@ -1339,7 +1378,8 @@ msgid ""
 "Examples:\n"
 "    lxc launch ubuntu:16.04 u1"
 msgstr ""
-"使い方: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"使い方: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] "
+"[--profile|-p <profile>...] [--config|-c <key=value>...]\n"
 "\n"
 "指定したイメージと名前を使ってコンテナを起動します。\n"
 "\n"
@@ -1350,6 +1390,7 @@ msgstr ""
 "    lxc launch ubuntu:16.04 u1"
 
 #: lxc/list.go:46
+#, fuzzy
 msgid ""
 "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <columns>] "
 "[--fast]\n"
@@ -1372,9 +1413,9 @@ msgid ""
 "\n"
 "    - \"u.blah=abc\" will do the same\n"
 "\n"
-"    - \"security.privileged=1\" will list all privileged containers\n"
+"    - \"security.privileged=true\" will list all privileged containers\n"
 "\n"
-"    - \"s.privileged=1\" will do the same\n"
+"    - \"s.privileged=true\" will do the same\n"
 "\n"
 "A regular expression matching a configuration item or its value. (e.g. "
 "volatile.eth0.hwaddr=00:16:3e:.*).\n"
@@ -1417,7 +1458,8 @@ msgid ""
 "    Shows a list of containers using the \"NAME\", \"STATE\", \"IPV4\", "
 "\"IPV6\" columns."
 msgstr ""
-"使い方: lxc list [<remote>:] [filters] [--format table|json] [-c columns] [--fast]\n"
+"使い方: lxc list [<remote>:] [filters] [--format table|json] [-c columns] [--"
+"fast]\n"
 "\n"
 "コンテナを一覧表示します。\n"
 "\n"
@@ -1441,7 +1483,8 @@ msgstr ""
 "\n"
 "    - \"s.privilaged=1\" は上記と同じ意味になります。\n"
 "\n"
-"設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:.*)\n"
+"設定項目もしくは値とマッチする正規表現 (例:volatile.eth0.hwaddr=00:16:3e:."
+"*)\n"
 "\n"
 "*カラム*\n"
 "\n"
@@ -1479,7 +1522,8 @@ msgstr ""
 "\n"
 "*例*\n"
 "lxc list -c ns46\n"
-"    コンテナの \"NAME\"、\"STATE\"、\"IPV4\"、\"IPV6\" を使って一覧表示します。"
+"    コンテナの \"NAME\"、\"STATE\"、\"IPV4\"、\"IPV6\" を使って一覧表示しま"
+"す。"
 
 #: lxc/manpage.go:20
 msgid ""
@@ -1534,7 +1578,8 @@ msgid ""
 "lxc move <container>/<old snapshot name> <container>/<new snapshot name>\n"
 "    Rename a snapshot."
 msgstr ""
-"使い方: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:][<container>[/<snapshot>]]\n"
+"使い方: lxc move [<remote>:]<container>[/<snapshot>] [<remote>:]"
+"[<container>[/<snapshot>]]\n"
 "\n"
 "LXD ホスト内、もしくは LXD ホスト間でコンテナを移動します。\n"
 "\n"
@@ -1703,7 +1748,8 @@ msgid ""
 "\n"
 "Publish containers as images."
 msgstr ""
-"使い方: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--alias=ALIAS...] [prop-key=prop-value...]\n"
+"使い方: lxc publish [<remote>:]<container>[/<snapshot>] [<remote>:] [--"
+"alias=ALIAS...] [prop-key=prop-value...]\n"
 "\n"
 "イメージとしてコンテナを publish します。"
 
@@ -1739,7 +1785,8 @@ msgstr ""
 "\n"
 "リモートの LXD サーバのリストを管理します。\n"
 "\n"
-"lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--password=PASSWORD] [--public] [--protocol=PROTOCOL]\n"
+"lxc remote add <remote> <IP|FQDN|URL> [--accept-certificate] [--"
+"password=PASSWORD] [--public] [--protocol=PROTOCOL]\n"
 "    <url> をリモートホスト <remote> として追加します。\n"
 "\n"
 "lxc remote remove <remote>\n"
@@ -1826,6 +1873,12 @@ msgstr ""
 msgid "User aborted delete operation."
 msgstr "ユーザが削除操作を中断しました。"
 
+#: lxc/utils.go:290
+msgid ""
+"User signaled us three times, exiting. The remote operation will keep "
+"running."
+msgstr ""
+
 #: lxc/restore.go:38
 msgid ""
 "Whether or not to restore the container's running state from snapshot (if "
@@ -1845,7 +1898,7 @@ msgstr ""
 msgid "You must specify a source container name"
 msgstr "コピー元のコンテナ名を指定してください"
 
-#: lxc/main.go:58
+#: lxc/main.go:68
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
@@ -1863,25 +1916,25 @@ msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
 "した"
 
-#: lxc/image.go:382
+#: lxc/image.go:402
 msgid "disabled"
 msgstr "無効"
 
-#: lxc/image.go:384
+#: lxc/image.go:404
 msgid "enabled"
 msgstr "有効"
 
-#: lxc/action.go:134 lxc/main.go:28 lxc/main.go:168
+#: lxc/action.go:134 lxc/main.go:29 lxc/main.go:178
 #, c-format
 msgid "error: %v"
 msgstr "エラー: %v"
 
-#: lxc/help.go:37 lxc/main.go:114
+#: lxc/help.go:37 lxc/main.go:124
 #, c-format
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/image.go:377 lxc/image.go:811
+#: lxc/image.go:397 lxc/image.go:835
 msgid "no"
 msgstr ""
 
@@ -1889,7 +1942,7 @@ msgstr ""
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
-#: lxc/main.go:303 lxc/main.go:307
+#: lxc/main.go:313 lxc/main.go:317
 #, c-format
 msgid "processing aliases failed %s\n"
 msgstr "エイリアスの処理が失敗しました %s\n"
@@ -1927,11 +1980,11 @@ msgstr ""
 msgid "taken at %s"
 msgstr "%s に取得しました"
 
-#: lxc/main.go:242
+#: lxc/main.go:252
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:45 lxc/image.go:379 lxc/image.go:815
+#: lxc/delete.go:45 lxc/image.go:399 lxc/image.go:839
 msgid "yes"
 msgstr ""
 
diff --git a/po/lxd.pot b/po/lxd.pot
index f02a08d31..6dddddc85 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-09-25 14:33-0400\n"
+        "POT-Creation-Date: 2017-10-03 00:05-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -65,11 +65,16 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:808
+#: lxc/image.go:832
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
 
+#: lxc/utils.go:294
+#, c-format
+msgid   "%v (interrupt two more times to force)"
+msgstr  ""
+
 #: lxc/snapshot.go:53
 msgid   "'/' not allowed in snapshot name"
 msgstr  ""
@@ -78,11 +83,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:829 lxc/image.go:858
+#: lxc/image.go:853 lxc/image.go:882
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:833
+#: lxc/image.go:857
 msgid   "ARCH"
 msgstr  ""
 
@@ -99,21 +104,21 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:411
+#: lxc/image.go:431
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:389 lxc/info.go:104
+#: lxc/image.go:409 lxc/info.go:104
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:419
+#: lxc/image.go:439
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
 
-#: lxc/image.go:493
+#: lxc/image.go:513
 #, c-format
 msgid   "Bad property: %s"
 msgstr  ""
@@ -174,12 +179,12 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:913 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:937 lxc/profile.go:237
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
 
-#: lxc/main.go:35
+#: lxc/main.go:36
 msgid   "Connection refused; is LXD running?"
 msgstr  ""
 
@@ -187,12 +192,12 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/copy.go:176 lxc/init.go:279
+#: lxc/copy.go:186 lxc/init.go:279
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
 
-#: lxc/publish.go:245
+#: lxc/publish.go:247
 #, c-format
 msgid   "Container published with fingerprint: %s"
 msgstr  ""
@@ -201,7 +206,7 @@ msgstr  ""
 msgid   "Copy aliases from source"
 msgstr  ""
 
-#: lxc/image.go:307
+#: lxc/image.go:314
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
@@ -210,7 +215,7 @@ msgstr  ""
 msgid   "Could not create server cert dir"
 msgstr  ""
 
-#: lxc/image.go:394 lxc/info.go:106
+#: lxc/image.go:414 lxc/info.go:106
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -224,7 +229,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:832 lxc/image.go:860
+#: lxc/image.go:856 lxc/image.go:884
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -242,7 +247,7 @@ msgstr  ""
 msgid   "Disk usage:"
 msgstr  ""
 
-#: lxc/list.go:500
+#: lxc/list.go:505
 msgid   "EPHEMERAL"
 msgstr  ""
 
@@ -250,11 +255,11 @@ msgstr  ""
 msgid   "EXPIRY DATE"
 msgstr  ""
 
-#: lxc/main.go:47
+#: lxc/main.go:48
 msgid   "Enable debug mode"
 msgstr  ""
 
-#: lxc/main.go:46
+#: lxc/main.go:47
 msgid   "Enable verbose mode"
 msgstr  ""
 
@@ -274,24 +279,29 @@ msgstr  ""
 msgid   "Event type to listen for"
 msgstr  ""
 
-#: lxc/image.go:398
+#: lxc/image.go:418
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:400
+#: lxc/image.go:420
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/image.go:677
+#: lxc/image.go:701
 #, c-format
 msgid   "Exporting the image: %s"
 msgstr  ""
 
-#: lxc/config.go:348 lxc/image.go:830 lxc/image.go:859
+#: lxc/config.go:348 lxc/image.go:854 lxc/image.go:883
 msgid   "FINGERPRINT"
 msgstr  ""
 
+#: lxc/utils.go:332
+#, c-format
+msgid   "Failed to create alias %s"
+msgstr  ""
+
 #: lxc/manpage.go:62
 #, c-format
 msgid   "Failed to generate 'lxc.%s.1': %v"
@@ -302,15 +312,20 @@ msgstr  ""
 msgid   "Failed to generate 'lxc.1': %v"
 msgstr  ""
 
-#: lxc/copy.go:171
+#: lxc/copy.go:181
 msgid   "Failed to get the new container name"
 msgstr  ""
 
+#: lxc/utils.go:322
+#, c-format
+msgid   "Failed to remove alias %s"
+msgstr  ""
+
 #: lxc/list.go:112
 msgid   "Fast mode (same as --columns=nsacPt)"
 msgstr  ""
 
-#: lxc/image.go:387
+#: lxc/image.go:407
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -323,7 +338,7 @@ msgstr  ""
 msgid   "Force the removal of stopped containers"
 msgstr  ""
 
-#: lxc/main.go:48
+#: lxc/main.go:49
 msgid   "Force using the local unix socket"
 msgstr  ""
 
@@ -331,7 +346,7 @@ msgstr  ""
 msgid   "Format (table|json)"
 msgstr  ""
 
-#: lxc/main.go:139
+#: lxc/main.go:149
 msgid   "Generating a client certificate. This may take a minute..."
 msgstr  ""
 
@@ -347,11 +362,11 @@ msgstr  ""
 msgid   "ISSUE DATE"
 msgstr  ""
 
-#: lxc/main.go:147
+#: lxc/main.go:157
 msgid   "If this is your first time using LXD, you should also run: sudo lxd init"
 msgstr  ""
 
-#: lxc/main.go:49
+#: lxc/main.go:50
 msgid   "Ignore aliases when determining what command to run"
 msgstr  ""
 
@@ -359,15 +374,15 @@ msgstr  ""
 msgid   "Ignore the container state (only for start)"
 msgstr  ""
 
-#: lxc/image.go:321
+#: lxc/image.go:328
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:727
+#: lxc/image.go:751
 msgid   "Image exported successfully!"
 msgstr  ""
 
-#: lxc/image.go:551
+#: lxc/image.go:571
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
@@ -403,16 +418,16 @@ msgstr  ""
 msgid   "Keep the image up to date after initial copy"
 msgstr  ""
 
-#: lxc/main.go:33
+#: lxc/main.go:34
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/image.go:403
+#: lxc/image.go:423
 #, c-format
 msgid   "Last used: %s"
 msgstr  ""
 
-#: lxc/image.go:405
+#: lxc/image.go:425
 msgid   "Last used: never"
 msgstr  ""
 
@@ -440,7 +455,7 @@ msgstr  ""
 msgid   "Memory usage:"
 msgstr  ""
 
-#: lxc/utils.go:197
+#: lxc/utils.go:226
 msgid   "Missing summary."
 msgstr  ""
 
@@ -485,11 +500,11 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:474
+#: lxc/image.go:494
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
-#: lxc/help.go:71 lxc/main.go:120 lxc/main.go:172
+#: lxc/help.go:71 lxc/main.go:130 lxc/main.go:182
 msgid   "Options:"
 msgstr  ""
 
@@ -497,7 +512,7 @@ msgstr  ""
 msgid   "Override the terminal mode (auto, interactive or non-interactive)"
 msgstr  ""
 
-#: lxc/list.go:502
+#: lxc/list.go:507
 msgid   "PERSISTENT"
 msgstr  ""
 
@@ -513,7 +528,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:831 lxc/remote.go:380
+#: lxc/image.go:855 lxc/remote.go:380
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -533,11 +548,11 @@ msgstr  ""
 msgid   "Path to an alternate server directory"
 msgstr  ""
 
-#: lxc/main.go:209
+#: lxc/main.go:219
 msgid   "Pause containers."
 msgstr  ""
 
-#: lxc/main.go:37
+#: lxc/main.go:38
 msgid   "Permission denied, are you in the lxd group?"
 msgstr  ""
 
@@ -550,7 +565,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:914
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:938
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -595,7 +610,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:407
+#: lxc/image.go:427
 msgid   "Properties:"
 msgstr  ""
 
@@ -603,7 +618,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:390
+#: lxc/image.go:410
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -612,6 +627,10 @@ msgstr  ""
 msgid   "Remote admin password"
 msgstr  ""
 
+#: lxc/utils.go:285
+msgid   "Remote operation canceled by user"
+msgstr  ""
+
 #: lxc/info.go:101
 #, c-format
 msgid   "Remote: %s"
@@ -630,7 +649,7 @@ msgstr  ""
 msgid   "Resources:"
 msgstr  ""
 
-#: lxc/main.go:217
+#: lxc/main.go:227
 msgid   "Restart containers."
 msgstr  ""
 
@@ -639,7 +658,7 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:834
+#: lxc/image.go:858
 msgid   "SIZE"
 msgstr  ""
 
@@ -695,7 +714,7 @@ msgstr  ""
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:388
+#: lxc/image.go:408
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -709,11 +728,11 @@ msgstr  ""
 msgid   "Some containers failed to %s"
 msgstr  ""
 
-#: lxc/image.go:421
+#: lxc/image.go:441
 msgid   "Source:"
 msgstr  ""
 
-#: lxc/main.go:227
+#: lxc/main.go:237
 msgid   "Start containers."
 msgstr  ""
 
@@ -727,7 +746,7 @@ msgstr  ""
 msgid   "Status: %s"
 msgstr  ""
 
-#: lxc/main.go:233
+#: lxc/main.go:243
 msgid   "Stop containers."
 msgstr  ""
 
@@ -800,15 +819,20 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:391
+#: lxc/image.go:411
 msgid   "Timestamps:"
 msgstr  ""
 
-#: lxc/main.go:148
+#: lxc/main.go:158
 msgid   "To start your first container, try: lxc launch ubuntu:16.04"
 msgstr  ""
 
-#: lxc/image.go:499
+#: lxc/copy.go:155
+#, c-format
+msgid   "Transferring container: %s"
+msgstr  ""
+
+#: lxc/image.go:519
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
@@ -826,7 +850,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:835
+#: lxc/image.go:859
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -842,7 +866,7 @@ msgstr  ""
 msgid   "Unable to read remote TLS certificate"
 msgstr  ""
 
-#: lxc/image.go:396
+#: lxc/image.go:416
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1110,9 +1134,9 @@ msgid   "Usage: lxc list [<remote>:] [filters] [--format table|json] [-c <column
         "\n"
         "    - \"u.blah=abc\" will do the same\n"
         "\n"
-        "    - \"security.privileged=1\" will list all privileged containers\n"
+        "    - \"security.privileged=true\" will list all privileged containers\n"
         "\n"
-        "    - \"s.privileged=1\" will do the same\n"
+        "    - \"s.privileged=true\" will do the same\n"
         "\n"
         "A regular expression matching a configuration item or its value. (e.g. volatile.eth0.hwaddr=00:16:3e:.*).\n"
         "\n"
@@ -1333,6 +1357,10 @@ msgstr  ""
 msgid   "User aborted delete operation."
 msgstr  ""
 
+#: lxc/utils.go:290
+msgid   "User signaled us three times, exiting. The remote operation will keep running."
+msgstr  ""
+
 #: lxc/restore.go:38
 msgid   "Whether or not to restore the container's running state from snapshot (if available)"
 msgstr  ""
@@ -1349,7 +1377,7 @@ msgstr  ""
 msgid   "You must specify a source container name"
 msgstr  ""
 
-#: lxc/main.go:58
+#: lxc/main.go:68
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
@@ -1365,25 +1393,25 @@ msgstr  ""
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:382
+#: lxc/image.go:402
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:384
+#: lxc/image.go:404
 msgid   "enabled"
 msgstr  ""
 
-#: lxc/action.go:134 lxc/main.go:28 lxc/main.go:168
+#: lxc/action.go:134 lxc/main.go:29 lxc/main.go:178
 #, c-format
 msgid   "error: %v"
 msgstr  ""
 
-#: lxc/help.go:37 lxc/main.go:114
+#: lxc/help.go:37 lxc/main.go:124
 #, c-format
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/image.go:377 lxc/image.go:811
+#: lxc/image.go:397 lxc/image.go:835
 msgid   "no"
 msgstr  ""
 
@@ -1391,7 +1419,7 @@ msgstr  ""
 msgid   "ok (y/n)?"
 msgstr  ""
 
-#: lxc/main.go:303 lxc/main.go:307
+#: lxc/main.go:313 lxc/main.go:317
 #, c-format
 msgid   "processing aliases failed %s\n"
 msgstr  ""
@@ -1429,11 +1457,11 @@ msgstr  ""
 msgid   "taken at %s"
 msgstr  ""
 
-#: lxc/main.go:242
+#: lxc/main.go:252
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:379 lxc/image.go:815
+#: lxc/delete.go:45 lxc/image.go:399 lxc/image.go:839
 msgid   "yes"
 msgstr  ""
 

From eefad601e92a4872adaf584df83be9d3106346b8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 1 Jul 2017 15:54:09 -0400
Subject: [PATCH 1073/1193] lxc: Make it possible to cancel image downloads
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #2294

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 2 +-
 lxc/init.go  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 64d18cb96..f711dccbc 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -319,7 +319,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		}
 
 		// Wait for operation to finish
-		err = op.Wait()
+		err = cancelableWait(op, &progress)
 		if err != nil {
 			progress.Done("")
 			return err
diff --git a/lxc/init.go b/lxc/init.go
index 0c962fd6a..a9c28e9de 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -255,7 +255,7 @@ func (c *initCmd) create(conf *config.Config, args []string) (lxd.ContainerServe
 		return nil, "", err
 	}
 
-	err = op.Wait()
+	err = cancelableWait(op, &progress)
 	if err != nil {
 		progress.Done("")
 		return nil, "", err

From 5e6e67189c70b696b124748574fbdae3b3a019b5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 27 Jun 2017 13:59:46 -0400
Subject: [PATCH 1074/1193] tests: Don't attempt to finger public remotes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/remote.sh | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index 457cd5e34..fbce8c8bd 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -28,7 +28,6 @@ test_remote_url() {
 
   for url in ${urls}; do
     lxc_remote remote add test "${url}"
-    lxc_remote finger test:
     lxc_remote remote remove test
   done
 }

From 10d53bd9be20be522ccb3d3339ea938f9bda8388 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 25 Aug 2017 13:50:07 -0400
Subject: [PATCH 1075/1193] apparmor: Support new stacking syntax
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/apparmor.go      | 1 +
 test/suites/basic.sh | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index 47a188139..f2920f421 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -347,6 +347,7 @@ func getAAProfileContent(c container) string {
   deny /sys/kernel/security?*{,/**} wklx,
   deny /sys/kernel?*{,/**} wklx,
 `
+		profile += fmt.Sprintf("  change_profile -> \":%s:*\",\n", AANamespace(c))
 		profile += fmt.Sprintf("  change_profile -> \":%s://*\",\n", AANamespace(c))
 	} else {
 		profile += "\n  ### Feature: apparmor stacking (not present)\n"
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index d3915799c..b6242a26c 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -323,7 +323,7 @@ test_basic_usage() {
 
     if [ "${MAJOR}" -gt "1" ] || ([ "${MAJOR}" = "1" ] && [ "${MINOR}" -ge "2" ]); then
       aa_namespace="lxd-lxd-apparmor-test_<$(echo "${LXD_DIR}" | sed -e 's/\//-/g' -e 's/^.//')>"
-      aa-status | grep ":${aa_namespace}://unconfined"
+      aa-status | grep -q ":${aa_namespace}:unconfined" || aa-status | grep -q ":${aa_namespace}://unconfined"
       lxc stop lxd-apparmor-test --force
       ! aa-status | grep -q ":${aa_namespace}:"
     else

From 6f4eaa71c577cd39397e0a9f95678191445b1ae5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 3 Oct 2017 01:14:51 -0400
Subject: [PATCH 1076/1193] lxc/exec: Fix Windows port
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/exec_windows.go | 14 ++------------
 1 file changed, 2 insertions(+), 12 deletions(-)

diff --git a/lxc/exec_windows.go b/lxc/exec_windows.go
index 67078df6e..22f8b8a4a 100644
--- a/lxc/exec_windows.go
+++ b/lxc/exec_windows.go
@@ -6,7 +6,6 @@ import (
 	"io"
 	"os"
 	"os/signal"
-	"syscall"
 
 	"github.com/gorilla/websocket"
 	"github.com/mattn/go-colorable"
@@ -42,16 +41,7 @@ func (c *execCmd) controlSocketHandler(control *websocket.Conn) {
 
 	for {
 		sig := <-ch
-		switch sig {
-		case os.Interrupt:
-			logger.Debugf("Received '%s signal', forwarding to executing program.", sig)
-			err := c.forwardSignal(control, syscall.SIGINT)
-			if err != nil {
-				logger.Debugf("Failed to forward signal '%s'.", syscall.SIGINT)
-				return
-			}
-		default:
-			break
-		}
+
+		logger.Debugf("Received '%s signal', updating window geometry.", sig)
 	}
 }

From 1c212689c938d3385d180566665fa96f883ff381 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Jul 2017 13:44:20 -0400
Subject: [PATCH 1077/1193] client: Simplify ConnectPublicLXD logic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go | 71 ++++++++++++++++++----------------------------------
 lxc/config/remote.go | 14 +++++++++--
 2 files changed, 37 insertions(+), 48 deletions(-)

diff --git a/client/connection.go b/client/connection.go
index ae13332d3..759ac3d25 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -44,33 +44,7 @@ type ConnectionArgs struct {
 func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 	logger.Infof("Connecting to a remote LXD over HTTPs")
 
-	// Use empty args if not specified
-	if args == nil {
-		args = &ConnectionArgs{}
-	}
-
-	// Initialize the client struct
-	server := ProtocolLXD{
-		httpCertificate: args.TLSServerCert,
-		httpHost:        url,
-		httpProtocol:    "https",
-		httpUserAgent:   args.UserAgent,
-	}
-
-	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
-	if err != nil {
-		return nil, err
-	}
-	server.http = httpClient
-
-	// Test the connection and seed the server information
-	_, _, err = server.GetServer()
-	if err != nil {
-		return nil, err
-	}
-
-	return &server, nil
+	return httpsLXD(url, args)
 }
 
 // ConnectLXDUnix lets you connect to a remote LXD daemon over a local unix socket.
@@ -127,17 +101,25 @@ func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error)
 func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 	logger.Infof("Connecting to a remote public LXD over HTTPs")
 
+	return httpsLXD(url, args)
+}
+
+// ConnectSimpleStreams lets you connect to a remote SimpleStreams image server over HTTPs.
+//
+// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
+func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error) {
+	logger.Infof("Connecting to a remote simplestreams server")
+
 	// Use empty args if not specified
 	if args == nil {
 		args = &ConnectionArgs{}
 	}
 
 	// Initialize the client struct
-	server := ProtocolLXD{
-		httpCertificate: args.TLSServerCert,
+	server := ProtocolSimpleStreams{
 		httpHost:        url,
-		httpProtocol:    "https",
 		httpUserAgent:   args.UserAgent,
+		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client
@@ -147,31 +129,26 @@ func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 	}
 	server.http = httpClient
 
-	// Test the connection and seed the server information
-	_, _, err = server.GetServer()
-	if err != nil {
-		return nil, err
-	}
+	// Get simplestreams client
+	ssClient := simplestreams.NewClient(url, *httpClient, args.UserAgent)
+	server.ssClient = ssClient
 
 	return &server, nil
 }
 
-// ConnectSimpleStreams lets you connect to a remote SimpleStreams image server over HTTPs.
-//
-// Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
-func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error) {
-	logger.Infof("Connecting to a remote simplestreams server")
-
+// Internal function called by ConnectLXD and ConnectPublicLXD
+func httpsLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 	// Use empty args if not specified
 	if args == nil {
 		args = &ConnectionArgs{}
 	}
 
 	// Initialize the client struct
-	server := ProtocolSimpleStreams{
+	server := ProtocolLXD{
+		httpCertificate: args.TLSServerCert,
 		httpHost:        url,
+		httpProtocol:    "https",
 		httpUserAgent:   args.UserAgent,
-		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client
@@ -181,9 +158,11 @@ func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error)
 	}
 	server.http = httpClient
 
-	// Get simplestreams client
-	ssClient := simplestreams.NewClient(url, *httpClient, args.UserAgent)
-	server.ssClient = ssClient
+	// Test the connection and seed the server information
+	_, _, err = server.GetServer()
+	if err != nil {
+		return nil, err
+	}
 
 	return &server, nil
 }
diff --git a/lxc/config/remote.go b/lxc/config/remote.go
index c9eaf1e75..3135f3876 100644
--- a/lxc/config/remote.go
+++ b/lxc/config/remote.go
@@ -113,8 +113,18 @@ func (c *Config) GetImageServer(name string) (lxd.ImageServer, error) {
 		return d, nil
 	}
 
-	// HTTPs (LXD)
-	d, err := lxd.ConnectPublicLXD(remote.Addr, args)
+	// HTTPs (public LXD)
+	if remote.Public {
+		d, err := lxd.ConnectPublicLXD(remote.Addr, args)
+		if err != nil {
+			return nil, err
+		}
+
+		return d, nil
+	}
+
+	// HTTPs (private LXD)
+	d, err := lxd.ConnectLXD(remote.Addr, args)
 	if err != nil {
 		return nil, err
 	}

From ed845766837c4b25c85f7062daf0faabac73875c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 20 Jul 2017 00:47:12 +0200
Subject: [PATCH 1078/1193] Implement instance types as proxy for limits
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This makes it possible to use standard instance types as used by major
cloud providers to define LXD resource limits.

  lxc launch ubuntu:16.04 c1 -t t2.micro
  lxc launch ubuntu:16.04 c2 -t n1-highcpu-16
  lxc launch ubuntu:16.04 c3 -t Standard_A8_v2

LXD will attempt to find a unique match for the instance type.
If it's ambiguous, the cloud provider can be specified with:

  lxc launch ubuntu:16.04 c4 -t azure:A11

This can also be used with an internal format string to specify simple limits:

  lxc launch ubuntu:16.04 c5 -t c2.5-m2

    stgraber at castiana:~$ lxc config show c5 | grep limits
      limits.cpu: "3"
      limits.cpu.allowance: 83%
      limits.memory: 2048MB

All provided memory amounts are in GB, LXD will convert internally to MB
to allow for fractions. For fractions of CPU, LXD will apply both hard
CPU limits through pinning and a limit on CPU time to match the exact
provided limit when under load.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 doc/api-extensions.md           |   4 +
 doc/rest-api.md                 |   1 +
 lxc/init.go                     |  13 ++-
 lxc/launch.go                   |   2 +-
 lxd/api_1.0.go                  |   1 +
 lxd/container_instance_types.go | 226 ++++++++++++++++++++++++++++++++++++++++
 lxd/containers_post.go          |  13 +++
 lxd/daemon.go                   |   3 +
 shared/api/container.go         |   2 +
 test/suites/basic.sh            |   7 ++
 10 files changed, 266 insertions(+), 6 deletions(-)
 create mode 100644 lxd/container_instance_types.go

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index f03bae372..6f8960dc3 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -14,3 +14,7 @@ Enables setting the `security.idmap.isolated` and `security.idmap.isolated`,
 This introduces a new security.idmap.base allowing the user to skip the
 map auto-selection process for isolated containers and specify what host
 uid/gid to use as the base.
+
+## instance\_types
+This adds the "instance\_type" field to the container creation request.
+Its value is expanded to LXD resource limits.
diff --git a/doc/rest-api.md b/doc/rest-api.md
index 8fd46bae6..890592673 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -362,6 +362,7 @@ Input (container based on a local image with the "ubuntu/devel" alias):
                 "type": "unix-char"
             },
         },
+        "instance_type": "c2.micro",                                        # An optional instance type to use as basis for limits
         "source": {"type": "image",                                         # Can be: "image", "migration", "copy" or "none"
                    "alias": "ubuntu/devel"},                                # Name of the alias
     }
diff --git a/lxc/init.go b/lxc/init.go
index a9c28e9de..a94898b36 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -61,9 +61,10 @@ func (f *profileList) Set(value string) error {
 var initRequestedEmptyProfiles bool
 
 type initCmd struct {
-	profArgs profileList
-	confArgs configList
-	ephem    bool
+	profArgs     profileList
+	confArgs     configList
+	ephem        bool
+	instanceType string
 }
 
 func (c *initCmd) showByDefault() bool {
@@ -72,7 +73,7 @@ func (c *initCmd) showByDefault() bool {
 
 func (c *initCmd) usage() string {
 	return i18n.G(
-		`Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
+		`Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance type>]
 
 Create containers from images.
 
@@ -137,6 +138,7 @@ func (c *initCmd) flags() {
 	gnuflag.Var(&c.profArgs, "p", i18n.G("Profile to apply to the new container"))
 	gnuflag.BoolVar(&c.ephem, "ephemeral", false, i18n.G("Ephemeral container"))
 	gnuflag.BoolVar(&c.ephem, "e", false, i18n.G("Ephemeral container"))
+	gnuflag.StringVar(&c.instanceType, "t", "", i18n.G("Instance type"))
 }
 
 func (c *initCmd) run(conf *config.Config, args []string) error {
@@ -210,7 +212,8 @@ func (c *initCmd) create(conf *config.Config, args []string) (lxd.ContainerServe
 
 	// Setup container creation request
 	req := api.ContainersPost{
-		Name: name,
+		Name:         name,
+		InstanceType: c.instanceType,
 	}
 	req.Config = configMap
 	if !initRequestedEmptyProfiles && len(profiles) == 0 {
diff --git a/lxc/launch.go b/lxc/launch.go
index 68f066c9a..dafbd729b 100644
--- a/lxc/launch.go
+++ b/lxc/launch.go
@@ -18,7 +18,7 @@ func (c *launchCmd) showByDefault() bool {
 
 func (c *launchCmd) usage() string {
 	return i18n.G(
-		`Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]
+		`Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance type>]
 
 Create and start containers from images.
 
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index de4545f07..3c6471e7a 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -60,6 +60,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		APIExtensions: []string{
 			"id_map",
 			"id_map_base",
+			"resource_limits",
 		},
 		APIStatus:  "stable",
 		APIVersion: version.APIVersion,
diff --git a/lxd/container_instance_types.go b/lxd/container_instance_types.go
new file mode 100644
index 000000000..77259e3bf
--- /dev/null
+++ b/lxd/container_instance_types.go
@@ -0,0 +1,226 @@
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"gopkg.in/yaml.v2"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
+	"github.com/lxc/lxd/shared/version"
+)
+
+type instanceType struct {
+	// Amount of CPUs (can be a fraction)
+	CPU float32 `yaml:"cpu"`
+
+	// Amount of memory in GB
+	Memory float32 `yaml:"mem"`
+}
+
+var instanceTypes map[string]map[string]*instanceType
+
+func instanceSaveCache() error {
+	if instanceTypes == nil {
+		return nil
+	}
+
+	data, err := yaml.Marshal(&instanceTypes)
+	if err != nil {
+		return err
+	}
+
+	err = ioutil.WriteFile(shared.CachePath("instance_types.yaml"), data, 0600)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func instanceLoadCache() error {
+	if !shared.PathExists(shared.CachePath("instance_types.yaml")) {
+		return nil
+	}
+
+	content, err := ioutil.ReadFile(shared.CachePath("instance_types.yaml"))
+	if err != nil {
+		return err
+	}
+
+	err = yaml.Unmarshal(content, &instanceTypes)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func instanceRefreshTypes(d *Daemon) error {
+	logger.Info("Updating instance types")
+
+	// Attempt to download the new definitions
+	downloadParse := func(filename string, target interface{}) error {
+		url := fmt.Sprintf("https://images.linuxcontainers.org/meta/instance-types/%s", filename)
+
+		httpClient, err := d.httpClient("")
+		if err != nil {
+			return err
+		}
+
+		httpReq, err := http.NewRequest("GET", url, nil)
+		if err != nil {
+			return err
+		}
+
+		httpReq.Header.Set("User-Agent", version.UserAgent)
+
+		resp, err := httpClient.Do(httpReq)
+		if err != nil {
+			return err
+		}
+		defer resp.Body.Close()
+
+		if resp.StatusCode != http.StatusOK {
+			return fmt.Errorf("Failed to get %s", url)
+		}
+
+		content, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+
+		err = yaml.Unmarshal(content, target)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+
+	// Set an initial value from the cache
+	if instanceTypes == nil {
+		instanceLoadCache()
+	}
+
+	// Get the list of instance type sources
+	sources := map[string]string{}
+	err := downloadParse(".yaml", &sources)
+	if err != nil {
+		logger.Warnf("Failed to update instance types: %v", err)
+		return err
+	}
+
+	// Parse the individual files
+	newInstanceTypes := map[string]map[string]*instanceType{}
+	for name, filename := range sources {
+		types := map[string]*instanceType{}
+		err = downloadParse(filename, &types)
+		if err != nil {
+			logger.Warnf("Failed to update instance types: %v", err)
+			return err
+		}
+
+		newInstanceTypes[name] = types
+	}
+
+	// Update the global map
+	instanceTypes = newInstanceTypes
+
+	// And save in the cache
+	err = instanceSaveCache()
+	if err != nil {
+		logger.Warnf("Failed to update instance types cache: %v", err)
+		return err
+	}
+
+	logger.Infof("Done updating instance types")
+	return nil
+}
+
+func instanceParseType(value string) (map[string]string, error) {
+	sourceName := ""
+	sourceType := ""
+	fields := strings.SplitN(value, ":", 2)
+
+	// Check if the name of the source was provided
+	if len(fields) != 2 {
+		sourceType = value
+	} else {
+		sourceName = fields[0]
+		sourceType = fields[1]
+	}
+
+	// If not, lets go look for a match
+	if instanceTypes != nil && sourceName == "" {
+		for name, types := range instanceTypes {
+			_, ok := types[sourceType]
+			if ok {
+				if sourceName != "" {
+					return nil, fmt.Errorf("Ambiguous instance type provided: %s", value)
+				}
+
+				sourceName = name
+			}
+		}
+	}
+
+	// Check if we have a limit for the provided value
+	limits, ok := instanceTypes[sourceName][sourceType]
+	if !ok {
+		// Check if it's maybe just a resource limit
+		if sourceName == "" && value != "" {
+			newLimits := instanceType{}
+			fields := strings.Split(value, "-")
+			for _, field := range fields {
+				if len(field) < 2 || (field[0] != 'c' && field[0] != 'm') {
+					return nil, fmt.Errorf("Bad instance type: %s", value)
+				}
+
+				value, err := strconv.ParseFloat(field[1:], 32)
+				if err != nil {
+					return nil, err
+				}
+
+				if field[0] == 'c' {
+					newLimits.CPU = float32(value)
+				} else if field[0] == 'm' {
+					newLimits.Memory = float32(value)
+				}
+			}
+
+			limits = &newLimits
+		}
+
+		if limits == nil {
+			return nil, fmt.Errorf("Provided instance type doesn't exist: %s", value)
+		}
+	}
+	out := map[string]string{}
+
+	// Handle CPU
+	if limits.CPU > 0 {
+		cpuCores := int(limits.CPU)
+		if float32(cpuCores) < limits.CPU {
+			cpuCores++
+		}
+		cpuTime := int(limits.CPU / float32(cpuCores) * 100.0)
+
+		out["limits.cpu"] = fmt.Sprintf("%d", cpuCores)
+		if cpuTime < 100 {
+			out["limits.cpu.allowance"] = fmt.Sprintf("%d%%", cpuTime)
+		}
+	}
+
+	// Handle memory
+	if limits.Memory > 0 {
+		rawLimit := int64(limits.Memory * 1024)
+		out["limits.memory"] = fmt.Sprintf("%dMB", rawLimit)
+	}
+
+	return out, nil
+}
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 333f51bd1..040d4ba96 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -382,6 +382,19 @@ func containersPost(d *Daemon, r *http.Request) Response {
 		req.Config = map[string]string{}
 	}
 
+	if req.InstanceType != "" {
+		conf, err := instanceParseType(req.InstanceType)
+		if err != nil {
+			return BadRequest(err)
+		}
+
+		for k, v := range conf {
+			if req.Config[k] == "" {
+				req.Config[k] = v
+			}
+		}
+	}
+
 	if strings.Contains(req.Name, shared.SnapshotDelimiter) {
 		return BadRequest(fmt.Errorf("Invalid container name: '%s' is reserved for snapshots", shared.SnapshotDelimiter))
 	}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index b66f6ca5c..48474d517 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1029,6 +1029,9 @@ func (d *Daemon) Ready() error {
 		}
 	}()
 
+	/* Auto-update instance types */
+	go instanceRefreshTypes(d)
+
 	/* Restore containers */
 	containersRestart(d)
 
diff --git a/shared/api/container.go b/shared/api/container.go
index 58cac56cf..1ecfff755 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -10,6 +10,8 @@ type ContainersPost struct {
 
 	Name   string          `json:"name" yaml:"name"`
 	Source ContainerSource `json:"source" yaml:"source"`
+
+	InstanceType string `json:"instance_type" yaml:"instance_type"`
 }
 
 // ContainerPost represents the fields required to rename/move a LXD container
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index b6242a26c..aee76664e 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -258,6 +258,13 @@ test_basic_usage() {
     false
   fi
 
+  # Test instance types
+  lxc launch testimage test-limits -t c0.5-m0.2
+  [ "$(lxc config get test-limits limits.cpu)" = "1" ]
+  [ "$(lxc config get test-limits limits.cpu.allowance)" = "50%" ]
+  [ "$(lxc config get test-limits limits.memory)" = "204MB" ]
+  lxc delete -f test-limits
+
   # check that we can set the environment
   lxc exec foo pwd | grep /root
   lxc exec --env BEST_BAND=meshuggah foo env | grep meshuggah

From a1d421c778e6b1c225983eb9570ceecf7803aa89 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 27 Jul 2017 22:35:35 -0400
Subject: [PATCH 1079/1193] Add missing refresh code for instance types
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Since cloud instance type definitions change pretty regularly (new ones
added), lets try to refresh our cache once a day.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 48474d517..4b949f1ab 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1030,7 +1030,13 @@ func (d *Daemon) Ready() error {
 	}()
 
 	/* Auto-update instance types */
-	go instanceRefreshTypes(d)
+	go func() {
+		// Background update
+		for {
+			instanceRefreshTypes(d)
+			time.Sleep(24 * time.Hour)
+		}
+	}()
 
 	/* Restore containers */
 	containersRestart(d)

From c9b5ca77d45ca9b5cca87658bec57692b8e58789 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Mon, 31 Jul 2017 15:21:36 +0200
Subject: [PATCH 1080/1193] Log a warning for unknown config keys and don't
 crash.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/daemon_config.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 37dcb2080..78cd28562 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -205,7 +205,8 @@ func daemonConfigInit(db *sql.DB) error {
 	for k, v := range dbValues {
 		_, ok := daemonConfig[k]
 		if !ok {
-			logger.Error("Found invalid configuration key in database", log.Ctx{"key": k})
+			logger.Error("Found unknown configuration key in database", log.Ctx{"key": k})
+			continue
 		}
 
 		daemonConfig[k].currentValue = v

From 11382d6dfb32eb8cb5d514bb9301785fe2ed6be1 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Mon, 31 Jul 2017 17:15:45 +0200
Subject: [PATCH 1081/1193] client: fix image copy

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxc/image.go | 30 +++++++-----------------------
 1 file changed, 7 insertions(+), 23 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index f711dccbc..95a3959f2 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -243,7 +243,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		return c.doImageAlias(conf, args)
 
 	case "copy":
-		/* copy [<remote>:]<image> [<rmeote>:]<image> */
+		/* copy [<remote>:]<image> [<remote>:]<image> */
 		if len(args) != 3 {
 			return errArgs
 		}
@@ -272,29 +272,13 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		// Attempt to resolve an image alias
-		var imgInfo *api.Image
-		image := inName
-		if c.copyAliases {
-			alias, _, err := d.GetImageAlias(image)
-			if err == nil {
-				image = alias.Target
-			}
-
-			// Get the image info
-			imgInfo, _, err = d.GetImage(image)
-			if err != nil {
-				return err
-			}
-		} else {
-			// Don't fetch full image info if we don't need aliases (since it's
-			// an expensive operation)
-			imgInfo = &api.Image{}
-			imgInfo.Fingerprint = image
-			imgInfo.Public = true
+		image := c.dereferenceAlias(d, inName)
+		imgInfo, _, err := d.GetImage(image)
+		if err != nil {
+			return err
 		}
 
-		if imgInfo.Public && imgInfo.Fingerprint != inName && !strings.HasPrefix(imgInfo.Fingerprint, inName) {
+		if imgInfo.Public && imgInfo.Fingerprint != inName && !strings.HasPrefix(imgInfo.Fingerprint, image) {
 			// If dealing with an alias, set the imgInfo fingerprint to match
 			imgInfo.Fingerprint = inName
 		}
@@ -338,7 +322,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 				aliases = append(aliases, alias)
 			}
 		}
-		err = ensureImageAliases(dest, aliases, image)
+		err = ensureImageAliases(dest, aliases, imgInfo.Fingerprint)
 		return err
 
 	case "delete":

From 04b624f35d39cb5bf7c91cefea1231a28e31404d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 1 Aug 2017 12:33:49 -0400
Subject: [PATCH 1082/1193] network: Make "dev" work as a network name
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 8 ++++----
 lxd/devices.go       | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index cbe87336f..e04d1dca4 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -4933,19 +4933,19 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 	if shared.StringInSlice(m["nictype"], []string{"bridged", "p2p"}) {
 		n2 := deviceNextVeth()
 
-		_, err := shared.RunCommand("ip", "link", "add", n1, "type", "veth", "peer", "name", n2)
+		_, err := shared.RunCommand("ip", "link", "add", "dev", n1, "type", "veth", "peer", "name", n2)
 		if err != nil {
 			return "", fmt.Errorf("Failed to create the veth interface: %s", err)
 		}
 
-		_, err = shared.RunCommand("ip", "link", "set", n1, "up")
+		_, err = shared.RunCommand("ip", "link", "set", "dev", n1, "up")
 		if err != nil {
 			return "", fmt.Errorf("Failed to bring up the veth interface %s: %s", n1, err)
 		}
 
 		if m["nictype"] == "bridged" {
 			if shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", m["parent"])) {
-				_, err = shared.RunCommand("ip", "link", "set", n1, "master", m["parent"])
+				_, err = shared.RunCommand("ip", "link", "set", "dev", n1, "master", m["parent"])
 				if err != nil {
 					deviceRemoveInterface(n2)
 					return "", fmt.Errorf("Failed to add interface to bridge: %s", err)
@@ -4975,7 +4975,7 @@ func (c *containerLXC) createNetworkDevice(name string, m types.Device) (string,
 	// Handle macvlan
 	if m["nictype"] == "macvlan" {
 
-		_, err := shared.RunCommand("ip", "link", "add", n1, "link", m["parent"], "type", "macvlan", "mode", "bridge")
+		_, err := shared.RunCommand("ip", "link", "add", "dev", n1, "link", m["parent"], "type", "macvlan", "mode", "bridge")
 		if err != nil {
 			return "", fmt.Errorf("Failed to create the new macvlan interface: %s", err)
 		}
diff --git a/lxd/devices.go b/lxd/devices.go
index 49d092490..d7dec2794 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -498,7 +498,7 @@ func deviceNextVeth() string {
 }
 
 func deviceRemoveInterface(nic string) error {
-	_, err := shared.RunCommand("ip", "link", "del", nic)
+	_, err := shared.RunCommand("ip", "link", "del", "dev", nic)
 	return err
 }
 

From 9d988071139781c9b1c3888193480c4c4aec10de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 1 Aug 2017 17:48:12 -0400
Subject: [PATCH 1083/1193] Make "lxc image copy" fast again
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 95a3959f2..656b7603e 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -272,14 +272,25 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			return err
 		}
 
-		image := c.dereferenceAlias(d, inName)
-		imgInfo, _, err := d.GetImage(image)
-		if err != nil {
-			return err
+		var imgInfo *api.Image
+		if conf.Remotes[remote].Protocol == "simplestreams" && !c.copyAliases {
+			// All simplestreams images are always public, so unless we
+			// need the aliases list too, we can skip the otherwise very expensive
+			// alias resolution and image info retrieval step.
+			imgInfo = &api.Image{}
+			imgInfo.Fingerprint = inName
+			imgInfo.Public = true
+		} else {
+			// Resolve any alias and then grab the image information from the source
+			image := c.dereferenceAlias(d, inName)
+			imgInfo, _, err = d.GetImage(image)
+			if err != nil {
+				return err
+			}
 		}
 
-		if imgInfo.Public && imgInfo.Fingerprint != inName && !strings.HasPrefix(imgInfo.Fingerprint, image) {
-			// If dealing with an alias, set the imgInfo fingerprint to match
+		if imgInfo.Public && imgInfo.Fingerprint != inName && !strings.HasPrefix(imgInfo.Fingerprint, inName) {
+			// If dealing with an alias, set the imgInfo fingerprint to match the provided alias (needed for auto-update)
 			imgInfo.Fingerprint = inName
 		}
 

From 5684262d32a0cdfaf803d2b00b972df201b5042e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 2 Aug 2017 04:05:29 -0400
Subject: [PATCH 1084/1193] lxc/image: Fix aliases with simplestreams remotes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 656b7603e..6bfacb4e4 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -273,9 +273,10 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 		}
 
 		var imgInfo *api.Image
-		if conf.Remotes[remote].Protocol == "simplestreams" && !c.copyAliases {
+		var fp string
+		if conf.Remotes[remote].Protocol == "simplestreams" && !c.copyAliases && len(c.addAliases) == 0 {
 			// All simplestreams images are always public, so unless we
-			// need the aliases list too, we can skip the otherwise very expensive
+			// need the aliases list too or the real fingerprint, we can skip the otherwise very expensive
 			// alias resolution and image info retrieval step.
 			imgInfo = &api.Image{}
 			imgInfo.Fingerprint = inName
@@ -287,6 +288,9 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			if err != nil {
 				return err
 			}
+
+			// Store the fingerprint for use when creating aliases later (as imgInfo.Fingerprint may be overriden)
+			fp = imgInfo.Fingerprint
 		}
 
 		if imgInfo.Public && imgInfo.Fingerprint != inName && !strings.HasPrefix(imgInfo.Fingerprint, inName) {
@@ -333,7 +337,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 				aliases = append(aliases, alias)
 			}
 		}
-		err = ensureImageAliases(dest, aliases, imgInfo.Fingerprint)
+		err = ensureImageAliases(dest, aliases, fp)
 		return err
 
 	case "delete":

From f664ae4e03e6f1cfe54ad1a3822036287f31cb2a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 3 Aug 2017 12:50:49 -0400
Subject: [PATCH 1085/1193] lxc-to-lxd: Properly handle lxc.seccomp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 6c3ddd3c7..14104a6fa 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -89,7 +89,7 @@ keys_to_check = [
     'lxc.mount.entry',
     'lxc.cap.drop'
     # 'lxc.cap.keep',
-    # 'lxc.seccomp',
+    'lxc.seccomp',
     # 'lxc.se_context',
     ]
 
@@ -218,7 +218,7 @@ def convert_container(lxd_socket, container_name, args):
     # Load the container
     try:
         container = lxc.Container(container_name, args.lxcpath)
-    except:
+    except Exception:
         print("Invalid container configuration, skipping...")
         return False
 
@@ -326,7 +326,7 @@ def convert_container(lxd_socket, container_name, args):
     print("Processing network configuration")
     try:
         count = len(container.get_config_item("lxc.network"))
-    except:
+    except Exception:
         count = 0
 
     for i in range(count):
@@ -457,7 +457,7 @@ def convert_container(lxd_socket, container_name, args):
     # Convert seccomp
     print("Processing container seccomp configuration")
     value = config_get(lxc_config, "lxc.seccomp")
-    if value:
+    if value and value != "/usr/share/lxc/config/common.seccomp":
         print("Custom seccomp profiles aren't supported, skipping...")
         return False
 
@@ -506,7 +506,7 @@ def convert_container(lxd_socket, container_name, args):
             new['architecture'] = arches[arch[0]]
         else:
             print("Unknown architecture, assuming native.")
-    except:
+    except Exception:
         print("Couldn't find container architecture, assuming native.")
 
     # Define the container in LXD

From 368b76cb63d694a4ac8ce7e291b02283550eb3fb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 3 Aug 2017 14:57:42 -0400
Subject: [PATCH 1086/1193] lxc/remote: Don't require a crt for public remotes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3627

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/remote.go | 31 ++++++++++++++++++++++++-------
 1 file changed, 24 insertions(+), 7 deletions(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index 951651353..a70d27119 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -16,6 +16,7 @@ import (
 
 	"golang.org/x/crypto/ssh/terminal"
 
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxc/config"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -180,7 +181,12 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 	conf.Remotes[server] = config.Remote{Addr: addr, Protocol: protocol}
 
 	// Attempt to connect
-	d, err := conf.GetContainerServer(server)
+	var d interface{}
+	if public {
+		d, err = conf.GetImageServer(server)
+	} else {
+		d, err = conf.GetContainerServer(server)
+	}
 
 	// Handle Unix socket connections
 	if strings.HasPrefix(addr, "unix:") {
@@ -230,20 +236,31 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 		certOut.Close()
 
 		// Setup a new connection, this time with the remote certificate
-		d, err = conf.GetContainerServer(server)
+		if public {
+			d, err = conf.GetImageServer(server)
+		} else {
+			d, err = conf.GetContainerServer(server)
+		}
+
 		if err != nil {
 			return err
 		}
 	}
 
+	// Handle public remotes
+	if public {
+		conf.Remotes[server] = config.Remote{Addr: addr, Public: true}
+		return nil
+	}
+
 	// Get server information
-	srv, _, err := d.GetServer()
+	srv, _, err := d.(lxd.ContainerServer).GetServer()
 	if err != nil {
 		return err
 	}
 
-	// Detect a public remote
-	if srv.Public || public {
+	// Detect public remotes
+	if srv.Public {
 		conf.Remotes[server] = config.Remote{Addr: addr, Public: true}
 		return nil
 	}
@@ -275,13 +292,13 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 	}
 	req.Type = "client"
 
-	err = d.CreateCertificate(req)
+	err = d.(lxd.ContainerServer).CreateCertificate(req)
 	if err != nil {
 		return err
 	}
 
 	// And check if trusted now
-	srv, _, err = d.GetServer()
+	srv, _, err = d.(lxd.ContainerServer).GetServer()
 	if err != nil {
 		return err
 	}

From b26b1b12bd392849bb876bde3e315b2e446f575a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 3 Aug 2017 21:48:50 -0400
Subject: [PATCH 1087/1193] lxc-to-lxd: Fix bad test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 scripts/lxc-to-lxd | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 14104a6fa..c8063b8e7 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -457,7 +457,7 @@ def convert_container(lxd_socket, container_name, args):
     # Convert seccomp
     print("Processing container seccomp configuration")
     value = config_get(lxc_config, "lxc.seccomp")
-    if value and value != "/usr/share/lxc/config/common.seccomp":
+    if value and value[0] != "/usr/share/lxc/config/common.seccomp":
         print("Custom seccomp profiles aren't supported, skipping...")
         return False
 

From 3a81a2d53a61dc6d63e4de150cc9aa95eae0ebfd Mon Sep 17 00:00:00 2001
From: Andrew Wilkins <axwalk at gmail.com>
Date: Fri, 4 Aug 2017 10:46:41 +0800
Subject: [PATCH 1088/1193] lxc-to-lxd: ignore sysfs/proc mounts

These are mounted in LXD containers by default,
so just ignore them, to avoid failing in the
os.path.exists test below.

Signed-off-by: Andrew Wilkins <axwalk at gmail.com>
---
 scripts/lxc-to-lxd | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index c8063b8e7..3be903f7a 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -399,6 +399,10 @@ def convert_container(lxd_socket, container_name, args):
             print("Invalid mount configuration, skipping...")
             return False
 
+        # Ignore mounts that are present in LXD containers by default.
+        if mount[0] in ("proc", "sysfs"):
+            continue
+
         device = {'type': "disk"}
 
         # Deal with read-only mounts

From 09c8e8653984eb0ae0241bd650baed8e40794f5a Mon Sep 17 00:00:00 2001
From: Andrew Wilkins <axwalk at gmail.com>
Date: Fri, 4 Aug 2017 10:55:09 +0800
Subject: [PATCH 1089/1193] lxc-to-lxd: ignore capabilities dropped by default

These capabilities are dropped by default in LXD
containers, so ignore them rather than bailing.

Also, add a missing comma at the end of the line for
lxc.cap.drop in the list of handled attributes.

Signed-off-by: Andrew Wilkins <axwalk at gmail.com>
---
 scripts/lxc-to-lxd | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/scripts/lxc-to-lxd b/scripts/lxc-to-lxd
index 3be903f7a..b9728d346 100755
--- a/scripts/lxc-to-lxd
+++ b/scripts/lxc-to-lxd
@@ -87,7 +87,7 @@ keys_to_check = [
     'lxc.rebootsignal',
     'lxc.stopsignal',
     'lxc.mount.entry',
-    'lxc.cap.drop'
+    'lxc.cap.drop',
     # 'lxc.cap.keep',
     'lxc.seccomp',
     # 'lxc.se_context',
@@ -476,8 +476,12 @@ def convert_container(lxd_socket, container_name, args):
     print("Processing container capabilities configuration")
     value = config_get(lxc_config, "lxc.cap.drop")
     if value:
-        print("Custom capabilities aren't supported, skipping...")
-        return False
+        for cap in value:
+            # Ignore capabilities that are dropped in LXD containers by default.
+            if cap in ("mac_admin", "mac_override", "sys_module", "sys_time"):
+                continue
+            print("Custom capabilities aren't supported, skipping...")
+            return False
 
     value = config_get(lxc_config, "lxc.cap.keep")
     if value:

From 5b409f5d5dccde1c7f0f5cb98a5db4920ff7f442 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 4 Aug 2017 15:47:20 -0400
Subject: [PATCH 1090/1193] daemon: Use select and save goroutines
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/main_daemon.go | 53 ++++++++++++++++-------------------------------------
 1 file changed, 16 insertions(+), 37 deletions(-)

diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
index bbf3e8001..39e92085d 100644
--- a/lxd/main_daemon.go
+++ b/lxd/main_daemon.go
@@ -6,7 +6,6 @@ import (
 	"os/exec"
 	"os/signal"
 	"runtime/pprof"
-	"sync"
 	"syscall"
 	"time"
 
@@ -62,46 +61,26 @@ func cmdDaemon() error {
 		return err
 	}
 
-	var ret error
-	var wg sync.WaitGroup
-	wg.Add(1)
+	ch := make(chan os.Signal)
+	signal.Notify(ch, syscall.SIGPWR)
+	signal.Notify(ch, syscall.SIGINT)
+	signal.Notify(ch, syscall.SIGQUIT)
+	signal.Notify(ch, syscall.SIGTERM)
 
-	go func() {
-		ch := make(chan os.Signal)
-		signal.Notify(ch, syscall.SIGPWR)
-		sig := <-ch
+	select {
+	case sig := <-ch:
 
-		logger.Infof("Received '%s signal', shutting down containers.", sig)
-
-		containersShutdown(d)
-
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	go func() {
-		<-d.shutdownChan
+		if sig == syscall.SIGPWR {
+			logger.Infof("Received '%s signal', shutting down containers.", sig)
+			containersShutdown(d)
+		} else {
+			logger.Infof("Received '%s signal', exiting.", sig)
+		}
 
+	case <-d.shutdownChan:
 		logger.Infof("Asked to shutdown by API, shutting down containers.")
-
 		containersShutdown(d)
+	}
 
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	go func() {
-		ch := make(chan os.Signal)
-		signal.Notify(ch, syscall.SIGINT)
-		signal.Notify(ch, syscall.SIGQUIT)
-		signal.Notify(ch, syscall.SIGTERM)
-		sig := <-ch
-
-		logger.Infof("Received '%s signal', exiting.", sig)
-		ret = d.Stop()
-		wg.Done()
-	}()
-
-	wg.Wait()
-	return ret
+	return d.Stop()
 }

From 257bc06a4b0265b06ad049b5690f0e15a6f5a4ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 8 Aug 2017 18:17:47 -0400
Subject: [PATCH 1091/1193] Add support for isolcpu in CPU scheduler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3624

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/devices.go | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/lxd/devices.go b/lxd/devices.go
index d7dec2794..323c8a397 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -191,6 +191,40 @@ func deviceTaskBalance(d *Daemon) {
 			return
 		}
 	}
+
+	effectiveCpusInt, err := parseCpuset(effectiveCpus)
+	if err != nil {
+		logger.Errorf("Error parsing effective CPU set")
+		return
+	}
+
+	isolatedCpusInt := effectiveCpusInt
+
+	if shared.PathExists("/sys/devices/system/cpu/isolated") {
+		isolatedCpus, err := ioutil.ReadFile("/sys/devices/system/cpu/isolated")
+		if err != nil {
+			logger.Errorf("Error reading host's isolated cpu")
+			return
+		}
+
+		isolatedCpusInt, err = parseCpuset(strings.TrimRight(string(isolatedCpus), "\n"))
+		if err != nil {
+			logger.Errorf("Error parsing isolated CPU set: %s", string(isolatedCpus))
+			return
+		}
+	}
+
+	effectiveCpusSlice := []string{}
+	for _, id := range effectiveCpusInt {
+		if shared.IntInSlice(id, isolatedCpusInt) {
+			continue
+		}
+
+		effectiveCpusSlice = append(effectiveCpusSlice, fmt.Sprintf("%d", id))
+	}
+
+	effectiveCpus = strings.Join(effectiveCpusSlice, ",")
+
 	err = cGroupSet("cpuset", "/lxc", "cpuset.cpus", effectiveCpus)
 	if err != nil && shared.PathExists("/sys/fs/cgroup/cpuset/lxc") {
 		logger.Warn("Error setting lxd's cpuset.cpus", log.Ctx{"err": err})

From 0c6f2f9d773ed4aa3598763c7925c9d38aead803 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 9 Aug 2017 17:34:23 +0200
Subject: [PATCH 1092/1193] devices: handle empty isolcpus set

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/devices.go | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index 323c8a397..1c8ad8a62 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -6,6 +6,7 @@ import (
 	"crypto/rand"
 	"encoding/hex"
 	"fmt"
+	"io/ioutil"
 	"math/big"
 	"os"
 	"path"
@@ -201,16 +202,20 @@ func deviceTaskBalance(d *Daemon) {
 	isolatedCpusInt := effectiveCpusInt
 
 	if shared.PathExists("/sys/devices/system/cpu/isolated") {
-		isolatedCpus, err := ioutil.ReadFile("/sys/devices/system/cpu/isolated")
+		buf, err := ioutil.ReadFile("/sys/devices/system/cpu/isolated")
 		if err != nil {
 			logger.Errorf("Error reading host's isolated cpu")
 			return
 		}
 
-		isolatedCpusInt, err = parseCpuset(strings.TrimRight(string(isolatedCpus), "\n"))
-		if err != nil {
-			logger.Errorf("Error parsing isolated CPU set: %s", string(isolatedCpus))
-			return
+		// File might exist even though there are no isolated cpus.
+		isolatedCpus := strings.TrimSpace(string(buf))
+		if isolatedCpus != "" {
+			isolatedCpusInt, err = parseCpuset(isolatedCpus)
+			if err != nil {
+				logger.Errorf("Error parsing isolated CPU set: %s", string(isolatedCpus))
+				return
+			}
 		}
 	}
 

From 5a8edc60d15e8ed2f9ebbe22419728719ddf0d99 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 2 Aug 2017 11:03:04 +0200
Subject: [PATCH 1093/1193] shift xattr ACLs uid/gid

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 shared/idmapset_linux.go |  5 ++++
 shared/util_linux.go     | 66 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 70 insertions(+), 1 deletion(-)

diff --git a/shared/idmapset_linux.go b/shared/idmapset_linux.go
index 7a8648aba..da1c59e7e 100644
--- a/shared/idmapset_linux.go
+++ b/shared/idmapset_linux.go
@@ -501,6 +501,11 @@ func (set *IdmapSet) doUidshiftIntoContainer(dir string, testmode bool, how stri
 			if err != nil {
 				return err
 			}
+			err = ShiftACL(
+				path, func(uid int64, gid int64) (int64, int64) { return set.doShiftIntoNs(uid, gid, how) })
+			if err != nil {
+				return err
+			}
 		}
 		return nil
 	}
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 3c971f26b..4bb5e5912 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -18,7 +18,7 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
-// #cgo LDFLAGS: -lutil -lpthread
+// #cgo LDFLAGS: -lutil -lpthread -lacl
 /*
 #define _GNU_SOURCE
 #include <errno.h>
@@ -35,6 +35,7 @@ import (
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/un.h>
+#include <sys/acl.h>
 
 #ifndef AT_SYMLINK_FOLLOW
 #define AT_SYMLINK_FOLLOW    0x400
@@ -251,6 +252,69 @@ func ShiftOwner(basepath string, path string, uid int, gid int) error {
 	return nil
 }
 
+func ShiftACL(path string, shiftIds func(uid int64, gid int64) (int64, int64)) error {
+	finfo, err := os.Lstat(path)
+	if err != nil {
+		return err
+	}
+	if finfo.Mode()&os.ModeSymlink != 0 {
+		return nil
+	}
+
+	cpath := C.CString(path)
+	defer C.free(unsafe.Pointer(cpath))
+
+	acl := C.acl_get_file(cpath, C.ACL_TYPE_ACCESS)
+	if acl == nil {
+		return nil
+	}
+	defer C.acl_free(unsafe.Pointer(acl))
+
+	for entryId := C.ACL_FIRST_ENTRY; ; entryId = C.ACL_NEXT_ENTRY {
+		var ent C.acl_entry_t
+		var tag C.acl_tag_t
+		updateACL := false
+
+		ret := C.acl_get_entry(acl, C.int(entryId), &ent)
+		if ret != 1 {
+			break
+		}
+
+		ret = C.acl_get_tag_type(ent, &tag)
+		if ret == -1 {
+			return fmt.Errorf("Failed to change ACLs on %s", path)
+		}
+
+		idp := (*C.id_t)(C.acl_get_qualifier(ent))
+		if idp == nil {
+			continue
+		}
+
+		var newId int64
+		switch tag {
+		case C.ACL_USER:
+			newId, _ = shiftIds((int64)(*idp), -1)
+			updateACL = true
+
+		case C.ACL_GROUP:
+			_, newId = shiftIds(-1, (int64)(*idp))
+			updateACL = true
+		}
+
+		if updateACL {
+			ret = C.acl_set_qualifier(ent, unsafe.Pointer(&newId))
+			if ret == -1 {
+				return fmt.Errorf("Failed to change ACLs on %s", path)
+			}
+			ret = C.acl_set_file(cpath, C.ACL_TYPE_ACCESS, acl)
+			if ret == -1 {
+				return fmt.Errorf("Failed to change ACLs on %s", path)
+			}
+		}
+	}
+	return nil
+}
+
 func OpenPty(uid, gid int64) (master *os.File, slave *os.File, err error) {
 	fd_master := C.int(-1)
 	fd_slave := C.int(-1)

From 51d45a4932c79682d559c480c585f80e48f69e57 Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Wed, 9 Aug 2017 19:55:46 +0200
Subject: [PATCH 1094/1193] devices: do not mark all cpus isolated by default

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 lxd/devices.go | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index 1c8ad8a62..a486ba027 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -199,8 +199,7 @@ func deviceTaskBalance(d *Daemon) {
 		return
 	}
 
-	isolatedCpusInt := effectiveCpusInt
-
+	isolatedCpusInt := []int{}
 	if shared.PathExists("/sys/devices/system/cpu/isolated") {
 		buf, err := ioutil.ReadFile("/sys/devices/system/cpu/isolated")
 		if err != nil {

From 227b2b9a229bbbd2c3b0cabc695329b7360e8105 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 10 Aug 2017 12:24:01 -0400
Subject: [PATCH 1095/1193] lxc: Re-introduce remote protocol migration
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This transitions "images:" from "lxd" to "simplestreams"

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/config/file.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/lxc/config/file.go b/lxc/config/file.go
index 716277445..c8539b1c0 100644
--- a/lxc/config/file.go
+++ b/lxc/config/file.go
@@ -38,6 +38,14 @@ func LoadConfig(path string) (*Config, error) {
 		c.Remotes[k] = v
 	}
 
+	// NOTE: Remove this once we only see a small fraction of non-simplestreams users
+	// Upgrade users to the "simplestreams" protocol
+	images, ok := c.Remotes["images"]
+	if ok && images.Protocol != ImagesRemote.Protocol && images.Addr == ImagesRemote.Addr {
+		c.Remotes["images"] = ImagesRemote
+		c.SaveConfig(path)
+	}
+
 	return &c, nil
 }
 

From 3d9a1988bd690b932ac9c3b629e578b80aaa03e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 10 Aug 2017 17:33:19 -0400
Subject: [PATCH 1096/1193] shared: Move GetRemoteCertificate from lxc/remote
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3606

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/remote.go  | 32 +-------------------------------
 shared/cert.go | 30 ++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/lxc/remote.go b/lxc/remote.go
index a70d27119..f389f5ab2 100644
--- a/lxc/remote.go
+++ b/lxc/remote.go
@@ -5,7 +5,6 @@ import (
 	"encoding/pem"
 	"fmt"
 	"net"
-	"net/http"
 	"net/url"
 	"os"
 	"path/filepath"
@@ -71,35 +70,6 @@ func (c *remoteCmd) flags() {
 	gnuflag.BoolVar(&c.public, "public", false, i18n.G("Public image server"))
 }
 
-func (c *remoteCmd) getRemoteCertificate(address string) (*x509.Certificate, error) {
-	// Setup a permissive TLS config
-	tlsConfig, err := shared.GetTLSConfig("", "", "", nil)
-	if err != nil {
-		return nil, err
-	}
-
-	tlsConfig.InsecureSkipVerify = true
-	tr := &http.Transport{
-		TLSClientConfig: tlsConfig,
-		Dial:            shared.RFC3493Dialer,
-		Proxy:           shared.ProxyFromEnvironment,
-	}
-
-	// Connect
-	client := &http.Client{Transport: tr}
-	resp, err := client.Get(address)
-	if err != nil {
-		return nil, err
-	}
-
-	// Retrieve the certificate
-	if resp.TLS == nil || len(resp.TLS.PeerCertificates) == 0 {
-		return nil, fmt.Errorf(i18n.G("Unable to read remote TLS certificate"))
-	}
-
-	return resp.TLS.PeerCertificates[0], nil
-}
-
 func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, acceptCert bool, password string, public bool, protocol string) error {
 	var rScheme string
 	var rHost string
@@ -197,7 +167,7 @@ func (c *remoteCmd) addServer(conf *config.Config, server string, addr string, a
 	var certificate *x509.Certificate
 	if err != nil {
 		// Failed to connect using the system CA, so retrieve the remote certificate
-		certificate, err = c.getRemoteCertificate(addr)
+		certificate, err = shared.GetRemoteCertificate(addr)
 		if err != nil {
 			return err
 		}
diff --git a/shared/cert.go b/shared/cert.go
index b264ee1d8..d9f19df5b 100644
--- a/shared/cert.go
+++ b/shared/cert.go
@@ -17,6 +17,7 @@ import (
 	"log"
 	"math/big"
 	"net"
+	"net/http"
 	"os"
 	"os/user"
 	"path"
@@ -222,3 +223,32 @@ func CertFingerprintStr(c string) (string, error) {
 
 	return CertFingerprint(cert), nil
 }
+
+func GetRemoteCertificate(address string) (*x509.Certificate, error) {
+	// Setup a permissive TLS config
+	tlsConfig, err := GetTLSConfig("", "", "", nil)
+	if err != nil {
+		return nil, err
+	}
+
+	tlsConfig.InsecureSkipVerify = true
+	tr := &http.Transport{
+		TLSClientConfig: tlsConfig,
+		Dial:            RFC3493Dialer,
+		Proxy:           ProxyFromEnvironment,
+	}
+
+	// Connect
+	client := &http.Client{Transport: tr}
+	resp, err := client.Get(address)
+	if err != nil {
+		return nil, err
+	}
+
+	// Retrieve the certificate
+	if resp.TLS == nil || len(resp.TLS.PeerCertificates) == 0 {
+		return nil, fmt.Errorf("Unable to read remote TLS certificate")
+	}
+
+	return resp.TLS.PeerCertificates[0], nil
+}

From b32fa3b386d8ed22eca68c696706464f2186f9f8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 3 Oct 2017 16:50:55 -0400
Subject: [PATCH 1097/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 152 ++++++++++++++++++++++++++++++-----------------------------
 po/fr.po   | 152 ++++++++++++++++++++++++++++++-----------------------------
 po/ja.po   | 157 ++++++++++++++++++++++++++++++++-----------------------------
 po/lxd.pot | 150 +++++++++++++++++++++++++++++-----------------------------
 4 files changed, 311 insertions(+), 300 deletions(-)

diff --git a/po/de.po b/po/de.po
index 5d1f6ec95..20a5df5e6 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 00:05-0400\n"
+"POT-Creation-Date: 2017-10-03 16:50-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -111,7 +111,7 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:832
+#: lxc/image.go:831
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -130,11 +130,11 @@ msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:853 lxc/image.go:882
+#: lxc/image.go:852 lxc/image.go:881
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:857
+#: lxc/image.go:856
 msgid "ARCH"
 msgstr ""
 
@@ -146,27 +146,27 @@ msgstr ""
 msgid "Accept certificate"
 msgstr "Akzeptiere Zertifikat"
 
-#: lxc/remote.go:258
+#: lxc/remote.go:245
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
 
-#: lxc/image.go:431
+#: lxc/image.go:430
 #, fuzzy
 msgid "Aliases:"
 msgstr "Aliasse:\n"
 
-#: lxc/image.go:409 lxc/info.go:104
+#: lxc/image.go:408 lxc/info.go:104
 #, fuzzy, c-format
 msgid "Architecture: %s"
 msgstr "Architektur: %s\n"
 
-#: lxc/image.go:439
+#: lxc/image.go:438
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/image.go:513
+#: lxc/image.go:512
 #, fuzzy, c-format
 msgid "Bad property: %s"
 msgstr "Ungültige Abbild Eigenschaft: %s\n"
@@ -206,12 +206,12 @@ msgstr ""
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:205
+#: lxc/remote.go:181
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %s"
 msgstr "Fingerabdruck des Zertifikats: % x\n"
 
-#: lxc/remote.go:293
+#: lxc/remote.go:280
 msgid "Client certificate stored at server: "
 msgstr "Gespeichertes Nutzerzertifikat auf dem Server: "
 
@@ -223,12 +223,12 @@ msgstr ""
 msgid "Commands:"
 msgstr ""
 
-#: lxc/init.go:134 lxc/init.go:135
+#: lxc/init.go:135 lxc/init.go:136
 #, fuzzy
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:937 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:936 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
@@ -241,7 +241,7 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/copy.go:186 lxc/init.go:279
+#: lxc/copy.go:186 lxc/init.go:282
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
@@ -255,31 +255,31 @@ msgstr "Abbild mit Fingerabdruck %s importiert\n"
 msgid "Copy aliases from source"
 msgstr "Kopiere Aliasse von der Quelle"
 
-#: lxc/image.go:314
+#: lxc/image.go:313
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:220
+#: lxc/remote.go:196
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
-#: lxc/image.go:414 lxc/info.go:106
+#: lxc/image.go:413 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:188
+#: lxc/init.go:190
 #, c-format
 msgid "Creating %s"
 msgstr ""
 
-#: lxc/init.go:186
+#: lxc/init.go:188
 #, fuzzy
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:856 lxc/image.go:884
+#: lxc/image.go:855 lxc/image.go:883
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -323,7 +323,7 @@ msgstr ""
 msgid "Environment:"
 msgstr ""
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:139 lxc/init.go:140
 msgid "Ephemeral container"
 msgstr "Flüchtiger Container"
 
@@ -331,21 +331,21 @@ msgstr "Flüchtiger Container"
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:418
+#: lxc/image.go:417
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:420
+#: lxc/image.go:419
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/image.go:701
+#: lxc/image.go:700
 #, c-format
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:348 lxc/image.go:854 lxc/image.go:883
+#: lxc/config.go:348 lxc/image.go:853 lxc/image.go:882
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -378,7 +378,7 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:407
+#: lxc/image.go:406
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Fingerabdruck: %s\n"
@@ -431,20 +431,24 @@ msgstr ""
 msgid "Ignore the container state (only for start)"
 msgstr "Herunterfahren des Containers erzwingen."
 
-#: lxc/image.go:328
+#: lxc/image.go:327
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:751
+#: lxc/image.go:750
 msgid "Image exported successfully!"
 msgstr ""
 
-#: lxc/image.go:571
+#: lxc/image.go:570
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
 
-#: lxc/remote.go:136
+#: lxc/init.go:141
+msgid "Instance type"
+msgstr ""
+
+#: lxc/remote.go:107
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
@@ -480,12 +484,12 @@ msgstr ""
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:423
+#: lxc/image.go:422
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:425
+#: lxc/image.go:424
 msgid "Last used: never"
 msgstr ""
 
@@ -528,11 +532,11 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "der Name des Ursprung Containers muss angegeben werden"
 
-#: lxc/list.go:416 lxc/remote.go:377
+#: lxc/list.go:416 lxc/remote.go:364
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:351 lxc/remote.go:356
+#: lxc/remote.go:338 lxc/remote.go:343
 msgid "NO"
 msgstr ""
 
@@ -558,11 +562,11 @@ msgstr "Kein Zertifikat zum hinzufügen bereitgestellt"
 msgid "No fingerprint specified."
 msgstr "Kein Fingerabdruck angegeben."
 
-#: lxc/remote.go:121
+#: lxc/remote.go:92
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:494
+#: lxc/image.go:493
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
@@ -586,11 +590,11 @@ msgstr ""
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:379
+#: lxc/remote.go:366
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:855 lxc/remote.go:380
+#: lxc/image.go:854 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -630,7 +634,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:938
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:937
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -661,7 +665,7 @@ msgstr "Profil %s erstellt\n"
 msgid "Profile %s deleted"
 msgstr "Profil %s gelöscht\n"
 
-#: lxc/init.go:136 lxc/init.go:137
+#: lxc/init.go:137 lxc/init.go:138
 #, fuzzy
 msgid "Profile to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
@@ -676,7 +680,7 @@ msgstr "Profil %s wurde auf %s angewandt\n"
 msgid "Profiles: %s"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/image.go:427
+#: lxc/image.go:426
 #, fuzzy
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
@@ -685,7 +689,7 @@ msgstr "Eigenschaften:\n"
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:410
+#: lxc/image.go:409
 #, fuzzy, c-format
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
@@ -722,12 +726,12 @@ msgstr ""
 msgid "Restart containers."
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/init.go:251
+#: lxc/init.go:254
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:858
+#: lxc/image.go:857
 msgid "SIZE"
 msgstr ""
 
@@ -739,15 +743,15 @@ msgstr ""
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:381
+#: lxc/remote.go:368
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:213
+#: lxc/remote.go:189
 msgid "Server certificate NACKed by user"
 msgstr "Server Zertifikat vom Benutzer nicht akzeptiert"
 
-#: lxc/remote.go:290
+#: lxc/remote.go:277
 msgid "Server doesn't trust us after adding our cert"
 msgstr ""
 "Der Server vertraut uns nicht nachdem er unser Zertifikat hinzugefügt hat"
@@ -784,7 +788,7 @@ msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:408
+#: lxc/image.go:407
 #, fuzzy, c-format
 msgid "Size: %.2fMB"
 msgstr "Größe: %.2vMB\n"
@@ -798,7 +802,7 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/image.go:441
+#: lxc/image.go:440
 msgid "Source:"
 msgstr ""
 
@@ -873,7 +877,7 @@ msgstr "entfernte Instanz %s existiert bereits"
 msgid "The device doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/init.go:305
+#: lxc/init.go:308
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -899,7 +903,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Wartezeit bevor der Container gestoppt wird."
 
-#: lxc/image.go:411
+#: lxc/image.go:410
 #, fuzzy
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
@@ -913,7 +917,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:519
+#: lxc/image.go:518
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
@@ -931,11 +935,11 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:859
+#: lxc/image.go:858
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:378
+#: lxc/remote.go:365
 msgid "URL"
 msgstr ""
 
@@ -943,11 +947,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/remote.go:96
-msgid "Unable to read remote TLS certificate"
-msgstr ""
-
-#: lxc/image.go:416
+#: lxc/image.go:415
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1208,11 +1208,12 @@ msgid ""
 "    For LXD server information."
 msgstr ""
 
-#: lxc/init.go:74
+#: lxc/init.go:75
 #, fuzzy
 msgid ""
 "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
-"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance "
+"type>]\n"
 "\n"
 "Create containers from images.\n"
 "\n"
@@ -1236,7 +1237,8 @@ msgstr ""
 #, fuzzy
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
-"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance "
+"type>]\n"
 "\n"
 "Create and start containers from images.\n"
 "\n"
@@ -1613,7 +1615,7 @@ msgstr ""
 msgid "Whether or not to snapshot the container's running state"
 msgstr "Zustand des laufenden Containers sichern oder nicht"
 
-#: lxc/remote.go:353 lxc/remote.go:358
+#: lxc/remote.go:340 lxc/remote.go:345
 msgid "YES"
 msgstr ""
 
@@ -1626,23 +1628,23 @@ msgstr "der Name des Ursprung Containers muss angegeben werden"
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/remote.go:341
+#: lxc/remote.go:328
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:354
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:273
+#: lxc/init.go:276
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
-#: lxc/image.go:402
+#: lxc/image.go:401
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:404
+#: lxc/image.go:403
 msgid "enabled"
 msgstr ""
 
@@ -1656,11 +1658,11 @@ msgstr "Fehler: %v\n"
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/image.go:397 lxc/image.go:835
+#: lxc/image.go:396 lxc/image.go:834
 msgid "no"
 msgstr ""
 
-#: lxc/remote.go:206
+#: lxc/remote.go:182
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "OK (y/n)? "
@@ -1670,22 +1672,22 @@ msgstr "OK (y/n)? "
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:403
+#: lxc/remote.go:390
 #, c-format
 msgid "remote %s already exists"
 msgstr "entfernte Instanz %s existiert bereits"
 
-#: lxc/remote.go:333 lxc/remote.go:395 lxc/remote.go:430 lxc/remote.go:446
+#: lxc/remote.go:320 lxc/remote.go:382 lxc/remote.go:417 lxc/remote.go:433
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "entfernte Instanz %s existiert nicht"
 
-#: lxc/remote.go:316
+#: lxc/remote.go:303
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "entfernte Instanz %s existiert als <%s>"
 
-#: lxc/remote.go:337 lxc/remote.go:399 lxc/remote.go:434
+#: lxc/remote.go:324 lxc/remote.go:386 lxc/remote.go:421
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
@@ -1707,7 +1709,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:45 lxc/image.go:399 lxc/image.go:839
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:838
 msgid "yes"
 msgstr ""
 
diff --git a/po/fr.po b/po/fr.po
index 4e0bad3b0..ab0232562 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 00:05-0400\n"
+"POT-Creation-Date: 2017-10-03 16:50-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -68,7 +68,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:832
+#: lxc/image.go:831
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -87,11 +87,11 @@ msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:853 lxc/image.go:882
+#: lxc/image.go:852 lxc/image.go:881
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:857
+#: lxc/image.go:856
 msgid "ARCH"
 msgstr ""
 
@@ -103,26 +103,26 @@ msgstr ""
 msgid "Accept certificate"
 msgstr ""
 
-#: lxc/remote.go:258
+#: lxc/remote.go:245
 #, c-format
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
 
-#: lxc/image.go:431
+#: lxc/image.go:430
 msgid "Aliases:"
 msgstr ""
 
-#: lxc/image.go:409 lxc/info.go:104
+#: lxc/image.go:408 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr ""
 
-#: lxc/image.go:439
+#: lxc/image.go:438
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/image.go:513
+#: lxc/image.go:512
 #, fuzzy, c-format
 msgid "Bad property: %s"
 msgstr "(Image invalide: %s\n"
@@ -162,12 +162,12 @@ msgstr ""
 msgid "Cannot provide container name to list"
 msgstr ""
 
-#: lxc/remote.go:205
+#: lxc/remote.go:181
 #, fuzzy, c-format
 msgid "Certificate fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/remote.go:293
+#: lxc/remote.go:280
 msgid "Client certificate stored at server: "
 msgstr "Certificat client enregistré avec le serveur: "
 
@@ -179,11 +179,11 @@ msgstr ""
 msgid "Commands:"
 msgstr ""
 
-#: lxc/init.go:134 lxc/init.go:135
+#: lxc/init.go:135 lxc/init.go:136
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:937 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:936 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
@@ -196,7 +196,7 @@ msgstr ""
 msgid "Container name is mandatory"
 msgstr ""
 
-#: lxc/copy.go:186 lxc/init.go:279
+#: lxc/copy.go:186 lxc/init.go:282
 #, c-format
 msgid "Container name is: %s"
 msgstr ""
@@ -210,30 +210,30 @@ msgstr "Empreinte du certificat: % x\n"
 msgid "Copy aliases from source"
 msgstr ""
 
-#: lxc/image.go:314
+#: lxc/image.go:313
 #, c-format
 msgid "Copying the image: %s"
 msgstr ""
 
-#: lxc/remote.go:220
+#: lxc/remote.go:196
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
-#: lxc/image.go:414 lxc/info.go:106
+#: lxc/image.go:413 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
 
-#: lxc/init.go:188
+#: lxc/init.go:190
 #, c-format
 msgid "Creating %s"
 msgstr ""
 
-#: lxc/init.go:186
+#: lxc/init.go:188
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:856 lxc/image.go:884
+#: lxc/image.go:855 lxc/image.go:883
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -277,7 +277,7 @@ msgstr ""
 msgid "Environment:"
 msgstr ""
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:139 lxc/init.go:140
 msgid "Ephemeral container"
 msgstr ""
 
@@ -285,21 +285,21 @@ msgstr ""
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:418
+#: lxc/image.go:417
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:420
+#: lxc/image.go:419
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/image.go:701
+#: lxc/image.go:700
 #, c-format
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:348 lxc/image.go:854 lxc/image.go:883
+#: lxc/config.go:348 lxc/image.go:853 lxc/image.go:882
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -331,7 +331,7 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:407
+#: lxc/image.go:406
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -384,20 +384,24 @@ msgstr ""
 msgid "Ignore the container state (only for start)"
 msgstr "Force l'arrêt du conteneur."
 
-#: lxc/image.go:328
+#: lxc/image.go:327
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:751
+#: lxc/image.go:750
 msgid "Image exported successfully!"
 msgstr ""
 
-#: lxc/image.go:571
+#: lxc/image.go:570
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
 
-#: lxc/remote.go:136
+#: lxc/init.go:141
+msgid "Instance type"
+msgstr ""
+
+#: lxc/remote.go:107
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr ""
@@ -434,12 +438,12 @@ msgstr ""
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:423
+#: lxc/image.go:422
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:425
+#: lxc/image.go:424
 msgid "Last used: never"
 msgstr ""
 
@@ -480,11 +484,11 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr ""
 
-#: lxc/list.go:416 lxc/remote.go:377
+#: lxc/list.go:416 lxc/remote.go:364
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:351 lxc/remote.go:356
+#: lxc/remote.go:338 lxc/remote.go:343
 msgid "NO"
 msgstr ""
 
@@ -510,11 +514,11 @@ msgstr "Un certificat n'a pas été fournis"
 msgid "No fingerprint specified."
 msgstr "Aucune empreinte n'a été spécifié."
 
-#: lxc/remote.go:121
+#: lxc/remote.go:92
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:494
+#: lxc/image.go:493
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
@@ -539,11 +543,11 @@ msgstr ""
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:379
+#: lxc/remote.go:366
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:855 lxc/remote.go:380
+#: lxc/image.go:854 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -583,7 +587,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:938
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:937
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -614,7 +618,7 @@ msgstr ""
 msgid "Profile %s deleted"
 msgstr ""
 
-#: lxc/init.go:136 lxc/init.go:137
+#: lxc/init.go:137 lxc/init.go:138
 msgid "Profile to apply to the new container"
 msgstr ""
 
@@ -628,7 +632,7 @@ msgstr "Mauvaise URL pour le conteneur %s"
 msgid "Profiles: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:427
+#: lxc/image.go:426
 msgid "Properties:"
 msgstr ""
 
@@ -636,7 +640,7 @@ msgstr ""
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:410
+#: lxc/image.go:409
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -673,12 +677,12 @@ msgstr ""
 msgid "Restart containers."
 msgstr "Liste de l'information sur les conteneurs.\n"
 
-#: lxc/init.go:251
+#: lxc/init.go:254
 #, c-format
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:858
+#: lxc/image.go:857
 msgid "SIZE"
 msgstr ""
 
@@ -690,15 +694,15 @@ msgstr ""
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:381
+#: lxc/remote.go:368
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:213
+#: lxc/remote.go:189
 msgid "Server certificate NACKed by user"
 msgstr "Le certificat serveur a été rejeté par l'utilisateur"
 
-#: lxc/remote.go:290
+#: lxc/remote.go:277
 msgid "Server doesn't trust us after adding our cert"
 msgstr "Identification refuse après l'ajout du certificat client"
 
@@ -734,7 +738,7 @@ msgstr ""
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:408
+#: lxc/image.go:407
 #, c-format
 msgid "Size: %.2fMB"
 msgstr ""
@@ -748,7 +752,7 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/image.go:441
+#: lxc/image.go:440
 msgid "Source:"
 msgstr ""
 
@@ -823,7 +827,7 @@ msgstr "le serveur distant %s existe déjà"
 msgid "The device doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/init.go:305
+#: lxc/init.go:308
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -849,7 +853,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Temps d'attente avant de tuer le conteneur."
 
-#: lxc/image.go:411
+#: lxc/image.go:410
 msgid "Timestamps:"
 msgstr ""
 
@@ -862,7 +866,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:519
+#: lxc/image.go:518
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
@@ -880,11 +884,11 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:859
+#: lxc/image.go:858
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:378
+#: lxc/remote.go:365
 msgid "URL"
 msgstr ""
 
@@ -892,11 +896,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/remote.go:96
-msgid "Unable to read remote TLS certificate"
-msgstr ""
-
-#: lxc/image.go:416
+#: lxc/image.go:415
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1149,10 +1149,11 @@ msgid ""
 "    For LXD server information."
 msgstr ""
 
-#: lxc/init.go:74
+#: lxc/init.go:75
 msgid ""
 "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
-"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance "
+"type>]\n"
 "\n"
 "Create containers from images.\n"
 "\n"
@@ -1166,7 +1167,8 @@ msgstr ""
 #: lxc/launch.go:20
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
-"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance "
+"type>]\n"
 "\n"
 "Create and start containers from images.\n"
 "\n"
@@ -1462,7 +1464,7 @@ msgstr ""
 "Est-ce que l'état de fonctionement du conteneur doit être inclus dans "
 "l'instantané (snapshot)"
 
-#: lxc/remote.go:353 lxc/remote.go:358
+#: lxc/remote.go:340 lxc/remote.go:345
 msgid "YES"
 msgstr ""
 
@@ -1474,24 +1476,24 @@ msgstr ""
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr ""
 
-#: lxc/remote.go:341
+#: lxc/remote.go:328
 msgid "can't remove the default remote"
 msgstr ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:354
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:273
+#: lxc/init.go:276
 #, fuzzy
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
 
-#: lxc/image.go:402
+#: lxc/image.go:401
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:404
+#: lxc/image.go:403
 msgid "enabled"
 msgstr ""
 
@@ -1505,11 +1507,11 @@ msgstr "erreur: %v\n"
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/image.go:397 lxc/image.go:835
+#: lxc/image.go:396 lxc/image.go:834
 msgid "no"
 msgstr ""
 
-#: lxc/remote.go:206
+#: lxc/remote.go:182
 #, fuzzy
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
@@ -1519,22 +1521,22 @@ msgstr "ok (y/n)?"
 msgid "processing aliases failed %s\n"
 msgstr ""
 
-#: lxc/remote.go:403
+#: lxc/remote.go:390
 #, c-format
 msgid "remote %s already exists"
 msgstr "le serveur distant %s existe déjà"
 
-#: lxc/remote.go:333 lxc/remote.go:395 lxc/remote.go:430 lxc/remote.go:446
+#: lxc/remote.go:320 lxc/remote.go:382 lxc/remote.go:417 lxc/remote.go:433
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "le serveur distant %s n'existe pas"
 
-#: lxc/remote.go:316
+#: lxc/remote.go:303
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "le serveur distant %s existe en tant que <%s>"
 
-#: lxc/remote.go:337 lxc/remote.go:399 lxc/remote.go:434
+#: lxc/remote.go:324 lxc/remote.go:386 lxc/remote.go:421
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr ""
@@ -1556,7 +1558,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:45 lxc/image.go:399 lxc/image.go:839
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:838
 msgid "yes"
 msgstr ""
 
diff --git a/po/ja.po b/po/ja.po
index 0c247d004..5dc1ccbbf 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 00:05-0400\n"
+"POT-Creation-Date: 2017-10-03 16:50-0400\n"
 "PO-Revision-Date: 2017-09-28 21:49+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -68,7 +68,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:832
+#: lxc/image.go:831
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -86,11 +86,11 @@ msgstr "'/' はスナップショットの名前には使用できません。"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:853 lxc/image.go:882
+#: lxc/image.go:852 lxc/image.go:881
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:857
+#: lxc/image.go:856
 msgid "ARCH"
 msgstr ""
 
@@ -102,26 +102,26 @@ msgstr ""
 msgid "Accept certificate"
 msgstr "証明書のフィンガープリントの確認なしで証明書を受け入れます"
 
-#: lxc/remote.go:258
+#: lxc/remote.go:245
 #, c-format
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
 
-#: lxc/image.go:431
+#: lxc/image.go:430
 msgid "Aliases:"
 msgstr "エイリアス:"
 
-#: lxc/image.go:409 lxc/info.go:104
+#: lxc/image.go:408 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr "アーキテクチャ: %s"
 
-#: lxc/image.go:439
+#: lxc/image.go:438
 #, c-format
 msgid "Auto update: %s"
 msgstr "自動更新: %s"
 
-#: lxc/image.go:513
+#: lxc/image.go:512
 #, c-format
 msgid "Bad property: %s"
 msgstr "不正なプロパティ: %s\n"
@@ -161,12 +161,12 @@ msgstr "キー '%s' が指定されていないので削除できません。"
 msgid "Cannot provide container name to list"
 msgstr "コンテナ名を取得できません"
 
-#: lxc/remote.go:205
+#: lxc/remote.go:181
 #, c-format
 msgid "Certificate fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
 
-#: lxc/remote.go:293
+#: lxc/remote.go:280
 msgid "Client certificate stored at server: "
 msgstr "クライアント証明書がサーバに格納されました: "
 
@@ -178,11 +178,11 @@ msgstr "カラムレイアウト"
 msgid "Commands:"
 msgstr "コマンド:"
 
-#: lxc/init.go:134 lxc/init.go:135
+#: lxc/init.go:135 lxc/init.go:136
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:937 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:936 lxc/profile.go:237
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
@@ -195,7 +195,7 @@ msgstr "接続が拒否されました。LXDが実行されていますか?"
 msgid "Container name is mandatory"
 msgstr "コンテナ名を指定する必要があります"
 
-#: lxc/copy.go:186 lxc/init.go:279
+#: lxc/copy.go:186 lxc/init.go:282
 #, c-format
 msgid "Container name is: %s"
 msgstr "コンテナ名: %s"
@@ -209,30 +209,30 @@ msgstr "コンテナは以下のフィンガープリントで publish されま
 msgid "Copy aliases from source"
 msgstr "ソースからエイリアスをコピーしました"
 
-#: lxc/image.go:314
+#: lxc/image.go:313
 #, c-format
 msgid "Copying the image: %s"
 msgstr "イメージのコピー中: %s"
 
-#: lxc/remote.go:220
+#: lxc/remote.go:196
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
-#: lxc/image.go:414 lxc/info.go:106
+#: lxc/image.go:413 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr "作成日時: %s"
 
-#: lxc/init.go:188
+#: lxc/init.go:190
 #, c-format
 msgid "Creating %s"
 msgstr "%s を作成中"
 
-#: lxc/init.go:186
+#: lxc/init.go:188
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:856 lxc/image.go:884
+#: lxc/image.go:855 lxc/image.go:883
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -274,7 +274,7 @@ msgstr "環境変数を HOME=/home/foo の形式で指定します"
 msgid "Environment:"
 msgstr "環境変数:"
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:139 lxc/init.go:140
 msgid "Ephemeral container"
 msgstr "Ephemeral コンテナ"
 
@@ -282,21 +282,21 @@ msgstr "Ephemeral コンテナ"
 msgid "Event type to listen for"
 msgstr "Listenするイベントタイプ"
 
-#: lxc/image.go:418
+#: lxc/image.go:417
 #, c-format
 msgid "Expires: %s"
 msgstr "失効日時: %s"
 
-#: lxc/image.go:420
+#: lxc/image.go:419
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/image.go:701
+#: lxc/image.go:700
 #, c-format
 msgid "Exporting the image: %s"
 msgstr "イメージのエクスポート中: %s"
 
-#: lxc/config.go:348 lxc/image.go:854 lxc/image.go:883
+#: lxc/config.go:348 lxc/image.go:853 lxc/image.go:882
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -328,7 +328,7 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
-#: lxc/image.go:407
+#: lxc/image.go:406
 #, c-format
 msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
@@ -378,20 +378,24 @@ msgstr "どのコマンドを実行するか決める際にエイリアスを無
 msgid "Ignore the container state (only for start)"
 msgstr "コンテナの状態を無視します (startのみ)。"
 
-#: lxc/image.go:328
+#: lxc/image.go:327
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:751
+#: lxc/image.go:750
 msgid "Image exported successfully!"
 msgstr "イメージのエクスポートに成功しました!"
 
-#: lxc/image.go:571
+#: lxc/image.go:570
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
 
-#: lxc/remote.go:136
+#: lxc/init.go:141
+msgid "Instance type"
+msgstr ""
+
+#: lxc/remote.go:107
 #, c-format
 msgid "Invalid URL scheme \"%s\" in \"%s\""
 msgstr "不正な URL スキーム \"%s\" (\"%s\" 内)"
@@ -426,12 +430,12 @@ msgstr "最初にコピーした後も常にイメージを最新の状態に保
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
-#: lxc/image.go:423
+#: lxc/image.go:422
 #, c-format
 msgid "Last used: %s"
 msgstr "最終使用: %s"
 
-#: lxc/image.go:425
+#: lxc/image.go:424
 msgid "Last used: never"
 msgstr "最終使用: 未使用"
 
@@ -473,11 +477,11 @@ msgstr ""
 msgid "Must supply container name for: "
 msgstr "コンテナ名を指定する必要があります: "
 
-#: lxc/list.go:416 lxc/remote.go:377
+#: lxc/list.go:416 lxc/remote.go:364
 msgid "NAME"
 msgstr ""
 
-#: lxc/remote.go:351 lxc/remote.go:356
+#: lxc/remote.go:338 lxc/remote.go:343
 msgid "NO"
 msgstr ""
 
@@ -502,11 +506,11 @@ msgstr "追加すべき証明書が提供されていません"
 msgid "No fingerprint specified."
 msgstr "フィンガープリントが指定されていません。"
 
-#: lxc/remote.go:121
+#: lxc/remote.go:92
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
-#: lxc/image.go:494
+#: lxc/image.go:493
 msgid "Only https:// is supported for remote image import."
 msgstr "リモートイメージのインポートは https:// のみをサポートします。"
 
@@ -530,11 +534,11 @@ msgstr ""
 msgid "PROFILES"
 msgstr ""
 
-#: lxc/remote.go:379
+#: lxc/remote.go:366
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:855 lxc/remote.go:380
+#: lxc/image.go:854 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -571,7 +575,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:938
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:937
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -602,7 +606,7 @@ msgstr "プロファイル %s を作成しました"
 msgid "Profile %s deleted"
 msgstr "プロファイル %s を削除しました"
 
-#: lxc/init.go:136 lxc/init.go:137
+#: lxc/init.go:137 lxc/init.go:138
 msgid "Profile to apply to the new container"
 msgstr "新しいコンテナに適用するプロファイル"
 
@@ -616,7 +620,7 @@ msgstr "プロファイル %s が %s に追加されました"
 msgid "Profiles: %s"
 msgstr "プロファイル: %s"
 
-#: lxc/image.go:427
+#: lxc/image.go:426
 msgid "Properties:"
 msgstr "プロパティ:"
 
@@ -624,7 +628,7 @@ msgstr "プロパティ:"
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
-#: lxc/image.go:410
+#: lxc/image.go:409
 #, c-format
 msgid "Public: %s"
 msgstr "パブリック: %s"
@@ -660,12 +664,12 @@ msgstr "リソース:"
 msgid "Restart containers."
 msgstr "コンテナを再起動します。"
 
-#: lxc/init.go:251
+#: lxc/init.go:254
 #, c-format
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:858
+#: lxc/image.go:857
 msgid "SIZE"
 msgstr ""
 
@@ -677,15 +681,15 @@ msgstr ""
 msgid "STATE"
 msgstr ""
 
-#: lxc/remote.go:381
+#: lxc/remote.go:368
 msgid "STATIC"
 msgstr ""
 
-#: lxc/remote.go:213
+#: lxc/remote.go:189
 msgid "Server certificate NACKed by user"
 msgstr "ユーザによりサーバ証明書が拒否されました"
 
-#: lxc/remote.go:290
+#: lxc/remote.go:277
 msgid "Server doesn't trust us after adding our cert"
 msgstr "サーバが我々の証明書を追加した後我々を信頼していません"
 
@@ -721,7 +725,7 @@ msgstr "コンテナログの最後の 100 行を表示しますか?"
 msgid "Show the expanded configuration"
 msgstr "拡張した設定を表示する"
 
-#: lxc/image.go:408
+#: lxc/image.go:407
 #, c-format
 msgid "Size: %.2fMB"
 msgstr "サイズ: %.2fMB"
@@ -735,7 +739,7 @@ msgstr "スナップショット:"
 msgid "Some containers failed to %s"
 msgstr "一部のコンテナで %s が失敗しました"
 
-#: lxc/image.go:441
+#: lxc/image.go:440
 msgid "Source:"
 msgstr "取得元:"
 
@@ -807,7 +811,7 @@ msgstr "デバイス %s は既に存在します"
 msgid "The device doesn't exist"
 msgstr "デバイスが存在しません"
 
-#: lxc/init.go:305
+#: lxc/init.go:308
 #, c-format
 msgid "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr ""
@@ -840,7 +844,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "コンテナを強制停止するまでの時間"
 
-#: lxc/image.go:411
+#: lxc/image.go:410
 msgid "Timestamps:"
 msgstr "タイムスタンプ:"
 
@@ -856,7 +860,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "イメージを転送中: %s"
 
-#: lxc/image.go:519
+#: lxc/image.go:518
 #, c-format
 msgid "Transferring image: %s"
 msgstr "イメージを転送中: %s"
@@ -874,11 +878,11 @@ msgstr "タイプ: ephemeral"
 msgid "Type: persistent"
 msgstr "タイプ: persistent"
 
-#: lxc/image.go:859
+#: lxc/image.go:858
 msgid "UPLOAD DATE"
 msgstr ""
 
-#: lxc/remote.go:378
+#: lxc/remote.go:365
 msgid "URL"
 msgstr ""
 
@@ -886,11 +890,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr "help2man が見つかりません。"
 
-#: lxc/remote.go:96
-msgid "Unable to read remote TLS certificate"
-msgstr "リモートの TLS 証明書を読めません"
-
-#: lxc/image.go:416
+#: lxc/image.go:415
 #, c-format
 msgid "Uploaded: %s"
 msgstr "アップロード日時: %s"
@@ -1341,10 +1341,12 @@ msgstr ""
 "lxc info [<remote>:]\n"
 "    LXD サーバの情報を表示します。"
 
-#: lxc/init.go:74
+#: lxc/init.go:75
+#, fuzzy
 msgid ""
 "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
-"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance "
+"type>]\n"
 "\n"
 "Create containers from images.\n"
 "\n"
@@ -1366,9 +1368,11 @@ msgstr ""
 "    lxc init ubuntu u1"
 
 #: lxc/launch.go:20
+#, fuzzy
 msgid ""
 "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--"
-"profile|-p <profile>...] [--config|-c <key=value>...]\n"
+"profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance "
+"type>]\n"
 "\n"
 "Create and start containers from images.\n"
 "\n"
@@ -1890,7 +1894,7 @@ msgstr ""
 msgid "Whether or not to snapshot the container's running state"
 msgstr "コンテナの稼動状態のスナップショットを取得するかどうか"
 
-#: lxc/remote.go:353 lxc/remote.go:358
+#: lxc/remote.go:340 lxc/remote.go:345
 msgid "YES"
 msgstr ""
 
@@ -1902,25 +1906,25 @@ msgstr "コピー元のコンテナ名を指定してください"
 msgid "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr "`lxc config profile` は廃止されました。`lxc profile` を使ってください"
 
-#: lxc/remote.go:341
+#: lxc/remote.go:328
 msgid "can't remove the default remote"
 msgstr "デフォルトのリモートは削除できません"
 
-#: lxc/remote.go:367
+#: lxc/remote.go:354
 msgid "default"
 msgstr ""
 
-#: lxc/init.go:273
+#: lxc/init.go:276
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
 "した"
 
-#: lxc/image.go:402
+#: lxc/image.go:401
 msgid "disabled"
 msgstr "無効"
 
-#: lxc/image.go:404
+#: lxc/image.go:403
 msgid "enabled"
 msgstr "有効"
 
@@ -1934,11 +1938,11 @@ msgstr "エラー: %v"
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/image.go:397 lxc/image.go:835
+#: lxc/image.go:396 lxc/image.go:834
 msgid "no"
 msgstr ""
 
-#: lxc/remote.go:206
+#: lxc/remote.go:182
 msgid "ok (y/n)?"
 msgstr "ok (y/n)?"
 
@@ -1947,22 +1951,22 @@ msgstr "ok (y/n)?"
 msgid "processing aliases failed %s\n"
 msgstr "エイリアスの処理が失敗しました %s\n"
 
-#: lxc/remote.go:403
+#: lxc/remote.go:390
 #, c-format
 msgid "remote %s already exists"
 msgstr "リモート %s は既に存在します"
 
-#: lxc/remote.go:333 lxc/remote.go:395 lxc/remote.go:430 lxc/remote.go:446
+#: lxc/remote.go:320 lxc/remote.go:382 lxc/remote.go:417 lxc/remote.go:433
 #, c-format
 msgid "remote %s doesn't exist"
 msgstr "リモート %s は存在しません"
 
-#: lxc/remote.go:316
+#: lxc/remote.go:303
 #, c-format
 msgid "remote %s exists as <%s>"
 msgstr "リモート %s は <%s> として存在します"
 
-#: lxc/remote.go:337 lxc/remote.go:399 lxc/remote.go:434
+#: lxc/remote.go:324 lxc/remote.go:386 lxc/remote.go:421
 #, c-format
 msgid "remote %s is static and cannot be modified"
 msgstr "リモート %s は static ですので変更できません"
@@ -1984,10 +1988,13 @@ msgstr "%s に取得しました"
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:45 lxc/image.go:399 lxc/image.go:839
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:838
 msgid "yes"
 msgstr ""
 
+#~ msgid "Unable to read remote TLS certificate"
+#~ msgstr "リモートの TLS 証明書を読めません"
+
 #~ msgid "can't copy to the same container name"
 #~ msgstr "同じコンテナ名へはコピーできません"
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 6dddddc85..4c33d66a0 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-10-03 00:05-0400\n"
+        "POT-Creation-Date: 2017-10-03 16:50-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -65,7 +65,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:832
+#: lxc/image.go:831
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -83,11 +83,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:853 lxc/image.go:882
+#: lxc/image.go:852 lxc/image.go:881
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:857
+#: lxc/image.go:856
 msgid   "ARCH"
 msgstr  ""
 
@@ -99,26 +99,26 @@ msgstr  ""
 msgid   "Accept certificate"
 msgstr  ""
 
-#: lxc/remote.go:258
+#: lxc/remote.go:245
 #, c-format
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:431
+#: lxc/image.go:430
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:409 lxc/info.go:104
+#: lxc/image.go:408 lxc/info.go:104
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:439
+#: lxc/image.go:438
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
 
-#: lxc/image.go:513
+#: lxc/image.go:512
 #, c-format
 msgid   "Bad property: %s"
 msgstr  ""
@@ -158,12 +158,12 @@ msgstr  ""
 msgid   "Cannot provide container name to list"
 msgstr  ""
 
-#: lxc/remote.go:205
+#: lxc/remote.go:181
 #, c-format
 msgid   "Certificate fingerprint: %s"
 msgstr  ""
 
-#: lxc/remote.go:293
+#: lxc/remote.go:280
 msgid   "Client certificate stored at server: "
 msgstr  ""
 
@@ -175,11 +175,11 @@ msgstr  ""
 msgid   "Commands:"
 msgstr  ""
 
-#: lxc/init.go:134 lxc/init.go:135
+#: lxc/init.go:135 lxc/init.go:136
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:937 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:936 lxc/profile.go:237
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -192,7 +192,7 @@ msgstr  ""
 msgid   "Container name is mandatory"
 msgstr  ""
 
-#: lxc/copy.go:186 lxc/init.go:279
+#: lxc/copy.go:186 lxc/init.go:282
 #, c-format
 msgid   "Container name is: %s"
 msgstr  ""
@@ -206,30 +206,30 @@ msgstr  ""
 msgid   "Copy aliases from source"
 msgstr  ""
 
-#: lxc/image.go:314
+#: lxc/image.go:313
 #, c-format
 msgid   "Copying the image: %s"
 msgstr  ""
 
-#: lxc/remote.go:220
+#: lxc/remote.go:196
 msgid   "Could not create server cert dir"
 msgstr  ""
 
-#: lxc/image.go:414 lxc/info.go:106
+#: lxc/image.go:413 lxc/info.go:106
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
 
-#: lxc/init.go:188
+#: lxc/init.go:190
 #, c-format
 msgid   "Creating %s"
 msgstr  ""
 
-#: lxc/init.go:186
+#: lxc/init.go:188
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:856 lxc/image.go:884
+#: lxc/image.go:855 lxc/image.go:883
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -271,7 +271,7 @@ msgstr  ""
 msgid   "Environment:"
 msgstr  ""
 
-#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:138 lxc/init.go:139
+#: lxc/copy.go:30 lxc/copy.go:31 lxc/init.go:139 lxc/init.go:140
 msgid   "Ephemeral container"
 msgstr  ""
 
@@ -279,21 +279,21 @@ msgstr  ""
 msgid   "Event type to listen for"
 msgstr  ""
 
-#: lxc/image.go:418
+#: lxc/image.go:417
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:420
+#: lxc/image.go:419
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/image.go:701
+#: lxc/image.go:700
 #, c-format
 msgid   "Exporting the image: %s"
 msgstr  ""
 
-#: lxc/config.go:348 lxc/image.go:854 lxc/image.go:883
+#: lxc/config.go:348 lxc/image.go:853 lxc/image.go:882
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -325,7 +325,7 @@ msgstr  ""
 msgid   "Fast mode (same as --columns=nsacPt)"
 msgstr  ""
 
-#: lxc/image.go:407
+#: lxc/image.go:406
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -374,20 +374,24 @@ msgstr  ""
 msgid   "Ignore the container state (only for start)"
 msgstr  ""
 
-#: lxc/image.go:328
+#: lxc/image.go:327
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:751
+#: lxc/image.go:750
 msgid   "Image exported successfully!"
 msgstr  ""
 
-#: lxc/image.go:571
+#: lxc/image.go:570
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
 
-#: lxc/remote.go:136
+#: lxc/init.go:141
+msgid   "Instance type"
+msgstr  ""
+
+#: lxc/remote.go:107
 #, c-format
 msgid   "Invalid URL scheme \"%s\" in \"%s\""
 msgstr  ""
@@ -422,12 +426,12 @@ msgstr  ""
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/image.go:423
+#: lxc/image.go:422
 #, c-format
 msgid   "Last used: %s"
 msgstr  ""
 
-#: lxc/image.go:425
+#: lxc/image.go:424
 msgid   "Last used: never"
 msgstr  ""
 
@@ -467,11 +471,11 @@ msgstr  ""
 msgid   "Must supply container name for: "
 msgstr  ""
 
-#: lxc/list.go:416 lxc/remote.go:377
+#: lxc/list.go:416 lxc/remote.go:364
 msgid   "NAME"
 msgstr  ""
 
-#: lxc/remote.go:351 lxc/remote.go:356
+#: lxc/remote.go:338 lxc/remote.go:343
 msgid   "NO"
 msgstr  ""
 
@@ -496,11 +500,11 @@ msgstr  ""
 msgid   "No fingerprint specified."
 msgstr  ""
 
-#: lxc/remote.go:121
+#: lxc/remote.go:92
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:494
+#: lxc/image.go:493
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -524,11 +528,11 @@ msgstr  ""
 msgid   "PROFILES"
 msgstr  ""
 
-#: lxc/remote.go:379
+#: lxc/remote.go:366
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:855 lxc/remote.go:380
+#: lxc/image.go:854 lxc/remote.go:367
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -565,7 +569,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:938
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:937
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -596,7 +600,7 @@ msgstr  ""
 msgid   "Profile %s deleted"
 msgstr  ""
 
-#: lxc/init.go:136 lxc/init.go:137
+#: lxc/init.go:137 lxc/init.go:138
 msgid   "Profile to apply to the new container"
 msgstr  ""
 
@@ -610,7 +614,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:427
+#: lxc/image.go:426
 msgid   "Properties:"
 msgstr  ""
 
@@ -618,7 +622,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:410
+#: lxc/image.go:409
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -653,12 +657,12 @@ msgstr  ""
 msgid   "Restart containers."
 msgstr  ""
 
-#: lxc/init.go:251
+#: lxc/init.go:254
 #, c-format
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:858
+#: lxc/image.go:857
 msgid   "SIZE"
 msgstr  ""
 
@@ -670,15 +674,15 @@ msgstr  ""
 msgid   "STATE"
 msgstr  ""
 
-#: lxc/remote.go:381
+#: lxc/remote.go:368
 msgid   "STATIC"
 msgstr  ""
 
-#: lxc/remote.go:213
+#: lxc/remote.go:189
 msgid   "Server certificate NACKed by user"
 msgstr  ""
 
-#: lxc/remote.go:290
+#: lxc/remote.go:277
 msgid   "Server doesn't trust us after adding our cert"
 msgstr  ""
 
@@ -714,7 +718,7 @@ msgstr  ""
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:408
+#: lxc/image.go:407
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -728,7 +732,7 @@ msgstr  ""
 msgid   "Some containers failed to %s"
 msgstr  ""
 
-#: lxc/image.go:441
+#: lxc/image.go:440
 msgid   "Source:"
 msgstr  ""
 
@@ -795,7 +799,7 @@ msgstr  ""
 msgid   "The device doesn't exist"
 msgstr  ""
 
-#: lxc/init.go:305
+#: lxc/init.go:308
 #, c-format
 msgid   "The local image '%s' couldn't be found, trying '%s:' instead."
 msgstr  ""
@@ -819,7 +823,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:411
+#: lxc/image.go:410
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -832,7 +836,7 @@ msgstr  ""
 msgid   "Transferring container: %s"
 msgstr  ""
 
-#: lxc/image.go:519
+#: lxc/image.go:518
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
@@ -850,11 +854,11 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:859
+#: lxc/image.go:858
 msgid   "UPLOAD DATE"
 msgstr  ""
 
-#: lxc/remote.go:378
+#: lxc/remote.go:365
 msgid   "URL"
 msgstr  ""
 
@@ -862,11 +866,7 @@ msgstr  ""
 msgid   "Unable to find help2man."
 msgstr  ""
 
-#: lxc/remote.go:96
-msgid   "Unable to read remote TLS certificate"
-msgstr  ""
-
-#: lxc/image.go:416
+#: lxc/image.go:415
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1092,8 +1092,8 @@ msgid   "Usage: lxc info [<remote>:][<container>] [--show-log]\n"
         "    For LXD server information."
 msgstr  ""
 
-#: lxc/init.go:74
-msgid   "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+#: lxc/init.go:75
+msgid   "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance type>]\n"
         "\n"
         "Create containers from images.\n"
         "\n"
@@ -1105,7 +1105,7 @@ msgid   "Usage: lxc init [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e]
 msgstr  ""
 
 #: lxc/launch.go:20
-msgid   "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...]\n"
+msgid   "Usage: lxc launch [<remote>:]<image> [<remote>:][<name>] [--ephemeral|-e] [--profile|-p <profile>...] [--config|-c <key=value>...] [--type|-t <instance type>]\n"
         "\n"
         "Create and start containers from images.\n"
         "\n"
@@ -1369,7 +1369,7 @@ msgstr  ""
 msgid   "Whether or not to snapshot the container's running state"
 msgstr  ""
 
-#: lxc/remote.go:353 lxc/remote.go:358
+#: lxc/remote.go:340 lxc/remote.go:345
 msgid   "YES"
 msgstr  ""
 
@@ -1381,23 +1381,23 @@ msgstr  ""
 msgid   "`lxc config profile` is deprecated, please use `lxc profile`"
 msgstr  ""
 
-#: lxc/remote.go:341
+#: lxc/remote.go:328
 msgid   "can't remove the default remote"
 msgstr  ""
 
-#: lxc/remote.go:367
+#: lxc/remote.go:354
 msgid   "default"
 msgstr  ""
 
-#: lxc/init.go:273
+#: lxc/init.go:276
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:402
+#: lxc/image.go:401
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:404
+#: lxc/image.go:403
 msgid   "enabled"
 msgstr  ""
 
@@ -1411,11 +1411,11 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/image.go:397 lxc/image.go:835
+#: lxc/image.go:396 lxc/image.go:834
 msgid   "no"
 msgstr  ""
 
-#: lxc/remote.go:206
+#: lxc/remote.go:182
 msgid   "ok (y/n)?"
 msgstr  ""
 
@@ -1424,22 +1424,22 @@ msgstr  ""
 msgid   "processing aliases failed %s\n"
 msgstr  ""
 
-#: lxc/remote.go:403
+#: lxc/remote.go:390
 #, c-format
 msgid   "remote %s already exists"
 msgstr  ""
 
-#: lxc/remote.go:333 lxc/remote.go:395 lxc/remote.go:430 lxc/remote.go:446
+#: lxc/remote.go:320 lxc/remote.go:382 lxc/remote.go:417 lxc/remote.go:433
 #, c-format
 msgid   "remote %s doesn't exist"
 msgstr  ""
 
-#: lxc/remote.go:316
+#: lxc/remote.go:303
 #, c-format
 msgid   "remote %s exists as <%s>"
 msgstr  ""
 
-#: lxc/remote.go:337 lxc/remote.go:399 lxc/remote.go:434
+#: lxc/remote.go:324 lxc/remote.go:386 lxc/remote.go:421
 #, c-format
 msgid   "remote %s is static and cannot be modified"
 msgstr  ""
@@ -1461,7 +1461,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:399 lxc/image.go:839
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:838
 msgid   "yes"
 msgstr  ""
 

From dd4deb77dbff94038d71f2eee89d33e04eb7b5e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 18 Aug 2017 00:56:07 -0400
Subject: [PATCH 1098/1193] client: Cleanup code duplication in image download
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/simplestreams_images.go | 39 +++++++++++++++++++++------------------
 1 file changed, 21 insertions(+), 18 deletions(-)

diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index fd82d9912..1ae9c6acf 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -2,6 +2,7 @@ package lxd
 
 import (
 	"fmt"
+	"io"
 	"strings"
 
 	"github.com/lxc/lxd/shared/api"
@@ -57,22 +58,32 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 	// Prepare the response
 	resp := ImageFileResponse{}
 
-	// Download the LXD image file
-	meta, ok := files["meta"]
-	if ok && req.MetaFile != nil {
+	// Download function
+	download := func(path string, filename string, sha256 string, target io.WriteSeeker) (int64, error) {
 		// Try over http
-		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), meta.Path)
+		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), path)
 
-		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "metadata", url, meta.Sha256, req.MetaFile)
+		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, filename, url, sha256, target)
 		if err != nil {
 			// Try over https
-			url = fmt.Sprintf("%s/%s", r.httpHost, meta.Path)
-			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "metadata", url, meta.Sha256, req.MetaFile)
+			url = fmt.Sprintf("%s/%s", r.httpHost, path)
+			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, filename, url, sha256, target)
 			if err != nil {
-				return nil, err
+				return -1, err
 			}
 		}
 
+		return size, nil
+	}
+
+	// Download the LXD image file
+	meta, ok := files["meta"]
+	if ok && req.MetaFile != nil {
+		size, err := download(meta.Path, "metadata", meta.Sha256, req.MetaFile)
+		if err != nil {
+			return nil, err
+		}
+
 		parts := strings.Split(meta.Path, "/")
 		resp.MetaName = parts[len(parts)-1]
 		resp.MetaSize = size
@@ -81,17 +92,9 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 	// Download the rootfs
 	rootfs, ok := files["root"]
 	if ok && req.RootfsFile != nil {
-		// Try over http
-		url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), rootfs.Path)
-
-		size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+		size, err := download(rootfs.Path, "rootfs", rootfs.Sha256, req.RootfsFile)
 		if err != nil {
-			// Try over https
-			url = fmt.Sprintf("%s/%s", r.httpHost, rootfs.Path)
-			size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
-			if err != nil {
-				return nil, err
-			}
+			return nil, err
 		}
 
 		parts := strings.Split(rootfs.Path, "/")

From 7ed9c52daeb5b7e6d1e59dd96ca55c4430d694fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 18 Aug 2017 01:40:37 -0400
Subject: [PATCH 1099/1193] shared/simplestreams: Add delta support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/simplestreams/simplestreams.go | 39 ++++++++++++++++++++++++++++++++++-
 1 file changed, 38 insertions(+), 1 deletion(-)

diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go
index 0615fe011..322f4fffe 100644
--- a/shared/simplestreams/simplestreams.go
+++ b/shared/simplestreams/simplestreams.go
@@ -115,8 +115,14 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 			var meta SimpleStreamsManifestProductVersionItem
 			var rootTar SimpleStreamsManifestProductVersionItem
 			var rootSquash SimpleStreamsManifestProductVersionItem
+			deltas := []SimpleStreamsManifestProductVersionItem{}
 
 			for _, item := range version.Items {
+				// Identify deltas
+				if item.FileType == "squashfs.vcdiff" {
+					deltas = append(deltas, item)
+				}
+
 				// Skip the files we don't care about
 				if !shared.StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz", "squashfs"}) {
 					continue
@@ -223,9 +229,39 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) {
 				}
 			}
 
-			downloads[fingerprint] = [][]string{
+			imgDownloads := [][]string{
 				{metaPath, metaHash, "meta", fmt.Sprintf("%d", metaSize)},
 				{rootfsPath, rootfsHash, "root", fmt.Sprintf("%d", rootfsSize)}}
+
+			// Add the deltas
+			for _, delta := range deltas {
+				srcImage, ok := product.Versions[delta.DeltaBase]
+				if !ok {
+					continue
+				}
+
+				var srcFingerprint string
+				for _, item := range srcImage.Items {
+					if item.FileType != "lxd.tar.xz" {
+						continue
+					}
+
+					srcFingerprint = item.LXDHashSha256SquashFs
+					break
+				}
+
+				if srcFingerprint == "" {
+					continue
+				}
+
+				imgDownloads = append(imgDownloads, []string{
+					delta.Path,
+					delta.HashSha256,
+					fmt.Sprintf("root.delta-%s", srcFingerprint),
+					fmt.Sprintf("%d", delta.Size)})
+			}
+
+			downloads[fingerprint] = imgDownloads
 			images = append(images, image)
 		}
 	}
@@ -261,6 +297,7 @@ type SimpleStreamsManifestProductVersionItem struct {
 	LXDHashSha256RootXz   string `json:"combined_rootxz_sha256"`
 	LXDHashSha256SquashFs string `json:"combined_squashfs_sha256"`
 	Size                  int64  `json:"size"`
+	DeltaBase             string `json:"delta_base"`
 }
 
 type SimpleStreamsIndex struct {

From 2e308ee92eed73ab317bae1763e411ef2cd1f8fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 18 Aug 2017 02:08:52 -0400
Subject: [PATCH 1100/1193] client: Add support for delta image transfer
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go           |  4 +++
 client/simplestreams_images.go | 76 ++++++++++++++++++++++++++++++++++++++----
 2 files changed, 74 insertions(+), 6 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index ed9b001a3..f6cb6006c 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -209,6 +209,10 @@ type ImageFileRequest struct {
 
 	// A canceler that can be used to interrupt some part of the image download request
 	Canceler *cancel.Canceler
+
+	// Path retriever for image delta downloads
+	// If set, it must return the path to the image file or an empty string if not available
+	DeltaSourceRetriever func(fingerprint string, file string) string
 }
 
 // The ImageFileResponse struct is used as the response for image downloads
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index 1ae9c6acf..39a1f5e87 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -3,8 +3,12 @@ package lxd
 import (
 	"fmt"
 	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
 	"strings"
 
+	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
 
@@ -92,14 +96,74 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 	// Download the rootfs
 	rootfs, ok := files["root"]
 	if ok && req.RootfsFile != nil {
-		size, err := download(rootfs.Path, "rootfs", rootfs.Sha256, req.RootfsFile)
-		if err != nil {
-			return nil, err
+		// Look for deltas (requires xdelta3)
+		downloaded := false
+		_, err := exec.LookPath("xdelta3")
+		if err == nil && req.DeltaSourceRetriever != nil {
+			for filename, file := range files {
+				if !strings.HasPrefix(filename, "root.delta-") {
+					continue
+				}
+
+				// Check if we have the source file for the delta
+				srcFingerprint := strings.Split(filename, "root.delta-")[1]
+				srcPath := req.DeltaSourceRetriever(srcFingerprint, "rootfs")
+				if srcPath == "" {
+					continue
+				}
+
+				// Create temporary file for the delta
+				deltaFile, err := ioutil.TempFile("", "lxc_image_")
+				if err != nil {
+					return nil, err
+				}
+				defer deltaFile.Close()
+				defer os.Remove(deltaFile.Name())
+
+				// Download the delta
+				_, err = download(file.Path, "rootfs delta", file.Sha256, deltaFile)
+				if err != nil {
+					return nil, err
+				}
+
+				// Create temporary file for the delta
+				patchedFile, err := ioutil.TempFile("", "lxc_image_")
+				if err != nil {
+					return nil, err
+				}
+				defer patchedFile.Close()
+				defer os.Remove(patchedFile.Name())
+
+				// Apply it
+				_, err = shared.RunCommand("xdelta3", "-f", "-d", "-s", srcPath, deltaFile.Name(), patchedFile.Name())
+				if err != nil {
+					return nil, err
+				}
+
+				// Copy to the target
+				size, err := io.Copy(req.RootfsFile, patchedFile)
+				if err != nil {
+					return nil, err
+				}
+
+				parts := strings.Split(rootfs.Path, "/")
+				resp.RootfsName = parts[len(parts)-1]
+				resp.RootfsSize = size
+				downloaded = true
+			}
 		}
 
-		parts := strings.Split(rootfs.Path, "/")
-		resp.RootfsName = parts[len(parts)-1]
-		resp.RootfsSize = size
+		// Download the whole file
+		if !downloaded {
+			size, err := download(rootfs.Path, "rootfs", rootfs.Sha256, req.RootfsFile)
+			if err != nil {
+				return nil, err
+			}
+
+			parts := strings.Split(rootfs.Path, "/")
+			resp.RootfsName = parts[len(parts)-1]
+			resp.RootfsSize = size
+		}
 	}
 
 	return &resp, nil

From 0ccd35fb520462dba7129c4ba4105b7d71b2af82 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 18 Aug 2017 02:30:27 -0400
Subject: [PATCH 1101/1193] lxd: Add support for delta images
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 7c1135381..05c0ceda0 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -362,6 +362,14 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			RootfsFile:      io.WriteSeeker(destRootfs),
 			ProgressHandler: progress,
 			Canceler:        canceler,
+			DeltaSourceRetriever: func(fingerprint string, file string) string {
+				path := shared.VarPath("images", fmt.Sprintf("%s.%s", fingerprint, file))
+				if shared.PathExists(path) {
+					return path
+				}
+
+				return ""
+			},
 		}
 
 		if secret != "" {

From 2f9f4d391a68fb432529599d2870f2d633befb60 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 18 Aug 2017 18:02:26 -0400
Subject: [PATCH 1102/1193] lxc: Fix race in progress reporter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/utils.go | 38 ++++++++++++++++++++++++++++++++++----
 1 file changed, 34 insertions(+), 4 deletions(-)

diff --git a/lxc/utils.go b/lxc/utils.go
index 1a29baa54..e203eef29 100644
--- a/lxc/utils.go
+++ b/lxc/utils.go
@@ -8,6 +8,7 @@ import (
 	"os/signal"
 	"sort"
 	"strings"
+	"sync"
 	"syscall"
 	"time"
 
@@ -23,11 +24,23 @@ type ProgressRenderer struct {
 	maxLength int
 	wait      time.Time
 	done      bool
+	lock      sync.Mutex
 }
 
 func (p *ProgressRenderer) Done(msg string) {
+	// Acquire rendering lock
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	// Check if we're already done
+	if p.done {
+		return
+	}
+
+	// Mark this renderer as done
 	p.done = true
 
+	// Print the new message
 	if msg != "" {
 		msg += "\n"
 	}
@@ -43,15 +56,22 @@ func (p *ProgressRenderer) Done(msg string) {
 }
 
 func (p *ProgressRenderer) Update(status string) {
-	if p.done {
-		return
-	}
-
+	// Wait if needed
 	timeout := p.wait.Sub(time.Now())
 	if timeout.Seconds() > 0 {
 		time.Sleep(timeout)
 	}
 
+	// Acquire rendering lock
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	// Check if we're already done
+	if p.done {
+		return
+	}
+
+	// Print the new message
 	msg := "%s"
 	if p.Format != "" {
 		msg = p.Format
@@ -69,6 +89,16 @@ func (p *ProgressRenderer) Update(status string) {
 }
 
 func (p *ProgressRenderer) Warn(status string, timeout time.Duration) {
+	// Acquire rendering lock
+	p.lock.Lock()
+	defer p.lock.Unlock()
+
+	// Check if we're already done
+	if p.done {
+		return
+	}
+
+	// Render the new message
 	p.wait = time.Now().Add(timeout)
 	msg := fmt.Sprintf("\r%s", status)
 

From 87847b696816b6a0e363b85e6f7a312bf66857b3 Mon Sep 17 00:00:00 2001
From: ajthemacboy <ajthemacboy at users.noreply.github.com>
Date: Sat, 19 Aug 2017 09:18:15 -0400
Subject: [PATCH 1103/1193] Typo: now -> know

Signed-off-by: AJ Ruckman <aj at eyestra.in>
---
 lxd/container_lxc.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index e04d1dca4..59d5a24a5 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -5699,7 +5699,7 @@ func (c *containerLXC) setNetworkLimits(name string, m types.Device) error {
 	veth := c.getHostInterface(m["name"])
 
 	if veth == "" {
-		return fmt.Errorf("LXC doesn't now about this device and the host_name property isn't set, can't find host side veth name")
+		return fmt.Errorf("LXC doesn't know about this device and the host_name property isn't set, can't find host side veth name")
 	}
 
 	// Apply max limit

From 9dae5d23828778efc3ee175036407a5f40865314 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Aug 2017 16:03:51 -0400
Subject: [PATCH 1104/1193] lxc/image: Expose the "cached" flag
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/lxc/image.go b/lxc/image.go
index 6bfacb4e4..1d9b145cd 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -398,6 +398,11 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			public = i18n.G("yes")
 		}
 
+		cached := i18n.G("no")
+		if info.Cached {
+			cached = i18n.G("yes")
+		}
+
 		autoUpdate := i18n.G("disabled")
 		if info.AutoUpdate {
 			autoUpdate = i18n.G("enabled")
@@ -435,6 +440,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 				fmt.Printf("    - %s\n", alias.Name)
 			}
 		}
+		fmt.Printf(i18n.G("Cached: %s")+"\n", cached)
 		fmt.Printf(i18n.G("Auto update: %s")+"\n", autoUpdate)
 		if info.UpdateSource != nil {
 			fmt.Println(i18n.G("Source:"))

From ac916f0124316f9f28f68130c4cd32892d2b9667 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Aug 2017 23:49:17 -0400
Subject: [PATCH 1105/1193] Another LXC 2.1 key rename, lxc.idmap
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 59d5a24a5..952102d2e 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -145,6 +145,8 @@ func lxcSetConfigItem(c *lxc.Container, key string, value string) error {
 			key = "lxc.init_uid"
 		case "lxc.init.gid":
 			key = "lxc.init_gid"
+		case "lxc.idmap":
+			key = "lxc.id_map"
 		}
 	}
 
@@ -1019,7 +1021,7 @@ func (c *containerLXC) initLXC() error {
 	if idmapset != nil {
 		lines := idmapset.ToLxcString()
 		for _, line := range lines {
-			err := lxcSetConfigItem(cc, "lxc.id_map", strings.TrimSuffix(line, "\n"))
+			err := lxcSetConfigItem(cc, "lxc.idmap", strings.TrimSuffix(line, "\n"))
 			if err != nil {
 				return err
 			}

From 065f9778f5a47b60f19522391caa8f64b0beb25d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Aug 2017 01:30:22 -0400
Subject: [PATCH 1106/1193] shared: Add wrapper to translate host paths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/util.go | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/shared/util.go b/shared/util.go
index 45dd4f3fc..bfc55cbcc 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -99,6 +99,31 @@ func IsUnixSocket(path string) bool {
 	return (stat.Mode() & os.ModeSocket) == os.ModeSocket
 }
 
+// HostPath returns the host path for the provided path
+// On a normal system, this does nothing
+// When inside of a snap environment, returns the real path
+func HostPath(path string) string {
+	// Ignore relative paths
+	if len(path) == 0 || path[1] != os.PathSeparator {
+		return path
+	}
+
+	// Check if we're running in a snap package
+	snap := os.Getenv("SNAP")
+	if snap == "" {
+		return path
+	}
+
+	// Check if the path is already snap-aware
+	for _, prefix := range []string{"/snap", "/var/snap", "/var/lib/snapd"} {
+		if strings.HasPrefix(path, prefix) {
+			return path
+		}
+	}
+
+	return fmt.Sprintf("/var/lib/snapd/hostfs%s", path)
+}
+
 // VarPath returns the provided path elements joined by a slash and
 // appended to the end of $LXD_DIR, which defaults to /var/lib/lxd.
 func VarPath(path ...string) string {

From 85347c8e43474364f01b7d6e414404168b1fd1f8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 21 Aug 2017 16:12:38 -0400
Subject: [PATCH 1107/1193] lxd/images: Carry old "cached" value on refresh
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3698

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/lxd/images.go b/lxd/images.go
index 1f4a35792..0b14b2a10 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -891,6 +891,14 @@ func autoUpdateImage(d *Daemon, op *operation, id int, info *api.Image) error {
 		return err
 	}
 
+	if info.Cached {
+		err = dbImageLastAccessInit(d.db, hash)
+		if err != nil {
+			logger.Error("Error setting cached flag", log.Ctx{"err": err, "fp": hash})
+			return err
+		}
+	}
+
 	err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedAt)
 	if err != nil {
 		logger.Error("Error setting last use date", log.Ctx{"err": err, "fp": hash})

From 1dd44a74415772eddaff2a91bf487d69689afa02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Aug 2017 01:30:42 -0400
Subject: [PATCH 1108/1193] Allow passing disk devices with the LXD snap
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3660

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_lxc.go | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 952102d2e..fefd902d0 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1333,7 +1333,7 @@ func (c *containerLXC) initLXC() error {
 			networkidx++
 		} else if m["type"] == "disk" {
 			// Prepare all the paths
-			srcPath := m["source"]
+			srcPath := shared.HostPath(m["source"])
 			tgtPath := strings.TrimPrefix(m["path"], "/")
 			devName := fmt.Sprintf("disk.%s", strings.Replace(tgtPath, "/", "-", -1))
 			devPath := filepath.Join(c.DevicesPath(), devName)
@@ -1502,7 +1502,7 @@ func (c *containerLXC) startCommon() (string, error) {
 		m := c.expandedDevices[name]
 		switch m["type"] {
 		case "disk":
-			if m["source"] != "" && !shared.PathExists(m["source"]) {
+			if m["source"] != "" && !shared.PathExists(shared.HostPath(m["source"])) {
 				return "", fmt.Errorf("Missing source '%s' for disk '%s'", m["source"], name)
 			}
 		case "nic":
@@ -5243,7 +5243,7 @@ func (c *containerLXC) removeNetworkDevice(name string, m types.Device) error {
 // Disk device handling
 func (c *containerLXC) createDiskDevice(name string, m types.Device) (string, error) {
 	// Prepare all the paths
-	srcPath := m["source"]
+	srcPath := shared.HostPath(m["source"])
 	tgtPath := strings.TrimPrefix(m["path"], "/")
 	devName := fmt.Sprintf("disk.%s", strings.Replace(tgtPath, "/", "-", -1))
 	devPath := filepath.Join(c.DevicesPath(), devName)
@@ -5487,7 +5487,7 @@ func (c *containerLXC) getDiskLimits() (map[string]deviceBlockLimit, error) {
 		}
 
 		// Set the source path
-		source := m["source"]
+		source := shared.HostPath(m["source"])
 		if source == "" {
 			source = c.RootfsPath()
 		}

From 09d9fef4900e4bb211196531b3344653654dbc49 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Aug 2017 02:35:40 -0400
Subject: [PATCH 1109/1193] Fix sorting order of devices
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Make sure we always get the exact same order.

Closes #2895

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/types/devices.go      | 13 +++++++++----
 lxd/types/devices_test.go |  2 +-
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/lxd/types/devices.go b/lxd/types/devices.go
index 7e8708075..bd7d82a90 100644
--- a/lxd/types/devices.go
+++ b/lxd/types/devices.go
@@ -132,14 +132,19 @@ 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.name < b.name
+		if a.device["path"] != b.device["path"] {
+			return a.device["path"] < b.device["path"]
 		}
-
-		return a.device["path"] < b.device["path"]
 	}
 
+	// Fallback to sorting by names
 	return a.name < b.name
 }
 
diff --git a/lxd/types/devices_test.go b/lxd/types/devices_test.go
index 4eee79195..1c5d61271 100644
--- a/lxd/types/devices_test.go
+++ b/lxd/types/devices_test.go
@@ -13,7 +13,7 @@ func TestSortableDevices(t *testing.T) {
 		"2": Device{"type": "nic"},
 	}
 
-	expected := []string{"1", "2", "4", "3"}
+	expected := []string{"4", "3", "1", "2"}
 
 	result := devices.DeviceNames()
 	if !reflect.DeepEqual(result, expected) {

From c6d6421fa15a560fe4f5ce91f5dde7d376a4b1b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 22 Aug 2017 16:38:26 -0400
Subject: [PATCH 1110/1193] Fix bad check for snap paths
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/util.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/util.go b/shared/util.go
index bfc55cbcc..a924c77b3 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -104,7 +104,7 @@ func IsUnixSocket(path string) bool {
 // When inside of a snap environment, returns the real path
 func HostPath(path string) string {
 	// Ignore relative paths
-	if len(path) == 0 || path[1] != os.PathSeparator {
+	if len(path) == 0 || path[0] != os.PathSeparator {
 		return path
 	}
 

From 265b8d00ccdd8f79b22327d9ee8eb670c42972c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Aug 2017 15:40:58 -0400
Subject: [PATCH 1111/1193] shared: Fix growing of buf in GroupId
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3711

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 shared/util_linux.go | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index 4bb5e5912..a8d92fc44 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -410,7 +410,6 @@ func GroupId(name string) (int, error) {
 	if buf == nil {
 		return -1, fmt.Errorf("allocation failed")
 	}
-	defer C.free(buf)
 
 	cname := C.CString(name)
 	defer C.free(unsafe.Pointer(cname))
@@ -421,7 +420,7 @@ again:
 		(*C.char)(buf),
 		bufSize,
 		&result)
-	if rv < 0 {
+	if rv != 0 {
 		// OOM killer will take care of us if we end up doing this too
 		// often.
 		if errno == syscall.ERANGE {
@@ -433,8 +432,11 @@ again:
 			buf = tmp
 			goto again
 		}
+
+		C.free(buf)
 		return -1, fmt.Errorf("failed group lookup: %s", syscall.Errno(rv))
 	}
+	C.free(buf)
 
 	if result == nil {
 		return -1, fmt.Errorf("unknown group %s", name)

From 3e992b6cb371ddba499c23d0843bac49ebe170d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 24 Aug 2017 19:28:10 -0400
Subject: [PATCH 1112/1193] Fix live migration (bad URL in dumpsuccess)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3715

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/lxd.go                  | 5 +----
 lxd/main_migratedumpsuccess.go | 7 ++++---
 2 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/client/lxd.go b/client/lxd.go
index 3473e317c..12ccdee88 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -76,10 +76,7 @@ func (r *ProtocolLXD) RawQuery(method string, path string, data interface{}, ETa
 //
 // This should only be used by internal LXD tools.
 func (r *ProtocolLXD) RawWebsocket(path string) (*websocket.Conn, error) {
-	// Generate the URL
-	url := fmt.Sprintf("%s%s", r.httpHost, path)
-
-	return r.rawWebsocket(url)
+	return r.websocket(path)
 }
 
 // Internal functions
diff --git a/lxd/main_migratedumpsuccess.go b/lxd/main_migratedumpsuccess.go
index 663e6f955..56fce2e11 100644
--- a/lxd/main_migratedumpsuccess.go
+++ b/lxd/main_migratedumpsuccess.go
@@ -2,7 +2,7 @@ package main
 
 import (
 	"fmt"
-	"net/url"
+	"strings"
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared/api"
@@ -18,13 +18,14 @@ func cmdMigrateDumpSuccess(args []string) error {
 		return err
 	}
 
-	conn, err := c.RawWebsocket(fmt.Sprintf("/1.0/operations/%s/websocket?%s", args[1], url.Values{"secret": []string{args[2]}}))
+	url := fmt.Sprintf("%s/websocket?secret=%s", strings.TrimPrefix(args[1], "/1.0"), args[2])
+	conn, err := c.RawWebsocket(url)
 	if err != nil {
 		return err
 	}
 	conn.Close()
 
-	resp, _, err := c.RawQuery("GET", fmt.Sprintf("/1.0/operations/%s/wait", args[1]), nil, "")
+	resp, _, err := c.RawQuery("GET", fmt.Sprintf("%s/wait", args[1]), nil, "")
 	if err != nil {
 		return err
 	}

From 95180c325b106b029c0bdc40a5bc5b7d52cf4aaf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sun, 27 Aug 2017 22:07:06 -0400
Subject: [PATCH 1113/1193] client: Reduce request logging to Debug
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go | 8 ++++----
 client/lxd.go        | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/client/connection.go b/client/connection.go
index 759ac3d25..a9474a18a 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -42,7 +42,7 @@ type ConnectionArgs struct {
 //
 // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
 func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
-	logger.Infof("Connecting to a remote LXD over HTTPs")
+	logger.Debugf("Connecting to a remote LXD over HTTPs")
 
 	return httpsLXD(url, args)
 }
@@ -52,7 +52,7 @@ func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 // If the path argument is empty, then $LXD_DIR/unix.socket will be used.
 // If that one isn't set either, then the path will default to /var/lib/lxd/unix.socket.
 func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error) {
-	logger.Infof("Connecting to a local LXD over a Unix socket")
+	logger.Debugf("Connecting to a local LXD over a Unix socket")
 
 	// Use empty args if not specified
 	if args == nil {
@@ -99,7 +99,7 @@ func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error)
 //
 // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
 func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
-	logger.Infof("Connecting to a remote public LXD over HTTPs")
+	logger.Debugf("Connecting to a remote public LXD over HTTPs")
 
 	return httpsLXD(url, args)
 }
@@ -108,7 +108,7 @@ func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 //
 // Unless the remote server is trusted by the system CA, the remote certificate must be provided (TLSServerCert).
 func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error) {
-	logger.Infof("Connecting to a remote simplestreams server")
+	logger.Debugf("Connecting to a remote simplestreams server")
 
 	// Use empty args if not specified
 	if args == nil {
diff --git a/client/lxd.go b/client/lxd.go
index 12ccdee88..f1c171ae3 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -111,7 +111,7 @@ func (r *ProtocolLXD) rawQuery(method string, url string, data interface{}, ETag
 	var err error
 
 	// Log the request
-	logger.Info("Sending request to LXD",
+	logger.Debug("Sending request to LXD",
 		"method", method,
 		"url", url,
 		"etag", ETag,

From 8966e9933dfe85ed769611fb95370d7314fa0b09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 28 Aug 2017 18:26:53 -0400
Subject: [PATCH 1114/1193] Fix ordering of compressor arguments
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Apparently some of them (pigz) are picky about this.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/images.go | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lxd/images.go b/lxd/images.go
index 0b14b2a10..12ea930fe 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -179,11 +179,12 @@ func unpackImage(d *Daemon, imagefname string, destpath string) error {
 func compressFile(path string, compress string) (string, error) {
 	reproducible := []string{"gzip"}
 
-	args := []string{path, "-c"}
+	args := []string{"-c"}
 	if shared.StringInSlice(compress, reproducible) {
 		args = append(args, "-n")
 	}
 
+	args = append(args, path)
 	cmd := exec.Command(compress, args...)
 
 	outfile, err := os.Create(path + ".compressed")

From 6dd2e21443f02fabe83fa71e8f2d2d7fe1c4718c Mon Sep 17 00:00:00 2001
From: Tatu Peltola <tatu.peltola at gmail.com>
Date: Tue, 29 Aug 2017 19:32:53 +0300
Subject: [PATCH 1115/1193] Fix handling of major and minor numbers in device
 IDs

Signed-off-by: Tatu Peltola <tatu.peltola at gmail.com>
---
 lxd/devices.go       |  4 ++--
 shared/util_linux.go | 12 ++++++++++--
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/lxd/devices.go b/lxd/devices.go
index a486ba027..c7ad0a481 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -505,8 +505,8 @@ func deviceGetAttributes(path string) (string, int, int, error) {
 	}
 
 	// Return the device information
-	major := int(stat.Rdev / 256)
-	minor := int(stat.Rdev % 256)
+	major := shared.Major(stat.Rdev)
+	minor := shared.Minor(stat.Rdev)
 	return dType, major, minor, nil
 }
 
diff --git a/shared/util_linux.go b/shared/util_linux.go
index a8d92fc44..e9a2d2bf0 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -447,6 +447,14 @@ again:
 
 // --- pure Go functions ---
 
+func Major(dev uint64) int {
+	return int(((dev >> 8) & 0xfff) | ((dev >> 32) & (0xfffff000)))
+}
+
+func Minor(dev uint64) int {
+	return int((dev & 0xff) | ((dev >> 12) & (0xfff00)))
+}
+
 func GetFileStat(p string) (uid int, gid int, major int, minor int,
 	inode uint64, nlink int, err error) {
 	var stat syscall.Stat_t
@@ -461,8 +469,8 @@ func GetFileStat(p string) (uid int, gid int, major int, minor int,
 	major = -1
 	minor = -1
 	if stat.Mode&syscall.S_IFBLK != 0 || stat.Mode&syscall.S_IFCHR != 0 {
-		major = int(stat.Rdev / 256)
-		minor = int(stat.Rdev % 256)
+		major = Major(stat.Rdev)
+		minor = Minor(stat.Rdev)
 	}
 
 	return

From 01d2fc9194e99f378ff5db75bffe2a983ea2a203 Mon Sep 17 00:00:00 2001
From: Tatu Peltola <tatu.peltola at gmail.com>
Date: Tue, 29 Aug 2017 20:52:25 +0300
Subject: [PATCH 1116/1193] Take all 32 bits of minor device number

Signed-off-by: Tatu Peltola <tatu.peltola at gmail.com>
---
 shared/util_linux.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index e9a2d2bf0..ba0070f28 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -452,7 +452,7 @@ func Major(dev uint64) int {
 }
 
 func Minor(dev uint64) int {
-	return int((dev & 0xff) | ((dev >> 12) & (0xfff00)))
+	return int((dev & 0xff) | ((dev >> 12) & (0xffffff00)))
 }
 
 func GetFileStat(p string) (uid int, gid int, major int, minor int,

From a8dc5ae62ef5ed66eba047f451915cdc6b897357 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 30 Aug 2017 14:39:16 -0400
Subject: [PATCH 1117/1193] lxd/images: Fix private image copy with partial fp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon_images.go | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 05c0ceda0..69feec333 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -486,6 +486,23 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	// Image is in the DB now, don't wipe on-disk files on failure
 	failure = false
 
+	// Check if the image path changed (private images)
+	newDestName := filepath.Join(destDir, fp)
+	if newDestName != destName {
+		err = shared.FileMove(destName, newDestName)
+		if err != nil {
+			return nil, err
+		}
+
+		if shared.PathExists(destName + ".rootfs") {
+			err = shared.FileMove(destName+".rootfs", newDestName+".rootfs")
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	// Record the image source
 	if alias != fp {
 		id, _, err := dbImageGet(d.db, fp, false, true)
 		if err != nil {

From 9d38869cce386cdc386719368b183b82739ca190 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Mon, 4 Sep 2017 12:18:41 +0000
Subject: [PATCH 1118/1193] Reset the images auto-update loop when
 configuration changes

This fixes a bug where changing the auto-update images interval with
the API would not take effect unless the daemon is restarted.

See also the comments to:

https://github.com/stgraber/lxd/commit/28e7d344175097877af7747fd5f2926e9965b17

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon_config.go | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 78cd28562..99249dd2f 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -184,7 +184,7 @@ func daemonConfigInit(db *sql.DB) error {
 		"core.trust_password":        {valueType: "string", hiddenValue: true, setter: daemonConfigSetPassword},
 
 		"images.auto_update_cached":    {valueType: "bool", defaultValue: "true"},
-		"images.auto_update_interval":  {valueType: "int", defaultValue: "6"},
+		"images.auto_update_interval":  {valueType: "int", defaultValue: "6", trigger: daemonConfigTriggerAutoUpdateInterval},
 		"images.compression_algorithm": {valueType: "string", validator: daemonConfigValidateCompression, defaultValue: "gzip"},
 		"images.remote_cache_expiry":   {valueType: "int", defaultValue: "10", trigger: daemonConfigTriggerExpiry},
 
@@ -322,6 +322,11 @@ func daemonConfigTriggerExpiry(d *Daemon, key string, value string) {
 	d.pruneChan <- true
 }
 
+func daemonConfigTriggerAutoUpdateInterval(d *Daemon, key string, value string) {
+	// Reset the auto-update interval loop
+	d.resetAutoUpdateChan <- true
+}
+
 func daemonConfigValidateCompression(d *Daemon, key string, value string) error {
 	if value == "none" {
 		return nil

From d1c75b745a75ed0a46424465164cc31b75b0e03e Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 18:05:22 +0200
Subject: [PATCH 1119/1193] Fix lxc delete --force description.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxc/delete.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxc/delete.go b/lxc/delete.go
index d112c3d03..0863cf38f 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -31,8 +31,8 @@ Delete containers and snapshots.`)
 }
 
 func (c *deleteCmd) flags() {
-	gnuflag.BoolVar(&c.force, "f", false, i18n.G("Force the removal of stopped containers"))
-	gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the removal of stopped containers"))
+	gnuflag.BoolVar(&c.force, "f", false, i18n.G("Force the removal of running containers"))
+	gnuflag.BoolVar(&c.force, "force", false, i18n.G("Force the removal of running containers"))
 	gnuflag.BoolVar(&c.interactive, "i", false, i18n.G("Require user confirmation"))
 	gnuflag.BoolVar(&c.interactive, "interactive", false, i18n.G("Require user confirmation"))
 }

From 4474a1f461e246bbff1c731089b6ba606ddcea12 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 3 Oct 2017 18:09:47 -0400
Subject: [PATCH 1120/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 103 +++++++++++++++++++++++++++++++------------------------------
 po/fr.po   | 103 +++++++++++++++++++++++++++++++------------------------------
 po/ja.po   |  92 ++++++++++++++++++++++++++++--------------------------
 po/lxd.pot |  91 ++++++++++++++++++++++++++++--------------------------
 4 files changed, 201 insertions(+), 188 deletions(-)

diff --git a/po/de.po b/po/de.po
index 20a5df5e6..b541374bb 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 16:50-0400\n"
+"POT-Creation-Date: 2017-10-03 18:09-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -111,12 +111,12 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:831
+#: lxc/image.go:837
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/utils.go:294
+#: lxc/utils.go:324
 #, c-format
 msgid "%v (interrupt two more times to force)"
 msgstr ""
@@ -130,11 +130,11 @@ msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:852 lxc/image.go:881
+#: lxc/image.go:858 lxc/image.go:887
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:856
+#: lxc/image.go:862
 msgid "ARCH"
 msgstr ""
 
@@ -151,22 +151,22 @@ msgstr "Akzeptiere Zertifikat"
 msgid "Admin password for %s: "
 msgstr "Administrator Passwort für %s: "
 
-#: lxc/image.go:430
+#: lxc/image.go:435
 #, fuzzy
 msgid "Aliases:"
 msgstr "Aliasse:\n"
 
-#: lxc/image.go:408 lxc/info.go:104
+#: lxc/image.go:413 lxc/info.go:104
 #, fuzzy, c-format
 msgid "Architecture: %s"
 msgstr "Architektur: %s\n"
 
-#: lxc/image.go:438
+#: lxc/image.go:444
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/image.go:512
+#: lxc/image.go:518
 #, fuzzy, c-format
 msgid "Bad property: %s"
 msgstr "Ungültige Abbild Eigenschaft: %s\n"
@@ -187,6 +187,15 @@ msgstr ""
 msgid "CREATED AT"
 msgstr ""
 
+#: lxc/image.go:443
+#, fuzzy, c-format
+msgid "Cached: %s"
+msgstr ""
+"Benutzung: %s\n"
+"\n"
+"Optionen:\n"
+"\n"
+
 #: lxc/config.go:156
 #, c-format
 msgid "Can't read from stdin: %s"
@@ -228,7 +237,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:936 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:942 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
@@ -264,7 +273,7 @@ msgstr ""
 msgid "Could not create server cert dir"
 msgstr "Kann Verzeichnis für Zertifikate auf dem Server nicht erstellen"
 
-#: lxc/image.go:413 lxc/info.go:106
+#: lxc/image.go:418 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
@@ -279,7 +288,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:855 lxc/image.go:883
+#: lxc/image.go:861 lxc/image.go:889
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -331,25 +340,25 @@ msgstr "Flüchtiger Container"
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:417
+#: lxc/image.go:422
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:419
+#: lxc/image.go:424
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/image.go:700
+#: lxc/image.go:706
 #, c-format
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:348 lxc/image.go:853 lxc/image.go:882
+#: lxc/config.go:348 lxc/image.go:859 lxc/image.go:888
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/utils.go:332
+#: lxc/utils.go:362
 #, c-format
 msgid "Failed to create alias %s"
 msgstr ""
@@ -369,7 +378,7 @@ msgstr ""
 msgid "Failed to get the new container name"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/utils.go:322
+#: lxc/utils.go:352
 #, c-format
 msgid "Failed to remove alias %s"
 msgstr ""
@@ -378,7 +387,7 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:406
+#: lxc/image.go:411
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Fingerabdruck: %s\n"
@@ -389,7 +398,7 @@ msgid "Force the container to shutdown"
 msgstr "Herunterfahren des Containers erzwingen."
 
 #: lxc/delete.go:34 lxc/delete.go:35
-msgid "Force the removal of stopped containers"
+msgid "Force the removal of running containers"
 msgstr ""
 
 #: lxc/main.go:49
@@ -435,11 +444,11 @@ msgstr "Herunterfahren des Containers erzwingen."
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:750
+#: lxc/image.go:756
 msgid "Image exported successfully!"
 msgstr ""
 
-#: lxc/image.go:570
+#: lxc/image.go:576
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
@@ -484,12 +493,12 @@ msgstr ""
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:422
+#: lxc/image.go:427
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:424
+#: lxc/image.go:429
 msgid "Last used: never"
 msgstr ""
 
@@ -518,7 +527,7 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/utils.go:226
+#: lxc/utils.go:256
 msgid "Missing summary."
 msgstr "Fehlende Zusammenfassung."
 
@@ -566,7 +575,7 @@ msgstr "Kein Fingerabdruck angegeben."
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:493
+#: lxc/image.go:499
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
@@ -594,7 +603,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:854 lxc/remote.go:367
+#: lxc/image.go:860 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -634,7 +643,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:937
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:943
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -680,7 +689,7 @@ msgstr "Profil %s wurde auf %s angewandt\n"
 msgid "Profiles: %s"
 msgstr "Profil %s erstellt\n"
 
-#: lxc/image.go:426
+#: lxc/image.go:431
 #, fuzzy
 msgid "Properties:"
 msgstr "Eigenschaften:\n"
@@ -689,7 +698,7 @@ msgstr "Eigenschaften:\n"
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:409
+#: lxc/image.go:414
 #, fuzzy, c-format
 msgid "Public: %s"
 msgstr "Öffentlich: %s\n"
@@ -698,7 +707,7 @@ msgstr "Öffentlich: %s\n"
 msgid "Remote admin password"
 msgstr "Entferntes Administrator Passwort"
 
-#: lxc/utils.go:285
+#: lxc/utils.go:315
 #, fuzzy
 msgid "Remote operation canceled by user"
 msgstr "Server Zertifikat vom Benutzer nicht akzeptiert"
@@ -731,7 +740,7 @@ msgstr "kann nicht zum selben Container Namen kopieren"
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:857
+#: lxc/image.go:863
 msgid "SIZE"
 msgstr ""
 
@@ -788,7 +797,7 @@ msgstr "Zeige die letzten 100 Zeilen Protokoll des Containers?"
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:407
+#: lxc/image.go:412
 #, fuzzy, c-format
 msgid "Size: %.2fMB"
 msgstr "Größe: %.2vMB\n"
@@ -802,7 +811,7 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "Anhalten des Containers fehlgeschlagen!"
 
-#: lxc/image.go:440
+#: lxc/image.go:446
 msgid "Source:"
 msgstr ""
 
@@ -903,7 +912,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Wartezeit bevor der Container gestoppt wird."
 
-#: lxc/image.go:410
+#: lxc/image.go:415
 #, fuzzy
 msgid "Timestamps:"
 msgstr "Zeitstempel:\n"
@@ -917,7 +926,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:518
+#: lxc/image.go:524
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
@@ -935,7 +944,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:858
+#: lxc/image.go:864
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -947,7 +956,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/image.go:415
+#: lxc/image.go:420
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1597,7 +1606,7 @@ msgstr ""
 msgid "User aborted delete operation."
 msgstr ""
 
-#: lxc/utils.go:290
+#: lxc/utils.go:320
 msgid ""
 "User signaled us three times, exiting. The remote operation will keep "
 "running."
@@ -1640,11 +1649,11 @@ msgstr ""
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr ""
 
-#: lxc/image.go:401
+#: lxc/image.go:406
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:403
+#: lxc/image.go:408
 msgid "enabled"
 msgstr ""
 
@@ -1658,7 +1667,7 @@ msgstr "Fehler: %v\n"
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/image.go:396 lxc/image.go:834
+#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:840
 msgid "no"
 msgstr ""
 
@@ -1709,7 +1718,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:838
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:844
 msgid "yes"
 msgstr ""
 
@@ -1934,14 +1943,6 @@ msgstr ""
 #~ "lxd help [—all]\n"
 
 #, fuzzy
-#~ msgid "Usage: %s"
-#~ msgstr ""
-#~ "Benutzung: %s\n"
-#~ "\n"
-#~ "Optionen:\n"
-#~ "\n"
-
-#, fuzzy
 #~ msgid "Profile %s added to %s"
 #~ msgstr "Profil %s wurde auf %s angewandt\n"
 
diff --git a/po/fr.po b/po/fr.po
index ab0232562..e7ee07764 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 16:50-0400\n"
+"POT-Creation-Date: 2017-10-03 18:09-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -68,12 +68,12 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:831
+#: lxc/image.go:837
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/utils.go:294
+#: lxc/utils.go:324
 #, c-format
 msgid "%v (interrupt two more times to force)"
 msgstr ""
@@ -87,11 +87,11 @@ msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:852 lxc/image.go:881
+#: lxc/image.go:858 lxc/image.go:887
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:856
+#: lxc/image.go:862
 msgid "ARCH"
 msgstr ""
 
@@ -108,21 +108,21 @@ msgstr ""
 msgid "Admin password for %s: "
 msgstr "Mot de passe administrateur pour %s: "
 
-#: lxc/image.go:430
+#: lxc/image.go:435
 msgid "Aliases:"
 msgstr ""
 
-#: lxc/image.go:408 lxc/info.go:104
+#: lxc/image.go:413 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr ""
 
-#: lxc/image.go:438
+#: lxc/image.go:444
 #, c-format
 msgid "Auto update: %s"
 msgstr ""
 
-#: lxc/image.go:512
+#: lxc/image.go:518
 #, fuzzy, c-format
 msgid "Bad property: %s"
 msgstr "(Image invalide: %s\n"
@@ -143,6 +143,15 @@ msgstr ""
 msgid "CREATED AT"
 msgstr ""
 
+#: lxc/image.go:443
+#, fuzzy, c-format
+msgid "Cached: %s"
+msgstr ""
+"Utilisation: %s\n"
+"\n"
+"Options:\n"
+"\n"
+
 #: lxc/config.go:156
 #, c-format
 msgid "Can't read from stdin: %s"
@@ -183,7 +192,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:936 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:942 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
@@ -219,7 +228,7 @@ msgstr ""
 msgid "Could not create server cert dir"
 msgstr "Le dossier de stockage des certificats serveurs n'a pas pû être créé"
 
-#: lxc/image.go:413 lxc/info.go:106
+#: lxc/image.go:418 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr ""
@@ -233,7 +242,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:855 lxc/image.go:883
+#: lxc/image.go:861 lxc/image.go:889
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -285,25 +294,25 @@ msgstr ""
 msgid "Event type to listen for"
 msgstr ""
 
-#: lxc/image.go:417
+#: lxc/image.go:422
 #, c-format
 msgid "Expires: %s"
 msgstr ""
 
-#: lxc/image.go:419
+#: lxc/image.go:424
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/image.go:700
+#: lxc/image.go:706
 #, c-format
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:348 lxc/image.go:853 lxc/image.go:882
+#: lxc/config.go:348 lxc/image.go:859 lxc/image.go:888
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/utils.go:332
+#: lxc/utils.go:362
 #, c-format
 msgid "Failed to create alias %s"
 msgstr ""
@@ -322,7 +331,7 @@ msgstr ""
 msgid "Failed to get the new container name"
 msgstr ""
 
-#: lxc/utils.go:322
+#: lxc/utils.go:352
 #, c-format
 msgid "Failed to remove alias %s"
 msgstr ""
@@ -331,7 +340,7 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr ""
 
-#: lxc/image.go:406
+#: lxc/image.go:411
 #, fuzzy, c-format
 msgid "Fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -342,7 +351,7 @@ msgid "Force the container to shutdown"
 msgstr "Force l'arrêt du conteneur."
 
 #: lxc/delete.go:34 lxc/delete.go:35
-msgid "Force the removal of stopped containers"
+msgid "Force the removal of running containers"
 msgstr ""
 
 #: lxc/main.go:49
@@ -388,11 +397,11 @@ msgstr "Force l'arrêt du conteneur."
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:750
+#: lxc/image.go:756
 msgid "Image exported successfully!"
 msgstr ""
 
-#: lxc/image.go:570
+#: lxc/image.go:576
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -438,12 +447,12 @@ msgstr ""
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr ""
 
-#: lxc/image.go:422
+#: lxc/image.go:427
 #, c-format
 msgid "Last used: %s"
 msgstr ""
 
-#: lxc/image.go:424
+#: lxc/image.go:429
 msgid "Last used: never"
 msgstr ""
 
@@ -471,7 +480,7 @@ msgstr ""
 msgid "Memory usage:"
 msgstr ""
 
-#: lxc/utils.go:226
+#: lxc/utils.go:256
 msgid "Missing summary."
 msgstr "Sommaire manquant."
 
@@ -518,7 +527,7 @@ msgstr "Aucune empreinte n'a été spécifié."
 msgid "Only https URLs are supported for simplestreams"
 msgstr ""
 
-#: lxc/image.go:493
+#: lxc/image.go:499
 msgid "Only https:// is supported for remote image import."
 msgstr ""
 
@@ -547,7 +556,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:854 lxc/remote.go:367
+#: lxc/image.go:860 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -587,7 +596,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:937
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:943
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -632,7 +641,7 @@ msgstr "Mauvaise URL pour le conteneur %s"
 msgid "Profiles: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:426
+#: lxc/image.go:431
 msgid "Properties:"
 msgstr ""
 
@@ -640,7 +649,7 @@ msgstr ""
 msgid "Public image server"
 msgstr ""
 
-#: lxc/image.go:409
+#: lxc/image.go:414
 #, c-format
 msgid "Public: %s"
 msgstr ""
@@ -649,7 +658,7 @@ msgstr ""
 msgid "Remote admin password"
 msgstr ""
 
-#: lxc/utils.go:285
+#: lxc/utils.go:315
 #, fuzzy
 msgid "Remote operation canceled by user"
 msgstr "Le certificat serveur a été rejeté par l'utilisateur"
@@ -682,7 +691,7 @@ msgstr "Liste de l'information sur les conteneurs.\n"
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:857
+#: lxc/image.go:863
 msgid "SIZE"
 msgstr ""
 
@@ -738,7 +747,7 @@ msgstr ""
 msgid "Show the expanded configuration"
 msgstr ""
 
-#: lxc/image.go:407
+#: lxc/image.go:412
 #, c-format
 msgid "Size: %.2fMB"
 msgstr ""
@@ -752,7 +761,7 @@ msgstr ""
 msgid "Some containers failed to %s"
 msgstr "L'arrêt du conteneur a échoué!"
 
-#: lxc/image.go:440
+#: lxc/image.go:446
 msgid "Source:"
 msgstr ""
 
@@ -853,7 +862,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "Temps d'attente avant de tuer le conteneur."
 
-#: lxc/image.go:410
+#: lxc/image.go:415
 msgid "Timestamps:"
 msgstr ""
 
@@ -866,7 +875,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:518
+#: lxc/image.go:524
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
@@ -884,7 +893,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:858
+#: lxc/image.go:864
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -896,7 +905,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr ""
 
-#: lxc/image.go:415
+#: lxc/image.go:420
 #, c-format
 msgid "Uploaded: %s"
 msgstr ""
@@ -1443,7 +1452,7 @@ msgstr "Montre le numéro de version de LXD.\n"
 msgid "User aborted delete operation."
 msgstr ""
 
-#: lxc/utils.go:290
+#: lxc/utils.go:320
 msgid ""
 "User signaled us three times, exiting. The remote operation will keep "
 "running."
@@ -1489,11 +1498,11 @@ msgstr ""
 msgid "didn't get any affected image, container or snapshot from server"
 msgstr "N'a pas pû obtenir de resource du serveur"
 
-#: lxc/image.go:401
+#: lxc/image.go:406
 msgid "disabled"
 msgstr ""
 
-#: lxc/image.go:403
+#: lxc/image.go:408
 msgid "enabled"
 msgstr ""
 
@@ -1507,7 +1516,7 @@ msgstr "erreur: %v\n"
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/image.go:396 lxc/image.go:834
+#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:840
 msgid "no"
 msgstr ""
 
@@ -1558,7 +1567,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:838
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:844
 msgid "yes"
 msgstr ""
 
@@ -1597,14 +1606,6 @@ msgstr ""
 #~ msgstr "Explique comment utiliser LXD.\n"
 
 #, fuzzy
-#~ msgid "Usage: %s"
-#~ msgstr ""
-#~ "Utilisation: %s\n"
-#~ "\n"
-#~ "Options:\n"
-#~ "\n"
-
-#, fuzzy
 #~ msgid ""
 #~ "Create a read-only snapshot of a container.\n"
 #~ "\n"
diff --git a/po/ja.po b/po/ja.po
index 5dc1ccbbf..ac79bab75 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 16:50-0400\n"
+"POT-Creation-Date: 2017-10-03 18:09-0400\n"
 "PO-Revision-Date: 2017-09-28 21:49+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -68,12 +68,12 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:831
+#: lxc/image.go:837
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
 
-#: lxc/utils.go:294
+#: lxc/utils.go:324
 #, c-format
 msgid "%v (interrupt two more times to force)"
 msgstr ""
@@ -86,11 +86,11 @@ msgstr "'/' はスナップショットの名前には使用できません。"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:852 lxc/image.go:881
+#: lxc/image.go:858 lxc/image.go:887
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:856
+#: lxc/image.go:862
 msgid "ARCH"
 msgstr ""
 
@@ -107,21 +107,21 @@ msgstr "証明書のフィンガープリントの確認なしで証明書を受
 msgid "Admin password for %s: "
 msgstr "%s の管理者パスワード: "
 
-#: lxc/image.go:430
+#: lxc/image.go:435
 msgid "Aliases:"
 msgstr "エイリアス:"
 
-#: lxc/image.go:408 lxc/info.go:104
+#: lxc/image.go:413 lxc/info.go:104
 #, c-format
 msgid "Architecture: %s"
 msgstr "アーキテクチャ: %s"
 
-#: lxc/image.go:438
+#: lxc/image.go:444
 #, c-format
 msgid "Auto update: %s"
 msgstr "自動更新: %s"
 
-#: lxc/image.go:512
+#: lxc/image.go:518
 #, c-format
 msgid "Bad property: %s"
 msgstr "不正なプロパティ: %s\n"
@@ -142,6 +142,11 @@ msgstr ""
 msgid "CREATED AT"
 msgstr ""
 
+#: lxc/image.go:443
+#, fuzzy, c-format
+msgid "Cached: %s"
+msgstr "作成日時: %s"
+
 #: lxc/config.go:156
 #, c-format
 msgid "Can't read from stdin: %s"
@@ -182,7 +187,7 @@ msgstr "コマンド:"
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:936 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:942 lxc/profile.go:237
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
@@ -218,7 +223,7 @@ msgstr "イメージのコピー中: %s"
 msgid "Could not create server cert dir"
 msgstr "サーバ証明書格納用のディレクトリを作成できません。"
 
-#: lxc/image.go:413 lxc/info.go:106
+#: lxc/image.go:418 lxc/info.go:106
 #, c-format
 msgid "Created: %s"
 msgstr "作成日時: %s"
@@ -232,7 +237,7 @@ msgstr "%s を作成中"
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:855 lxc/image.go:883
+#: lxc/image.go:861 lxc/image.go:889
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -282,25 +287,25 @@ msgstr "Ephemeral コンテナ"
 msgid "Event type to listen for"
 msgstr "Listenするイベントタイプ"
 
-#: lxc/image.go:417
+#: lxc/image.go:422
 #, c-format
 msgid "Expires: %s"
 msgstr "失効日時: %s"
 
-#: lxc/image.go:419
+#: lxc/image.go:424
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/image.go:700
+#: lxc/image.go:706
 #, c-format
 msgid "Exporting the image: %s"
 msgstr "イメージのエクスポート中: %s"
 
-#: lxc/config.go:348 lxc/image.go:853 lxc/image.go:882
+#: lxc/config.go:348 lxc/image.go:859 lxc/image.go:888
 msgid "FINGERPRINT"
 msgstr ""
 
-#: lxc/utils.go:332
+#: lxc/utils.go:362
 #, fuzzy, c-format
 msgid "Failed to create alias %s"
 msgstr "'lxc.%s.1' の生成が失敗しました: %v"
@@ -319,7 +324,7 @@ msgstr "'lxc.1' の生成が失敗しました: %v"
 msgid "Failed to get the new container name"
 msgstr "コピー先のコンテナ名が取得できませんでした"
 
-#: lxc/utils.go:322
+#: lxc/utils.go:352
 #, c-format
 msgid "Failed to remove alias %s"
 msgstr ""
@@ -328,7 +333,7 @@ msgstr ""
 msgid "Fast mode (same as --columns=nsacPt)"
 msgstr "Fast モード (--columns=nsacPt と同じ)"
 
-#: lxc/image.go:406
+#: lxc/image.go:411
 #, c-format
 msgid "Fingerprint: %s"
 msgstr "証明書のフィンガープリント: %s"
@@ -338,7 +343,8 @@ msgid "Force the container to shutdown"
 msgstr "コンテナを強制シャットダウンします。"
 
 #: lxc/delete.go:34 lxc/delete.go:35
-msgid "Force the removal of stopped containers"
+#, fuzzy
+msgid "Force the removal of running containers"
 msgstr "停止したコンテナを強制的に削除します。"
 
 #: lxc/main.go:49
@@ -382,11 +388,11 @@ msgstr "コンテナの状態を無視します (startのみ)。"
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:750
+#: lxc/image.go:756
 msgid "Image exported successfully!"
 msgstr "イメージのエクスポートに成功しました!"
 
-#: lxc/image.go:570
+#: lxc/image.go:576
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
@@ -430,12 +436,12 @@ msgstr "最初にコピーした後も常にイメージを最新の状態に保
 msgid "LXD socket not found; is LXD installed and running?"
 msgstr "LXD のソケットが見つかりません。LXD が実行されていますか?"
 
-#: lxc/image.go:422
+#: lxc/image.go:427
 #, c-format
 msgid "Last used: %s"
 msgstr "最終使用: %s"
 
-#: lxc/image.go:424
+#: lxc/image.go:429
 msgid "Last used: never"
 msgstr "最終使用: 未使用"
 
@@ -463,7 +469,7 @@ msgstr "メモリ (ピーク)"
 msgid "Memory usage:"
 msgstr "メモリ消費量:"
 
-#: lxc/utils.go:226
+#: lxc/utils.go:256
 msgid "Missing summary."
 msgstr "サマリーはありません。"
 
@@ -510,7 +516,7 @@ msgstr "フィンガープリントが指定されていません。"
 msgid "Only https URLs are supported for simplestreams"
 msgstr "simplestreams は https の URL のみサポートします"
 
-#: lxc/image.go:493
+#: lxc/image.go:499
 msgid "Only https:// is supported for remote image import."
 msgstr "リモートイメージのインポートは https:// のみをサポートします。"
 
@@ -538,7 +544,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:854 lxc/remote.go:367
+#: lxc/image.go:860 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -575,7 +581,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:937
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:943
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -620,7 +626,7 @@ msgstr "プロファイル %s が %s に追加されました"
 msgid "Profiles: %s"
 msgstr "プロファイル: %s"
 
-#: lxc/image.go:426
+#: lxc/image.go:431
 msgid "Properties:"
 msgstr "プロパティ:"
 
@@ -628,7 +634,7 @@ msgstr "プロパティ:"
 msgid "Public image server"
 msgstr "Public なイメージサーバとして設定します"
 
-#: lxc/image.go:409
+#: lxc/image.go:414
 #, c-format
 msgid "Public: %s"
 msgstr "パブリック: %s"
@@ -637,7 +643,7 @@ msgstr "パブリック: %s"
 msgid "Remote admin password"
 msgstr "リモートの管理者パスワード"
 
-#: lxc/utils.go:285
+#: lxc/utils.go:315
 #, fuzzy
 msgid "Remote operation canceled by user"
 msgstr "ユーザによりサーバ証明書が拒否されました"
@@ -669,7 +675,7 @@ msgstr "コンテナを再起動します。"
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:857
+#: lxc/image.go:863
 msgid "SIZE"
 msgstr ""
 
@@ -725,7 +731,7 @@ msgstr "コンテナログの最後の 100 行を表示しますか?"
 msgid "Show the expanded configuration"
 msgstr "拡張した設定を表示する"
 
-#: lxc/image.go:407
+#: lxc/image.go:412
 #, c-format
 msgid "Size: %.2fMB"
 msgstr "サイズ: %.2fMB"
@@ -739,7 +745,7 @@ msgstr "スナップショット:"
 msgid "Some containers failed to %s"
 msgstr "一部のコンテナで %s が失敗しました"
 
-#: lxc/image.go:440
+#: lxc/image.go:446
 msgid "Source:"
 msgstr "取得元:"
 
@@ -844,7 +850,7 @@ msgstr ""
 msgid "Time to wait for the container before killing it"
 msgstr "コンテナを強制停止するまでの時間"
 
-#: lxc/image.go:410
+#: lxc/image.go:415
 msgid "Timestamps:"
 msgstr "タイムスタンプ:"
 
@@ -860,7 +866,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "イメージを転送中: %s"
 
-#: lxc/image.go:518
+#: lxc/image.go:524
 #, c-format
 msgid "Transferring image: %s"
 msgstr "イメージを転送中: %s"
@@ -878,7 +884,7 @@ msgstr "タイプ: ephemeral"
 msgid "Type: persistent"
 msgstr "タイプ: persistent"
 
-#: lxc/image.go:858
+#: lxc/image.go:864
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -890,7 +896,7 @@ msgstr ""
 msgid "Unable to find help2man."
 msgstr "help2man が見つかりません。"
 
-#: lxc/image.go:415
+#: lxc/image.go:420
 #, c-format
 msgid "Uploaded: %s"
 msgstr "アップロード日時: %s"
@@ -1877,7 +1883,7 @@ msgstr ""
 msgid "User aborted delete operation."
 msgstr "ユーザが削除操作を中断しました。"
 
-#: lxc/utils.go:290
+#: lxc/utils.go:320
 msgid ""
 "User signaled us three times, exiting. The remote operation will keep "
 "running."
@@ -1920,11 +1926,11 @@ msgstr ""
 "サーバから変更されたイメージ、コンテナ、スナップショットを取得できませんで\n"
 "した"
 
-#: lxc/image.go:401
+#: lxc/image.go:406
 msgid "disabled"
 msgstr "無効"
 
-#: lxc/image.go:403
+#: lxc/image.go:408
 msgid "enabled"
 msgstr "有効"
 
@@ -1938,7 +1944,7 @@ msgstr "エラー: %v"
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/image.go:396 lxc/image.go:834
+#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:840
 msgid "no"
 msgstr ""
 
@@ -1988,7 +1994,7 @@ msgstr "%s に取得しました"
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:838
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:844
 msgid "yes"
 msgstr ""
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 4c33d66a0..9f3d98c82 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-10-03 16:50-0400\n"
+        "POT-Creation-Date: 2017-10-03 18:09-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -65,12 +65,12 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:831
+#: lxc/image.go:837
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
 
-#: lxc/utils.go:294
+#: lxc/utils.go:324
 #, c-format
 msgid   "%v (interrupt two more times to force)"
 msgstr  ""
@@ -83,11 +83,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:852 lxc/image.go:881
+#: lxc/image.go:858 lxc/image.go:887
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:856
+#: lxc/image.go:862
 msgid   "ARCH"
 msgstr  ""
 
@@ -104,21 +104,21 @@ msgstr  ""
 msgid   "Admin password for %s: "
 msgstr  ""
 
-#: lxc/image.go:430
+#: lxc/image.go:435
 msgid   "Aliases:"
 msgstr  ""
 
-#: lxc/image.go:408 lxc/info.go:104
+#: lxc/image.go:413 lxc/info.go:104
 #, c-format
 msgid   "Architecture: %s"
 msgstr  ""
 
-#: lxc/image.go:438
+#: lxc/image.go:444
 #, c-format
 msgid   "Auto update: %s"
 msgstr  ""
 
-#: lxc/image.go:512
+#: lxc/image.go:518
 #, c-format
 msgid   "Bad property: %s"
 msgstr  ""
@@ -139,6 +139,11 @@ msgstr  ""
 msgid   "CREATED AT"
 msgstr  ""
 
+#: lxc/image.go:443
+#, c-format
+msgid   "Cached: %s"
+msgstr  ""
+
 #: lxc/config.go:156
 #, c-format
 msgid   "Can't read from stdin: %s"
@@ -179,7 +184,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:936 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:942 lxc/profile.go:237
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -215,7 +220,7 @@ msgstr  ""
 msgid   "Could not create server cert dir"
 msgstr  ""
 
-#: lxc/image.go:413 lxc/info.go:106
+#: lxc/image.go:418 lxc/info.go:106
 #, c-format
 msgid   "Created: %s"
 msgstr  ""
@@ -229,7 +234,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:855 lxc/image.go:883
+#: lxc/image.go:861 lxc/image.go:889
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -279,25 +284,25 @@ msgstr  ""
 msgid   "Event type to listen for"
 msgstr  ""
 
-#: lxc/image.go:417
+#: lxc/image.go:422
 #, c-format
 msgid   "Expires: %s"
 msgstr  ""
 
-#: lxc/image.go:419
+#: lxc/image.go:424
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/image.go:700
+#: lxc/image.go:706
 #, c-format
 msgid   "Exporting the image: %s"
 msgstr  ""
 
-#: lxc/config.go:348 lxc/image.go:853 lxc/image.go:882
+#: lxc/config.go:348 lxc/image.go:859 lxc/image.go:888
 msgid   "FINGERPRINT"
 msgstr  ""
 
-#: lxc/utils.go:332
+#: lxc/utils.go:362
 #, c-format
 msgid   "Failed to create alias %s"
 msgstr  ""
@@ -316,7 +321,7 @@ msgstr  ""
 msgid   "Failed to get the new container name"
 msgstr  ""
 
-#: lxc/utils.go:322
+#: lxc/utils.go:352
 #, c-format
 msgid   "Failed to remove alias %s"
 msgstr  ""
@@ -325,7 +330,7 @@ msgstr  ""
 msgid   "Fast mode (same as --columns=nsacPt)"
 msgstr  ""
 
-#: lxc/image.go:406
+#: lxc/image.go:411
 #, c-format
 msgid   "Fingerprint: %s"
 msgstr  ""
@@ -335,7 +340,7 @@ msgid   "Force the container to shutdown"
 msgstr  ""
 
 #: lxc/delete.go:34 lxc/delete.go:35
-msgid   "Force the removal of stopped containers"
+msgid   "Force the removal of running containers"
 msgstr  ""
 
 #: lxc/main.go:49
@@ -378,11 +383,11 @@ msgstr  ""
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:750
+#: lxc/image.go:756
 msgid   "Image exported successfully!"
 msgstr  ""
 
-#: lxc/image.go:570
+#: lxc/image.go:576
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
@@ -426,12 +431,12 @@ msgstr  ""
 msgid   "LXD socket not found; is LXD installed and running?"
 msgstr  ""
 
-#: lxc/image.go:422
+#: lxc/image.go:427
 #, c-format
 msgid   "Last used: %s"
 msgstr  ""
 
-#: lxc/image.go:424
+#: lxc/image.go:429
 msgid   "Last used: never"
 msgstr  ""
 
@@ -459,7 +464,7 @@ msgstr  ""
 msgid   "Memory usage:"
 msgstr  ""
 
-#: lxc/utils.go:226
+#: lxc/utils.go:256
 msgid   "Missing summary."
 msgstr  ""
 
@@ -504,7 +509,7 @@ msgstr  ""
 msgid   "Only https URLs are supported for simplestreams"
 msgstr  ""
 
-#: lxc/image.go:493
+#: lxc/image.go:499
 msgid   "Only https:// is supported for remote image import."
 msgstr  ""
 
@@ -532,7 +537,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:854 lxc/remote.go:367
+#: lxc/image.go:860 lxc/remote.go:367
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -569,7 +574,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:937
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:943
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -614,7 +619,7 @@ msgstr  ""
 msgid   "Profiles: %s"
 msgstr  ""
 
-#: lxc/image.go:426
+#: lxc/image.go:431
 msgid   "Properties:"
 msgstr  ""
 
@@ -622,7 +627,7 @@ msgstr  ""
 msgid   "Public image server"
 msgstr  ""
 
-#: lxc/image.go:409
+#: lxc/image.go:414
 #, c-format
 msgid   "Public: %s"
 msgstr  ""
@@ -631,7 +636,7 @@ msgstr  ""
 msgid   "Remote admin password"
 msgstr  ""
 
-#: lxc/utils.go:285
+#: lxc/utils.go:315
 msgid   "Remote operation canceled by user"
 msgstr  ""
 
@@ -662,7 +667,7 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:857
+#: lxc/image.go:863
 msgid   "SIZE"
 msgstr  ""
 
@@ -718,7 +723,7 @@ msgstr  ""
 msgid   "Show the expanded configuration"
 msgstr  ""
 
-#: lxc/image.go:407
+#: lxc/image.go:412
 #, c-format
 msgid   "Size: %.2fMB"
 msgstr  ""
@@ -732,7 +737,7 @@ msgstr  ""
 msgid   "Some containers failed to %s"
 msgstr  ""
 
-#: lxc/image.go:440
+#: lxc/image.go:446
 msgid   "Source:"
 msgstr  ""
 
@@ -823,7 +828,7 @@ msgstr  ""
 msgid   "Time to wait for the container before killing it"
 msgstr  ""
 
-#: lxc/image.go:410
+#: lxc/image.go:415
 msgid   "Timestamps:"
 msgstr  ""
 
@@ -836,7 +841,7 @@ msgstr  ""
 msgid   "Transferring container: %s"
 msgstr  ""
 
-#: lxc/image.go:518
+#: lxc/image.go:524
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
@@ -854,7 +859,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:858
+#: lxc/image.go:864
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -866,7 +871,7 @@ msgstr  ""
 msgid   "Unable to find help2man."
 msgstr  ""
 
-#: lxc/image.go:415
+#: lxc/image.go:420
 #, c-format
 msgid   "Uploaded: %s"
 msgstr  ""
@@ -1357,7 +1362,7 @@ msgstr  ""
 msgid   "User aborted delete operation."
 msgstr  ""
 
-#: lxc/utils.go:290
+#: lxc/utils.go:320
 msgid   "User signaled us three times, exiting. The remote operation will keep running."
 msgstr  ""
 
@@ -1393,11 +1398,11 @@ msgstr  ""
 msgid   "didn't get any affected image, container or snapshot from server"
 msgstr  ""
 
-#: lxc/image.go:401
+#: lxc/image.go:406
 msgid   "disabled"
 msgstr  ""
 
-#: lxc/image.go:403
+#: lxc/image.go:408
 msgid   "enabled"
 msgstr  ""
 
@@ -1411,7 +1416,7 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/image.go:396 lxc/image.go:834
+#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:840
 msgid   "no"
 msgstr  ""
 
@@ -1461,7 +1466,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:838
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:844
 msgid   "yes"
 msgstr  ""
 

From 77b025de480cca702f87005153c0ad0a768d1aea Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 18:43:42 +0200
Subject: [PATCH 1121/1193] benchmark: move lxd-benchmark to top dir

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 {test/lxd-benchmark => lxd-benchmark}/main.go | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename {test/lxd-benchmark => lxd-benchmark}/main.go (100%)

diff --git a/test/lxd-benchmark/main.go b/lxd-benchmark/main.go
similarity index 100%
rename from test/lxd-benchmark/main.go
rename to lxd-benchmark/main.go

From 065b2c1c5baec135da6b8544a96a03401c58b987 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 12:02:45 +0200
Subject: [PATCH 1122/1193] benchmark: extract logic to separate package

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 327 +++++++++++++++++++++++++++++++++++
 lxd-benchmark/main.go                | 324 +---------------------------------
 2 files changed, 330 insertions(+), 321 deletions(-)
 create mode 100644 lxd-benchmark/benchmark/benchmark.go

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
new file mode 100644
index 000000000..e8fe5e8f7
--- /dev/null
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -0,0 +1,327 @@
+package benchmark
+
+import (
+	"fmt"
+	"io/ioutil"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxc/config"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/version"
+)
+
+func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image string, privileged bool, freeze bool) error {
+	batch := parallel
+	if batch < 1 {
+		// Detect the number of parallel actions
+		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
+		if err != nil {
+			return err
+		}
+
+		batch = len(cpus)
+	}
+
+	batches := count / batch
+	remainder := count % batch
+
+	// Print the test header
+	st, _, err := c.GetServer()
+	if err != nil {
+		return err
+	}
+
+	privilegedStr := "unprivileged"
+	if privileged {
+		privilegedStr = "privileged"
+	}
+
+	mode := "normal startup"
+	if freeze {
+		mode = "start and freeze"
+	}
+
+	fmt.Printf("Test environment:\n")
+	fmt.Printf("  Server backend: %s\n", st.Environment.Server)
+	fmt.Printf("  Server version: %s\n", st.Environment.ServerVersion)
+	fmt.Printf("  Kernel: %s\n", st.Environment.Kernel)
+	fmt.Printf("  Kernel architecture: %s\n", st.Environment.KernelArchitecture)
+	fmt.Printf("  Kernel version: %s\n", st.Environment.KernelVersion)
+	fmt.Printf("  Storage backend: %s\n", st.Environment.Storage)
+	fmt.Printf("  Storage version: %s\n", st.Environment.StorageVersion)
+	fmt.Printf("  Container backend: %s\n", st.Environment.Driver)
+	fmt.Printf("  Container version: %s\n", st.Environment.DriverVersion)
+	fmt.Printf("\n")
+	fmt.Printf("Test variables:\n")
+	fmt.Printf("  Container count: %d\n", count)
+	fmt.Printf("  Container mode: %s\n", privilegedStr)
+	fmt.Printf("  Startup mode: %s\n", mode)
+	fmt.Printf("  Image: %s\n", image)
+	fmt.Printf("  Batches: %d\n", batches)
+	fmt.Printf("  Batch size: %d\n", batch)
+	fmt.Printf("  Remainder: %d\n", remainder)
+	fmt.Printf("\n")
+
+	// Pre-load the image
+	var fingerprint string
+	if strings.Contains(image, ":") {
+		var remote string
+
+		defaultConfig := config.DefaultConfig
+		defaultConfig.UserAgent = version.UserAgent
+
+		remote, fingerprint, err = defaultConfig.ParseRemote(image)
+		if err != nil {
+			return err
+		}
+
+		d, err := defaultConfig.GetImageServer(remote)
+		if err != nil {
+			return err
+		}
+
+		if fingerprint == "" {
+			fingerprint = "default"
+		}
+
+		alias, _, err := d.GetImageAlias(fingerprint)
+		if err == nil {
+			fingerprint = alias.Target
+		}
+
+		_, _, err = c.GetImage(fingerprint)
+		if err != nil {
+			logf("Importing image into local store: %s", fingerprint)
+			image, _, err := d.GetImage(fingerprint)
+			if err != nil {
+				logf(fmt.Sprintf("Failed to import image: %s", err))
+				return err
+			}
+
+			op, err := c.CopyImage(d, *image, nil)
+			if err != nil {
+				logf(fmt.Sprintf("Failed to import image: %s", err))
+				return err
+			}
+
+			err = op.Wait()
+			if err != nil {
+				logf(fmt.Sprintf("Failed to import image: %s", err))
+				return err
+			}
+		} else {
+			logf("Found image in local store: %s", fingerprint)
+		}
+	} else {
+		fingerprint = image
+		logf("Found image in local store: %s", fingerprint)
+	}
+
+	// Start the containers
+	spawnedCount := 0
+	nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
+	wgBatch := sync.WaitGroup{}
+	nextStat := batch
+
+	startContainer := func(name string) {
+		defer wgBatch.Done()
+
+		// Configure
+		config := map[string]string{}
+		if privileged {
+			config["security.privileged"] = "true"
+		}
+		config["user.lxd-benchmark"] = "true"
+
+		// Create
+		req := api.ContainersPost{
+			Name: name,
+			Source: api.ContainerSource{
+				Type:        "image",
+				Fingerprint: fingerprint,
+			},
+		}
+		req.Config = config
+
+		op, err := c.CreateContainer(req)
+		if err != nil {
+			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+			return
+		}
+
+		err = op.Wait()
+		if err != nil {
+			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+			return
+		}
+
+		// Start
+		op, err = c.UpdateContainerState(name, api.ContainerStatePut{Action: "start", Timeout: -1}, "")
+		if err != nil {
+			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+			return
+		}
+
+		err = op.Wait()
+		if err != nil {
+			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+			return
+		}
+
+		// Freeze
+		if freeze {
+			op, err := c.UpdateContainerState(name, api.ContainerStatePut{Action: "freeze", Timeout: -1}, "")
+			if err != nil {
+				logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+				return
+			}
+
+			err = op.Wait()
+			if err != nil {
+				logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+				return
+			}
+		}
+	}
+
+	logf("Starting the test")
+	timeStart := time.Now()
+
+	for i := 0; i < batches; i++ {
+		for j := 0; j < batch; j++ {
+			spawnedCount = spawnedCount + 1
+			name := fmt.Sprintf(nameFormat, spawnedCount)
+
+			wgBatch.Add(1)
+			go startContainer(name)
+		}
+		wgBatch.Wait()
+
+		if spawnedCount >= nextStat {
+			interval := time.Since(timeStart).Seconds()
+			logf("Started %d containers in %.3fs (%.3f/s)", spawnedCount, interval, float64(spawnedCount)/interval)
+			nextStat = nextStat * 2
+		}
+	}
+
+	for k := 0; k < remainder; k++ {
+		spawnedCount = spawnedCount + 1
+		name := fmt.Sprintf(nameFormat, spawnedCount)
+
+		wgBatch.Add(1)
+		go startContainer(name)
+	}
+	wgBatch.Wait()
+
+	logf("Test completed in %.3fs", time.Since(timeStart).Seconds())
+
+	return nil
+}
+
+func DeleteContainers(c lxd.ContainerServer, parallel int) error {
+	batch := parallel
+	if batch < 1 {
+		// Detect the number of parallel actions
+		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
+		if err != nil {
+			return err
+		}
+
+		batch = len(cpus)
+	}
+
+	// List all the containers
+	allContainers, err := c.GetContainers()
+	if err != nil {
+		return err
+	}
+
+	containers := []api.Container{}
+	for _, container := range allContainers {
+		if container.Config["user.lxd-benchmark"] != "true" {
+			continue
+		}
+
+		containers = append(containers, container)
+	}
+
+	// Delete them all
+	count := len(containers)
+	logf("%d containers to delete", count)
+
+	batches := count / batch
+
+	deletedCount := 0
+	wgBatch := sync.WaitGroup{}
+	nextStat := batch
+
+	deleteContainer := func(ct api.Container) {
+		defer wgBatch.Done()
+
+		// Stop
+		if ct.IsActive() {
+			op, err := c.UpdateContainerState(ct.Name, api.ContainerStatePut{Action: "stop", Timeout: -1, Force: true}, "")
+			if err != nil {
+				logf(fmt.Sprintf("Failed to delete container '%s': %s", ct.Name, err))
+				return
+			}
+
+			err = op.Wait()
+			if err != nil {
+				logf(fmt.Sprintf("Failed to delete container '%s': %s", ct.Name, err))
+				return
+			}
+		}
+
+		// Delete
+		op, err := c.DeleteContainer(ct.Name)
+		if err != nil {
+			logf("Failed to delete container: %s", ct.Name)
+			return
+		}
+
+		err = op.Wait()
+		if err != nil {
+			logf("Failed to delete container: %s", ct.Name)
+			return
+		}
+	}
+
+	logf("Starting the cleanup")
+	timeStart := time.Now()
+
+	for i := 0; i < batches; i++ {
+		for j := 0; j < batch; j++ {
+			wgBatch.Add(1)
+			go deleteContainer(containers[deletedCount])
+
+			deletedCount = deletedCount + 1
+		}
+		wgBatch.Wait()
+
+		if deletedCount >= nextStat {
+			interval := time.Since(timeStart).Seconds()
+			logf("Deleted %d containers in %.3fs (%.3f/s)", deletedCount, interval, float64(deletedCount)/interval)
+			nextStat = nextStat * 2
+		}
+	}
+
+	for k := deletedCount; k < count; k++ {
+		wgBatch.Add(1)
+		go deleteContainer(containers[deletedCount])
+
+		deletedCount = deletedCount + 1
+	}
+	wgBatch.Wait()
+
+	logf("Cleanup completed")
+
+	return nil
+}
+
+func logf(format string, args ...interface{}) {
+	fmt.Printf(fmt.Sprintf("[%s] %s\n", time.Now().Format(time.StampMilli), format), args...)
+}
diff --git a/lxd-benchmark/main.go b/lxd-benchmark/main.go
index 9c8eb3c11..a958993e7 100644
--- a/lxd-benchmark/main.go
+++ b/lxd-benchmark/main.go
@@ -2,16 +2,11 @@ package main
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
-	"strings"
-	"sync"
-	"time"
 
 	"github.com/lxc/lxd/client"
-	"github.com/lxc/lxd/lxc/config"
+	"github.com/lxc/lxd/lxd-benchmark/benchmark"
 	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -68,323 +63,10 @@ func run(args []string) error {
 
 	switch os.Args[1] {
 	case "spawn":
-		return spawnContainers(c, *argCount, *argImage, *argPrivileged)
+		return benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argFreeze)
 	case "delete":
-		return deleteContainers(c)
+		return benchmark.DeleteContainers(c, *argParallel)
 	}
 
 	return nil
 }
-
-func logf(format string, args ...interface{}) {
-	fmt.Printf(fmt.Sprintf("[%s] %s\n", time.Now().Format(time.StampMilli), format), args...)
-}
-
-func spawnContainers(c lxd.ContainerServer, count int, image string, privileged bool) error {
-	batch := *argParallel
-	if batch < 1 {
-		// Detect the number of parallel actions
-		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
-		if err != nil {
-			return err
-		}
-
-		batch = len(cpus)
-	}
-
-	batches := count / batch
-	remainder := count % batch
-
-	// Print the test header
-	st, _, err := c.GetServer()
-	if err != nil {
-		return err
-	}
-
-	privilegedStr := "unprivileged"
-	if privileged {
-		privilegedStr = "privileged"
-	}
-
-	mode := "normal startup"
-	if *argFreeze {
-		mode = "start and freeze"
-	}
-
-	fmt.Printf("Test environment:\n")
-	fmt.Printf("  Server backend: %s\n", st.Environment.Server)
-	fmt.Printf("  Server version: %s\n", st.Environment.ServerVersion)
-	fmt.Printf("  Kernel: %s\n", st.Environment.Kernel)
-	fmt.Printf("  Kernel architecture: %s\n", st.Environment.KernelArchitecture)
-	fmt.Printf("  Kernel version: %s\n", st.Environment.KernelVersion)
-	fmt.Printf("  Storage backend: %s\n", st.Environment.Storage)
-	fmt.Printf("  Storage version: %s\n", st.Environment.StorageVersion)
-	fmt.Printf("  Container backend: %s\n", st.Environment.Driver)
-	fmt.Printf("  Container version: %s\n", st.Environment.DriverVersion)
-	fmt.Printf("\n")
-	fmt.Printf("Test variables:\n")
-	fmt.Printf("  Container count: %d\n", count)
-	fmt.Printf("  Container mode: %s\n", privilegedStr)
-	fmt.Printf("  Startup mode: %s\n", mode)
-	fmt.Printf("  Image: %s\n", image)
-	fmt.Printf("  Batches: %d\n", batches)
-	fmt.Printf("  Batch size: %d\n", batch)
-	fmt.Printf("  Remainder: %d\n", remainder)
-	fmt.Printf("\n")
-
-	// Pre-load the image
-	var fingerprint string
-	if strings.Contains(image, ":") {
-		var remote string
-
-		defaultConfig := config.DefaultConfig
-		defaultConfig.UserAgent = version.UserAgent
-
-		remote, fingerprint, err = defaultConfig.ParseRemote(image)
-		if err != nil {
-			return err
-		}
-
-		d, err := defaultConfig.GetImageServer(remote)
-		if err != nil {
-			return err
-		}
-
-		if fingerprint == "" {
-			fingerprint = "default"
-		}
-
-		alias, _, err := d.GetImageAlias(fingerprint)
-		if err == nil {
-			fingerprint = alias.Target
-		}
-
-		_, _, err = c.GetImage(fingerprint)
-		if err != nil {
-			logf("Importing image into local store: %s", fingerprint)
-			image, _, err := d.GetImage(fingerprint)
-			if err != nil {
-				logf(fmt.Sprintf("Failed to import image: %s", err))
-				return err
-			}
-
-			op, err := c.CopyImage(d, *image, nil)
-			if err != nil {
-				logf(fmt.Sprintf("Failed to import image: %s", err))
-				return err
-			}
-
-			err = op.Wait()
-			if err != nil {
-				logf(fmt.Sprintf("Failed to import image: %s", err))
-				return err
-			}
-		} else {
-			logf("Found image in local store: %s", fingerprint)
-		}
-	} else {
-		fingerprint = image
-		logf("Found image in local store: %s", fingerprint)
-	}
-
-	// Start the containers
-	spawnedCount := 0
-	nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
-	wgBatch := sync.WaitGroup{}
-	nextStat := batch
-
-	startContainer := func(name string) {
-		defer wgBatch.Done()
-
-		// Configure
-		config := map[string]string{}
-		if privileged {
-			config["security.privileged"] = "true"
-		}
-		config["user.lxd-benchmark"] = "true"
-
-		// Create
-		req := api.ContainersPost{
-			Name: name,
-			Source: api.ContainerSource{
-				Type:        "image",
-				Fingerprint: fingerprint,
-			},
-		}
-		req.Config = config
-
-		op, err := c.CreateContainer(req)
-		if err != nil {
-			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
-			return
-		}
-
-		err = op.Wait()
-		if err != nil {
-			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
-			return
-		}
-
-		// Start
-		op, err = c.UpdateContainerState(name, api.ContainerStatePut{Action: "start", Timeout: -1}, "")
-		if err != nil {
-			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
-			return
-		}
-
-		err = op.Wait()
-		if err != nil {
-			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
-			return
-		}
-
-		// Freeze
-		if *argFreeze {
-			op, err := c.UpdateContainerState(name, api.ContainerStatePut{Action: "freeze", Timeout: -1}, "")
-			if err != nil {
-				logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
-				return
-			}
-
-			err = op.Wait()
-			if err != nil {
-				logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
-				return
-			}
-		}
-	}
-
-	logf("Starting the test")
-	timeStart := time.Now()
-
-	for i := 0; i < batches; i++ {
-		for j := 0; j < batch; j++ {
-			spawnedCount = spawnedCount + 1
-			name := fmt.Sprintf(nameFormat, spawnedCount)
-
-			wgBatch.Add(1)
-			go startContainer(name)
-		}
-		wgBatch.Wait()
-
-		if spawnedCount >= nextStat {
-			interval := time.Since(timeStart).Seconds()
-			logf("Started %d containers in %.3fs (%.3f/s)", spawnedCount, interval, float64(spawnedCount)/interval)
-			nextStat = nextStat * 2
-		}
-	}
-
-	for k := 0; k < remainder; k++ {
-		spawnedCount = spawnedCount + 1
-		name := fmt.Sprintf(nameFormat, spawnedCount)
-
-		wgBatch.Add(1)
-		go startContainer(name)
-	}
-	wgBatch.Wait()
-
-	logf("Test completed in %.3fs", time.Since(timeStart).Seconds())
-
-	return nil
-}
-
-func deleteContainers(c lxd.ContainerServer) error {
-	batch := *argParallel
-	if batch < 1 {
-		// Detect the number of parallel actions
-		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
-		if err != nil {
-			return err
-		}
-
-		batch = len(cpus)
-	}
-
-	// List all the containers
-	allContainers, err := c.GetContainers()
-	if err != nil {
-		return err
-	}
-
-	containers := []api.Container{}
-	for _, container := range allContainers {
-		if container.Config["user.lxd-benchmark"] != "true" {
-			continue
-		}
-
-		containers = append(containers, container)
-	}
-
-	// Delete them all
-	count := len(containers)
-	logf("%d containers to delete", count)
-
-	batches := count / batch
-
-	deletedCount := 0
-	wgBatch := sync.WaitGroup{}
-	nextStat := batch
-
-	deleteContainer := func(ct api.Container) {
-		defer wgBatch.Done()
-
-		// Stop
-		if ct.IsActive() {
-			op, err := c.UpdateContainerState(ct.Name, api.ContainerStatePut{Action: "stop", Timeout: -1, Force: true}, "")
-			if err != nil {
-				logf(fmt.Sprintf("Failed to delete container '%s': %s", ct.Name, err))
-				return
-			}
-
-			err = op.Wait()
-			if err != nil {
-				logf(fmt.Sprintf("Failed to delete container '%s': %s", ct.Name, err))
-				return
-			}
-		}
-
-		// Delete
-		op, err := c.DeleteContainer(ct.Name)
-		if err != nil {
-			logf("Failed to delete container: %s", ct.Name)
-			return
-		}
-
-		err = op.Wait()
-		if err != nil {
-			logf("Failed to delete container: %s", ct.Name)
-			return
-		}
-	}
-
-	logf("Starting the cleanup")
-	timeStart := time.Now()
-
-	for i := 0; i < batches; i++ {
-		for j := 0; j < batch; j++ {
-			wgBatch.Add(1)
-			go deleteContainer(containers[deletedCount])
-
-			deletedCount = deletedCount + 1
-		}
-		wgBatch.Wait()
-
-		if deletedCount >= nextStat {
-			interval := time.Since(timeStart).Seconds()
-			logf("Deleted %d containers in %.3fs (%.3f/s)", deletedCount, interval, float64(deletedCount)/interval)
-			nextStat = nextStat * 2
-		}
-	}
-
-	for k := deletedCount; k < count; k++ {
-		wgBatch.Add(1)
-		go deleteContainer(containers[deletedCount])
-
-		deletedCount = deletedCount + 1
-	}
-	wgBatch.Wait()
-
-	logf("Cleanup completed")
-
-	return nil
-}

From 597ece75e9f045dbc9b570162ef1a257f89958b0 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 12:24:36 +0200
Subject: [PATCH 1123/1193] benchmark: extract PrintServerInfo function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 41 ++++++++++++++++++++----------------
 lxd-benchmark/main.go                |  2 ++
 2 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index e8fe5e8f7..26e2a60a1 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -13,6 +13,28 @@ import (
 	"github.com/lxc/lxd/shared/version"
 )
 
+// PrintServerInfo prints out information about the server.
+func PrintServerInfo(c lxd.ContainerServer) error {
+	server, _, err := c.GetServer()
+	if err != nil {
+		return err
+	}
+	env := server.Environment
+	fmt.Printf("Test environment:\n")
+	fmt.Printf("  Server backend: %s\n", env.Server)
+	fmt.Printf("  Server version: %s\n", env.ServerVersion)
+	fmt.Printf("  Kernel: %s\n", env.Kernel)
+	fmt.Printf("  Kernel architecture: %s\n", env.KernelArchitecture)
+	fmt.Printf("  Kernel version: %s\n", env.KernelVersion)
+	fmt.Printf("  Storage backend: %s\n", env.Storage)
+	fmt.Printf("  Storage version: %s\n", env.StorageVersion)
+	fmt.Printf("  Container backend: %s\n", env.Driver)
+	fmt.Printf("  Container version: %s\n", env.DriverVersion)
+	fmt.Printf("\n")
+	return nil
+}
+
+// SpawnContainers launches a set of containers.
 func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image string, privileged bool, freeze bool) error {
 	batch := parallel
 	if batch < 1 {
@@ -29,32 +51,14 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 	remainder := count % batch
 
 	// Print the test header
-	st, _, err := c.GetServer()
-	if err != nil {
-		return err
-	}
-
 	privilegedStr := "unprivileged"
 	if privileged {
 		privilegedStr = "privileged"
 	}
-
 	mode := "normal startup"
 	if freeze {
 		mode = "start and freeze"
 	}
-
-	fmt.Printf("Test environment:\n")
-	fmt.Printf("  Server backend: %s\n", st.Environment.Server)
-	fmt.Printf("  Server version: %s\n", st.Environment.ServerVersion)
-	fmt.Printf("  Kernel: %s\n", st.Environment.Kernel)
-	fmt.Printf("  Kernel architecture: %s\n", st.Environment.KernelArchitecture)
-	fmt.Printf("  Kernel version: %s\n", st.Environment.KernelVersion)
-	fmt.Printf("  Storage backend: %s\n", st.Environment.Storage)
-	fmt.Printf("  Storage version: %s\n", st.Environment.StorageVersion)
-	fmt.Printf("  Container backend: %s\n", st.Environment.Driver)
-	fmt.Printf("  Container version: %s\n", st.Environment.DriverVersion)
-	fmt.Printf("\n")
 	fmt.Printf("Test variables:\n")
 	fmt.Printf("  Container count: %d\n", count)
 	fmt.Printf("  Container mode: %s\n", privilegedStr)
@@ -69,6 +73,7 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 	var fingerprint string
 	if strings.Contains(image, ":") {
 		var remote string
+		var err error
 
 		defaultConfig := config.DefaultConfig
 		defaultConfig.UserAgent = version.UserAgent
diff --git a/lxd-benchmark/main.go b/lxd-benchmark/main.go
index a958993e7..c9bd9fc05 100644
--- a/lxd-benchmark/main.go
+++ b/lxd-benchmark/main.go
@@ -61,6 +61,8 @@ func run(args []string) error {
 		return err
 	}
 
+	benchmark.PrintServerInfo(c)
+
 	switch os.Args[1] {
 	case "spawn":
 		return benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argFreeze)

From dc7d30d1548fbafb4adb0b0723531fcdecd2950c Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 12:40:01 +0200
Subject: [PATCH 1124/1193] benchmark: extract printTestConfig function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 44 ++++++++++++++++++++----------------
 1 file changed, 25 insertions(+), 19 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 26e2a60a1..141066bb4 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -47,28 +47,11 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 		batch = len(cpus)
 	}
 
+	printTestConfig(count, batch, image, privileged, freeze)
+
 	batches := count / batch
 	remainder := count % batch
 
-	// Print the test header
-	privilegedStr := "unprivileged"
-	if privileged {
-		privilegedStr = "privileged"
-	}
-	mode := "normal startup"
-	if freeze {
-		mode = "start and freeze"
-	}
-	fmt.Printf("Test variables:\n")
-	fmt.Printf("  Container count: %d\n", count)
-	fmt.Printf("  Container mode: %s\n", privilegedStr)
-	fmt.Printf("  Startup mode: %s\n", mode)
-	fmt.Printf("  Image: %s\n", image)
-	fmt.Printf("  Batches: %d\n", batches)
-	fmt.Printf("  Batch size: %d\n", batch)
-	fmt.Printf("  Remainder: %d\n", remainder)
-	fmt.Printf("\n")
-
 	// Pre-load the image
 	var fingerprint string
 	if strings.Contains(image, ":") {
@@ -330,3 +313,26 @@ func DeleteContainers(c lxd.ContainerServer, parallel int) error {
 func logf(format string, args ...interface{}) {
 	fmt.Printf(fmt.Sprintf("[%s] %s\n", time.Now().Format(time.StampMilli), format), args...)
 }
+
+func printTestConfig(count int, batchSize int, image string, privileged bool, freeze bool) {
+	privilegedStr := "unprivileged"
+	if privileged {
+		privilegedStr = "privileged"
+	}
+	mode := "normal startup"
+	if freeze {
+		mode = "start and freeze"
+	}
+
+	batches := count / batchSize
+	remainder := count % batchSize
+	fmt.Printf("Test variables:\n")
+	fmt.Printf("  Container count: %d\n", count)
+	fmt.Printf("  Container mode: %s\n", privilegedStr)
+	fmt.Printf("  Startup mode: %s\n", mode)
+	fmt.Printf("  Image: %s\n", image)
+	fmt.Printf("  Batches: %d\n", batches)
+	fmt.Printf("  Batch size: %d\n", batchSize)
+	fmt.Printf("  Remainder: %d\n", remainder)
+	fmt.Printf("\n")
+}

From a6d3d890f4dd2636ee7a0f7f2f26c7990e656691 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 12:54:22 +0200
Subject: [PATCH 1125/1193] benchmark: extract getBatchSize function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 56 +++++++++++++++++++-----------------
 1 file changed, 30 insertions(+), 26 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 141066bb4..d9911fd64 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -36,21 +36,15 @@ func PrintServerInfo(c lxd.ContainerServer) error {
 
 // SpawnContainers launches a set of containers.
 func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image string, privileged bool, freeze bool) error {
-	batch := parallel
-	if batch < 1 {
-		// Detect the number of parallel actions
-		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
-		if err != nil {
-			return err
-		}
-
-		batch = len(cpus)
+	batchSize, err := getBatchSize(parallel)
+	if err != nil {
+		return err
 	}
 
-	printTestConfig(count, batch, image, privileged, freeze)
+	printTestConfig(count, batchSize, image, privileged, freeze)
 
-	batches := count / batch
-	remainder := count % batch
+	batches := count / batchSize
+	remainder := count % batchSize
 
 	// Pre-load the image
 	var fingerprint string
@@ -112,7 +106,7 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 	spawnedCount := 0
 	nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
 	wgBatch := sync.WaitGroup{}
-	nextStat := batch
+	nextStat := batchSize
 
 	startContainer := func(name string) {
 		defer wgBatch.Done()
@@ -179,7 +173,7 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 	timeStart := time.Now()
 
 	for i := 0; i < batches; i++ {
-		for j := 0; j < batch; j++ {
+		for j := 0; j < batchSize; j++ {
 			spawnedCount = spawnedCount + 1
 			name := fmt.Sprintf(nameFormat, spawnedCount)
 
@@ -209,16 +203,11 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 	return nil
 }
 
+// DeleteContainers removes containers created by the benchmark.
 func DeleteContainers(c lxd.ContainerServer, parallel int) error {
-	batch := parallel
-	if batch < 1 {
-		// Detect the number of parallel actions
-		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
-		if err != nil {
-			return err
-		}
-
-		batch = len(cpus)
+	batchSize, err := getBatchSize(parallel)
+	if err != nil {
+		return err
 	}
 
 	// List all the containers
@@ -240,11 +229,11 @@ func DeleteContainers(c lxd.ContainerServer, parallel int) error {
 	count := len(containers)
 	logf("%d containers to delete", count)
 
-	batches := count / batch
+	batches := count / batchSize
 
 	deletedCount := 0
 	wgBatch := sync.WaitGroup{}
-	nextStat := batch
+	nextStat := batchSize
 
 	deleteContainer := func(ct api.Container) {
 		defer wgBatch.Done()
@@ -282,7 +271,7 @@ func DeleteContainers(c lxd.ContainerServer, parallel int) error {
 	timeStart := time.Now()
 
 	for i := 0; i < batches; i++ {
-		for j := 0; j < batch; j++ {
+		for j := 0; j < batchSize; j++ {
 			wgBatch.Add(1)
 			go deleteContainer(containers[deletedCount])
 
@@ -310,6 +299,21 @@ func DeleteContainers(c lxd.ContainerServer, parallel int) error {
 	return nil
 }
 
+func getBatchSize(parallel int) (int, error) {
+	batchSize := parallel
+	if batchSize < 1 {
+		// Detect the number of parallel actions
+		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
+		if err != nil {
+			return -1, err
+		}
+
+		batchSize = len(cpus)
+	}
+
+	return batchSize, nil
+}
+
 func logf(format string, args ...interface{}) {
 	fmt.Printf(fmt.Sprintf("[%s] %s\n", time.Now().Format(time.StampMilli), format), args...)
 }

From bea9bf0518d89977685e024710acbb700955124c Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 13:01:34 +0200
Subject: [PATCH 1126/1193] benchmark: extract GetContainers function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 27 +++++++++++++++------------
 lxd-benchmark/main.go                |  6 +++++-
 2 files changed, 20 insertions(+), 13 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index d9911fd64..02f407feb 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -203,20 +203,15 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 	return nil
 }
 
-// DeleteContainers removes containers created by the benchmark.
-func DeleteContainers(c lxd.ContainerServer, parallel int) error {
-	batchSize, err := getBatchSize(parallel)
-	if err != nil {
-		return err
-	}
+// GetContainers returns containers created by the benchmark.
+func GetContainers(c lxd.ContainerServer) ([]api.Container, error) {
+	containers := []api.Container{}
 
-	// List all the containers
 	allContainers, err := c.GetContainers()
 	if err != nil {
-		return err
+		return containers, err
 	}
 
-	containers := []api.Container{}
 	for _, container := range allContainers {
 		if container.Config["user.lxd-benchmark"] != "true" {
 			continue
@@ -225,10 +220,18 @@ func DeleteContainers(c lxd.ContainerServer, parallel int) error {
 		containers = append(containers, container)
 	}
 
-	// Delete them all
-	count := len(containers)
-	logf("%d containers to delete", count)
+	return containers, nil
+}
+
+// DeleteContainers removes containers created by the benchmark.
+func DeleteContainers(c lxd.ContainerServer, containers []api.Container, parallel int) error {
+	batchSize, err := getBatchSize(parallel)
+	if err != nil {
+		return err
+	}
 
+	count := len(containers)
+	logf("Deleting %d containers", count)
 	batches := count / batchSize
 
 	deletedCount := 0
diff --git a/lxd-benchmark/main.go b/lxd-benchmark/main.go
index c9bd9fc05..b6e15d5fb 100644
--- a/lxd-benchmark/main.go
+++ b/lxd-benchmark/main.go
@@ -67,7 +67,11 @@ func run(args []string) error {
 	case "spawn":
 		return benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argFreeze)
 	case "delete":
-		return benchmark.DeleteContainers(c, *argParallel)
+		containers, err := benchmark.GetContainers(c)
+		if err != nil {
+			return err
+		}
+		return benchmark.DeleteContainers(c, containers, *argParallel)
 	}
 
 	return nil

From feacea691d266237dd4a990d953f7f1cab8f88bd Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 16:25:20 +0200
Subject: [PATCH 1127/1193] benchmark: add processBatch function, use it in
 SpawnContainers and DeleteContainers

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 141 ++++++++++++++---------------------
 1 file changed, 56 insertions(+), 85 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 02f407feb..188ce6898 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -43,9 +43,6 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 
 	printTestConfig(count, batchSize, image, privileged, freeze)
 
-	batches := count / batchSize
-	remainder := count % batchSize
-
 	// Pre-load the image
 	var fingerprint string
 	if strings.Contains(image, ":") {
@@ -102,14 +99,8 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 		logf("Found image in local store: %s", fingerprint)
 	}
 
-	// Start the containers
-	spawnedCount := 0
-	nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
-	wgBatch := sync.WaitGroup{}
-	nextStat := batchSize
-
-	startContainer := func(name string) {
-		defer wgBatch.Done()
+	startContainer := func(index int, wg *sync.WaitGroup) {
+		defer wg.Done()
 
 		// Configure
 		config := map[string]string{}
@@ -119,6 +110,8 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 		config["user.lxd-benchmark"] = "true"
 
 		// Create
+		nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
+		name := fmt.Sprintf(nameFormat, index+1)
 		req := api.ContainersPost{
 			Name: name,
 			Source: api.ContainerSource{
@@ -130,26 +123,26 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 
 		op, err := c.CreateContainer(req)
 		if err != nil {
-			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+			logf("Failed to spawn container '%s': %s", name, err)
 			return
 		}
 
 		err = op.Wait()
 		if err != nil {
-			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+			logf("Failed to spawn container '%s': %s", name, err)
 			return
 		}
 
 		// Start
 		op, err = c.UpdateContainerState(name, api.ContainerStatePut{Action: "start", Timeout: -1}, "")
 		if err != nil {
-			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+			logf("Failed to spawn container '%s': %s", name, err)
 			return
 		}
 
 		err = op.Wait()
 		if err != nil {
-			logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+			logf("Failed to spawn container '%s': %s", name, err)
 			return
 		}
 
@@ -157,49 +150,19 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 		if freeze {
 			op, err := c.UpdateContainerState(name, api.ContainerStatePut{Action: "freeze", Timeout: -1}, "")
 			if err != nil {
-				logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+				logf("Failed to spawn container '%s': %s", name, err)
 				return
 			}
 
 			err = op.Wait()
 			if err != nil {
-				logf(fmt.Sprintf("Failed to spawn container '%s': %s", name, err))
+				logf("Failed to spawn container '%s': %s", name, err)
 				return
 			}
 		}
 	}
 
-	logf("Starting the test")
-	timeStart := time.Now()
-
-	for i := 0; i < batches; i++ {
-		for j := 0; j < batchSize; j++ {
-			spawnedCount = spawnedCount + 1
-			name := fmt.Sprintf(nameFormat, spawnedCount)
-
-			wgBatch.Add(1)
-			go startContainer(name)
-		}
-		wgBatch.Wait()
-
-		if spawnedCount >= nextStat {
-			interval := time.Since(timeStart).Seconds()
-			logf("Started %d containers in %.3fs (%.3f/s)", spawnedCount, interval, float64(spawnedCount)/interval)
-			nextStat = nextStat * 2
-		}
-	}
-
-	for k := 0; k < remainder; k++ {
-		spawnedCount = spawnedCount + 1
-		name := fmt.Sprintf(nameFormat, spawnedCount)
-
-		wgBatch.Add(1)
-		go startContainer(name)
-	}
-	wgBatch.Wait()
-
-	logf("Test completed in %.3fs", time.Since(timeStart).Seconds())
-
+	processBatch(count, batchSize, startContainer)
 	return nil
 }
 
@@ -232,26 +195,23 @@ func DeleteContainers(c lxd.ContainerServer, containers []api.Container, paralle
 
 	count := len(containers)
 	logf("Deleting %d containers", count)
-	batches := count / batchSize
 
-	deletedCount := 0
-	wgBatch := sync.WaitGroup{}
-	nextStat := batchSize
+	deleteContainer := func(index int, wg *sync.WaitGroup) {
+		defer wg.Done()
 
-	deleteContainer := func(ct api.Container) {
-		defer wgBatch.Done()
+		ct := containers[index]
 
 		// Stop
 		if ct.IsActive() {
 			op, err := c.UpdateContainerState(ct.Name, api.ContainerStatePut{Action: "stop", Timeout: -1, Force: true}, "")
 			if err != nil {
-				logf(fmt.Sprintf("Failed to delete container '%s': %s", ct.Name, err))
+				logf("Failed to delete container '%s': %s", ct.Name, err)
 				return
 			}
 
 			err = op.Wait()
 			if err != nil {
-				logf(fmt.Sprintf("Failed to delete container '%s': %s", ct.Name, err))
+				logf("Failed to delete container '%s': %s", ct.Name, err)
 				return
 			}
 		}
@@ -270,35 +230,7 @@ func DeleteContainers(c lxd.ContainerServer, containers []api.Container, paralle
 		}
 	}
 
-	logf("Starting the cleanup")
-	timeStart := time.Now()
-
-	for i := 0; i < batches; i++ {
-		for j := 0; j < batchSize; j++ {
-			wgBatch.Add(1)
-			go deleteContainer(containers[deletedCount])
-
-			deletedCount = deletedCount + 1
-		}
-		wgBatch.Wait()
-
-		if deletedCount >= nextStat {
-			interval := time.Since(timeStart).Seconds()
-			logf("Deleted %d containers in %.3fs (%.3f/s)", deletedCount, interval, float64(deletedCount)/interval)
-			nextStat = nextStat * 2
-		}
-	}
-
-	for k := deletedCount; k < count; k++ {
-		wgBatch.Add(1)
-		go deleteContainer(containers[deletedCount])
-
-		deletedCount = deletedCount + 1
-	}
-	wgBatch.Wait()
-
-	logf("Cleanup completed")
-
+	processBatch(count, batchSize, deleteContainer)
 	return nil
 }
 
@@ -317,6 +249,45 @@ func getBatchSize(parallel int) (int, error) {
 	return batchSize, nil
 }
 
+func processBatch(count int, batchSize int, process func(index int, wg *sync.WaitGroup)) time.Duration {
+	batches := count / batchSize
+	remainder := count % batchSize
+	processed := 0
+	wg := sync.WaitGroup{}
+	nextStat := batchSize
+
+	logf("Batch processing start")
+	timeStart := time.Now()
+
+	for i := 0; i < batches; i++ {
+		for j := 0; j < batchSize; j++ {
+			wg.Add(1)
+			go process(processed, &wg)
+			processed++
+		}
+		wg.Wait()
+
+		if processed >= nextStat {
+			interval := time.Since(timeStart).Seconds()
+			logf("Processed %d containers in %.3fs (%.3f/s)", processed, interval, float64(processed)/interval)
+			nextStat = nextStat * 2
+		}
+
+	}
+
+	for k := 0; k < remainder; k++ {
+		wg.Add(1)
+		go process(processed, &wg)
+		processed++
+	}
+	wg.Wait()
+
+	timeEnd := time.Now()
+	duration := timeEnd.Sub(timeStart)
+	logf("Batch processing completed in %.3fs", duration.Seconds())
+	return duration
+}
+
 func logf(format string, args ...interface{}) {
 	fmt.Printf(fmt.Sprintf("[%s] %s\n", time.Now().Format(time.StampMilli), format), args...)
 }

From 7951b3feaca08c412b1fda2b05426fa603b3c299 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 16:51:46 +0200
Subject: [PATCH 1128/1193] benchmark: extract ensureImage function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 114 ++++++++++++++++++-----------------
 1 file changed, 60 insertions(+), 54 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 188ce6898..5b5fb6fdd 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -43,60 +43,9 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 
 	printTestConfig(count, batchSize, image, privileged, freeze)
 
-	// Pre-load the image
-	var fingerprint string
-	if strings.Contains(image, ":") {
-		var remote string
-		var err error
-
-		defaultConfig := config.DefaultConfig
-		defaultConfig.UserAgent = version.UserAgent
-
-		remote, fingerprint, err = defaultConfig.ParseRemote(image)
-		if err != nil {
-			return err
-		}
-
-		d, err := defaultConfig.GetImageServer(remote)
-		if err != nil {
-			return err
-		}
-
-		if fingerprint == "" {
-			fingerprint = "default"
-		}
-
-		alias, _, err := d.GetImageAlias(fingerprint)
-		if err == nil {
-			fingerprint = alias.Target
-		}
-
-		_, _, err = c.GetImage(fingerprint)
-		if err != nil {
-			logf("Importing image into local store: %s", fingerprint)
-			image, _, err := d.GetImage(fingerprint)
-			if err != nil {
-				logf(fmt.Sprintf("Failed to import image: %s", err))
-				return err
-			}
-
-			op, err := c.CopyImage(d, *image, nil)
-			if err != nil {
-				logf(fmt.Sprintf("Failed to import image: %s", err))
-				return err
-			}
-
-			err = op.Wait()
-			if err != nil {
-				logf(fmt.Sprintf("Failed to import image: %s", err))
-				return err
-			}
-		} else {
-			logf("Found image in local store: %s", fingerprint)
-		}
-	} else {
-		fingerprint = image
-		logf("Found image in local store: %s", fingerprint)
+	fingerprint, err := ensureImage(c, image)
+	if err != nil {
+		return err
 	}
 
 	startContainer := func(index int, wg *sync.WaitGroup) {
@@ -249,6 +198,63 @@ func getBatchSize(parallel int) (int, error) {
 	return batchSize, nil
 }
 
+func ensureImage(c lxd.ContainerServer, image string) (string, error) {
+	var fingerprint string
+
+	if strings.Contains(image, ":") {
+		defaultConfig := config.DefaultConfig
+		defaultConfig.UserAgent = version.UserAgent
+
+		remote, fp, err := defaultConfig.ParseRemote(image)
+		if err != nil {
+			return "", err
+		}
+		fingerprint = fp
+
+		imageServer, err := defaultConfig.GetImageServer(remote)
+		if err != nil {
+			return "", err
+		}
+
+		if fingerprint == "" {
+			fingerprint = "default"
+		}
+
+		alias, _, err := imageServer.GetImageAlias(fingerprint)
+		if err == nil {
+			fingerprint = alias.Target
+		}
+
+		_, _, err = c.GetImage(fingerprint)
+		if err != nil {
+			logf("Importing image into local store: %s", fingerprint)
+			image, _, err := imageServer.GetImage(fingerprint)
+			if err != nil {
+				logf("Failed to import image: %s", err)
+				return "", err
+			}
+
+			op, err := c.CopyImage(imageServer, *image, nil)
+			if err != nil {
+				logf("Failed to import image: %s", err)
+				return "", err
+			}
+
+			err = op.Wait()
+			if err != nil {
+				logf("Failed to import image: %s", err)
+				return "", err
+			}
+		} else {
+			logf("Found image in local store: %s", fingerprint)
+		}
+	} else {
+		fingerprint = image
+		logf("Found image in local store: %s", fingerprint)
+	}
+	return fingerprint, nil
+}
+
 func processBatch(count int, batchSize int, process func(index int, wg *sync.WaitGroup)) time.Duration {
 	batches := count / batchSize
 	remainder := count % batchSize

From 4ec8b8340b0c43a9eeab1f2f94da0a9aedf2cb98 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 18:01:47 +0200
Subject: [PATCH 1129/1193] benchmark: add StopContainers function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 38 +++++++++++++++++++++++++++---------
 lxd-benchmark/benchmark/operation.go | 16 +++++++++++++++
 2 files changed, 45 insertions(+), 9 deletions(-)
 create mode 100644 lxd-benchmark/benchmark/operation.go

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 5b5fb6fdd..7ea369b6f 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -135,6 +135,33 @@ func GetContainers(c lxd.ContainerServer) ([]api.Container, error) {
 	return containers, nil
 }
 
+// StopContainers stops containers created by the benchmark.
+func StopContainers(c lxd.ContainerServer, containers []api.Container, parallel int) error {
+	batchSize, err := getBatchSize(parallel)
+	if err != nil {
+		return err
+	}
+
+	count := len(containers)
+	logf("Stopping %d containers", count)
+
+	stopContainer := func(index int, wg *sync.WaitGroup) {
+		defer wg.Done()
+
+		container := containers[index]
+		if container.IsActive() {
+			err := stopContainer(c, container.Name)
+			if err != nil {
+				logf("Failed to stop container '%s': %s", container.Name, err)
+				return
+			}
+		}
+	}
+
+	processBatch(count, batchSize, stopContainer)
+	return nil
+}
+
 // DeleteContainers removes containers created by the benchmark.
 func DeleteContainers(c lxd.ContainerServer, containers []api.Container, parallel int) error {
 	batchSize, err := getBatchSize(parallel)
@@ -150,17 +177,10 @@ func DeleteContainers(c lxd.ContainerServer, containers []api.Container, paralle
 
 		ct := containers[index]
 
-		// Stop
 		if ct.IsActive() {
-			op, err := c.UpdateContainerState(ct.Name, api.ContainerStatePut{Action: "stop", Timeout: -1, Force: true}, "")
-			if err != nil {
-				logf("Failed to delete container '%s': %s", ct.Name, err)
-				return
-			}
-
-			err = op.Wait()
+			err := stopContainer(c, ct.Name)
 			if err != nil {
-				logf("Failed to delete container '%s': %s", ct.Name, err)
+				logf("Failed to stop container '%s': %s", ct.Name, err)
 				return
 			}
 		}
diff --git a/lxd-benchmark/benchmark/operation.go b/lxd-benchmark/benchmark/operation.go
new file mode 100644
index 000000000..bbf648c32
--- /dev/null
+++ b/lxd-benchmark/benchmark/operation.go
@@ -0,0 +1,16 @@
+package benchmark
+
+import (
+	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/shared/api"
+)
+
+func stopContainer(c lxd.ContainerServer, name string) error {
+	op, err := c.UpdateContainerState(
+		name, api.ContainerStatePut{Action: "stop", Timeout: -1, Force: true}, "")
+	if err != nil {
+		return err
+	}
+
+	return op.Wait()
+}

From ccce8f1a392b07477a558ea20a99dfdb18f237e0 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 5 Sep 2017 18:14:25 +0200
Subject: [PATCH 1130/1193] benchmark: return operations duration

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 32 +++++++++++++++++++-------------
 lxd-benchmark/main.go                |  6 ++++--
 2 files changed, 23 insertions(+), 15 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 7ea369b6f..9ea7301ed 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -35,17 +35,19 @@ func PrintServerInfo(c lxd.ContainerServer) error {
 }
 
 // SpawnContainers launches a set of containers.
-func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image string, privileged bool, freeze bool) error {
+func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image string, privileged bool, freeze bool) (time.Duration, error) {
+	var duration time.Duration
+
 	batchSize, err := getBatchSize(parallel)
 	if err != nil {
-		return err
+		return duration, err
 	}
 
 	printTestConfig(count, batchSize, image, privileged, freeze)
 
 	fingerprint, err := ensureImage(c, image)
 	if err != nil {
-		return err
+		return duration, err
 	}
 
 	startContainer := func(index int, wg *sync.WaitGroup) {
@@ -111,8 +113,8 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 		}
 	}
 
-	processBatch(count, batchSize, startContainer)
-	return nil
+	duration = processBatch(count, batchSize, startContainer)
+	return duration, nil
 }
 
 // GetContainers returns containers created by the benchmark.
@@ -136,10 +138,12 @@ func GetContainers(c lxd.ContainerServer) ([]api.Container, error) {
 }
 
 // StopContainers stops containers created by the benchmark.
-func StopContainers(c lxd.ContainerServer, containers []api.Container, parallel int) error {
+func StopContainers(c lxd.ContainerServer, containers []api.Container, parallel int) (time.Duration, error) {
+	var duration time.Duration
+
 	batchSize, err := getBatchSize(parallel)
 	if err != nil {
-		return err
+		return duration, err
 	}
 
 	count := len(containers)
@@ -158,15 +162,17 @@ func StopContainers(c lxd.ContainerServer, containers []api.Container, parallel
 		}
 	}
 
-	processBatch(count, batchSize, stopContainer)
-	return nil
+	duration = processBatch(count, batchSize, stopContainer)
+	return duration, nil
 }
 
 // DeleteContainers removes containers created by the benchmark.
-func DeleteContainers(c lxd.ContainerServer, containers []api.Container, parallel int) error {
+func DeleteContainers(c lxd.ContainerServer, containers []api.Container, parallel int) (time.Duration, error) {
+	var duration time.Duration
+
 	batchSize, err := getBatchSize(parallel)
 	if err != nil {
-		return err
+		return duration, err
 	}
 
 	count := len(containers)
@@ -199,8 +205,8 @@ func DeleteContainers(c lxd.ContainerServer, containers []api.Container, paralle
 		}
 	}
 
-	processBatch(count, batchSize, deleteContainer)
-	return nil
+	duration = processBatch(count, batchSize, deleteContainer)
+	return duration, nil
 }
 
 func getBatchSize(parallel int) (int, error) {
diff --git a/lxd-benchmark/main.go b/lxd-benchmark/main.go
index b6e15d5fb..1b84652d0 100644
--- a/lxd-benchmark/main.go
+++ b/lxd-benchmark/main.go
@@ -65,13 +65,15 @@ func run(args []string) error {
 
 	switch os.Args[1] {
 	case "spawn":
-		return benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argFreeze)
+		_, err = benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argFreeze)
+		return err
 	case "delete":
 		containers, err := benchmark.GetContainers(c)
 		if err != nil {
 			return err
 		}
-		return benchmark.DeleteContainers(c, containers, *argParallel)
+		_, err = benchmark.DeleteContainers(c, containers, *argParallel)
+		return err
 	}
 
 	return nil

From cd55fcf5d47d53b18a8fa05dfc172a3017140683 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 6 Sep 2017 09:52:22 +0200
Subject: [PATCH 1131/1193] benchmark: add CreateContainers function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 60 ++++++++++++++++++++----------------
 lxd-benchmark/benchmark/operation.go | 24 +++++++++++++++
 2 files changed, 58 insertions(+), 26 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 9ea7301ed..7b1a0637f 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -53,39 +53,16 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 	startContainer := func(index int, wg *sync.WaitGroup) {
 		defer wg.Done()
 
-		// Configure
-		config := map[string]string{}
-		if privileged {
-			config["security.privileged"] = "true"
-		}
-		config["user.lxd-benchmark"] = "true"
-
-		// Create
-		nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
-		name := fmt.Sprintf(nameFormat, index+1)
-		req := api.ContainersPost{
-			Name: name,
-			Source: api.ContainerSource{
-				Type:        "image",
-				Fingerprint: fingerprint,
-			},
-		}
-		req.Config = config
-
-		op, err := c.CreateContainer(req)
-		if err != nil {
-			logf("Failed to spawn container '%s': %s", name, err)
-			return
-		}
+		name := getContainerName(count, index)
 
-		err = op.Wait()
+		err := createContainer(c, fingerprint, name, privileged)
 		if err != nil {
 			logf("Failed to spawn container '%s': %s", name, err)
 			return
 		}
 
 		// Start
-		op, err = c.UpdateContainerState(name, api.ContainerStatePut{Action: "start", Timeout: -1}, "")
+		op, err := c.UpdateContainerState(name, api.ContainerStatePut{Action: "start", Timeout: -1}, "")
 		if err != nil {
 			logf("Failed to spawn container '%s': %s", name, err)
 			return
@@ -117,6 +94,32 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 	return duration, nil
 }
 
+// CreateContainers create the specified number of containers.
+func CreateContainers(c lxd.ContainerServer, count int, parallel int, fingerprint string, privileged bool) (time.Duration, error) {
+	var duration time.Duration
+
+	batchSize, err := getBatchSize(parallel)
+	if err != nil {
+		return duration, err
+	}
+
+	createContainer := func(index int, wg *sync.WaitGroup) {
+		defer wg.Done()
+
+		name := getContainerName(count, index)
+
+		err := createContainer(c, fingerprint, name, privileged)
+		if err != nil {
+			logf("Failed to spawn container '%s': %s", name, err)
+			return
+		}
+	}
+
+	duration = processBatch(count, batchSize, createContainer)
+
+	return duration, nil
+}
+
 // GetContainers returns containers created by the benchmark.
 func GetContainers(c lxd.ContainerServer) ([]api.Container, error) {
 	containers := []api.Container{}
@@ -224,6 +227,11 @@ func getBatchSize(parallel int) (int, error) {
 	return batchSize, nil
 }
 
+func getContainerName(count int, index int) string {
+	nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
+	return fmt.Sprintf(nameFormat, index+1)
+}
+
 func ensureImage(c lxd.ContainerServer, image string) (string, error) {
 	var fingerprint string
 
diff --git a/lxd-benchmark/benchmark/operation.go b/lxd-benchmark/benchmark/operation.go
index bbf648c32..c0d829875 100644
--- a/lxd-benchmark/benchmark/operation.go
+++ b/lxd-benchmark/benchmark/operation.go
@@ -5,6 +5,30 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
+func createContainer(c lxd.ContainerServer, fingerprint string, name string, privileged bool) error {
+	config := map[string]string{}
+	if privileged {
+		config["security.privileged"] = "true"
+	}
+	config["user.lxd-benchmark"] = "true"
+
+	req := api.ContainersPost{
+		Name: name,
+		Source: api.ContainerSource{
+			Type:        "image",
+			Fingerprint: fingerprint,
+		},
+	}
+	req.Config = config
+
+	op, err := c.CreateContainer(req)
+	if err != nil {
+		return err
+	}
+
+	return op.Wait()
+}
+
 func stopContainer(c lxd.ContainerServer, name string) error {
 	op, err := c.UpdateContainerState(
 		name, api.ContainerStatePut{Action: "stop", Timeout: -1, Force: true}, "")

From 6ffe78779120f36615490463a930198d83feafe1 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 6 Sep 2017 10:21:25 +0200
Subject: [PATCH 1132/1193] benchmark: add StartContainers function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 40 ++++++++++++++++++++++++++++--------
 lxd-benchmark/benchmark/operation.go | 10 +++++++++
 2 files changed, 41 insertions(+), 9 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 7b1a0637f..e096f83ea 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -61,16 +61,9 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 			return
 		}
 
-		// Start
-		op, err := c.UpdateContainerState(name, api.ContainerStatePut{Action: "start", Timeout: -1}, "")
+		err = startContainer(c, name)
 		if err != nil {
-			logf("Failed to spawn container '%s': %s", name, err)
-			return
-		}
-
-		err = op.Wait()
-		if err != nil {
-			logf("Failed to spawn container '%s': %s", name, err)
+			logf("Failed to start container '%s': %s", name, err)
 			return
 		}
 
@@ -140,6 +133,35 @@ func GetContainers(c lxd.ContainerServer) ([]api.Container, error) {
 	return containers, nil
 }
 
+// StartContainers starts containers created by the benchmark.
+func StartContainers(c lxd.ContainerServer, containers []api.Container, parallel int) (time.Duration, error) {
+	var duration time.Duration
+
+	batchSize, err := getBatchSize(parallel)
+	if err != nil {
+		return duration, err
+	}
+
+	count := len(containers)
+	logf("Starting %d containers", count)
+
+	startContainer := func(index int, wg *sync.WaitGroup) {
+		defer wg.Done()
+
+		container := containers[index]
+		if !container.IsActive() {
+			err := startContainer(c, container.Name)
+			if err != nil {
+				logf("Failed to start container '%s': %s", container.Name, err)
+				return
+			}
+		}
+	}
+
+	duration = processBatch(count, batchSize, startContainer)
+	return duration, nil
+}
+
 // StopContainers stops containers created by the benchmark.
 func StopContainers(c lxd.ContainerServer, containers []api.Container, parallel int) (time.Duration, error) {
 	var duration time.Duration
diff --git a/lxd-benchmark/benchmark/operation.go b/lxd-benchmark/benchmark/operation.go
index c0d829875..d5771d2ae 100644
--- a/lxd-benchmark/benchmark/operation.go
+++ b/lxd-benchmark/benchmark/operation.go
@@ -29,6 +29,16 @@ func createContainer(c lxd.ContainerServer, fingerprint string, name string, pri
 	return op.Wait()
 }
 
+func startContainer(c lxd.ContainerServer, name string) error {
+	op, err := c.UpdateContainerState(
+		name, api.ContainerStatePut{Action: "start", Timeout: -1}, "")
+	if err != nil {
+		return err
+	}
+
+	return op.Wait()
+}
+
 func stopContainer(c lxd.ContainerServer, name string) error {
 	op, err := c.UpdateContainerState(
 		name, api.ContainerStatePut{Action: "stop", Timeout: -1, Force: true}, "")

From e842db6eea120afd87a854cdd8e54c8220cc1d23 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 6 Sep 2017 10:42:31 +0200
Subject: [PATCH 1133/1193] benchmark: add freezeContainer function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 12 ++----------
 lxd-benchmark/benchmark/operation.go | 10 ++++++++++
 2 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index e096f83ea..9121e4231 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -67,17 +67,9 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 			return
 		}
 
-		// Freeze
 		if freeze {
-			op, err := c.UpdateContainerState(name, api.ContainerStatePut{Action: "freeze", Timeout: -1}, "")
-			if err != nil {
-				logf("Failed to spawn container '%s': %s", name, err)
-				return
-			}
-
-			err = op.Wait()
-			if err != nil {
-				logf("Failed to spawn container '%s': %s", name, err)
+			if freezeContainer(c, name) != nil {
+				logf("Failed to freeze container '%s': %s", name, err)
 				return
 			}
 		}
diff --git a/lxd-benchmark/benchmark/operation.go b/lxd-benchmark/benchmark/operation.go
index d5771d2ae..3274bfc59 100644
--- a/lxd-benchmark/benchmark/operation.go
+++ b/lxd-benchmark/benchmark/operation.go
@@ -48,3 +48,13 @@ func stopContainer(c lxd.ContainerServer, name string) error {
 
 	return op.Wait()
 }
+
+func freezeContainer(c lxd.ContainerServer, name string) error {
+	op, err := c.UpdateContainerState(
+		name, api.ContainerStatePut{Action: "freeze", Timeout: -1}, "")
+	if err != nil {
+		return err
+	}
+
+	return op.Wait()
+}

From 94691fae3279cd38a6dbcff712ab986b50d076c2 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 6 Sep 2017 10:13:52 +0200
Subject: [PATCH 1134/1193] benchmark: split private functions to separate
 files

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/batch.go     |  61 ++++++++++++++++++
 lxd-benchmark/benchmark/benchmark.go | 118 +++++------------------------------
 lxd-benchmark/benchmark/operation.go |   2 +-
 lxd-benchmark/benchmark/util.go      |  38 +++++++++++
 lxd-benchmark/main.go                |   7 ++-
 5 files changed, 121 insertions(+), 105 deletions(-)
 create mode 100644 lxd-benchmark/benchmark/batch.go
 create mode 100644 lxd-benchmark/benchmark/util.go

diff --git a/lxd-benchmark/benchmark/batch.go b/lxd-benchmark/benchmark/batch.go
new file mode 100644
index 000000000..0e2399480
--- /dev/null
+++ b/lxd-benchmark/benchmark/batch.go
@@ -0,0 +1,61 @@
+package benchmark
+
+import (
+	"io/ioutil"
+	"sync"
+	"time"
+)
+
+func getBatchSize(parallel int) (int, error) {
+	batchSize := parallel
+	if batchSize < 1 {
+		// Detect the number of parallel actions
+		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
+		if err != nil {
+			return -1, err
+		}
+
+		batchSize = len(cpus)
+	}
+
+	return batchSize, nil
+}
+
+func processBatch(count int, batchSize int, process func(index int, wg *sync.WaitGroup)) time.Duration {
+	batches := count / batchSize
+	remainder := count % batchSize
+	processed := 0
+	wg := sync.WaitGroup{}
+	nextStat := batchSize
+
+	logf("Batch processing start")
+	timeStart := time.Now()
+
+	for i := 0; i < batches; i++ {
+		for j := 0; j < batchSize; j++ {
+			wg.Add(1)
+			go process(processed, &wg)
+			processed++
+		}
+		wg.Wait()
+
+		if processed >= nextStat {
+			interval := time.Since(timeStart).Seconds()
+			logf("Processed %d containers in %.3fs (%.3f/s)", processed, interval, float64(processed)/interval)
+			nextStat = nextStat * 2
+		}
+
+	}
+
+	for k := 0; k < remainder; k++ {
+		wg.Add(1)
+		go process(processed, &wg)
+		processed++
+	}
+	wg.Wait()
+
+	timeEnd := time.Now()
+	duration := timeEnd.Sub(timeStart)
+	logf("Batch processing completed in %.3fs", duration.Seconds())
+	return duration
+}
diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 9121e4231..1f4bf82e7 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -2,7 +2,6 @@ package benchmark
 
 import (
 	"fmt"
-	"io/ioutil"
 	"strings"
 	"sync"
 	"time"
@@ -13,6 +12,8 @@ import (
 	"github.com/lxc/lxd/shared/version"
 )
 
+const userConfigKey = "user.lxd-benchmark"
+
 // PrintServerInfo prints out information about the server.
 func PrintServerInfo(c lxd.ContainerServer) error {
 	server, _, err := c.GetServer()
@@ -35,7 +36,7 @@ func PrintServerInfo(c lxd.ContainerServer) error {
 }
 
 // SpawnContainers launches a set of containers.
-func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image string, privileged bool, freeze bool) (time.Duration, error) {
+func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image string, privileged bool, start bool, freeze bool) (time.Duration, error) {
 	var duration time.Duration
 
 	batchSize, err := getBatchSize(parallel)
@@ -61,17 +62,20 @@ func SpawnContainers(c lxd.ContainerServer, count int, parallel int, image strin
 			return
 		}
 
-		err = startContainer(c, name)
-		if err != nil {
-			logf("Failed to start container '%s': %s", name, err)
-			return
-		}
-
-		if freeze {
-			if freezeContainer(c, name) != nil {
-				logf("Failed to freeze container '%s': %s", name, err)
+		if start {
+			err := startContainer(c, name)
+			if err != nil {
+				logf("Failed to start container '%s': %s", name, err)
 				return
 			}
+
+			if freeze {
+				err := freezeContainer(c, name)
+				if err != nil {
+					logf("Failed to freeze container '%s': %s", name, err)
+					return
+				}
+			}
 		}
 	}
 
@@ -115,11 +119,9 @@ func GetContainers(c lxd.ContainerServer) ([]api.Container, error) {
 	}
 
 	for _, container := range allContainers {
-		if container.Config["user.lxd-benchmark"] != "true" {
-			continue
+		if container.Config[userConfigKey] == "true" {
+			containers = append(containers, container)
 		}
-
-		containers = append(containers, container)
 	}
 
 	return containers, nil
@@ -226,26 +228,6 @@ func DeleteContainers(c lxd.ContainerServer, containers []api.Container, paralle
 	return duration, nil
 }
 
-func getBatchSize(parallel int) (int, error) {
-	batchSize := parallel
-	if batchSize < 1 {
-		// Detect the number of parallel actions
-		cpus, err := ioutil.ReadDir("/sys/bus/cpu/devices")
-		if err != nil {
-			return -1, err
-		}
-
-		batchSize = len(cpus)
-	}
-
-	return batchSize, nil
-}
-
-func getContainerName(count int, index int) string {
-	nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
-	return fmt.Sprintf(nameFormat, index+1)
-}
-
 func ensureImage(c lxd.ContainerServer, image string) (string, error) {
 	var fingerprint string
 
@@ -302,69 +284,3 @@ func ensureImage(c lxd.ContainerServer, image string) (string, error) {
 	}
 	return fingerprint, nil
 }
-
-func processBatch(count int, batchSize int, process func(index int, wg *sync.WaitGroup)) time.Duration {
-	batches := count / batchSize
-	remainder := count % batchSize
-	processed := 0
-	wg := sync.WaitGroup{}
-	nextStat := batchSize
-
-	logf("Batch processing start")
-	timeStart := time.Now()
-
-	for i := 0; i < batches; i++ {
-		for j := 0; j < batchSize; j++ {
-			wg.Add(1)
-			go process(processed, &wg)
-			processed++
-		}
-		wg.Wait()
-
-		if processed >= nextStat {
-			interval := time.Since(timeStart).Seconds()
-			logf("Processed %d containers in %.3fs (%.3f/s)", processed, interval, float64(processed)/interval)
-			nextStat = nextStat * 2
-		}
-
-	}
-
-	for k := 0; k < remainder; k++ {
-		wg.Add(1)
-		go process(processed, &wg)
-		processed++
-	}
-	wg.Wait()
-
-	timeEnd := time.Now()
-	duration := timeEnd.Sub(timeStart)
-	logf("Batch processing completed in %.3fs", duration.Seconds())
-	return duration
-}
-
-func logf(format string, args ...interface{}) {
-	fmt.Printf(fmt.Sprintf("[%s] %s\n", time.Now().Format(time.StampMilli), format), args...)
-}
-
-func printTestConfig(count int, batchSize int, image string, privileged bool, freeze bool) {
-	privilegedStr := "unprivileged"
-	if privileged {
-		privilegedStr = "privileged"
-	}
-	mode := "normal startup"
-	if freeze {
-		mode = "start and freeze"
-	}
-
-	batches := count / batchSize
-	remainder := count % batchSize
-	fmt.Printf("Test variables:\n")
-	fmt.Printf("  Container count: %d\n", count)
-	fmt.Printf("  Container mode: %s\n", privilegedStr)
-	fmt.Printf("  Startup mode: %s\n", mode)
-	fmt.Printf("  Image: %s\n", image)
-	fmt.Printf("  Batches: %d\n", batches)
-	fmt.Printf("  Batch size: %d\n", batchSize)
-	fmt.Printf("  Remainder: %d\n", remainder)
-	fmt.Printf("\n")
-}
diff --git a/lxd-benchmark/benchmark/operation.go b/lxd-benchmark/benchmark/operation.go
index 3274bfc59..e01e5aaf3 100644
--- a/lxd-benchmark/benchmark/operation.go
+++ b/lxd-benchmark/benchmark/operation.go
@@ -10,7 +10,7 @@ func createContainer(c lxd.ContainerServer, fingerprint string, name string, pri
 	if privileged {
 		config["security.privileged"] = "true"
 	}
-	config["user.lxd-benchmark"] = "true"
+	config[userConfigKey] = "true"
 
 	req := api.ContainersPost{
 		Name: name,
diff --git a/lxd-benchmark/benchmark/util.go b/lxd-benchmark/benchmark/util.go
new file mode 100644
index 000000000..76972c684
--- /dev/null
+++ b/lxd-benchmark/benchmark/util.go
@@ -0,0 +1,38 @@
+package benchmark
+
+import (
+	"fmt"
+	"time"
+)
+
+func getContainerName(count int, index int) string {
+	nameFormat := "benchmark-%." + fmt.Sprintf("%d", len(fmt.Sprintf("%d", count))) + "d"
+	return fmt.Sprintf(nameFormat, index+1)
+}
+
+func logf(format string, args ...interface{}) {
+	fmt.Printf(fmt.Sprintf("[%s] %s\n", time.Now().Format(time.StampMilli), format), args...)
+}
+
+func printTestConfig(count int, batchSize int, image string, privileged bool, freeze bool) {
+	privilegedStr := "unprivileged"
+	if privileged {
+		privilegedStr = "privileged"
+	}
+	mode := "normal startup"
+	if freeze {
+		mode = "start and freeze"
+	}
+
+	batches := count / batchSize
+	remainder := count % batchSize
+	fmt.Printf("Test variables:\n")
+	fmt.Printf("  Container count: %d\n", count)
+	fmt.Printf("  Container mode: %s\n", privilegedStr)
+	fmt.Printf("  Startup mode: %s\n", mode)
+	fmt.Printf("  Image: %s\n", image)
+	fmt.Printf("  Batches: %d\n", batches)
+	fmt.Printf("  Batch size: %d\n", batchSize)
+	fmt.Printf("  Remainder: %d\n", remainder)
+	fmt.Printf("\n")
+}
diff --git a/lxd-benchmark/main.go b/lxd-benchmark/main.go
index 1b84652d0..d0396f12f 100644
--- a/lxd-benchmark/main.go
+++ b/lxd-benchmark/main.go
@@ -15,6 +15,7 @@ var argCount = gnuflag.Int("count", 100, "Number of containers to create")
 var argParallel = gnuflag.Int("parallel", -1, "Number of threads to use")
 var argImage = gnuflag.String("image", "ubuntu:", "Image to use for the test")
 var argPrivileged = gnuflag.Bool("privileged", false, "Use privileged containers")
+var argStart = gnuflag.Bool("start", true, "Start the container after creation")
 var argFreeze = gnuflag.Bool("freeze", false, "Freeze the container right after start")
 
 func main() {
@@ -41,7 +42,7 @@ func run(args []string) error {
 		}
 		gnuflag.SetOut(out)
 
-		fmt.Fprintf(out, "Usage: %s spawn [--count=COUNT] [--image=IMAGE] [--privileged=BOOL] [--parallel=COUNT]\n", os.Args[0])
+		fmt.Fprintf(out, "Usage: %s spawn [--count=COUNT] [--image=IMAGE] [--privileged=BOOL] [--start=BOOL] [--freeze=BOOL] [--parallel=COUNT]\n", os.Args[0])
 		fmt.Fprintf(out, "       %s delete [--parallel=COUNT]\n\n", os.Args[0])
 		gnuflag.PrintDefaults()
 		fmt.Fprintf(out, "\n")
@@ -50,7 +51,7 @@ func run(args []string) error {
 			return nil
 		}
 
-		return fmt.Errorf("An valid action (spawn or delete) must be passed.")
+		return fmt.Errorf("A valid action (spawn or delete) must be passed.")
 	}
 
 	gnuflag.Parse(true)
@@ -65,7 +66,7 @@ func run(args []string) error {
 
 	switch os.Args[1] {
 	case "spawn":
-		_, err = benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argFreeze)
+		_, err = benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argStart, *argFreeze)
 		return err
 	case "delete":
 		containers, err := benchmark.GetContainers(c)

From fab0bcd821a777737fd1415afebef6fc0aebbfa4 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 6 Sep 2017 12:06:35 +0200
Subject: [PATCH 1135/1193] lxd-benchmark: add start and stop commands

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/main.go | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/lxd-benchmark/main.go b/lxd-benchmark/main.go
index d0396f12f..f9c28d791 100644
--- a/lxd-benchmark/main.go
+++ b/lxd-benchmark/main.go
@@ -30,7 +30,7 @@ func main() {
 
 func run(args []string) error {
 	// Parse command line
-	if len(os.Args) == 1 || !shared.StringInSlice(os.Args[1], []string{"spawn", "delete"}) {
+	if len(os.Args) == 1 || !shared.StringInSlice(os.Args[1], []string{"spawn", "start", "stop", "delete"}) {
 		if len(os.Args) > 1 && os.Args[1] == "--version" {
 			fmt.Println(version.Version)
 			return nil
@@ -43,6 +43,8 @@ func run(args []string) error {
 		gnuflag.SetOut(out)
 
 		fmt.Fprintf(out, "Usage: %s spawn [--count=COUNT] [--image=IMAGE] [--privileged=BOOL] [--start=BOOL] [--freeze=BOOL] [--parallel=COUNT]\n", os.Args[0])
+		fmt.Fprintf(out, "       %s start [--parallel=COUNT]\n", os.Args[0])
+		fmt.Fprintf(out, "       %s stop [--parallel=COUNT]\n", os.Args[0])
 		fmt.Fprintf(out, "       %s delete [--parallel=COUNT]\n\n", os.Args[0])
 		gnuflag.PrintDefaults()
 		fmt.Fprintf(out, "\n")
@@ -51,7 +53,7 @@ func run(args []string) error {
 			return nil
 		}
 
-		return fmt.Errorf("A valid action (spawn or delete) must be passed.")
+		return fmt.Errorf("A valid action (spawn, start, stop, delete) must be passed.")
 	}
 
 	gnuflag.Parse(true)
@@ -68,6 +70,20 @@ func run(args []string) error {
 	case "spawn":
 		_, err = benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argStart, *argFreeze)
 		return err
+	case "start":
+		containers, err := benchmark.GetContainers(c)
+		if err != nil {
+			return err
+		}
+		_, err = benchmark.StartContainers(c, containers, *argParallel)
+		return err
+	case "stop":
+		containers, err := benchmark.GetContainers(c)
+		if err != nil {
+			return err
+		}
+		_, err = benchmark.StopContainers(c, containers, *argParallel)
+		return err
 	case "delete":
 		containers, err := benchmark.GetContainers(c)
 		if err != nil {

From b87a6a68c0fa82dbec0fea4085c6a8ff25201cca Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 6 Sep 2017 18:08:36 +0200
Subject: [PATCH 1136/1193] benchmark: add csv reporting

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/report.go | 95 +++++++++++++++++++++++++++++++++++++++
 lxd-benchmark/main.go             | 54 ++++++++++++++++++----
 2 files changed, 140 insertions(+), 9 deletions(-)
 create mode 100644 lxd-benchmark/benchmark/report.go

diff --git a/lxd-benchmark/benchmark/report.go b/lxd-benchmark/benchmark/report.go
new file mode 100644
index 000000000..60ab02f6d
--- /dev/null
+++ b/lxd-benchmark/benchmark/report.go
@@ -0,0 +1,95 @@
+package benchmark
+
+import (
+	"encoding/csv"
+	"fmt"
+	"io"
+	"os"
+	"time"
+)
+
+// Subset of JMeter CSV log format that are required by Jenkins performance
+// plugin
+// (see http://jmeter.apache.org/usermanual/listeners.html#csvlogformat)
+var csvFields = []string{
+	"timeStamp", // in milliseconds since 1/1/1970
+	"elapsed",   // in milliseconds
+	"label",
+	"responseCode",
+	"success", // "true" or "false"
+}
+
+// CSVReport reads/writes a CSV report file.
+type CSVReport struct {
+	Filename string
+
+	records [][]string
+}
+
+// Load reads current content of the filename and loads records.
+func (r *CSVReport) Load() error {
+	file, err := os.Open(r.Filename)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	reader := csv.NewReader(file)
+	for line := 1; err != io.EOF; line++ {
+		record, err := reader.Read()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			return err
+		}
+
+		err = r.addRecord(record)
+		if err != nil {
+			return err
+		}
+	}
+	logf("Loaded report file %s", r.Filename)
+	return nil
+}
+
+// Write writes current records to file.
+func (r *CSVReport) Write() error {
+	file, err := os.OpenFile(r.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	writer := csv.NewWriter(file)
+	err = writer.WriteAll(r.records)
+	if err != nil {
+		return err
+	}
+
+	logf("Written report file %s", r.Filename)
+	return nil
+}
+
+// AddRecord adds a record to the report.
+func (r *CSVReport) AddRecord(label string, elapsed time.Duration) error {
+	if len(r.records) == 0 {
+		r.addRecord(csvFields)
+	}
+
+	record := []string{
+		fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond)), // timestamp
+		fmt.Sprintf("%d", elapsed/time.Millisecond),
+		label,
+		"",     // responseCode is not used
+		"true", // success"
+	}
+	return r.addRecord(record)
+}
+
+func (r *CSVReport) addRecord(record []string) error {
+	if len(record) != len(csvFields) {
+		return fmt.Errorf("Invalid number of fields : %q", record)
+	}
+	r.records = append(r.records, record)
+	return nil
+}
diff --git a/lxd-benchmark/main.go b/lxd-benchmark/main.go
index f9c28d791..76bc9e2fd 100644
--- a/lxd-benchmark/main.go
+++ b/lxd-benchmark/main.go
@@ -3,6 +3,7 @@ package main
 import (
 	"fmt"
 	"os"
+	"time"
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd-benchmark/benchmark"
@@ -17,6 +18,8 @@ var argImage = gnuflag.String("image", "ubuntu:", "Image to use for the test")
 var argPrivileged = gnuflag.Bool("privileged", false, "Use privileged containers")
 var argStart = gnuflag.Bool("start", true, "Start the container after creation")
 var argFreeze = gnuflag.Bool("freeze", false, "Freeze the container right after start")
+var argReportFile = gnuflag.String("report-file", "", "A CSV file to write test file to. If the file is present, it will be appended to.")
+var argReportLabel = gnuflag.String("report-label", "", "A label for the report entry. By default, the action is used.")
 
 func main() {
 	err := run(os.Args)
@@ -66,32 +69,65 @@ func run(args []string) error {
 
 	benchmark.PrintServerInfo(c)
 
-	switch os.Args[1] {
+	var report *benchmark.CSVReport
+	if *argReportFile != "" {
+		report = &benchmark.CSVReport{Filename: *argReportFile}
+		if shared.PathExists(*argReportFile) {
+			err := report.Load()
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	action := os.Args[1]
+	var duration time.Duration
+	switch action {
 	case "spawn":
-		_, err = benchmark.SpawnContainers(c, *argCount, *argParallel, *argImage, *argPrivileged, *argStart, *argFreeze)
-		return err
+		duration, err = benchmark.SpawnContainers(
+			c, *argCount, *argParallel, *argImage, *argPrivileged, *argStart, *argFreeze)
+		if err != nil {
+			return err
+		}
 	case "start":
 		containers, err := benchmark.GetContainers(c)
 		if err != nil {
 			return err
 		}
-		_, err = benchmark.StartContainers(c, containers, *argParallel)
-		return err
+		duration, err = benchmark.StartContainers(c, containers, *argParallel)
+		if err != nil {
+			return err
+		}
 	case "stop":
 		containers, err := benchmark.GetContainers(c)
 		if err != nil {
 			return err
 		}
-		_, err = benchmark.StopContainers(c, containers, *argParallel)
-		return err
+		duration, err = benchmark.StopContainers(c, containers, *argParallel)
+		if err != nil {
+			return err
+		}
 	case "delete":
 		containers, err := benchmark.GetContainers(c)
 		if err != nil {
 			return err
 		}
-		_, err = benchmark.DeleteContainers(c, containers, *argParallel)
-		return err
+		duration, err = benchmark.DeleteContainers(c, containers, *argParallel)
+		if err != nil {
+			return err
+		}
 	}
 
+	if report != nil {
+		label := action
+		if *argReportLabel != "" {
+			label = *argReportLabel
+		}
+		report.AddRecord(label, duration)
+		err := report.Write()
+		if err != nil {
+			return err
+		}
+	}
 	return nil
 }

From db2d3dfe084924828a69f55b5f676f5134c0420b Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 7 Sep 2017 10:51:05 +0200
Subject: [PATCH 1137/1193] benchmark: fix ensureImage when a local alias is
 passed

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index 1f4bf82e7..aca077cb4 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -275,12 +275,22 @@ func ensureImage(c lxd.ContainerServer, image string) (string, error) {
 				logf("Failed to import image: %s", err)
 				return "", err
 			}
-		} else {
-			logf("Found image in local store: %s", fingerprint)
 		}
 	} else {
+		alias, _, err := c.GetImageAlias(image)
+		if err == nil {
+			fingerprint = alias.Target
+		} else {
+			_, _, err = c.GetImage(image)
+		}
+
+		if err != nil {
+			logf("Image not found in local store: %s", image)
+			return "", err
+		}
 		fingerprint = image
-		logf("Found image in local store: %s", fingerprint)
 	}
+
+	logf("Found image in local store: %s", fingerprint)
 	return fingerprint, nil
 }

From 562fd1a4abf7c59940af0bd2b1c9e38514f27042 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 7 Sep 2017 11:00:11 +0200
Subject: [PATCH 1138/1193] benchmark: extract deleteContainer and copyImage
 functions

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd-benchmark/benchmark/benchmark.go | 29 ++++++++---------------------
 lxd-benchmark/benchmark/operation.go | 18 ++++++++++++++++++
 2 files changed, 26 insertions(+), 21 deletions(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index aca077cb4..b6cae2e9c 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -200,26 +200,19 @@ func DeleteContainers(c lxd.ContainerServer, containers []api.Container, paralle
 	deleteContainer := func(index int, wg *sync.WaitGroup) {
 		defer wg.Done()
 
-		ct := containers[index]
-
-		if ct.IsActive() {
-			err := stopContainer(c, ct.Name)
+		container := containers[index]
+		name := container.Name
+		if container.IsActive() {
+			err := stopContainer(c, name)
 			if err != nil {
-				logf("Failed to stop container '%s': %s", ct.Name, err)
+				logf("Failed to stop container '%s': %s", name, err)
 				return
 			}
 		}
 
-		// Delete
-		op, err := c.DeleteContainer(ct.Name)
-		if err != nil {
-			logf("Failed to delete container: %s", ct.Name)
-			return
-		}
-
-		err = op.Wait()
+		err = deleteContainer(c, name)
 		if err != nil {
-			logf("Failed to delete container: %s", ct.Name)
+			logf("Failed to delete container: %s", name)
 			return
 		}
 	}
@@ -264,13 +257,7 @@ func ensureImage(c lxd.ContainerServer, image string) (string, error) {
 				return "", err
 			}
 
-			op, err := c.CopyImage(imageServer, *image, nil)
-			if err != nil {
-				logf("Failed to import image: %s", err)
-				return "", err
-			}
-
-			err = op.Wait()
+			err = copyImage(c, imageServer, *image)
 			if err != nil {
 				logf("Failed to import image: %s", err)
 				return "", err
diff --git a/lxd-benchmark/benchmark/operation.go b/lxd-benchmark/benchmark/operation.go
index e01e5aaf3..b4f4f3cea 100644
--- a/lxd-benchmark/benchmark/operation.go
+++ b/lxd-benchmark/benchmark/operation.go
@@ -58,3 +58,21 @@ func freezeContainer(c lxd.ContainerServer, name string) error {
 
 	return op.Wait()
 }
+
+func deleteContainer(c lxd.ContainerServer, name string) error {
+	op, err := c.DeleteContainer(name)
+	if err != nil {
+		return err
+	}
+
+	return op.Wait()
+}
+
+func copyImage(c lxd.ContainerServer, s lxd.ImageServer, image api.Image) error {
+	op, err := c.CopyImage(s, image, nil)
+	if err != nil {
+		return err
+	}
+
+	return op.Wait()
+}

From b9b67cb16dbbf21413c5a282c91c57a813659903 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 17 Aug 2017 16:32:28 +0200
Subject: [PATCH 1139/1193] add performance regression tests

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 test/perf.sh | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)
 create mode 100755 test/perf.sh

diff --git a/test/perf.sh b/test/perf.sh
new file mode 100755
index 000000000..ab9b76551
--- /dev/null
+++ b/test/perf.sh
@@ -0,0 +1,82 @@
+#!/bin/sh -eu
+#
+# Performance tests runner
+#
+
+[ -n "${GOPATH:-}" ] && export "PATH=${GOPATH}/bin:${PATH}"
+
+PERF_LOG_CSV="perf.csv"
+
+import_subdir_files() {
+    test  "$1"
+    # shellcheck disable=SC2039
+    local file
+    for file in "$1"/*.sh; do
+        # shellcheck disable=SC1090
+        . "$file"
+    done
+}
+
+import_subdir_files includes
+
+log_message() {
+    echo "==>" "$@"
+}
+
+run_benchmark() {
+    # shellcheck disable=SC2039
+    local label description opts
+    label="$1"
+    description="$2"
+    shift 2
+
+    log_message "Benchmark start: $label - $description"
+    lxd_benchmark "$@" --report-file "$PERF_LOG_CSV" --report-label "$label"
+    log_message "Benchmark completed: $label"
+}
+
+lxd_benchmark() {
+    # shellcheck disable=SC2039
+    local opts
+    [ "${LXD_TEST_IMAGE:-}" ] && opts="--image $LXD_TEST_IMAGE" || opts=""
+    lxd-benchmark "$@" $opts
+}
+
+cleanup() {
+    if [ "$TEST_RESULT" != "success" ]; then
+        rm -f "$PERF_LOG_CSV"
+    fi
+    lxd_benchmark delete  # ensure all test containers have been deleted
+    kill_lxd "$LXD_DIR"
+    cleanup_lxds "$TEST_DIR"
+    log_message "Performance tests result: $TEST_RESULT"
+}
+
+trap cleanup EXIT HUP INT TERM
+
+# Setup test directories
+TEST_DIR=$(mktemp -d -p "$(pwd)" tmp.XXX)
+LXD_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
+export LXD_DIR
+chmod +x "${TEST_DIR}" "${LXD_DIR}"
+
+if [ -z "${LXD_BACKEND:-}" ]; then
+    LXD_BACKEND="dir"
+fi
+
+import_storage_backends
+
+spawn_lxd "${LXD_DIR}" true
+
+# shellcheck disable=SC2034
+TEST_RESULT=failure
+
+run_benchmark "create-one" "create 1 container" spawn --count 1 --start=false
+run_benchmark "start-one" "start 1 container" start
+run_benchmark "stop-one" "stop 1 container" stop
+run_benchmark "delete-one" "delete 1 container" delete
+run_benchmark "create-128" "create 128 containers" spawn --count 128 --start=false
+run_benchmark "delete-128" "delete 128 containers" delete
+
+# shellcheck disable=SC2034
+TEST_RESULT=success

From dc29680a292077990242b8cf01c0a1119da4f1ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 7 Sep 2017 18:47:18 -0400
Subject: [PATCH 1140/1193] tests: Add support for LXD_TMPFS to perf.sh
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/perf.sh | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/test/perf.sh b/test/perf.sh
index ab9b76551..8f4a34e97 100755
--- a/test/perf.sh
+++ b/test/perf.sh
@@ -56,6 +56,11 @@ trap cleanup EXIT HUP INT TERM
 
 # Setup test directories
 TEST_DIR=$(mktemp -d -p "$(pwd)" tmp.XXX)
+
+if [ -n "${LXD_TMPFS:-}" ]; then
+  mount -t tmpfs tmpfs "${TEST_DIR}" -o mode=0751
+fi
+
 LXD_DIR=$(mktemp -d -p "${TEST_DIR}" XXX)
 export LXD_DIR
 chmod +x "${TEST_DIR}" "${LXD_DIR}"

From d35c02667005b6362366dbc7f3c1d1ab9d018550 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 7 Sep 2017 18:47:31 -0400
Subject: [PATCH 1141/1193] tests: Also measure batch startup time in perf.sh
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/perf.sh | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/perf.sh b/test/perf.sh
index 8f4a34e97..818e1b377 100755
--- a/test/perf.sh
+++ b/test/perf.sh
@@ -81,6 +81,7 @@ run_benchmark "start-one" "start 1 container" start
 run_benchmark "stop-one" "stop 1 container" stop
 run_benchmark "delete-one" "delete 1 container" delete
 run_benchmark "create-128" "create 128 containers" spawn --count 128 --start=false
+run_benchmark "start-128" "start 128 containers" start
 run_benchmark "delete-128" "delete 128 containers" delete
 
 # shellcheck disable=SC2034

From bc200f60df67d698f19c65296db9d165770baf21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 8 Sep 2017 01:34:10 -0400
Subject: [PATCH 1142/1193] tests: Use testimage for perf testing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/perf.sh | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/test/perf.sh b/test/perf.sh
index 818e1b377..22f82fd39 100755
--- a/test/perf.sh
+++ b/test/perf.sh
@@ -72,15 +72,16 @@ fi
 import_storage_backends
 
 spawn_lxd "${LXD_DIR}" true
+ensure_import_testimage
 
 # shellcheck disable=SC2034
 TEST_RESULT=failure
 
-run_benchmark "create-one" "create 1 container" spawn --count 1 --start=false
+run_benchmark "create-one" "create 1 container" spawn --count 1 --start=false --image=testimage
 run_benchmark "start-one" "start 1 container" start
 run_benchmark "stop-one" "stop 1 container" stop
 run_benchmark "delete-one" "delete 1 container" delete
-run_benchmark "create-128" "create 128 containers" spawn --count 128 --start=false
+run_benchmark "create-128" "create 128 containers" spawn --count 128 --start=false --image=testimage
 run_benchmark "start-128" "start 128 containers" start
 run_benchmark "delete-128" "delete 128 containers" delete
 

From fa2c424e4dca282bdd59909b7dfb456e2dc2384d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 8 Sep 2017 02:01:31 -0400
Subject: [PATCH 1143/1193] lxd-benchmark: Fix local image handling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd-benchmark/benchmark/benchmark.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxd-benchmark/benchmark/benchmark.go b/lxd-benchmark/benchmark/benchmark.go
index b6cae2e9c..94dd8058e 100644
--- a/lxd-benchmark/benchmark/benchmark.go
+++ b/lxd-benchmark/benchmark/benchmark.go
@@ -264,6 +264,7 @@ func ensureImage(c lxd.ContainerServer, image string) (string, error) {
 			}
 		}
 	} else {
+		fingerprint = image
 		alias, _, err := c.GetImageAlias(image)
 		if err == nil {
 			fingerprint = alias.Target
@@ -275,7 +276,6 @@ func ensureImage(c lxd.ContainerServer, image string) (string, error) {
 			logf("Image not found in local store: %s", image)
 			return "", err
 		}
-		fingerprint = image
 	}
 
 	logf("Found image in local store: %s", fingerprint)

From 578f81547d629e34c2660d4217fe2cafa49a969a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 3 Oct 2017 23:22:11 -0400
Subject: [PATCH 1144/1193] Move lxd-benchmark back under test/
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This remains an internal test tool for the 2.0 branch.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 {lxd-benchmark => test/lxd-benchmark}/benchmark/batch.go     | 0
 {lxd-benchmark => test/lxd-benchmark}/benchmark/benchmark.go | 0
 {lxd-benchmark => test/lxd-benchmark}/benchmark/operation.go | 0
 {lxd-benchmark => test/lxd-benchmark}/benchmark/report.go    | 0
 {lxd-benchmark => test/lxd-benchmark}/benchmark/util.go      | 0
 {lxd-benchmark => test/lxd-benchmark}/main.go                | 2 +-
 6 files changed, 1 insertion(+), 1 deletion(-)
 rename {lxd-benchmark => test/lxd-benchmark}/benchmark/batch.go (100%)
 rename {lxd-benchmark => test/lxd-benchmark}/benchmark/benchmark.go (100%)
 rename {lxd-benchmark => test/lxd-benchmark}/benchmark/operation.go (100%)
 rename {lxd-benchmark => test/lxd-benchmark}/benchmark/report.go (100%)
 rename {lxd-benchmark => test/lxd-benchmark}/benchmark/util.go (100%)
 rename {lxd-benchmark => test/lxd-benchmark}/main.go (98%)

diff --git a/lxd-benchmark/benchmark/batch.go b/test/lxd-benchmark/benchmark/batch.go
similarity index 100%
rename from lxd-benchmark/benchmark/batch.go
rename to test/lxd-benchmark/benchmark/batch.go
diff --git a/lxd-benchmark/benchmark/benchmark.go b/test/lxd-benchmark/benchmark/benchmark.go
similarity index 100%
rename from lxd-benchmark/benchmark/benchmark.go
rename to test/lxd-benchmark/benchmark/benchmark.go
diff --git a/lxd-benchmark/benchmark/operation.go b/test/lxd-benchmark/benchmark/operation.go
similarity index 100%
rename from lxd-benchmark/benchmark/operation.go
rename to test/lxd-benchmark/benchmark/operation.go
diff --git a/lxd-benchmark/benchmark/report.go b/test/lxd-benchmark/benchmark/report.go
similarity index 100%
rename from lxd-benchmark/benchmark/report.go
rename to test/lxd-benchmark/benchmark/report.go
diff --git a/lxd-benchmark/benchmark/util.go b/test/lxd-benchmark/benchmark/util.go
similarity index 100%
rename from lxd-benchmark/benchmark/util.go
rename to test/lxd-benchmark/benchmark/util.go
diff --git a/lxd-benchmark/main.go b/test/lxd-benchmark/main.go
similarity index 98%
rename from lxd-benchmark/main.go
rename to test/lxd-benchmark/main.go
index 76bc9e2fd..c4ba0b537 100644
--- a/lxd-benchmark/main.go
+++ b/test/lxd-benchmark/main.go
@@ -6,10 +6,10 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/client"
-	"github.com/lxc/lxd/lxd-benchmark/benchmark"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/version"
+	"github.com/lxc/lxd/test/lxd-benchmark/benchmark"
 )
 
 var argCount = gnuflag.Int("count", 100, "Number of containers to create")

From b03b82e73c9cff4e45d9e35b9f91f76f5f232b39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 6 Sep 2017 01:10:22 -0400
Subject: [PATCH 1145/1193] lxd/events: Fix race condition in event handlers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This adds additional locking around the event handler code to ensure
that there is no race between the time where we first fail to send a
message and the time where the listener is removed from the list.

Closes #3770

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/events.go | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)

diff --git a/lxd/events.go b/lxd/events.go
index df3bdc600..d7aed0422 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -51,7 +51,8 @@ type eventListener struct {
 	messageTypes []string
 	active       chan bool
 	id           string
-	msgLock      sync.Mutex
+	lock         sync.Mutex
+	done         bool
 }
 
 type eventsServe struct {
@@ -92,13 +93,6 @@ func eventsSocket(r *http.Request, w http.ResponseWriter) error {
 
 	<-listener.active
 
-	eventsLock.Lock()
-	delete(eventListeners, listener.id)
-	eventsLock.Unlock()
-
-	listener.connection.Close()
-	logger.Debugf("Disconnected events listener: %s", listener.id)
-
 	return nil
 }
 
@@ -127,16 +121,32 @@ func eventSend(eventType string, eventMessage interface{}) error {
 		}
 
 		go func(listener *eventListener, body []byte) {
+			// Check that the listener still exists
 			if listener == nil {
 				return
 			}
 
-			listener.msgLock.Lock()
-			err = listener.connection.WriteMessage(websocket.TextMessage, body)
-			listener.msgLock.Unlock()
+			// Ensure there is only a single even going out at the time
+			listener.lock.Lock()
+			defer listener.lock.Unlock()
 
+			// Make sure we're not done already
+			if listener.done {
+				return
+			}
+
+			err = listener.connection.WriteMessage(websocket.TextMessage, body)
 			if err != nil {
+				// Remove the listener from the list
+				eventsLock.Lock()
+				delete(eventListeners, listener.id)
+				eventsLock.Unlock()
+
+				// Disconnect the listener
+				listener.connection.Close()
 				listener.active <- false
+				listener.done = true
+				logger.Debugf("Disconnected events listener: %s", listener.id)
 			}
 		}(listener, body)
 	}

From 4b82b6f799ae0163d43b38041105d5f20f211892 Mon Sep 17 00:00:00 2001
From: Jason Travis <JasonTravis at nau.edu>
Date: Wed, 6 Sep 2017 06:39:45 +0000
Subject: [PATCH 1146/1193] util linux: guess size when sysconf() returns -1

Signed-off-by: Jason Travis <JasonTravis at nau.edu>
---
 shared/util_linux.go | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/shared/util_linux.go b/shared/util_linux.go
index ba0070f28..e165be404 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -354,12 +354,12 @@ func UserId(name string) (int, error) {
 	var pw C.struct_passwd
 	var result *C.struct_passwd
 
-	bufSize := C.size_t(C.sysconf(C._SC_GETPW_R_SIZE_MAX))
+	bufSize := C.sysconf(C._SC_GETPW_R_SIZE_MAX)
 	if bufSize < 0 {
 		bufSize = 4096
 	}
 
-	buf := C.malloc(bufSize)
+	buf := C.malloc(C.size_t(bufSize))
 	if buf == nil {
 		return -1, fmt.Errorf("allocation failed")
 	}
@@ -372,14 +372,14 @@ again:
 	rv, errno := C.getpwnam_r(cname,
 		&pw,
 		(*C.char)(buf),
-		bufSize,
+		C.size_t(bufSize),
 		&result)
 	if rv < 0 {
 		// OOM killer will take care of us if we end up doing this too
 		// often.
 		if errno == syscall.ERANGE {
 			bufSize *= 2
-			tmp := C.realloc(buf, bufSize)
+			tmp := C.realloc(buf, C.size_t(bufSize))
 			if tmp == nil {
 				return -1, fmt.Errorf("allocation failed")
 			}
@@ -401,12 +401,12 @@ func GroupId(name string) (int, error) {
 	var grp C.struct_group
 	var result *C.struct_group
 
-	bufSize := C.size_t(C.sysconf(C._SC_GETGR_R_SIZE_MAX))
+	bufSize := C.sysconf(C._SC_GETGR_R_SIZE_MAX)
 	if bufSize < 0 {
 		bufSize = 4096
 	}
 
-	buf := C.malloc(bufSize)
+	buf := C.malloc(C.size_t(bufSize))
 	if buf == nil {
 		return -1, fmt.Errorf("allocation failed")
 	}
@@ -418,14 +418,14 @@ again:
 	rv, errno := C.getgrnam_r(cname,
 		&grp,
 		(*C.char)(buf),
-		bufSize,
+		C.size_t(bufSize),
 		&result)
 	if rv != 0 {
 		// OOM killer will take care of us if we end up doing this too
 		// often.
 		if errno == syscall.ERANGE {
 			bufSize *= 2
-			tmp := C.realloc(buf, bufSize)
+			tmp := C.realloc(buf, C.size_t(bufSize))
 			if tmp == nil {
 				return -1, fmt.Errorf("allocation failed")
 			}

From 560d7985f519cf70b57f274926923ed0b53f1fc3 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 6 Sep 2017 14:18:55 +0200
Subject: [PATCH 1147/1193] Fix typo in comment.

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxc/image.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lxc/image.go b/lxc/image.go
index 1d9b145cd..dded32647 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -289,7 +289,7 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 				return err
 			}
 
-			// Store the fingerprint for use when creating aliases later (as imgInfo.Fingerprint may be overriden)
+			// Store the fingerprint for use when creating aliases later (as imgInfo.Fingerprint may be overridden)
 			fp = imgInfo.Fingerprint
 		}
 

From e038a7157b05112ea131ca45a047f00fc98029a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 6 Sep 2017 23:24:46 -0400
Subject: [PATCH 1148/1193] test: Don't copy running lvm/ceph containers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Running containers when backed by LVM or Ceph may be in an inconsistent
state during copy.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/template.sh | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/test/suites/template.sh b/test/suites/template.sh
index da6b1d5c2..1f6ab7edf 100644
--- a/test/suites/template.sh
+++ b/test/suites/template.sh
@@ -1,4 +1,8 @@
 test_template() {
+  # shellcheck disable=2039
+  local lxd_backend
+  lxd_backend=$(storage_backend "$LXD_DIR")
+
   # Import a template which only triggers on create
   deps/import-busybox --alias template-test --template create
   lxc init template-test template
@@ -10,6 +14,10 @@ test_template() {
   lxc start template
   lxc file pull template/template - | grep "^name: template$"
 
+  if [ "$lxd_backend" = "lvm" ]; then
+    lxc stop template --force
+  fi
+
   # Confirm it's not applied on copies
   lxc copy template template1
   lxc file pull template1/template - | grep "^name: template$"
@@ -25,6 +33,9 @@ test_template() {
 
   # Confirm that the template doesn't trigger on create
   ! lxc file pull template/template -
+  if [ "$lxd_backend" = "lvm" ]; then
+    lxc stop template --force
+  fi
 
   # Copy the container
   lxc copy template template1

From 82fab1d06b8aac58def021ecd883009b6aa109fd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 7 Sep 2017 02:40:37 -0400
Subject: [PATCH 1149/1193] test: Wait up to 2 minutes for image updates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Ceph is very slow so 20s often doesn't cut it.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/image_auto_update.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/image_auto_update.sh b/test/suites/image_auto_update.sh
index efcf20396..e3c677894 100644
--- a/test/suites/image_auto_update.sh
+++ b/test/suites/image_auto_update.sh
@@ -46,7 +46,7 @@ test_image_auto_update() {
   #
   # XXX: Since the auto-update logic runs asynchronously we need to wait
   #      a little bit before it actually completes.
-  retries=10
+  retries=60
   while [ "${retries}" != "0" ]; do
     if lxc image info "${fp1}" > /dev/null 2>&1; then
 	sleep 2

From 6298e080ecdbff69bed36196faba98ad4880447e Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 7 Sep 2017 12:22:13 +0200
Subject: [PATCH 1150/1193] tests: use "--force" everywhere on stop

This was clearly not the monitor's fault but rather the init system not
responding to any signals anymore and not shutting down correctly:

root     24564  0.0  0.0 841480  9264 ?        Ss   05:20   0:00 [lxc monitor] /lxc-ci/build/tmp.INNANBKics/go/src/github.com/lxc/lxd/test/tmp.d0I/uRA/containers baz
100000   24579  0.0  0.0   1980  1120 ?        Ds   05:20   0:00  \_ init
root     25943  0.0  0.0 842504  5220 ?        Ss   05:22   0:00 [lxc monitor] /lxc-ci/build/tmp.5WycOQWk5G/go/src/github.com/lxc/lxd/test/tmp.9aH/DTS/containers baz
100000   25957  0.0  0.0   1980  1164 ?        Ds   05:22   0:00  \_ init
root     17789  0.0  0.0 841540  5108 ?        Ss   06:21   0:00 [lxc monitor] /lxc-ci/build/tmp.ZBrLKpAj3m/go/src/github.com/lxc/lxd/test/tmp.vKG/Asy/containers baz
100000   17805  0.0  0.0   1980  1164 ?        Ds   06:21   0:00  \_ init
root     18054  0.0  0.0 840772  9196 ?        Ss   06:22   0:00 [lxc monitor] /lxc-ci/build/tmp.d9Z1lf9oqN/go/src/github.com/lxc/lxd/test/tmp.2yk/ZrM/containers baz
100000   18070  0.0  0.0   1980  1120 ?        Ds   06:22   0:00  \_ init

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/basic.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index aee76664e..443bc849e 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -125,7 +125,7 @@ test_basic_usage() {
   lxc launch testimage baz
   # change the container filesystem so the resulting image is different
   lxc exec baz touch /somefile
-  lxc stop baz
+  lxc stop baz --force
   # publishing another image with same alias doesn't fail
   lxc publish baz --alias=foo-image
   lxc delete baz

From aa840f956db950655e79208597c45f870c64067d Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Thu, 7 Sep 2017 21:08:21 +0200
Subject: [PATCH 1151/1193] tests: bump image auto update limit to 20min

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/image_auto_update.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/image_auto_update.sh b/test/suites/image_auto_update.sh
index e3c677894..235516f8a 100644
--- a/test/suites/image_auto_update.sh
+++ b/test/suites/image_auto_update.sh
@@ -46,7 +46,7 @@ test_image_auto_update() {
   #
   # XXX: Since the auto-update logic runs asynchronously we need to wait
   #      a little bit before it actually completes.
-  retries=60
+  retries=600
   while [ "${retries}" != "0" ]; do
     if lxc image info "${fp1}" > /dev/null 2>&1; then
 	sleep 2

From b50ee7d6163d86254b11cb5b21560d3c98a7bbba Mon Sep 17 00:00:00 2001
From: Christian Brauner <christian.brauner at ubuntu.com>
Date: Fri, 8 Sep 2017 19:58:43 +0200
Subject: [PATCH 1152/1193] tests: include lvm in image auto update

Signed-off-by: Christian Brauner <christian.brauner at ubuntu.com>
---
 test/suites/image_auto_update.sh | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/test/suites/image_auto_update.sh b/test/suites/image_auto_update.sh
index 235516f8a..29e93bf55 100644
--- a/test/suites/image_auto_update.sh
+++ b/test/suites/image_auto_update.sh
@@ -1,12 +1,4 @@
 test_image_auto_update() {
-  # XXX this test appears to be flaky when running on Jenkins
-  # against the LVM backend. Needs further investigation.
-  # shellcheck disable=2153
-  backend=$(storage_backend "$LXD_DIR")
-  if [ "${backend}" = "lvm" ]; then
-      return 0
-  fi
-
   if lxc image alias list | grep -q "^| testimage\s*|.*$"; then
       lxc image delete testimage
   fi

From 3c224dd3ea7d5a4ffe55c65a62b828150d126bc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 16 Sep 2017 15:29:11 -0700
Subject: [PATCH 1153/1193] lxc: Fix import crash when adding properties
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3803

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go         | 4 ++++
 test/suites/basic.sh | 3 +++
 2 files changed, 7 insertions(+)

diff --git a/lxc/image.go b/lxc/image.go
index dded32647..97abe2d33 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -518,6 +518,10 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 				return fmt.Errorf(i18n.G("Bad property: %s"), entry)
 			}
 
+			if image.Properties == nil {
+				image.Properties = map[string]string{}
+			}
+
 			image.Properties[strings.TrimSpace(fields[0])] = strings.TrimSpace(fields[1])
 		}
 
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 443bc849e..0c8622928 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -47,6 +47,9 @@ test_basic_usage() {
 
   # Re-import the image
   mv "${LXD_DIR}/${sum}.tar.xz" "${LXD_DIR}/testimage.tar.xz"
+  lxc image import "${LXD_DIR}/testimage.tar.xz" --alias testimage user.foo=bar
+  lxc image show testimage | grep -q "user.foo: bar"
+  lxc image delete testimage
   lxc image import "${LXD_DIR}/testimage.tar.xz" --alias testimage
   rm "${LXD_DIR}/testimage.tar.xz"
 

From 013e3f1b325bea50dd21498932fadfae143d86da Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 20 Sep 2017 12:57:39 -0400
Subject: [PATCH 1154/1193] tls: Add some more TLS ciphers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3822

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go     | 6 ++++++
 shared/network.go | 6 +++++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 4b949f1ab..a9cbfd566 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -836,6 +836,12 @@ func (d *Daemon) Init() error {
 			MinVersion:   tls.VersionTLS12,
 			MaxVersion:   tls.VersionTLS12,
 			CipherSuites: []uint16{
+				tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+				tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+				tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+				tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+				tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
 				tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 				tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
 			PreferServerCipherSuites: true,
diff --git a/shared/network.go b/shared/network.go
index a2ee54740..1301e051b 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -41,9 +41,13 @@ func initTLSConfig() *tls.Config {
 		MinVersion: tls.VersionTLS12,
 		MaxVersion: tls.VersionTLS12,
 		CipherSuites: []uint16{
+			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
 			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
-			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
 			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
 		PreferServerCipherSuites: true,
 	}

From 8a566db86f2f1a48dd0645e1f6fe0112f9df06bc Mon Sep 17 00:00:00 2001
From: Ivan Georgive <vanko.georgiev at gmail.com>
Date: Wed, 20 Sep 2017 16:17:57 +0300
Subject: [PATCH 1155/1193] Added insecureSkipVerify flag the ConnectionArgs
 struct

Signed-off-by: Ivan Georgiev <vanko.georgiev at gmail.com>
---
 client/connection.go | 7 +++++--
 client/util.go       | 4 ++--
 shared/network.go    | 4 ++--
 3 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/client/connection.go b/client/connection.go
index a9474a18a..560546be6 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -32,6 +32,9 @@ type ConnectionArgs struct {
 
 	// Custom HTTP Client (used as base for the connection)
 	HTTPClient *http.Client
+
+	// Controls whether a client verifies the server's certificate chain and host name.
+	InsecureSkipVerify bool
 }
 
 // ConnectLXD lets you connect to a remote LXD daemon over HTTPs.
@@ -123,7 +126,7 @@ func ConnectSimpleStreams(url string, args *ConnectionArgs) (ImageServer, error)
 	}
 
 	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
+	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.InsecureSkipVerify, args.Proxy)
 	if err != nil {
 		return nil, err
 	}
@@ -152,7 +155,7 @@ func httpsLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 	}
 
 	// Setup the HTTP client
-	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.Proxy)
+	httpClient, err := tlsHTTPClient(args.HTTPClient, args.TLSClientCert, args.TLSClientKey, args.TLSCA, args.TLSServerCert, args.InsecureSkipVerify, args.Proxy)
 	if err != nil {
 		return nil, err
 	}
diff --git a/client/util.go b/client/util.go
index 3649a0a83..e041fd979 100644
--- a/client/util.go
+++ b/client/util.go
@@ -13,9 +13,9 @@ import (
 	"github.com/lxc/lxd/shared/ioprogress"
 )
 
-func tlsHTTPClient(client *http.Client, tlsClientCert string, tlsClientKey string, tlsCA string, tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
+func tlsHTTPClient(client *http.Client, tlsClientCert string, tlsClientKey string, tlsCA string, tlsServerCert string, insecureSkipVerify bool, proxy func(req *http.Request) (*url.URL, error)) (*http.Client, error) {
 	// Get the TLS configuration
-	tlsConfig, err := shared.GetTLSConfigMem(tlsClientCert, tlsClientKey, tlsCA, tlsServerCert)
+	tlsConfig, err := shared.GetTLSConfigMem(tlsClientCert, tlsClientKey, tlsCA, tlsServerCert, insecureSkipVerify)
 	if err != nil {
 		return nil, err
 	}
diff --git a/shared/network.go b/shared/network.go
index 1301e051b..63385bfbc 100644
--- a/shared/network.go
+++ b/shared/network.go
@@ -107,9 +107,9 @@ func GetTLSConfig(tlsClientCertFile string, tlsClientKeyFile string, tlsClientCA
 	return tlsConfig, nil
 }
 
-func GetTLSConfigMem(tlsClientCert string, tlsClientKey string, tlsClientCA string, tlsRemoteCertPEM string) (*tls.Config, error) {
+func GetTLSConfigMem(tlsClientCert string, tlsClientKey string, tlsClientCA string, tlsRemoteCertPEM string, insecureSkipVerify bool) (*tls.Config, error) {
 	tlsConfig := initTLSConfig()
-
+	tlsConfig.InsecureSkipVerify = insecureSkipVerify
 	// Client authentication
 	if tlsClientCert != "" && tlsClientKey != "" {
 		cert, err := tls.X509KeyPair([]byte(tlsClientCert), []byte(tlsClientKey))

From e3e085c6fc3df025ddc6e5987903b56de13df63f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 28 Sep 2017 09:10:17 -0400
Subject: [PATCH 1156/1193] tests: Fix dependency check
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 test/suites/static_analysis.sh | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 55567b229..b33fea8fe 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -65,10 +65,11 @@ test_static_analysis() {
     fi
 
     if which godeps >/dev/null 2>&1; then
-      OUT=$(godeps . ./shared | cut -f1)
-      if [ "${OUT}" != "$(printf "github.com/gorilla/websocket\ngopkg.in/yaml.v2\n")" ]; then
+      OUT=$(godeps -T ./client ./lxc/config ./shared/api 2> /dev/null | grep -v lxc/lxd | grep -v gorilla/websocket | grep -v yaml.v2 || true)
+      if [ -n "${OUT}" ]; then
         echo "ERROR: you added a new dependency to the client or shared; please make sure this is what you want"
         echo "${OUT}"
+        exit 1
       fi
     fi
 

From 45329fec627d70671480a4a652054bff085ac5e8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 28 Sep 2017 16:42:29 -0400
Subject: [PATCH 1157/1193] lxc/image: Fix regression in exported filename
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3869

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxc/image.go          | 10 ++++++++++
 test/suites/basic.sh  |  4 ++--
 test/suites/remote.sh |  4 ++--
 3 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/lxc/image.go b/lxc/image.go
index 97abe2d33..1bad9fb06 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -755,6 +755,16 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 					return err
 				}
 			}
+		} else if resp.RootfsSize == 0 && len(args) > 2 {
+			if resp.MetaName != "" {
+				extension := strings.SplitN(resp.MetaName, ".", 2)[1]
+				err := os.Rename(targetMeta, fmt.Sprintf("%s.%s", targetMeta, extension))
+				if err != nil {
+					os.Remove(targetMeta)
+					progress.Done("")
+					return err
+				}
+			}
 		}
 
 		progress.Done(i18n.G("Image exported successfully!"))
diff --git a/test/suites/basic.sh b/test/suites/basic.sh
index 0c8622928..caf1f6783 100644
--- a/test/suites/basic.sh
+++ b/test/suites/basic.sh
@@ -60,8 +60,8 @@ test_basic_usage() {
 
   # Test custom filename for image export
   lxc image export testimage "${LXD_DIR}/foo"
-  [ "${sum}" = "$(sha256sum "${LXD_DIR}/foo" | cut -d' ' -f1)" ]
-  rm "${LXD_DIR}/foo"
+  [ "${sum}" = "$(sha256sum "${LXD_DIR}/foo.tar.xz" | cut -d' ' -f1)" ]
+  rm "${LXD_DIR}/foo.tar.xz"
 
 
   # Test image export with a split image.
diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index fbce8c8bd..94b4a9b92 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -95,8 +95,8 @@ test_remote_usage() {
 
   lxc_remote image export localhost:testimage "${LXD_DIR}/foo"
   lxc_remote image delete localhost:testimage
-  sum=$(sha256sum "${LXD_DIR}/foo" | cut -d' ' -f1)
-  lxc_remote image import "${LXD_DIR}/foo" localhost: --public
+  sum=$(sha256sum "${LXD_DIR}/foo.tar.xz" | cut -d' ' -f1)
+  lxc_remote image import "${LXD_DIR}/foo.tar.xz" localhost: --public
   lxc_remote image alias create localhost:testimage "${sum}"
 
   lxc_remote image delete "lxd2:${sum}" || true

From 14a050d852c7e6948ddbd70fdd03b01232731209 Mon Sep 17 00:00:00 2001
From: Jan Vansteenkiste <jan at vstone.eu>
Date: Thu, 28 Sep 2017 20:13:14 +0200
Subject: [PATCH 1158/1193] Document that squashfs images can also be used.

Signed-off-by: Jan Vansteenkiste <jan at vstone.eu>
---
 doc/image-handling.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/doc/image-handling.md b/doc/image-handling.md
index 34bb0871d..738368d5c 100644
--- a/doc/image-handling.md
+++ b/doc/image-handling.md
@@ -71,6 +71,10 @@ rootfs.tar contains a Linux root filesystem at its root.
 In this mode the image identifier is the SHA-256 of the concatenation of
 the metadata and rootfs tarball (in that order).
 
+## Supported compression
+The tarball(s) can be compressed using bz2, gz, xz, lzma, tar (uncompressed) or
+it can also be a squashfs image.
+
 ## Content
 The rootfs directory (or tarball) contains a full file system tree of what will become the container's /.
 

From 744ac34bacc99ac94aa1c3fd909763d290b35a20 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho at tycho.ws>
Date: Mon, 2 Oct 2017 16:53:57 -0600
Subject: [PATCH 1159/1193] drop useless apparmor denies

mem and kmem are really in /dev, and they're not propagated into lxd
containers, privileged or otherwise anyways, so these are useless.

Signed-off-by: Tycho Andersen <tycho at tycho.ws>
---
 lxd/apparmor.go | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lxd/apparmor.go b/lxd/apparmor.go
index f2920f421..9c018491d 100644
--- a/lxd/apparmor.go
+++ b/lxd/apparmor.go
@@ -80,8 +80,6 @@ const AA_PROFILE_BASE = `
 
   # block some other dangerous paths
   deny @{PROC}/kcore rwklx,
-  deny @{PROC}/kmem rwklx,
-  deny @{PROC}/mem rwklx,
   deny @{PROC}/sysrq-trigger rwklx,
 
   # deny writes in /sys except for /sys/fs/cgroup, also allow

From bb068851d0cec10f03908117afec45809eaec7ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 3 Oct 2017 23:36:35 -0400
Subject: [PATCH 1160/1193] i18n: Update translation templates
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 po/de.po   | 34 +++++++++++++++++-----------------
 po/fr.po   | 34 +++++++++++++++++-----------------
 po/ja.po   | 34 +++++++++++++++++-----------------
 po/lxd.pot | 34 +++++++++++++++++-----------------
 4 files changed, 68 insertions(+), 68 deletions(-)

diff --git a/po/de.po b/po/de.po
index b541374bb..ae53483f4 100644
--- a/po/de.po
+++ b/po/de.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 18:09-0400\n"
+"POT-Creation-Date: 2017-10-03 23:36-0400\n"
 "PO-Revision-Date: 2015-06-13 06:10+0200\n"
 "Last-Translator: Felix Engelmann <felix-lxd at nlogn.org>\n"
 "Language-Team: \n"
@@ -111,7 +111,7 @@ msgstr ""
 "###\n"
 "### Der Name wird zwar angezeigt, lässt sich jedoch nicht ändern.\n"
 
-#: lxc/image.go:837
+#: lxc/image.go:851
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -130,11 +130,11 @@ msgstr "'/' ist kein gültiges Zeichen im Namen eines Sicherungspunktes\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:858 lxc/image.go:887
+#: lxc/image.go:872 lxc/image.go:901
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:862
+#: lxc/image.go:876
 msgid "ARCH"
 msgstr ""
 
@@ -237,7 +237,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:942 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:956 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "YAML Analyse Fehler %v\n"
@@ -288,7 +288,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:861 lxc/image.go:889
+#: lxc/image.go:875 lxc/image.go:903
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -349,12 +349,12 @@ msgstr ""
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/image.go:706
+#: lxc/image.go:710
 #, c-format
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:348 lxc/image.go:859 lxc/image.go:888
+#: lxc/config.go:348 lxc/image.go:873 lxc/image.go:902
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -444,11 +444,11 @@ msgstr "Herunterfahren des Containers erzwingen."
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:756
+#: lxc/image.go:770
 msgid "Image exported successfully!"
 msgstr ""
 
-#: lxc/image.go:576
+#: lxc/image.go:580
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Abbild mit Fingerabdruck %s importiert\n"
@@ -603,7 +603,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:860 lxc/remote.go:367
+#: lxc/image.go:874 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -643,7 +643,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:943
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:957
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -740,7 +740,7 @@ msgstr "kann nicht zum selben Container Namen kopieren"
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:863
+#: lxc/image.go:877
 msgid "SIZE"
 msgstr ""
 
@@ -926,7 +926,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "kann nicht zum selben Container Namen kopieren"
 
-#: lxc/image.go:524
+#: lxc/image.go:528
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
@@ -944,7 +944,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:864
+#: lxc/image.go:878
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -1667,7 +1667,7 @@ msgstr "Fehler: %v\n"
 msgid "error: unknown command: %s"
 msgstr "Fehler: unbekannter Befehl: %s\n"
 
-#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:840
+#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:854
 msgid "no"
 msgstr ""
 
@@ -1718,7 +1718,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "falsche Anzahl an Parametern für Unterbefehl"
 
-#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:844
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:858
 msgid "yes"
 msgstr ""
 
diff --git a/po/fr.po b/po/fr.po
index e7ee07764..3403b8d53 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 18:09-0400\n"
+"POT-Creation-Date: 2017-10-03 23:36-0400\n"
 "PO-Revision-Date: 2015-02-26 02:05-0600\n"
 "Last-Translator: Stéphane Graber <stgraber at ubuntu.com\n"
 "Language-Team: French <fr at li.org>\n"
@@ -68,7 +68,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:837
+#: lxc/image.go:851
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -87,11 +87,11 @@ msgstr "'/' n'est pas autorisé dans le nom d'un instantané (snapshot)\n"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:858 lxc/image.go:887
+#: lxc/image.go:872 lxc/image.go:901
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:862
+#: lxc/image.go:876
 msgid "ARCH"
 msgstr ""
 
@@ -192,7 +192,7 @@ msgstr ""
 msgid "Config key/value to apply to the new container"
 msgstr ""
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:942 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:956 lxc/profile.go:237
 #, fuzzy, c-format
 msgid "Config parsing error: %s"
 msgstr "erreur: %v\n"
@@ -242,7 +242,7 @@ msgstr ""
 msgid "Creating the container"
 msgstr ""
 
-#: lxc/image.go:861 lxc/image.go:889
+#: lxc/image.go:875 lxc/image.go:903
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -303,12 +303,12 @@ msgstr ""
 msgid "Expires: never"
 msgstr ""
 
-#: lxc/image.go:706
+#: lxc/image.go:710
 #, c-format
 msgid "Exporting the image: %s"
 msgstr ""
 
-#: lxc/config.go:348 lxc/image.go:859 lxc/image.go:888
+#: lxc/config.go:348 lxc/image.go:873 lxc/image.go:902
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -397,11 +397,11 @@ msgstr "Force l'arrêt du conteneur."
 msgid "Image copied successfully!"
 msgstr ""
 
-#: lxc/image.go:756
+#: lxc/image.go:770
 msgid "Image exported successfully!"
 msgstr ""
 
-#: lxc/image.go:576
+#: lxc/image.go:580
 #, fuzzy, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "Empreinte du certificat: % x\n"
@@ -556,7 +556,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:860 lxc/remote.go:367
+#: lxc/image.go:874 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -596,7 +596,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:943
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:957
 msgid "Press enter to start the editor again"
 msgstr ""
 
@@ -691,7 +691,7 @@ msgstr "Liste de l'information sur les conteneurs.\n"
 msgid "Retrieving image: %s"
 msgstr ""
 
-#: lxc/image.go:863
+#: lxc/image.go:877
 msgid "SIZE"
 msgstr ""
 
@@ -875,7 +875,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "Mauvaise URL pour le conteneur %s"
 
-#: lxc/image.go:524
+#: lxc/image.go:528
 #, c-format
 msgid "Transferring image: %s"
 msgstr ""
@@ -893,7 +893,7 @@ msgstr ""
 msgid "Type: persistent"
 msgstr ""
 
-#: lxc/image.go:864
+#: lxc/image.go:878
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -1516,7 +1516,7 @@ msgstr "erreur: %v\n"
 msgid "error: unknown command: %s"
 msgstr "erreur: comande inconnue: %s\n"
 
-#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:840
+#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:854
 msgid "no"
 msgstr ""
 
@@ -1567,7 +1567,7 @@ msgstr ""
 msgid "wrong number of subcommand arguments"
 msgstr "nombre d'argument incorrect pour la sous-comande"
 
-#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:844
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:858
 msgid "yes"
 msgstr ""
 
diff --git a/po/ja.po b/po/ja.po
index ac79bab75..9b70f645b 100644
--- a/po/ja.po
+++ b/po/ja.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: LXD\n"
 "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-"POT-Creation-Date: 2017-10-03 18:09-0400\n"
+"POT-Creation-Date: 2017-10-03 23:36-0400\n"
 "PO-Revision-Date: 2017-09-28 21:49+0900\n"
 "Last-Translator: KATOH Yasufumi <karma at jazz.email.ne.jp>\n"
 "Language-Team: Japanese <lxd-ja-language-team at googlegroups.com>\n"
@@ -68,7 +68,7 @@ msgid ""
 "### Note that the name is shown but cannot be changed"
 msgstr ""
 
-#: lxc/image.go:837
+#: lxc/image.go:851
 #, c-format
 msgid "%s (%d more)"
 msgstr ""
@@ -86,11 +86,11 @@ msgstr "'/' はスナップショットの名前には使用できません。"
 msgid "(none)"
 msgstr ""
 
-#: lxc/image.go:858 lxc/image.go:887
+#: lxc/image.go:872 lxc/image.go:901
 msgid "ALIAS"
 msgstr ""
 
-#: lxc/image.go:862
+#: lxc/image.go:876
 msgid "ARCH"
 msgstr ""
 
@@ -187,7 +187,7 @@ msgstr "コマンド:"
 msgid "Config key/value to apply to the new container"
 msgstr "新しいコンテナに適用するキー/値の設定"
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:942 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:956 lxc/profile.go:237
 #, c-format
 msgid "Config parsing error: %s"
 msgstr "設定の構文エラー: %s"
@@ -237,7 +237,7 @@ msgstr "%s を作成中"
 msgid "Creating the container"
 msgstr "コンテナを作成中"
 
-#: lxc/image.go:861 lxc/image.go:889
+#: lxc/image.go:875 lxc/image.go:903
 msgid "DESCRIPTION"
 msgstr ""
 
@@ -296,12 +296,12 @@ msgstr "失効日時: %s"
 msgid "Expires: never"
 msgstr "失効日時: 失効しない"
 
-#: lxc/image.go:706
+#: lxc/image.go:710
 #, c-format
 msgid "Exporting the image: %s"
 msgstr "イメージのエクスポート中: %s"
 
-#: lxc/config.go:348 lxc/image.go:859 lxc/image.go:888
+#: lxc/config.go:348 lxc/image.go:873 lxc/image.go:902
 msgid "FINGERPRINT"
 msgstr ""
 
@@ -388,11 +388,11 @@ msgstr "コンテナの状態を無視します (startのみ)。"
 msgid "Image copied successfully!"
 msgstr "イメージのコピーが成功しました!"
 
-#: lxc/image.go:756
+#: lxc/image.go:770
 msgid "Image exported successfully!"
 msgstr "イメージのエクスポートに成功しました!"
 
-#: lxc/image.go:576
+#: lxc/image.go:580
 #, c-format
 msgid "Image imported with fingerprint: %s"
 msgstr "イメージは以下のフィンガープリントでインポートされました: %s"
@@ -544,7 +544,7 @@ msgstr ""
 msgid "PROTOCOL"
 msgstr ""
 
-#: lxc/image.go:860 lxc/remote.go:367
+#: lxc/image.go:874 lxc/remote.go:367
 msgid "PUBLIC"
 msgstr ""
 
@@ -581,7 +581,7 @@ msgstr ""
 msgid "Press enter to open the editor again"
 msgstr "再度エディタを開くためには Enter キーを押します"
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:943
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:957
 msgid "Press enter to start the editor again"
 msgstr "再度エディタを起動するには Enter キーを押します"
 
@@ -675,7 +675,7 @@ msgstr "コンテナを再起動します。"
 msgid "Retrieving image: %s"
 msgstr "イメージの取得中: %s"
 
-#: lxc/image.go:863
+#: lxc/image.go:877
 msgid "SIZE"
 msgstr ""
 
@@ -866,7 +866,7 @@ msgstr ""
 msgid "Transferring container: %s"
 msgstr "イメージを転送中: %s"
 
-#: lxc/image.go:524
+#: lxc/image.go:528
 #, c-format
 msgid "Transferring image: %s"
 msgstr "イメージを転送中: %s"
@@ -884,7 +884,7 @@ msgstr "タイプ: ephemeral"
 msgid "Type: persistent"
 msgstr "タイプ: persistent"
 
-#: lxc/image.go:864
+#: lxc/image.go:878
 msgid "UPLOAD DATE"
 msgstr ""
 
@@ -1944,7 +1944,7 @@ msgstr "エラー: %v"
 msgid "error: unknown command: %s"
 msgstr "エラー: 未知のコマンド: %s"
 
-#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:840
+#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:854
 msgid "no"
 msgstr ""
 
@@ -1994,7 +1994,7 @@ msgstr "%s に取得しました"
 msgid "wrong number of subcommand arguments"
 msgstr "サブコマンドの引数の数が正しくありません"
 
-#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:844
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:858
 msgid "yes"
 msgstr ""
 
diff --git a/po/lxd.pot b/po/lxd.pot
index 9f3d98c82..1d6cd7a82 100644
--- a/po/lxd.pot
+++ b/po/lxd.pot
@@ -7,7 +7,7 @@
 msgid   ""
 msgstr  "Project-Id-Version: lxd\n"
         "Report-Msgid-Bugs-To: lxc-devel at lists.linuxcontainers.org\n"
-        "POT-Creation-Date: 2017-10-03 18:09-0400\n"
+        "POT-Creation-Date: 2017-10-03 23:36-0400\n"
         "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
         "Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
         "Language-Team: LANGUAGE <LL at li.org>\n"
@@ -65,7 +65,7 @@ msgid   "### This is a yaml representation of the profile.\n"
         "### Note that the name is shown but cannot be changed"
 msgstr  ""
 
-#: lxc/image.go:837
+#: lxc/image.go:851
 #, c-format
 msgid   "%s (%d more)"
 msgstr  ""
@@ -83,11 +83,11 @@ msgstr  ""
 msgid   "(none)"
 msgstr  ""
 
-#: lxc/image.go:858 lxc/image.go:887
+#: lxc/image.go:872 lxc/image.go:901
 msgid   "ALIAS"
 msgstr  ""
 
-#: lxc/image.go:862
+#: lxc/image.go:876
 msgid   "ARCH"
 msgstr  ""
 
@@ -184,7 +184,7 @@ msgstr  ""
 msgid   "Config key/value to apply to the new container"
 msgstr  ""
 
-#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:942 lxc/profile.go:237
+#: lxc/config.go:647 lxc/config.go:712 lxc/image.go:956 lxc/profile.go:237
 #, c-format
 msgid   "Config parsing error: %s"
 msgstr  ""
@@ -234,7 +234,7 @@ msgstr  ""
 msgid   "Creating the container"
 msgstr  ""
 
-#: lxc/image.go:861 lxc/image.go:889
+#: lxc/image.go:875 lxc/image.go:903
 msgid   "DESCRIPTION"
 msgstr  ""
 
@@ -293,12 +293,12 @@ msgstr  ""
 msgid   "Expires: never"
 msgstr  ""
 
-#: lxc/image.go:706
+#: lxc/image.go:710
 #, c-format
 msgid   "Exporting the image: %s"
 msgstr  ""
 
-#: lxc/config.go:348 lxc/image.go:859 lxc/image.go:888
+#: lxc/config.go:348 lxc/image.go:873 lxc/image.go:902
 msgid   "FINGERPRINT"
 msgstr  ""
 
@@ -383,11 +383,11 @@ msgstr  ""
 msgid   "Image copied successfully!"
 msgstr  ""
 
-#: lxc/image.go:756
+#: lxc/image.go:770
 msgid   "Image exported successfully!"
 msgstr  ""
 
-#: lxc/image.go:576
+#: lxc/image.go:580
 #, c-format
 msgid   "Image imported with fingerprint: %s"
 msgstr  ""
@@ -537,7 +537,7 @@ msgstr  ""
 msgid   "PROTOCOL"
 msgstr  ""
 
-#: lxc/image.go:860 lxc/remote.go:367
+#: lxc/image.go:874 lxc/remote.go:367
 msgid   "PUBLIC"
 msgstr  ""
 
@@ -574,7 +574,7 @@ msgstr  ""
 msgid   "Press enter to open the editor again"
 msgstr  ""
 
-#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:943
+#: lxc/config.go:648 lxc/config.go:713 lxc/image.go:957
 msgid   "Press enter to start the editor again"
 msgstr  ""
 
@@ -667,7 +667,7 @@ msgstr  ""
 msgid   "Retrieving image: %s"
 msgstr  ""
 
-#: lxc/image.go:863
+#: lxc/image.go:877
 msgid   "SIZE"
 msgstr  ""
 
@@ -841,7 +841,7 @@ msgstr  ""
 msgid   "Transferring container: %s"
 msgstr  ""
 
-#: lxc/image.go:524
+#: lxc/image.go:528
 #, c-format
 msgid   "Transferring image: %s"
 msgstr  ""
@@ -859,7 +859,7 @@ msgstr  ""
 msgid   "Type: persistent"
 msgstr  ""
 
-#: lxc/image.go:864
+#: lxc/image.go:878
 msgid   "UPLOAD DATE"
 msgstr  ""
 
@@ -1416,7 +1416,7 @@ msgstr  ""
 msgid   "error: unknown command: %s"
 msgstr  ""
 
-#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:840
+#: lxc/image.go:396 lxc/image.go:401 lxc/image.go:854
 msgid   "no"
 msgstr  ""
 
@@ -1466,7 +1466,7 @@ msgstr  ""
 msgid   "wrong number of subcommand arguments"
 msgstr  ""
 
-#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:844
+#: lxc/delete.go:45 lxc/image.go:398 lxc/image.go:403 lxc/image.go:858
 msgid   "yes"
 msgstr  ""
 

From ec44874fc6aafbc07e3c9674d675f52c27c862c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 3 Oct 2017 23:40:11 -0400
Subject: [PATCH 1161/1193] legacy client: Fix shared.GetTLSConfigMem call
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client.go b/client.go
index 645e547ef..2f9d82e07 100644
--- a/client.go
+++ b/client.go
@@ -227,7 +227,7 @@ func connectViaUnix(c *Client, remote *RemoteConfig) error {
 }
 
 func connectViaHttp(c *Client, remote *RemoteConfig, clientCert, clientKey, serverCert string) error {
-	tlsconfig, err := shared.GetTLSConfigMem(clientCert, clientKey, "", serverCert)
+	tlsconfig, err := shared.GetTLSConfigMem(clientCert, clientKey, "", serverCert, false)
 	if err != nil {
 		return err
 	}

From d30da9cc874344efb9ffa313a9c0caf98db104e8 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 16 Aug 2017 11:36:03 +0000
Subject: [PATCH 1162/1193] Drop dependencies on Daemon in db.go

This branch drops any mention of the Daemon structure in db.go, and is
a step towards making the db functionality self-contained (in order to
put it in a separate db/ package).

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/.dir-locals.el |  3 +++
 lxd/daemon.go      |  9 +--------
 lxd/db.go          | 12 ++++++++++++
 lxd/db_test.go     | 46 +++++++++++++++++-----------------------------
 4 files changed, 33 insertions(+), 37 deletions(-)
 create mode 100644 lxd/.dir-locals.el

diff --git a/lxd/.dir-locals.el b/lxd/.dir-locals.el
new file mode 100644
index 000000000..9bebcc48c
--- /dev/null
+++ b/lxd/.dir-locals.el
@@ -0,0 +1,3 @@
+;;; Directory Local Variables
+;;; For more information see (info "(emacs) Directory Variables")
+((go-mode . ((go-test-args . "-tags libsqlite3"))))
diff --git a/lxd/daemon.go b/lxd/daemon.go
index a9cbfd566..b93c69038 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -1323,17 +1323,10 @@ func (s *lxdHttpServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 
 // Create a database connection and perform any updates needed.
 func initializeDbObject(d *Daemon, path string) error {
-	var openPath string
 	var err error
 
-	timeout := 5 // TODO - make this command-line configurable?
-
-	// These are used to tune the transaction BEGIN behavior instead of using the
-	// similar "locking_mode" pragma (locking for the whole database connection).
-	openPath = fmt.Sprintf("%s?_busy_timeout=%d&_txlock=exclusive", path, timeout*1000)
-
 	// Open the database. If the file doesn't exist it is created.
-	d.db, err = sql.Open("sqlite3_with_fk", openPath)
+	d.db, err = openDb(path)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/db.go b/lxd/db.go
index 1c6d9d64f..c0a5d3c57 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -176,6 +176,18 @@ func init() {
 	sql.Register("sqlite3_with_fk", &sqlite3.SQLiteDriver{ConnectHook: enableForeignKeys})
 }
 
+// Open the database with the correct parameters for LXD.
+func openDb(path string) (*sql.DB, error) {
+	timeout := 5 // TODO - make this command-line configurable?
+
+	// These are used to tune the transaction BEGIN behavior instead of using the
+	// similar "locking_mode" pragma (locking for the whole database connection).
+	openPath := fmt.Sprintf("%s?_busy_timeout=%d&_txlock=exclusive", path, timeout*1000)
+
+	// Open the database. If the file doesn't exist it is created.
+	return sql.Open("sqlite3_with_fk", openPath)
+}
+
 // Create the initial (current) schema for a given SQLite DB connection.
 func createDb(db *sql.DB) (err error) {
 	latestVersion := dbGetSchema(db)
diff --git a/lxd/db_test.go b/lxd/db_test.go
index bdfa428ac..eda2a88d8 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -37,6 +37,8 @@ type dbTestSuite struct {
 
 func (s *dbTestSuite) SetupTest() {
 	s.db = s.CreateTestDb()
+	_, err := s.db.Exec(DB_FIXTURES)
+	s.Nil(err)
 }
 
 func (s *dbTestSuite) TearDownTest() {
@@ -44,7 +46,7 @@ func (s *dbTestSuite) TearDownTest() {
 }
 
 // Initialize a test in-memory DB.
-func (s *dbTestSuite) CreateTestDb() (db *sql.DB) {
+func (s *dbTestSuite) CreateTestDb() *sql.DB {
 	// Setup logging if main() hasn't been called/when testing
 	if logger.Log == nil {
 		var err error
@@ -52,15 +54,12 @@ func (s *dbTestSuite) CreateTestDb() (db *sql.DB) {
 		s.Nil(err)
 	}
 
-	var err error
-	d := &Daemon{MockMode: true}
-	err = initializeDbObject(d, ":memory:")
+	db, err := openDb(":memory:")
 	s.Nil(err)
-	db = d.db
+	s.Nil(createDb(db))
+	s.Nil(dbUpdatesApplyAll(db, false, nil))
+	return db
 
-	_, err = db.Exec(DB_FIXTURES)
-	s.Nil(err)
-	return // db is a named output param
 }
 
 func TestDBTestSuite(t *testing.T) {
@@ -162,18 +161,12 @@ func (s *dbTestSuite) Test_deleting_an_image_cascades_on_related_tables() {
 }
 
 func (s *dbTestSuite) Test_initializing_db_is_idempotent() {
-	var db *sql.DB
-	var err error
-
 	// This calls "createDb" once already.
-	d := &Daemon{MockMode: true}
-	err = initializeDbObject(d, ":memory:")
-	db = d.db
+	db := s.CreateTestDb()
 	defer db.Close()
 
 	// Let's call it a second time.
-	err = createDb(db)
-	s.Nil(err)
+	s.Nil(createDb(db))
 }
 
 func (s *dbTestSuite) Test_get_schema_returns_0_on_uninitialized_db() {
@@ -193,9 +186,8 @@ func (s *dbTestSuite) Test_running_dbUpdateFromV6_adds_on_delete_cascade() {
 	var err error
 	var count int
 
-	d := &Daemon{MockMode: true}
-	err = initializeDbObject(d, ":memory:")
-	defer d.db.Close()
+	db := s.CreateTestDb()
+	defer db.Close()
 
 	statements := `
 CREATE TABLE IF NOT EXISTS containers (
@@ -219,28 +211,28 @@ CREATE TABLE IF NOT EXISTS containers_config (
 INSERT INTO containers (name, architecture, type) VALUES ('thename', 1, 1);
 INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 'thevalue');`
 
-	_, err = d.db.Exec(statements)
+	_, err = db.Exec(statements)
 	s.Nil(err)
 
 	// Run the upgrade from V6 code
-	err = dbUpdateFromV6(5, 6, d.db)
+	err = dbUpdateFromV6(5, 6, db)
 	s.Nil(err)
 
 	// Make sure the inserted data is still there.
 	statements = `SELECT count(*) FROM containers_config;`
-	err = d.db.QueryRow(statements).Scan(&count)
+	err = db.QueryRow(statements).Scan(&count)
 	s.Nil(err)
 	s.Equal(count, 1, "There should be exactly one entry in containers_config!")
 
 	// Drop the container.
 	statements = `DELETE FROM containers WHERE name = 'thename';`
 
-	_, err = d.db.Exec(statements)
+	_, err = db.Exec(statements)
 	s.Nil(err)
 
 	// Make sure there are 0 container_profiles entries left.
 	statements = `SELECT count(*) FROM containers_profiles;`
-	err = d.db.QueryRow(statements).Scan(&count)
+	err = db.QueryRow(statements).Scan(&count)
 	s.Nil(err)
 	s.Equal(count, 0, "Deleting a container didn't delete the profile association!")
 }
@@ -321,11 +313,7 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 
 	// The "foreign key" on containers_config now points to nothing.
 	// Let's run the schema upgrades.
-	d := &Daemon{MockMode: true}
-	d.db = db
-	daemonConfigInit(db)
-
-	err = dbUpdatesApplyAll(d.db, false, nil)
+	err = dbUpdatesApplyAll(db, false, nil)
 	s.Nil(err)
 
 	result := dbGetSchema(db)

From 1c3673dc1df3f42845ff5ff1916da37cdaaae157 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:04:42 +0000
Subject: [PATCH 1163/1193] Move db*.go files into their own db/ sub-package

This branch is very large but almost completely mechanical. It moves
all db-related code into a standalone subpackage (db). This
facilitates testing and makes further refactoring easier to reason
about.

The only point worth mentioning is that containerArgs and
storagePoolVolumeTypeToName had to be moved to the db package, to
avoid circular dependencies. We will probably want to clean that up
down the road.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/api_1.0.go                                 |   3 +-
 lxd/certificates.go                            |  15 ++--
 lxd/container.go                               |  50 ++++-------
 lxd/container_lxc.go                           |  71 ++++++++--------
 lxd/container_post.go                          |   3 +-
 lxd/container_put.go                           |   3 +-
 lxd/container_snapshot.go                      |  11 +--
 lxd/container_state.go                         |   3 +-
 lxd/container_test.go                          |  69 +++++++--------
 lxd/containers.go                              |   9 +-
 lxd/containers_get.go                          |   5 +-
 lxd/containers_post.go                         |  29 +++----
 lxd/daemon.go                                  |  13 +--
 lxd/daemon_config.go                           |   5 +-
 lxd/daemon_images.go                           |  13 +--
 lxd/{db_certificates.go => db/certificates.go} |  34 ++++----
 lxd/{db_config.go => db/config.go}             |  10 +--
 lxd/{db_containers.go => db/containers.go}     | 111 +++++++++++++++----------
 lxd/{ => db}/db.go                             |  44 +++++-----
 lxd/{ => db}/db_test.go                        |  64 +++++++-------
 lxd/{db_devices.go => db/devices.go}           |  10 +--
 lxd/{db_images.go => db/images.go}             |  80 +++++++++---------
 lxd/{db_patches.go => db/patches.go}           |   8 +-
 lxd/{db_profiles.go => db/profiles.go}         |  62 +++++++-------
 lxd/{db_update.go => db/update.go}             |  10 +--
 lxd/devices.go                                 |   5 +-
 lxd/devlxd.go                                  |   3 +-
 lxd/images.go                                  |  77 ++++++++---------
 lxd/main_activateifneeded.go                   |   3 +-
 lxd/networks.go                                |   3 +-
 lxd/patches.go                                 |  22 +++--
 lxd/profiles.go                                |  19 +++--
 lxd/profiles_test.go                           |  10 ++-
 lxd/profiles_utils.go                          |  19 +++--
 lxd/response.go                                |   5 +-
 lxd/storage.go                                 |   7 +-
 lxd/storage_lvm.go                             |   5 +-
 37 files changed, 476 insertions(+), 437 deletions(-)
 rename lxd/{db_certificates.go => db/certificates.go} (66%)
 rename lxd/{db_config.go => db/config.go} (81%)
 rename lxd/{db_containers.go => db/containers.go} (71%)
 rename lxd/{ => db}/db.go (92%)
 rename lxd/{ => db}/db_test.go (89%)
 rename lxd/{db_devices.go => db/devices.go} (91%)
 rename lxd/{db_images.go => db/images.go} (76%)
 rename lxd/{db_patches.go => db/patches.go} (71%)
 rename lxd/{db_profiles.go => db/profiles.go} (73%)
 rename lxd/{db_update.go => db/update.go} (98%)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 3c6471e7a..54f9548b7 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -10,6 +10,7 @@ import (
 
 	"gopkg.in/lxc/go-lxc.v2"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
@@ -160,7 +161,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 }
 
 func api10Put(d *Daemon, r *http.Request) Response {
-	oldConfig, err := dbConfigValuesGet(d.db)
+	oldConfig, err := db.ConfigValuesGet(d.db)
 	if err != nil {
 		return SmartError(err)
 	}
diff --git a/lxd/certificates.go b/lxd/certificates.go
index dfbe11e2c..c175487ff 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -22,7 +23,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 	if recursion {
 		certResponses := []api.Certificate{}
 
-		baseCerts, err := dbCertsGet(d.db)
+		baseCerts, err := db.CertsGet(d.db)
 		if err != nil {
 			return SmartError(err)
 		}
@@ -52,7 +53,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 func readSavedClientCAList(d *Daemon) {
 	d.clientCerts = []x509.Certificate{}
 
-	dbCerts, err := dbCertsGet(d.db)
+	dbCerts, err := db.CertsGet(d.db)
 	if err != nil {
 		logger.Infof("Error reading certificates from database: %s", err)
 		return
@@ -75,7 +76,7 @@ func readSavedClientCAList(d *Daemon) {
 }
 
 func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
-	baseCert := new(dbCertInfo)
+	baseCert := new(db.CertInfo)
 	baseCert.Fingerprint = shared.CertFingerprint(cert)
 	baseCert.Type = 1
 	baseCert.Name = host
@@ -83,7 +84,7 @@ func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
 		pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}),
 	)
 
-	return dbCertSave(d.db, baseCert)
+	return db.CertSave(d.db, baseCert)
 }
 
 func certificatesPost(d *Daemon, r *http.Request) Response {
@@ -173,7 +174,7 @@ func certificateFingerprintGet(d *Daemon, r *http.Request) Response {
 func doCertificateGet(d *Daemon, fingerprint string) (api.Certificate, error) {
 	resp := api.Certificate{}
 
-	dbCertInfo, err := dbCertGet(d.db, fingerprint)
+	dbCertInfo, err := db.CertGet(d.db, fingerprint)
 	if err != nil {
 		return resp, err
 	}
@@ -193,12 +194,12 @@ func doCertificateGet(d *Daemon, fingerprint string) (api.Certificate, error) {
 func certificateFingerprintDelete(d *Daemon, r *http.Request) Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
-	certInfo, err := dbCertGet(d.db, fingerprint)
+	certInfo, err := db.CertGet(d.db, fingerprint)
 	if err != nil {
 		return NotFound
 	}
 
-	err = dbCertDelete(d.db, certInfo.Fingerprint)
+	err = db.CertDelete(d.db, certInfo.Fingerprint)
 	if err != nil {
 		return SmartError(err)
 	}
diff --git a/lxd/container.go b/lxd/container.go
index 93bb49da1..f208ddadb 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -11,6 +11,7 @@ import (
 
 	"gopkg.in/lxc/go-lxc.v2"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -466,23 +467,6 @@ func containerValidDevices(devices types.Devices, profile bool, expanded bool) e
 	return nil
 }
 
-// The container arguments
-type containerArgs struct {
-	// Don't set manually
-	Id int
-
-	Architecture int
-	BaseImage    string
-	Config       map[string]string
-	CreationDate time.Time
-	Ctype        containerType
-	Devices      types.Devices
-	Ephemeral    bool
-	Name         string
-	Profiles     []string
-	Stateful     bool
-}
-
 // The container interface
 type container interface {
 	// Container actions
@@ -502,7 +486,7 @@ type container interface {
 
 	// Config handling
 	Rename(newName string) error
-	Update(newConfig containerArgs, userRequested bool) error
+	Update(newConfig db.ContainerArgs, userRequested bool) error
 
 	Delete() error
 	Export(w io.Writer, properties map[string]string) error
@@ -579,7 +563,7 @@ type container interface {
 }
 
 // Loader functions
-func containerCreateAsEmpty(d *Daemon, args containerArgs) (container, error) {
+func containerCreateAsEmpty(d *Daemon, args db.ContainerArgs) (container, error) {
 	// Create the container
 	c, err := containerCreateInternal(d, args)
 	if err != nil {
@@ -602,7 +586,7 @@ func containerCreateAsEmpty(d *Daemon, args containerArgs) (container, error) {
 	return c, nil
 }
 
-func containerCreateEmptySnapshot(d *Daemon, args containerArgs) (container, error) {
+func containerCreateEmptySnapshot(d *Daemon, args db.ContainerArgs) (container, error) {
 	// Create the snapshot
 	c, err := containerCreateInternal(d, args)
 	if err != nil {
@@ -618,7 +602,7 @@ func containerCreateEmptySnapshot(d *Daemon, args containerArgs) (container, err
 	return c, nil
 }
 
-func containerCreateFromImage(d *Daemon, args containerArgs, hash string) (container, error) {
+func containerCreateFromImage(d *Daemon, args db.ContainerArgs, hash string) (container, error) {
 	// Set the BaseImage field (regardless of previous value)
 	args.BaseImage = hash
 
@@ -628,7 +612,7 @@ func containerCreateFromImage(d *Daemon, args containerArgs, hash string) (conta
 		return nil, err
 	}
 
-	if err := dbImageLastAccessUpdate(d.db, hash, time.Now().UTC()); err != nil {
+	if err := db.ImageLastAccessUpdate(d.db, hash, time.Now().UTC()); err != nil {
 		return nil, fmt.Errorf("Error updating image last use date: %s", err)
 	}
 
@@ -648,8 +632,8 @@ func containerCreateFromImage(d *Daemon, args containerArgs, hash string) (conta
 	return c, nil
 }
 
-func containerCreateAsCopy(d *Daemon, args containerArgs, sourceContainer container) (container, error) {
-	// Create the container
+func containerCreateAsCopy(d *Daemon, args db.ContainerArgs, sourceContainer container) (container, error) {
+	// Create the container.
 	c, err := containerCreateInternal(d, args)
 	if err != nil {
 		return nil, err
@@ -671,7 +655,7 @@ func containerCreateAsCopy(d *Daemon, args containerArgs, sourceContainer contai
 	return c, nil
 }
 
-func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer container) (container, error) {
+func containerCreateAsSnapshot(d *Daemon, args db.ContainerArgs, sourceContainer container) (container, error) {
 	// Deal with state
 	if args.Stateful {
 		if !sourceContainer.IsRunning() {
@@ -726,7 +710,7 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 	return c, nil
 }
 
-func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
+func containerCreateInternal(d *Daemon, args db.ContainerArgs) (container, error) {
 	// Set default values
 	if args.Profiles == nil {
 		args.Profiles = []string{"default"}
@@ -749,7 +733,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Validate container name
-	if args.Ctype == cTypeRegular {
+	if args.Ctype == db.CTypeRegular {
 		err := containerValidName(args.Name)
 		if err != nil {
 			return nil, err
@@ -779,7 +763,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Validate profiles
-	profiles, err := dbProfiles(d.db)
+	profiles, err := db.Profiles(d.db)
 	if err != nil {
 		return nil, err
 	}
@@ -791,9 +775,9 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	}
 
 	// Create the container entry
-	id, err := dbContainerCreate(d.db, args)
+	id, err := db.ContainerCreate(d.db, args)
 	if err != nil {
-		if err == DbErrAlreadyDefined {
+		if err == db.DbErrAlreadyDefined {
 			thing := "Container"
 			if shared.IsSnapshot(args.Name) {
 				thing = "Snapshot"
@@ -809,7 +793,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	args.Id = id
 
 	// Read the timestamp from the database
-	dbArgs, err := dbContainerGet(d.db, args.Name)
+	dbArgs, err := db.ContainerGet(d.db, args.Name)
 	if err != nil {
 		return nil, err
 	}
@@ -849,7 +833,7 @@ func containerConfigureInternal(c container) error {
 
 func containerLoadById(d *Daemon, id int) (container, error) {
 	// Get the DB record
-	name, err := dbContainerName(d.db, id)
+	name, err := db.ContainerName(d.db, id)
 	if err != nil {
 		return nil, err
 	}
@@ -859,7 +843,7 @@ func containerLoadById(d *Daemon, id int) (container, error) {
 
 func containerLoadByName(d *Daemon, name string) (container, error) {
 	// Get the DB record
-	args, err := dbContainerGet(d.db, name)
+	args, err := db.ContainerGet(d.db, name)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index fefd902d0..ef616ffd1 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -24,6 +24,7 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -237,7 +238,7 @@ func lxcStatusCode(state lxc.State) api.StatusCode {
 }
 
 // Loader functions
-func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
+func containerLXCCreate(d *Daemon, args db.ContainerArgs) (container, error) {
 	// Create the container struct
 	c := &containerLXC{
 		daemon:       d,
@@ -283,7 +284,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 
 		c.localDevices[deviceName] = types.Device{"type": "disk", "path": "/"}
 
-		updateArgs := containerArgs{
+		updateArgs := db.ContainerArgs{
 			Architecture: c.architecture,
 			Config:       c.localConfig,
 			Devices:      c.localDevices,
@@ -387,7 +388,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 	return c, nil
 }
 
-func containerLXCLoad(d *Daemon, args containerArgs) (container, error) {
+func containerLXCLoad(d *Daemon, args db.ContainerArgs) (container, error) {
 	// Create the container struct
 	c := &containerLXC{
 		daemon:       d,
@@ -422,7 +423,7 @@ func containerLXCLoad(d *Daemon, args containerArgs) (container, error) {
 type containerLXC struct {
 	// Properties
 	architecture int
-	cType        containerType
+	cType        db.ContainerType
 	creationDate time.Time
 	ephemeral    bool
 	id           int
@@ -663,7 +664,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configBase stri
 	idmapLock.Lock()
 	defer idmapLock.Unlock()
 
-	cs, err := dbContainersList(daemon.db, cTypeRegular)
+	cs, err := db.ContainersList(daemon.db, db.CTypeRegular)
 	if err != nil {
 		return nil, 0, err
 	}
@@ -1441,7 +1442,7 @@ func (c *containerLXC) expandConfig() error {
 
 	// Apply all the profiles
 	for _, name := range c.profiles {
-		profileConfig, err := dbProfileConfig(c.daemon.db, name)
+		profileConfig, err := db.ProfileConfig(c.daemon.db, name)
 		if err != nil {
 			return err
 		}
@@ -1465,7 +1466,7 @@ func (c *containerLXC) expandDevices() error {
 
 	// Apply all the profiles
 	for _, p := range c.profiles {
-		profileDevices, err := dbDevices(c.daemon.db, p, true)
+		profileDevices, err := db.Devices(c.daemon.db, p, true)
 		if err != nil {
 			return err
 		}
@@ -1736,7 +1737,7 @@ func (c *containerLXC) Start(stateful bool) error {
 		os.RemoveAll(c.StatePath())
 		c.stateful = false
 
-		err = dbContainerSetStateful(c.daemon.db, c.id, false)
+		err = db.ContainerSetStateful(c.daemon.db, c.id, false)
 		if err != nil {
 			logger.Error("Failed starting container", ctxMap)
 			return err
@@ -1753,7 +1754,7 @@ func (c *containerLXC) Start(stateful bool) error {
 		}
 
 		c.stateful = false
-		err = dbContainerSetStateful(c.daemon.db, c.id, false)
+		err = db.ContainerSetStateful(c.daemon.db, c.id, false)
 		if err != nil {
 			return err
 		}
@@ -1847,7 +1848,7 @@ func (c *containerLXC) OnStart() error {
 		}
 
 		// Remove the volatile key from the DB
-		err := dbContainerConfigRemove(c.daemon.db, c.id, key)
+		err := db.ContainerConfigRemove(c.daemon.db, c.id, key)
 		if err != nil {
 			AADestroy(c)
 			c.StorageStop()
@@ -1897,7 +1898,7 @@ func (c *containerLXC) OnStart() error {
 	}
 
 	// Record current state
-	err = dbContainerSetState(c.daemon.db, c.id, "RUNNING")
+	err = db.ContainerSetState(c.daemon.db, c.id, "RUNNING")
 	if err != nil {
 		return err
 	}
@@ -1950,7 +1951,7 @@ func (c *containerLXC) Stop(stateful bool) error {
 		}
 
 		c.stateful = true
-		err = dbContainerSetStateful(c.daemon.db, c.id, true)
+		err = db.ContainerSetStateful(c.daemon.db, c.id, true)
 		if err != nil {
 			op.Done(err)
 			logger.Error("Failed stopping container", ctxMap)
@@ -2124,7 +2125,7 @@ func (c *containerLXC) OnStop(target string) error {
 		deviceTaskSchedulerTrigger("container", c.name, "stopped")
 
 		// Record current state
-		err = dbContainerSetState(c.daemon.db, c.id, "STOPPED")
+		err = db.ContainerSetState(c.daemon.db, c.id, "STOPPED")
 		if err != nil {
 			logger.Error("Failed to set container state", log.Ctx{"container": c.Name(), "err": err})
 		}
@@ -2315,7 +2316,7 @@ func (c *containerLXC) RenderState() (*api.ContainerState, error) {
 
 func (c *containerLXC) Snapshots() ([]container, error) {
 	// Get all the snapshots
-	snaps, err := dbContainerGetSnapshots(c.daemon.db, c.name)
+	snaps, err := db.ContainerGetSnapshots(c.daemon.db, c.name)
 	if err != nil {
 		return nil, err
 	}
@@ -2377,7 +2378,7 @@ func (c *containerLXC) Restore(sourceContainer container) error {
 	}
 
 	// Restore the configuration
-	args := containerArgs{
+	args := db.ContainerArgs{
 		Architecture: sourceContainer.Architecture(),
 		Config:       sourceContainer.LocalConfig(),
 		Devices:      sourceContainer.LocalDevices(),
@@ -2474,7 +2475,7 @@ func (c *containerLXC) Delete() error {
 	}
 
 	// Remove the database record
-	if err := dbContainerRemove(c.daemon.db, c.Name()); err != nil {
+	if err := db.ContainerRemove(c.daemon.db, c.Name()); err != nil {
 		logger.Error("Failed deleting container entry", log.Ctx{"name": c.Name(), "err": err})
 		return err
 	}
@@ -2529,14 +2530,14 @@ func (c *containerLXC) Rename(newName string) error {
 	}
 
 	// Rename the database entry
-	if err := dbContainerRename(c.daemon.db, oldName, newName); err != nil {
+	if err := db.ContainerRename(c.daemon.db, oldName, newName); err != nil {
 		logger.Error("Failed renaming container", ctxMap)
 		return err
 	}
 
 	if !c.IsSnapshot() {
 		// Rename all the snapshots
-		results, err := dbContainerGetSnapshots(c.daemon.db, oldName)
+		results, err := db.ContainerGetSnapshots(c.daemon.db, oldName)
 		if err != nil {
 			logger.Error("Failed renaming container", ctxMap)
 			return err
@@ -2546,7 +2547,7 @@ func (c *containerLXC) Rename(newName string) error {
 			// Rename the snapshot
 			baseSnapName := filepath.Base(sname)
 			newSnapshotName := newName + shared.SnapshotDelimiter + baseSnapName
-			if err := dbContainerRename(c.daemon.db, sname, newSnapshotName); err != nil {
+			if err := db.ContainerRename(c.daemon.db, sname, newSnapshotName); err != nil {
 				logger.Error("Failed renaming container", ctxMap)
 				return err
 			}
@@ -2603,7 +2604,7 @@ func (c *containerLXC) CGroupSet(key string, value string) error {
 func (c *containerLXC) ConfigKeySet(key string, value string) error {
 	c.localConfig[key] = value
 
-	args := containerArgs{
+	args := db.ContainerArgs{
 		Architecture: c.architecture,
 		Config:       c.localConfig,
 		Devices:      c.localDevices,
@@ -2614,7 +2615,7 @@ func (c *containerLXC) ConfigKeySet(key string, value string) error {
 	return c.Update(args, false)
 }
 
-func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
+func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 	// Set sane defaults for unset keys
 	if args.Architecture == 0 {
 		args.Architecture = c.architecture
@@ -2645,7 +2646,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	}
 
 	// Validate the new profiles
-	profiles, err := dbProfiles(c.daemon.db)
+	profiles, err := db.Profiles(c.daemon.db)
 	if err != nil {
 		return err
 	}
@@ -3275,42 +3276,42 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 	}
 
 	// Finally, apply the changes to the database
-	tx, err := dbBegin(c.daemon.db)
+	tx, err := db.Begin(c.daemon.db)
 	if err != nil {
 		return err
 	}
 
-	err = dbContainerConfigClear(tx, c.id)
+	err = db.ContainerConfigClear(tx, c.id)
 	if err != nil {
 		tx.Rollback()
 		return err
 	}
 
-	err = dbContainerConfigInsert(tx, c.id, c.localConfig)
+	err = db.ContainerConfigInsert(tx, c.id, c.localConfig)
 	if err != nil {
 		tx.Rollback()
 		return err
 	}
 
-	err = dbContainerProfilesInsert(tx, c.id, c.profiles)
+	err = db.ContainerProfilesInsert(tx, c.id, c.profiles)
 	if err != nil {
 		tx.Rollback()
 		return err
 	}
 
-	err = dbDevicesAdd(tx, "container", int64(c.id), c.localDevices)
+	err = db.DevicesAdd(tx, "container", int64(c.id), c.localDevices)
 	if err != nil {
 		tx.Rollback()
 		return err
 	}
 
-	err = dbContainerUpdate(tx, c.id, c.architecture, c.ephemeral)
+	err = db.ContainerUpdate(tx, c.id, c.architecture, c.ephemeral)
 	if err != nil {
 		tx.Rollback()
 		return err
 	}
 
-	if err := txCommit(tx); err != nil {
+	if err := db.TxCommit(tx); err != nil {
 		return err
 	}
 
@@ -5069,18 +5070,18 @@ func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Dev
 	}
 
 	updateKey := func(key string, value string) error {
-		tx, err := dbBegin(c.daemon.db)
+		tx, err := db.Begin(c.daemon.db)
 		if err != nil {
 			return err
 		}
 
-		err = dbContainerConfigInsert(tx, c.id, map[string]string{key: value})
+		err = db.ContainerConfigInsert(tx, c.id, map[string]string{key: value})
 		if err != nil {
 			tx.Rollback()
 			return err
 		}
 
-		err = txCommit(tx)
+		err = db.TxCommit(tx)
 		if err != nil {
 			return err
 		}
@@ -5103,7 +5104,7 @@ func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Dev
 			err = updateKey(configKey, volatileHwaddr)
 			if err != nil {
 				// Check if something else filled it in behind our back
-				value, err1 := dbContainerConfigGet(c.daemon.db, c.id, configKey)
+				value, err1 := db.ContainerConfigGet(c.daemon.db, c.id, configKey)
 				if err1 != nil || value == "" {
 					return nil, err
 				}
@@ -5133,7 +5134,7 @@ func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Dev
 			err = updateKey(configKey, volatileName)
 			if err != nil {
 				// Check if something else filled it in behind our back
-				value, err1 := dbContainerConfigGet(c.daemon.db, c.id, configKey)
+				value, err1 := db.ContainerConfigGet(c.daemon.db, c.id, configKey)
 				if err1 != nil || value == "" {
 					return nil, err
 				}
@@ -5791,7 +5792,7 @@ func (c *containerLXC) IsRunning() bool {
 }
 
 func (c *containerLXC) IsSnapshot() bool {
-	return c.cType == cTypeSnapshot
+	return c.cType == db.CTypeSnapshot
 }
 
 // Various property query functions
diff --git a/lxd/container_post.go b/lxd/container_post.go
index 90e9e4d3e..f2f7e2d19 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared/api"
 )
 
@@ -46,7 +47,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Check that the name isn't already in use
-	id, _ := dbContainerId(d.db, body.Name)
+	id, _ := db.ContainerId(d.db, body.Name)
 	if id > 0 {
 		return Conflict
 	}
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 425531cd3..e82d8195d 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -42,7 +43,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
 	if configRaw.Restore == "" {
 		// Update container configuration
 		do = func(op *operation) error {
-			args := containerArgs{
+			args := db.ContainerArgs{
 				Architecture: architecture,
 				Config:       configRaw.Config,
 				Devices:      configRaw.Devices,
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index b57a1624c..bdea9d8b4 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
@@ -65,9 +66,9 @@ func nextSnapshot(d *Daemon, name string) int {
 	length := len(base)
 	q := fmt.Sprintf("SELECT name FROM containers WHERE type=? AND SUBSTR(name,1,?)=?")
 	var numstr string
-	inargs := []interface{}{cTypeSnapshot, length, base}
+	inargs := []interface{}{db.CTypeSnapshot, length, base}
 	outfmt := []interface{}{numstr}
-	results, err := dbQueryScan(d.db, q, inargs, outfmt)
+	results, err := db.QueryScan(d.db, q, inargs, outfmt)
 	if err != nil {
 		return 0
 	}
@@ -122,9 +123,9 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 		req.Name
 
 	snapshot := func(op *operation) error {
-		args := containerArgs{
+		args := db.ContainerArgs{
 			Name:         fullName,
-			Ctype:        cTypeSnapshot,
+			Ctype:        db.CTypeSnapshot,
 			Config:       c.LocalConfig(),
 			Profiles:     c.Profiles(),
 			Ephemeral:    c.IsEphemeral(),
@@ -219,7 +220,7 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
 	fullName := containerName + shared.SnapshotDelimiter + newName
 
 	// Check that the name isn't already in use
-	id, _ := dbContainerId(d.db, fullName)
+	id, _ := db.ContainerId(d.db, fullName)
 	if id > 0 {
 		return Conflict
 	}
diff --git a/lxd/container_state.go b/lxd/container_state.go
index 3115f586a..05901ff95 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -99,7 +100,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 
 			if ephemeral {
 				// Unset ephemeral flag
-				args := containerArgs{
+				args := db.ContainerArgs{
 					Architecture: c.Architecture(),
 					Config:       c.LocalConfig(),
 					Devices:      c.LocalDevices(),
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 687a8de4e..53b292b2f 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -15,8 +16,8 @@ type containerTestSuite struct {
 }
 
 func (suite *containerTestSuite) TestContainer_ProfilesDefault() {
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Name:      "testFoo",
 	}
@@ -39,7 +40,7 @@ func (suite *containerTestSuite) TestContainer_ProfilesDefault() {
 
 func (suite *containerTestSuite) TestContainer_ProfilesMulti() {
 	// Create an unprivileged profile
-	_, err := dbProfileCreate(
+	_, err := db.ProfileCreate(
 		suite.d.db,
 		"unprivileged",
 		"unprivileged",
@@ -48,11 +49,11 @@ func (suite *containerTestSuite) TestContainer_ProfilesMulti() {
 
 	suite.Req.Nil(err, "Failed to create the unprivileged profile.")
 	defer func() {
-		dbProfileDelete(suite.d.db, "unprivileged")
+		db.ProfileDelete(suite.d.db, "unprivileged")
 	}()
 
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Profiles:  []string{"default", "unprivileged"},
 		Name:      "testFoo",
@@ -74,8 +75,8 @@ func (suite *containerTestSuite) TestContainer_ProfilesMulti() {
 }
 
 func (suite *containerTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Config:    map[string]string{"security.privileged": "true"},
 		Devices: types.Devices{
@@ -104,8 +105,8 @@ func (suite *containerTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 }
 
 func (suite *containerTestSuite) TestContainer_LoadFromDB() {
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Config:    map[string]string{"security.privileged": "true"},
 		Devices: types.Devices{
@@ -134,8 +135,8 @@ func (suite *containerTestSuite) TestContainer_LoadFromDB() {
 
 func (suite *containerTestSuite) TestContainer_Path_Regular() {
 	// Regular
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Name:      "testFoo",
 	}
@@ -151,8 +152,8 @@ func (suite *containerTestSuite) TestContainer_Path_Regular() {
 
 func (suite *containerTestSuite) TestContainer_Path_Snapshot() {
 	// Snapshot
-	args := containerArgs{
-		Ctype:     cTypeSnapshot,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeSnapshot,
 		Ephemeral: false,
 		Name:      "test/snap0",
 	}
@@ -171,8 +172,8 @@ func (suite *containerTestSuite) TestContainer_Path_Snapshot() {
 }
 
 func (suite *containerTestSuite) TestContainer_LogPath() {
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Name:      "testFoo",
 	}
@@ -185,8 +186,8 @@ func (suite *containerTestSuite) TestContainer_LogPath() {
 }
 
 func (suite *containerTestSuite) TestContainer_IsPrivileged_Privileged() {
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Config:    map[string]string{"security.privileged": "true"},
 		Name:      "testFoo",
@@ -201,8 +202,8 @@ func (suite *containerTestSuite) TestContainer_IsPrivileged_Privileged() {
 }
 
 func (suite *containerTestSuite) TestContainer_IsPrivileged_Unprivileged() {
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Config:    map[string]string{"security.privileged": "false"},
 		Name:      "testFoo",
@@ -217,8 +218,8 @@ func (suite *containerTestSuite) TestContainer_IsPrivileged_Unprivileged() {
 }
 
 func (suite *containerTestSuite) TestContainer_Rename() {
-	args := containerArgs{
-		Ctype:     cTypeRegular,
+	args := db.ContainerArgs{
+		Ctype:     db.CTypeRegular,
 		Ephemeral: false,
 		Name:      "testFoo",
 	}
@@ -232,8 +233,8 @@ func (suite *containerTestSuite) TestContainer_Rename() {
 }
 
 func (suite *containerTestSuite) TestContainer_findIdmap_isolated() {
-	c1, err := containerCreateInternal(suite.d, containerArgs{
-		Ctype: cTypeRegular,
+	c1, err := containerCreateInternal(suite.d, db.ContainerArgs{
+		Ctype: db.CTypeRegular,
 		Name:  "isol-1",
 		Config: map[string]string{
 			"security.idmap.isolated": "true",
@@ -242,8 +243,8 @@ func (suite *containerTestSuite) TestContainer_findIdmap_isolated() {
 	suite.Req.Nil(err)
 	defer c1.Delete()
 
-	c2, err := containerCreateInternal(suite.d, containerArgs{
-		Ctype: cTypeRegular,
+	c2, err := containerCreateInternal(suite.d, db.ContainerArgs{
+		Ctype: db.CTypeRegular,
 		Name:  "isol-2",
 		Config: map[string]string{
 			"security.idmap.isolated": "true",
@@ -273,8 +274,8 @@ func (suite *containerTestSuite) TestContainer_findIdmap_isolated() {
 }
 
 func (suite *containerTestSuite) TestContainer_findIdmap_mixed() {
-	c1, err := containerCreateInternal(suite.d, containerArgs{
-		Ctype: cTypeRegular,
+	c1, err := containerCreateInternal(suite.d, db.ContainerArgs{
+		Ctype: db.CTypeRegular,
 		Name:  "isol-1",
 		Config: map[string]string{
 			"security.idmap.isolated": "false",
@@ -283,8 +284,8 @@ func (suite *containerTestSuite) TestContainer_findIdmap_mixed() {
 	suite.Req.Nil(err)
 	defer c1.Delete()
 
-	c2, err := containerCreateInternal(suite.d, containerArgs{
-		Ctype: cTypeRegular,
+	c2, err := containerCreateInternal(suite.d, db.ContainerArgs{
+		Ctype: db.CTypeRegular,
 		Name:  "isol-2",
 		Config: map[string]string{
 			"security.idmap.isolated": "true",
@@ -314,8 +315,8 @@ func (suite *containerTestSuite) TestContainer_findIdmap_mixed() {
 }
 
 func (suite *containerTestSuite) TestContainer_findIdmap_raw() {
-	c1, err := containerCreateInternal(suite.d, containerArgs{
-		Ctype: cTypeRegular,
+	c1, err := containerCreateInternal(suite.d, db.ContainerArgs{
+		Ctype: db.CTypeRegular,
 		Name:  "isol-1",
 		Config: map[string]string{
 			"security.idmap.isolated": "false",
@@ -353,8 +354,8 @@ func (suite *containerTestSuite) TestContainer_findIdmap_maxed() {
 	maps := []*shared.IdmapSet{}
 
 	for i := 0; i < 7; i++ {
-		c, err := containerCreateInternal(suite.d, containerArgs{
-			Ctype: cTypeRegular,
+		c, err := containerCreateInternal(suite.d, db.ContainerArgs{
+			Ctype: db.CTypeRegular,
 			Name:  fmt.Sprintf("isol-%d", i),
 			Config: map[string]string{
 				"security.idmap.isolated": "true",
diff --git a/lxd/containers.go b/lxd/containers.go
index 03e79f81d..33a0dedb6 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -6,6 +6,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 
@@ -81,7 +82,7 @@ func (slice containerAutostartList) Swap(i, j int) {
 
 func containersRestart(d *Daemon) error {
 	// Get all the containers
-	result, err := dbContainersList(d.db, cTypeRegular)
+	result, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return err
 	}
@@ -128,13 +129,13 @@ func containersShutdown(d *Daemon) error {
 	var wg sync.WaitGroup
 
 	// Get all the containers
-	results, err := dbContainersList(d.db, cTypeRegular)
+	results, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return err
 	}
 
 	// Reset all container states
-	_, err = dbExec(d.db, "DELETE FROM containers_config WHERE key='volatile.last_state.power'")
+	_, err = db.Exec(d.db, "DELETE FROM containers_config WHERE key='volatile.last_state.power'")
 	if err != nil {
 		return err
 	}
@@ -172,7 +173,7 @@ func containerDeleteSnapshots(d *Daemon, cname string) error {
 	logger.Debug("containerDeleteSnapshots",
 		log.Ctx{"container": cname})
 
-	results, err := dbContainerGetSnapshots(d.db, cname)
+	results, err := db.ContainerGetSnapshots(d.db, cname)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index bb5809b47..adf6d384c 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -5,6 +5,7 @@ import (
 	"net/http"
 	"time"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
@@ -16,7 +17,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 		if err == nil {
 			return SyncResponse(true, result)
 		}
-		if !isDbLockedError(err) {
+		if !db.IsDbLockedError(err) {
 			logger.Debugf("DBERR: containersGet: error %q", err)
 			return SmartError(err)
 		}
@@ -31,7 +32,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 }
 
 func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
-	result, err := dbContainersList(d.db, cTypeRegular)
+	result, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 040d4ba96..64249162a 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -11,6 +11,7 @@ import (
 	"github.com/dustinkirkland/golang-petname"
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -30,7 +31,7 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 		if req.Source.Server != "" {
 			hash = req.Source.Alias
 		} else {
-			_, alias, err := dbImageAliasGet(d.db, req.Source.Alias, true)
+			_, alias, err := db.ImageAliasGet(d.db, req.Source.Alias, true)
 			if err != nil {
 				return SmartError(err)
 			}
@@ -42,7 +43,7 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 			return BadRequest(fmt.Errorf("Property match is only supported for local images"))
 		}
 
-		hashes, err := dbImagesGet(d.db, false)
+		hashes, err := db.ImagesGet(d.db, false)
 		if err != nil {
 			return SmartError(err)
 		}
@@ -50,7 +51,7 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 		var image *api.Image
 
 		for _, imageHash := range hashes {
-			_, img, err := dbImageGet(d.db, imageHash, false, true)
+			_, img, err := db.ImageGet(d.db, imageHash, false, true)
 			if err != nil {
 				continue
 			}
@@ -84,9 +85,9 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 	}
 
 	run := func(op *operation) error {
-		args := containerArgs{
+		args := db.ContainerArgs{
 			Config:    req.Config,
-			Ctype:     cTypeRegular,
+			Ctype:     db.CTypeRegular,
 			Devices:   req.Devices,
 			Ephemeral: req.Ephemeral,
 			Name:      req.Name,
@@ -102,7 +103,7 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 				return err
 			}
 		} else {
-			_, info, err = dbImageGet(d.db, hash, false, false)
+			_, info, err = db.ImageGet(d.db, hash, false, false)
 			if err != nil {
 				return err
 			}
@@ -129,9 +130,9 @@ func createFromImage(d *Daemon, req *api.ContainersPost) Response {
 }
 
 func createFromNone(d *Daemon, req *api.ContainersPost) Response {
-	args := containerArgs{
+	args := db.ContainerArgs{
 		Config:    req.Config,
-		Ctype:     cTypeRegular,
+		Ctype:     db.CTypeRegular,
 		Devices:   req.Devices,
 		Ephemeral: req.Ephemeral,
 		Name:      req.Name,
@@ -177,11 +178,11 @@ func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
 	}
 
 	// Prepare the container creation request
-	args := containerArgs{
+	args := db.ContainerArgs{
 		Architecture: architecture,
 		BaseImage:    req.Source.BaseImage,
 		Config:       req.Config,
-		Ctype:        cTypeRegular,
+		Ctype:        db.CTypeRegular,
 		Devices:      req.Devices,
 		Ephemeral:    req.Ephemeral,
 		Name:         req.Name,
@@ -201,7 +202,7 @@ func createFromMigration(d *Daemon, req *api.ContainersPost) Response {
 	 * point and just negotiate it over the migration control
 	 * socket. Anyway, it'll happen later :)
 	 */
-	_, _, err = dbImageGet(d.db, req.Source.BaseImage, false, true)
+	_, _, err = db.ImageGet(d.db, req.Source.BaseImage, false, true)
 	if err == nil && d.Storage.MigrationType() == MigrationFSType_RSYNC {
 		c, err = containerCreateFromImage(d, args, req.Source.BaseImage)
 		if err != nil {
@@ -314,11 +315,11 @@ func createFromCopy(d *Daemon, req *api.ContainersPost) Response {
 		req.Profiles = source.Profiles()
 	}
 
-	args := containerArgs{
+	args := db.ContainerArgs{
 		Architecture: source.Architecture(),
 		BaseImage:    req.Source.BaseImage,
 		Config:       req.Config,
-		Ctype:        cTypeRegular,
+		Ctype:        db.CTypeRegular,
 		Devices:      req.Devices,
 		Ephemeral:    req.Ephemeral,
 		Name:         req.Name,
@@ -354,7 +355,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	}
 
 	if req.Name == "" {
-		cs, err := dbContainersList(d.db, cTypeRegular)
+		cs, err := db.ContainersList(d.db, db.CTypeRegular)
 		if err != nil {
 			return SmartError(err)
 		}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index b93c69038..6338d90b2 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -30,6 +30,7 @@ import (
 	"gopkg.in/tomb.v2"
 
 	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
@@ -1068,7 +1069,7 @@ func (d *Daemon) CheckTrustState(cert x509.Certificate) bool {
 }
 
 func (d *Daemon) numRunningContainers() (int, error) {
-	results, err := dbContainersList(d.db, cTypeRegular)
+	results, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return 0, err
 	}
@@ -1177,7 +1178,7 @@ func (d *Daemon) ExpireLogs() error {
 		return err
 	}
 
-	result, err := dbContainersList(d.db, cTypeRegular)
+	result, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return err
 	}
@@ -1326,19 +1327,19 @@ func initializeDbObject(d *Daemon, path string) error {
 	var err error
 
 	// Open the database. If the file doesn't exist it is created.
-	d.db, err = openDb(path)
+	d.db, err = db.OpenDb(path)
 	if err != nil {
 		return err
 	}
 
 	// Create the DB if it doesn't exist.
-	err = createDb(d.db)
+	err = db.CreateDb(d.db, patchesGetNames())
 	if err != nil {
 		return fmt.Errorf("Error creating database: %s", err)
 	}
 
 	// Detect LXD downgrades
-	if dbGetSchema(d.db) > dbGetLatestSchema() {
+	if db.GetSchema(d.db) > db.GetLatestSchema() {
 		return fmt.Errorf("The database schema is more recent than LXD's schema.")
 	}
 
@@ -1349,7 +1350,7 @@ func initializeDbObject(d *Daemon, path string) error {
 	// patches mechanism was introduced in lxd/patches.go. The
 	// rest of non-db patches will be applied separately via
 	// patchesApplyAll. See PR #3322 for more details.
-	err = dbUpdatesApplyAll(d.db, true, func(version int) error {
+	err = db.UpdatesApplyAll(d.db, true, func(version int) error {
 		if legacyPatch, ok := legacyPatches[version]; ok {
 			return legacyPatch(d)
 		}
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 99249dd2f..14ae6cf1a 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -14,6 +14,7 @@ import (
 	"golang.org/x/crypto/scrypt"
 	log "gopkg.in/inconshreveable/log15.v2"
 
+	dbapi "github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -127,7 +128,7 @@ func (k *daemonConfigKey) Set(d *Daemon, value string) error {
 	k.currentValue = value
 	daemonConfigLock.Unlock()
 
-	err = dbConfigValueSet(d.db, name, value)
+	err = dbapi.ConfigValueSet(d.db, name, value)
 	if err != nil {
 		return err
 	}
@@ -196,7 +197,7 @@ func daemonConfigInit(db *sql.DB) error {
 	}
 
 	// Load the values from the DB
-	dbValues, err := dbConfigValuesGet(db)
+	dbValues, err := dbapi.ConfigValuesGet(db)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index 69feec333..f8dc4ac24 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -15,6 +15,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/cancel"
@@ -224,7 +225,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	}
 
 	// Check if the image already exists (partial hash match)
-	_, imgInfo, err := dbImageGet(d.db, fp, false, true)
+	_, imgInfo, err := db.ImageGet(d.db, fp, false, true)
 	if err == nil {
 		logger.Debug("Image already exists in the db", log.Ctx{"image": fp})
 		info = imgInfo
@@ -246,7 +247,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		<-waitChannel
 
 		// Grab the database entry
-		_, imgInfo, err := dbImageGet(d.db, fp, false, true)
+		_, imgInfo, err := db.ImageGet(d.db, fp, false, true)
 		if err != nil {
 			// Other download failed, lets try again
 			logger.Error("Other image download didn't succeed", log.Ctx{"image": fp})
@@ -478,7 +479,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	}
 
 	// Create the database entry
-	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+	err = db.ImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
 		return nil, err
 	}
@@ -504,12 +505,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	// Record the image source
 	if alias != fp {
-		id, _, err := dbImageGet(d.db, fp, false, true)
+		id, _, err := db.ImageGet(d.db, fp, false, true)
 		if err != nil {
 			return nil, err
 		}
 
-		err = dbImageSourceInsert(d.db, id, server, protocol, certificate, alias)
+		err = db.ImageSourceInsert(d.db, id, server, protocol, certificate, alias)
 		if err != nil {
 			return nil, err
 		}
@@ -517,7 +518,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 
 	// Mark the image as "cached" if downloading for a container
 	if forContainer {
-		err := dbImageLastAccessInit(d.db, fp)
+		err := db.ImageLastAccessInit(d.db, fp)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/db_certificates.go b/lxd/db/certificates.go
similarity index 66%
rename from lxd/db_certificates.go
rename to lxd/db/certificates.go
index 63fd0e52c..a272e3f1c 100644
--- a/lxd/db_certificates.go
+++ b/lxd/db/certificates.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -6,9 +6,9 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 )
 
-// dbCertInfo is here to pass the certificates content
+// CertInfo is here to pass the certificates content
 // from the database around
-type dbCertInfo struct {
+type CertInfo struct {
 	ID          int
 	Fingerprint string
 	Type        int
@@ -16,8 +16,8 @@ type dbCertInfo struct {
 	Certificate string
 }
 
-// dbCertsGet returns all certificates from the DB as CertBaseInfo objects.
-func dbCertsGet(db *sql.DB) (certs []*dbCertInfo, err error) {
+// CertsGet returns all certificates from the DB as CertBaseInfo objects.
+func CertsGet(db *sql.DB) (certs []*CertInfo, err error) {
 	rows, err := dbQuery(
 		db,
 		"SELECT id, fingerprint, type, name, certificate FROM certificates",
@@ -29,7 +29,7 @@ func dbCertsGet(db *sql.DB) (certs []*dbCertInfo, err error) {
 	defer rows.Close()
 
 	for rows.Next() {
-		cert := new(dbCertInfo)
+		cert := new(CertInfo)
 		rows.Scan(
 			&cert.ID,
 			&cert.Fingerprint,
@@ -43,13 +43,13 @@ func dbCertsGet(db *sql.DB) (certs []*dbCertInfo, err error) {
 	return certs, nil
 }
 
-// dbCertGet gets an CertBaseInfo object from the database.
+// CertGet gets an CertBaseInfo object from the database.
 // The argument fingerprint will be queried with a LIKE query, means you can
 // pass a shortform and will get the full fingerprint.
 // There can never be more than one image with a given fingerprint, as it is
 // enforced by a UNIQUE constraint in the schema.
-func dbCertGet(db *sql.DB, fingerprint string) (cert *dbCertInfo, err error) {
-	cert = new(dbCertInfo)
+func CertGet(db *sql.DB, fingerprint string) (cert *CertInfo, err error) {
+	cert = new(CertInfo)
 
 	inargs := []interface{}{fingerprint + "%"}
 	outfmt := []interface{}{
@@ -74,10 +74,10 @@ func dbCertGet(db *sql.DB, fingerprint string) (cert *dbCertInfo, err error) {
 	return cert, err
 }
 
-// dbCertSave stores a CertBaseInfo object in the db,
-// it will ignore the ID field from the dbCertInfo.
-func dbCertSave(db *sql.DB, cert *dbCertInfo) error {
-	tx, err := dbBegin(db)
+// CertSave stores a CertBaseInfo object in the db,
+// it will ignore the ID field from the CertInfo.
+func CertSave(db *sql.DB, cert *CertInfo) error {
+	tx, err := Begin(db)
 	if err != nil {
 		return err
 	}
@@ -105,12 +105,12 @@ func dbCertSave(db *sql.DB, cert *dbCertInfo) error {
 		return err
 	}
 
-	return txCommit(tx)
+	return TxCommit(tx)
 }
 
-// dbCertDelete deletes a certificate from the db.
-func dbCertDelete(db *sql.DB, fingerprint string) error {
-	_, err := dbExec(db, "DELETE FROM certificates WHERE fingerprint=?", fingerprint)
+// CertDelete deletes a certificate from the db.
+func CertDelete(db *sql.DB, fingerprint string) error {
+	_, err := Exec(db, "DELETE FROM certificates WHERE fingerprint=?", fingerprint)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/db_config.go b/lxd/db/config.go
similarity index 81%
rename from lxd/db_config.go
rename to lxd/db/config.go
index d61395e60..f2798be57 100644
--- a/lxd/db_config.go
+++ b/lxd/db/config.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -6,7 +6,7 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 )
 
-func dbConfigValuesGet(db *sql.DB) (map[string]string, error) {
+func ConfigValuesGet(db *sql.DB) (map[string]string, error) {
 	q := "SELECT key, value FROM config"
 	rows, err := dbQuery(db, q)
 	if err != nil {
@@ -25,8 +25,8 @@ func dbConfigValuesGet(db *sql.DB) (map[string]string, error) {
 	return results, nil
 }
 
-func dbConfigValueSet(db *sql.DB, key string, value string) error {
-	tx, err := dbBegin(db)
+func ConfigValueSet(db *sql.DB, key string, value string) error {
+	tx, err := Begin(db)
 	if err != nil {
 		return err
 	}
@@ -52,7 +52,7 @@ func dbConfigValueSet(db *sql.DB, key string, value string) error {
 		}
 	}
 
-	err = txCommit(tx)
+	err = TxCommit(tx)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/db_containers.go b/lxd/db/containers.go
similarity index 71%
rename from lxd/db_containers.go
rename to lxd/db/containers.go
index 749f53dda..e2a3cfc97 100644
--- a/lxd/db_containers.go
+++ b/lxd/db/containers.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -12,20 +12,39 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-type containerType int
+// ContainerArgs is a value object holding all db-related details about a
+// container.
+type ContainerArgs struct {
+	// Don't set manually
+	Id int
+
+	Architecture int
+	BaseImage    string
+	Config       map[string]string
+	CreationDate time.Time
+	Ctype        ContainerType
+	Devices      types.Devices
+	Ephemeral    bool
+	Name         string
+	Profiles     []string
+	Stateful     bool
+}
+
+// ContainerType encodes the type of container (either regular or snapshot).
+type ContainerType int
 
 const (
-	cTypeRegular  containerType = 0
-	cTypeSnapshot containerType = 1
+	CTypeRegular  ContainerType = 0
+	CTypeSnapshot ContainerType = 1
 )
 
-func dbContainerRemove(db *sql.DB, name string) error {
-	id, err := dbContainerId(db, name)
+func ContainerRemove(db *sql.DB, name string) error {
+	id, err := ContainerId(db, name)
 	if err != nil {
 		return err
 	}
 
-	_, err = dbExec(db, "DELETE FROM containers WHERE id=?", id)
+	_, err = Exec(db, "DELETE FROM containers WHERE id=?", id)
 	if err != nil {
 		return err
 	}
@@ -33,7 +52,7 @@ func dbContainerRemove(db *sql.DB, name string) error {
 	return nil
 }
 
-func dbContainerName(db *sql.DB, id int) (string, error) {
+func ContainerName(db *sql.DB, id int) (string, error) {
 	q := "SELECT name FROM containers WHERE id=?"
 	name := ""
 	arg1 := []interface{}{id}
@@ -42,7 +61,7 @@ func dbContainerName(db *sql.DB, id int) (string, error) {
 	return name, err
 }
 
-func dbContainerId(db *sql.DB, name string) (int, error) {
+func ContainerId(db *sql.DB, name string) (int, error) {
 	q := "SELECT id FROM containers WHERE name=?"
 	id := -1
 	arg1 := []interface{}{name}
@@ -51,8 +70,8 @@ func dbContainerId(db *sql.DB, name string) (int, error) {
 	return id, err
 }
 
-func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
-	args := containerArgs{}
+func ContainerGet(db *sql.DB, name string) (ContainerArgs, error) {
+	args := ContainerArgs{}
 	args.Name = name
 
 	ephemInt := -1
@@ -77,13 +96,13 @@ func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
 		args.Stateful = true
 	}
 
-	config, err := dbContainerConfig(db, args.Id)
+	config, err := ContainerConfig(db, args.Id)
 	if err != nil {
 		return args, err
 	}
 	args.Config = config
 
-	profiles, err := dbContainerProfiles(db, args.Id)
+	profiles, err := ContainerProfiles(db, args.Id)
 	if err != nil {
 		return args, err
 	}
@@ -91,7 +110,7 @@ func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
 
 	/* get container_devices */
 	args.Devices = types.Devices{}
-	newdevs, err := dbDevices(db, name, false)
+	newdevs, err := Devices(db, name, false)
 	if err != nil {
 		return args, err
 	}
@@ -103,13 +122,13 @@ func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
 	return args, nil
 }
 
-func dbContainerCreate(db *sql.DB, args containerArgs) (int, error) {
-	id, err := dbContainerId(db, args.Name)
+func ContainerCreate(db *sql.DB, args ContainerArgs) (int, error) {
+	id, err := ContainerId(db, args.Name)
 	if err == nil {
 		return 0, DbErrAlreadyDefined
 	}
 
-	tx, err := dbBegin(db)
+	tx, err := Begin(db)
 	if err != nil {
 		return 0, err
 	}
@@ -146,25 +165,25 @@ func dbContainerCreate(db *sql.DB, args containerArgs) (int, error) {
 	}
 	// TODO: is this really int64? we should fix it everywhere if so
 	id = int(id64)
-	if err := dbContainerConfigInsert(tx, id, args.Config); err != nil {
+	if err := ContainerConfigInsert(tx, id, args.Config); err != nil {
 		tx.Rollback()
 		return 0, err
 	}
 
-	if err := dbContainerProfilesInsert(tx, id, args.Profiles); err != nil {
+	if err := ContainerProfilesInsert(tx, id, args.Profiles); err != nil {
 		tx.Rollback()
 		return 0, err
 	}
 
-	if err := dbDevicesAdd(tx, "container", int64(id), args.Devices); err != nil {
+	if err := DevicesAdd(tx, "container", int64(id), args.Devices); err != nil {
 		tx.Rollback()
 		return 0, err
 	}
 
-	return id, txCommit(tx)
+	return id, TxCommit(tx)
 }
 
-func dbContainerConfigClear(tx *sql.Tx, id int) error {
+func ContainerConfigClear(tx *sql.Tx, id int) error {
 	_, err := tx.Exec("DELETE FROM containers_config WHERE container_id=?", id)
 	if err != nil {
 		return err
@@ -185,7 +204,7 @@ func dbContainerConfigClear(tx *sql.Tx, id int) error {
 	return err
 }
 
-func dbContainerConfigInsert(tx *sql.Tx, id int, config map[string]string) error {
+func ContainerConfigInsert(tx *sql.Tx, id int, config map[string]string) error {
 	str := "INSERT INTO containers_config (container_id, key, value) values (?, ?, ?)"
 	stmt, err := tx.Prepare(str)
 	if err != nil {
@@ -205,7 +224,7 @@ func dbContainerConfigInsert(tx *sql.Tx, id int, config map[string]string) error
 	return nil
 }
 
-func dbContainerConfigGet(db *sql.DB, id int, key string) (string, error) {
+func ContainerConfigGet(db *sql.DB, id int, key string) (string, error) {
 	q := "SELECT value FROM containers_config WHERE container_id=? AND key=?"
 	value := ""
 	arg1 := []interface{}{id, key}
@@ -214,22 +233,22 @@ func dbContainerConfigGet(db *sql.DB, id int, key string) (string, error) {
 	return value, err
 }
 
-func dbContainerConfigRemove(db *sql.DB, id int, name string) error {
-	_, err := dbExec(db, "DELETE FROM containers_config WHERE key=? AND container_id=?", name, id)
+func ContainerConfigRemove(db *sql.DB, id int, name string) error {
+	_, err := Exec(db, "DELETE FROM containers_config WHERE key=? AND container_id=?", name, id)
 	return err
 }
 
-func dbContainerSetStateful(db *sql.DB, id int, stateful bool) error {
+func ContainerSetStateful(db *sql.DB, id int, stateful bool) error {
 	statefulInt := 0
 	if stateful {
 		statefulInt = 1
 	}
 
-	_, err := dbExec(db, "UPDATE containers SET stateful=? WHERE id=?", statefulInt, id)
+	_, err := Exec(db, "UPDATE containers SET stateful=? WHERE id=?", statefulInt, id)
 	return err
 }
 
-func dbContainerProfilesInsert(tx *sql.Tx, id int, profiles []string) error {
+func ContainerProfilesInsert(tx *sql.Tx, id int, profiles []string) error {
 	applyOrder := 1
 	str := `INSERT INTO containers_profiles (container_id, profile_id, apply_order) VALUES
 		(?, (SELECT id FROM profiles WHERE name=?), ?);`
@@ -252,7 +271,7 @@ func dbContainerProfilesInsert(tx *sql.Tx, id int, profiles []string) error {
 }
 
 // Get a list of profiles for a given container id.
-func dbContainerProfiles(db *sql.DB, containerId int) ([]string, error) {
+func ContainerProfiles(db *sql.DB, containerId int) ([]string, error) {
 	var name string
 	var profiles []string
 
@@ -264,7 +283,7 @@ func dbContainerProfiles(db *sql.DB, containerId int) ([]string, error) {
 	inargs := []interface{}{containerId}
 	outfmt := []interface{}{name}
 
-	results, err := dbQueryScan(db, query, inargs, outfmt)
+	results, err := QueryScan(db, query, inargs, outfmt)
 	if err != nil {
 		return nil, err
 	}
@@ -278,8 +297,8 @@ func dbContainerProfiles(db *sql.DB, containerId int) ([]string, error) {
 	return profiles, nil
 }
 
-// dbContainerConfig gets the container configuration map from the DB
-func dbContainerConfig(db *sql.DB, containerId int) (map[string]string, error) {
+// ContainerConfig gets the container configuration map from the DB
+func ContainerConfig(db *sql.DB, containerId int) (map[string]string, error) {
 	var key, value string
 	q := `SELECT key, value FROM containers_config WHERE container_id=?`
 
@@ -287,7 +306,7 @@ func dbContainerConfig(db *sql.DB, containerId int) (map[string]string, error) {
 	outfmt := []interface{}{key, value}
 
 	// Results is already a slice here, not db Rows anymore.
-	results, err := dbQueryScan(db, q, inargs, outfmt)
+	results, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return nil, err //SmartError will wrap this and make "not found" errors pretty
 	}
@@ -304,12 +323,12 @@ func dbContainerConfig(db *sql.DB, containerId int) (map[string]string, error) {
 	return config, nil
 }
 
-func dbContainersList(db *sql.DB, cType containerType) ([]string, error) {
+func ContainersList(db *sql.DB, cType ContainerType) ([]string, error) {
 	q := fmt.Sprintf("SELECT name FROM containers WHERE type=? ORDER BY name")
 	inargs := []interface{}{cType}
 	var container string
 	outfmt := []interface{}{container}
-	result, err := dbQueryScan(db, q, inargs, outfmt)
+	result, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return nil, err
 	}
@@ -322,8 +341,8 @@ func dbContainersList(db *sql.DB, cType containerType) ([]string, error) {
 	return ret, nil
 }
 
-func dbContainerSetState(db *sql.DB, id int, state string) error {
-	tx, err := dbBegin(db)
+func ContainerSetState(db *sql.DB, id int, state string) error {
+	tx, err := Begin(db)
 	if err != nil {
 		return err
 	}
@@ -356,11 +375,11 @@ func dbContainerSetState(db *sql.DB, id int, state string) error {
 		return err
 	}
 
-	return txCommit(tx)
+	return TxCommit(tx)
 }
 
-func dbContainerRename(db *sql.DB, oldName string, newName string) error {
-	tx, err := dbBegin(db)
+func ContainerRename(db *sql.DB, oldName string, newName string) error {
+	tx, err := Begin(db)
 	if err != nil {
 		return err
 	}
@@ -384,10 +403,10 @@ func dbContainerRename(db *sql.DB, oldName string, newName string) error {
 		return err
 	}
 
-	return txCommit(tx)
+	return TxCommit(tx)
 }
 
-func dbContainerUpdate(tx *sql.Tx, id int, architecture int, ephemeral bool) error {
+func ContainerUpdate(tx *sql.Tx, id int, architecture int, ephemeral bool) error {
 	str := fmt.Sprintf("UPDATE containers SET architecture=?, ephemeral=? WHERE id=?")
 	stmt, err := tx.Prepare(str)
 	if err != nil {
@@ -407,15 +426,15 @@ func dbContainerUpdate(tx *sql.Tx, id int, architecture int, ephemeral bool) err
 	return nil
 }
 
-func dbContainerGetSnapshots(db *sql.DB, name string) ([]string, error) {
+func ContainerGetSnapshots(db *sql.DB, name string) ([]string, error) {
 	result := []string{}
 
 	regexp := name + shared.SnapshotDelimiter
 	length := len(regexp)
 	q := "SELECT name FROM containers WHERE type=? AND SUBSTR(name,1,?)=?"
-	inargs := []interface{}{cTypeSnapshot, length, regexp}
+	inargs := []interface{}{CTypeSnapshot, length, regexp}
 	outfmt := []interface{}{name}
-	dbResults, err := dbQueryScan(db, q, inargs, outfmt)
+	dbResults, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return result, err
 	}
diff --git a/lxd/db.go b/lxd/db/db.go
similarity index 92%
rename from lxd/db.go
rename to lxd/db/db.go
index c0a5d3c57..3ba85661b 100644
--- a/lxd/db.go
+++ b/lxd/db/db.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -176,8 +176,8 @@ func init() {
 	sql.Register("sqlite3_with_fk", &sqlite3.SQLiteDriver{ConnectHook: enableForeignKeys})
 }
 
-// Open the database with the correct parameters for LXD.
-func openDb(path string) (*sql.DB, error) {
+// OpenDb opens the database with the correct parameters for LXD.
+func OpenDb(path string) (*sql.DB, error) {
 	timeout := 5 // TODO - make this command-line configurable?
 
 	// These are used to tune the transaction BEGIN behavior instead of using the
@@ -189,8 +189,8 @@ func openDb(path string) (*sql.DB, error) {
 }
 
 // Create the initial (current) schema for a given SQLite DB connection.
-func createDb(db *sql.DB) (err error) {
-	latestVersion := dbGetSchema(db)
+func CreateDb(db *sql.DB, patchNames []string) (err error) {
+	latestVersion := GetSchema(db)
 
 	if latestVersion != 0 {
 		return nil
@@ -203,17 +203,17 @@ func createDb(db *sql.DB) (err error) {
 
 	// There isn't an entry for schema version, let's put it in.
 	insertStmt := `INSERT INTO schema (version, updated_at) values (?, strftime("%s"));`
-	_, err = db.Exec(insertStmt, dbGetLatestSchema())
+	_, err = db.Exec(insertStmt, GetLatestSchema())
 	if err != nil {
 		return err
 	}
 
 	// Mark all existing patches as applied
-	for _, p := range patches {
-		dbPatchesMarkApplied(db, p.name)
+	for _, patchName := range patchNames {
+		PatchesMarkApplied(db, patchName)
 	}
 
-	err = dbProfileCreateDefault(db)
+	err = ProfileCreateDefault(db)
 	if err != nil {
 		return err
 	}
@@ -221,7 +221,7 @@ func createDb(db *sql.DB) (err error) {
 	return dbProfileCreateDocker(db)
 }
 
-func dbGetSchema(db *sql.DB) (v int) {
+func GetSchema(db *sql.DB) (v int) {
 	arg1 := []interface{}{}
 	arg2 := []interface{}{&v}
 	q := "SELECT max(version) FROM schema"
@@ -232,7 +232,7 @@ func dbGetSchema(db *sql.DB) (v int) {
 	return v
 }
 
-func dbGetLatestSchema() int {
+func GetLatestSchema() int {
 	if len(dbUpdates) == 0 {
 		return 0
 	}
@@ -240,7 +240,7 @@ func dbGetLatestSchema() int {
 	return dbUpdates[len(dbUpdates)-1].version
 }
 
-func isDbLockedError(err error) bool {
+func IsDbLockedError(err error) bool {
 	if err == nil {
 		return false
 	}
@@ -263,13 +263,13 @@ func isNoMatchError(err error) bool {
 	return false
 }
 
-func dbBegin(db *sql.DB) (*sql.Tx, error) {
+func Begin(db *sql.DB) (*sql.Tx, error) {
 	for i := 0; i < 1000; i++ {
 		tx, err := db.Begin()
 		if err == nil {
 			return tx, nil
 		}
-		if !isDbLockedError(err) {
+		if !IsDbLockedError(err) {
 			logger.Debugf("DbBegin: error %q", err)
 			return nil, err
 		}
@@ -281,13 +281,13 @@ func dbBegin(db *sql.DB) (*sql.Tx, error) {
 	return nil, fmt.Errorf("DB is locked")
 }
 
-func txCommit(tx *sql.Tx) error {
+func TxCommit(tx *sql.Tx) error {
 	for i := 0; i < 1000; i++ {
 		err := tx.Commit()
 		if err == nil {
 			return nil
 		}
-		if !isDbLockedError(err) {
+		if !IsDbLockedError(err) {
 			logger.Debugf("Txcommit: error %q", err)
 			return err
 		}
@@ -308,7 +308,7 @@ func dbQueryRowScan(db *sql.DB, q string, args []interface{}, outargs []interfac
 		if isNoMatchError(err) {
 			return err
 		}
-		if !isDbLockedError(err) {
+		if !IsDbLockedError(err) {
 			return err
 		}
 		time.Sleep(30 * time.Millisecond)
@@ -325,7 +325,7 @@ func dbQuery(db *sql.DB, q string, args ...interface{}) (*sql.Rows, error) {
 		if err == nil {
 			return result, nil
 		}
-		if !isDbLockedError(err) {
+		if !IsDbLockedError(err) {
 			logger.Debugf("DbQuery: query %q error %q", q, err)
 			return nil, err
 		}
@@ -399,13 +399,13 @@ func doDbQueryScan(db *sql.DB, q string, args []interface{}, outargs []interface
  * The result will be an array (one per output row) of arrays (one per output argument)
  * of interfaces, containing pointers to the actual output arguments.
  */
-func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{}) ([][]interface{}, error) {
+func QueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{}) ([][]interface{}, error) {
 	for i := 0; i < 1000; i++ {
 		result, err := doDbQueryScan(db, q, inargs, outfmt)
 		if err == nil {
 			return result, nil
 		}
-		if !isDbLockedError(err) {
+		if !IsDbLockedError(err) {
 			logger.Debugf("DbQuery: query %q error %q", q, err)
 			return nil, err
 		}
@@ -417,13 +417,13 @@ func dbQueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{
 	return nil, fmt.Errorf("DB is locked")
 }
 
-func dbExec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) {
+func Exec(db *sql.DB, q string, args ...interface{}) (sql.Result, error) {
 	for i := 0; i < 1000; i++ {
 		result, err := db.Exec(q, args...)
 		if err == nil {
 			return result, nil
 		}
-		if !isDbLockedError(err) {
+		if !IsDbLockedError(err) {
 			logger.Debugf("DbExec: query %q error %q", q, err)
 			return nil, err
 		}
diff --git a/lxd/db_test.go b/lxd/db/db_test.go
similarity index 89%
rename from lxd/db_test.go
rename to lxd/db/db_test.go
index eda2a88d8..bac862791 100644
--- a/lxd/db_test.go
+++ b/lxd/db/db_test.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -54,10 +54,10 @@ func (s *dbTestSuite) CreateTestDb() *sql.DB {
 		s.Nil(err)
 	}
 
-	db, err := openDb(":memory:")
+	db, err := OpenDb(":memory:")
 	s.Nil(err)
-	s.Nil(createDb(db))
-	s.Nil(dbUpdatesApplyAll(db, false, nil))
+	s.Nil(CreateDb(db, []string{}))
+	s.Nil(UpdatesApplyAll(db, false, nil))
 	return db
 
 }
@@ -161,12 +161,12 @@ func (s *dbTestSuite) Test_deleting_an_image_cascades_on_related_tables() {
 }
 
 func (s *dbTestSuite) Test_initializing_db_is_idempotent() {
-	// This calls "createDb" once already.
+	// This calls "CreateDb" once already.
 	db := s.CreateTestDb()
 	defer db.Close()
 
 	// Let's call it a second time.
-	s.Nil(createDb(db))
+	s.Nil(CreateDb(db, []string{}))
 }
 
 func (s *dbTestSuite) Test_get_schema_returns_0_on_uninitialized_db() {
@@ -175,11 +175,11 @@ func (s *dbTestSuite) Test_get_schema_returns_0_on_uninitialized_db() {
 
 	db, err = sql.Open("sqlite3", ":memory:")
 	s.Nil(err)
-	result := dbGetSchema(db)
+	result := GetSchema(db)
 	s.Equal(0, result, "getSchema should return 0 on uninitialized db!")
 }
 
-func (s *dbTestSuite) Test_running_dbUpdateFromV6_adds_on_delete_cascade() {
+func (s *dbTestSuite) Test_running_UpdateFromV6_adds_on_delete_cascade() {
 	// Upgrading the database schema with updateFromV6 adds ON DELETE CASCADE
 	// to sqlite tables that require it, and conserve the data.
 
@@ -313,11 +313,11 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 
 	// The "foreign key" on containers_config now points to nothing.
 	// Let's run the schema upgrades.
-	err = dbUpdatesApplyAll(db, false, nil)
+	err = UpdatesApplyAll(db, false, nil)
 	s.Nil(err)
 
-	result := dbGetSchema(db)
-	s.Equal(result, dbGetLatestSchema(), "The schema is not at the latest version after update!")
+	result := GetSchema(db)
+	s.Equal(result, GetLatestSchema(), "The schema is not at the latest version after update!")
 
 	// Make sure there are 0 containers_config entries left.
 	statements = `SELECT count(*) FROM containers_config;`
@@ -326,11 +326,11 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	s.Equal(count, 0, "updateDb did not delete orphaned child entries after adding ON DELETE CASCADE!")
 }
 
-func (s *dbTestSuite) Test_dbImageGet_finds_image_for_fingerprint() {
+func (s *dbTestSuite) Test_ImageGet_finds_image_for_fingerprint() {
 	var err error
 	var result *api.Image
 
-	_, result, err = dbImageGet(s.db, "fingerprint", false, false)
+	_, result, err = ImageGet(s.db, "fingerprint", false, false)
 	s.Nil(err)
 	s.NotNil(result)
 	s.Equal(result.Filename, "filename")
@@ -339,51 +339,51 @@ func (s *dbTestSuite) Test_dbImageGet_finds_image_for_fingerprint() {
 	s.Equal(result.UploadedAt.UTC(), time.Unix(1431547176, 0).UTC())
 }
 
-func (s *dbTestSuite) Test_dbImageGet_for_missing_fingerprint() {
+func (s *dbTestSuite) Test_ImageGet_for_missing_fingerprint() {
 	var err error
 
-	_, _, err = dbImageGet(s.db, "unknown", false, false)
+	_, _, err = ImageGet(s.db, "unknown", false, false)
 	s.Equal(err, sql.ErrNoRows)
 }
 
-func (s *dbTestSuite) Test_dbImageExists_true() {
+func (s *dbTestSuite) Test_ImageExists_true() {
 	var err error
 
-	exists, err := dbImageExists(s.db, "fingerprint")
+	exists, err := ImageExists(s.db, "fingerprint")
 	s.Nil(err)
 	s.True(exists)
 }
 
-func (s *dbTestSuite) Test_dbImageExists_false() {
+func (s *dbTestSuite) Test_ImageExists_false() {
 	var err error
 
-	exists, err := dbImageExists(s.db, "foobar")
+	exists, err := ImageExists(s.db, "foobar")
 	s.Nil(err)
 	s.False(exists)
 }
 
-func (s *dbTestSuite) Test_dbImageAliasGet_alias_exists() {
+func (s *dbTestSuite) Test_ImageAliasGet_alias_exists() {
 	var err error
 
-	_, alias, err := dbImageAliasGet(s.db, "somealias", true)
+	_, alias, err := ImageAliasGet(s.db, "somealias", true)
 	s.Nil(err)
 	s.Equal(alias.Target, "fingerprint")
 }
 
-func (s *dbTestSuite) Test_dbImageAliasGet_alias_does_not_exists() {
+func (s *dbTestSuite) Test_ImageAliasGet_alias_does_not_exists() {
 	var err error
 
-	_, _, err = dbImageAliasGet(s.db, "whatever", true)
+	_, _, err = ImageAliasGet(s.db, "whatever", true)
 	s.Equal(err, NoSuchObjectError)
 }
 
-func (s *dbTestSuite) Test_dbImageAliasAdd() {
+func (s *dbTestSuite) Test_ImageAliasAdd() {
 	var err error
 
-	err = dbImageAliasAdd(s.db, "Chaosphere", 1, "Someone will like the name")
+	err = ImageAliasAdd(s.db, "Chaosphere", 1, "Someone will like the name")
 	s.Nil(err)
 
-	_, alias, err := dbImageAliasGet(s.db, "Chaosphere", true)
+	_, alias, err := ImageAliasGet(s.db, "Chaosphere", true)
 	s.Nil(err)
 	s.Equal(alias.Target, "fingerprint")
 }
@@ -396,7 +396,7 @@ func (s *dbTestSuite) Test_dbContainerConfig() {
 	_, err = s.db.Exec("INSERT INTO containers_config (container_id, key, value) VALUES (1, 'something', 'something else');")
 	s.Nil(err)
 
-	result, err = dbContainerConfig(s.db, 1)
+	result, err = ContainerConfig(s.db, 1)
 	s.Nil(err)
 
 	expected = map[string]string{"thekey": "thevalue", "something": "something else"}
@@ -415,7 +415,7 @@ func (s *dbTestSuite) Test_dbProfileConfig() {
 	_, err = s.db.Exec("INSERT INTO profiles_config (profile_id, key, value) VALUES (3, 'something', 'something else');")
 	s.Nil(err)
 
-	result, err = dbProfileConfig(s.db, "theprofile")
+	result, err = ProfileConfig(s.db, "theprofile")
 	s.Nil(err)
 
 	expected = map[string]string{"thekey": "thevalue", "something": "something else"}
@@ -426,13 +426,13 @@ func (s *dbTestSuite) Test_dbProfileConfig() {
 	}
 }
 
-func (s *dbTestSuite) Test_dbContainerProfiles() {
+func (s *dbTestSuite) Test_ContainerProfiles() {
 	var err error
 	var result []string
 	var expected []string
 
 	expected = []string{"theprofile"}
-	result, err = dbContainerProfiles(s.db, 1)
+	result, err = ContainerProfiles(s.db, 1)
 	s.Nil(err)
 
 	for i := range expected {
@@ -447,7 +447,7 @@ func (s *dbTestSuite) Test_dbDevices_profiles() {
 	var subresult types.Device
 	var expected types.Device
 
-	result, err = dbDevices(s.db, "theprofile", true)
+	result, err = Devices(s.db, "theprofile", true)
 	s.Nil(err)
 
 	expected = types.Device{"type": "nic", "devicekey": "devicevalue"}
@@ -465,7 +465,7 @@ func (s *dbTestSuite) Test_dbDevices_containers() {
 	var subresult types.Device
 	var expected types.Device
 
-	result, err = dbDevices(s.db, "thename", false)
+	result, err = Devices(s.db, "thename", false)
 	s.Nil(err)
 
 	expected = types.Device{"type": "nic", "configkey": "configvalue"}
diff --git a/lxd/db_devices.go b/lxd/db/devices.go
similarity index 91%
rename from lxd/db_devices.go
rename to lxd/db/devices.go
index 71164feb8..646f75a3e 100644
--- a/lxd/db_devices.go
+++ b/lxd/db/devices.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -43,7 +43,7 @@ func dbDeviceTypeToInt(t string) (int, error) {
 	}
 }
 
-func dbDevicesAdd(tx *sql.Tx, w string, cID int64, devices types.Devices) error {
+func DevicesAdd(tx *sql.Tx, w string, cID int64, devices types.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)
@@ -107,7 +107,7 @@ func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (types.Device, error) {
 		query = `SELECT key, value FROM containers_devices_config WHERE container_device_id=?`
 	}
 
-	results, err := dbQueryScan(db, query, inargs, outfmt)
+	results, err := QueryScan(db, query, inargs, outfmt)
 
 	if err != nil {
 		return newdev, err
@@ -122,7 +122,7 @@ func dbDeviceConfig(db *sql.DB, id int, isprofile bool) (types.Device, error) {
 	return newdev, nil
 }
 
-func dbDevices(db *sql.DB, qName string, isprofile bool) (types.Devices, error) {
+func Devices(db *sql.DB, qName string, isprofile bool) (types.Devices, error) {
 	var q string
 	if isprofile {
 		q = `SELECT profiles_devices.id, profiles_devices.name, profiles_devices.type
@@ -139,7 +139,7 @@ func dbDevices(db *sql.DB, qName string, isprofile bool) (types.Devices, error)
 	var name, stype string
 	inargs := []interface{}{qName}
 	outfmt := []interface{}{id, name, dtype}
-	results, err := dbQueryScan(db, q, inargs, outfmt)
+	results, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/db_images.go b/lxd/db/images.go
similarity index 76%
rename from lxd/db_images.go
rename to lxd/db/images.go
index 3a7a01feb..81fb346f5 100644
--- a/lxd/db_images.go
+++ b/lxd/db/images.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -11,13 +11,13 @@ import (
 	"github.com/lxc/lxd/shared/osarch"
 )
 
-var dbImageSourceProtocol = map[int]string{
+var ImageSourceProtocol = map[int]string{
 	0: "lxd",
 	1: "direct",
 	2: "simplestreams",
 }
 
-func dbImagesGet(db *sql.DB, public bool) ([]string, error) {
+func ImagesGet(db *sql.DB, public bool) ([]string, error) {
 	q := "SELECT fingerprint FROM images"
 	if public == true {
 		q = "SELECT fingerprint FROM images WHERE public=1"
@@ -26,7 +26,7 @@ func dbImagesGet(db *sql.DB, public bool) ([]string, error) {
 	var fp string
 	inargs := []interface{}{}
 	outfmt := []interface{}{fp}
-	dbResults, err := dbQueryScan(db, q, inargs, outfmt)
+	dbResults, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return []string{}, err
 	}
@@ -39,13 +39,13 @@ func dbImagesGet(db *sql.DB, public bool) ([]string, error) {
 	return results, nil
 }
 
-func dbImagesGetExpired(db *sql.DB, expiry int64) ([]string, error) {
+func ImagesGetExpired(db *sql.DB, expiry int64) ([]string, error) {
 	q := `SELECT fingerprint FROM images WHERE cached=1 AND creation_date<=strftime('%s', date('now', '-` + fmt.Sprintf("%d", expiry) + ` day'))`
 
 	var fp string
 	inargs := []interface{}{}
 	outfmt := []interface{}{fp}
-	dbResults, err := dbQueryScan(db, q, inargs, outfmt)
+	dbResults, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return []string{}, err
 	}
@@ -58,11 +58,11 @@ func dbImagesGetExpired(db *sql.DB, expiry int64) ([]string, error) {
 	return results, nil
 }
 
-func dbImageSourceInsert(db *sql.DB, imageId int, server string, protocol string, certificate string, alias string) error {
+func ImageSourceInsert(db *sql.DB, imageId int, server string, protocol string, certificate string, alias string) error {
 	stmt := `INSERT INTO images_source (image_id, server, protocol, certificate, alias) values (?, ?, ?, ?, ?)`
 
 	protocolInt := -1
-	for protoInt, protoString := range dbImageSourceProtocol {
+	for protoInt, protoString := range ImageSourceProtocol {
 		if protoString == protocol {
 			protocolInt = protoInt
 		}
@@ -72,11 +72,11 @@ func dbImageSourceInsert(db *sql.DB, imageId int, server string, protocol string
 		return fmt.Errorf("Invalid protocol: %s", protocol)
 	}
 
-	_, err := dbExec(db, stmt, imageId, server, protocolInt, certificate, alias)
+	_, err := Exec(db, stmt, imageId, server, protocolInt, certificate, alias)
 	return err
 }
 
-func dbImageSourceGet(db *sql.DB, imageId int) (int, api.ImageSource, error) {
+func ImageSourceGet(db *sql.DB, imageId int) (int, api.ImageSource, error) {
 	q := `SELECT id, server, protocol, certificate, alias FROM images_source WHERE image_id=?`
 
 	id := 0
@@ -94,7 +94,7 @@ func dbImageSourceGet(db *sql.DB, imageId int) (int, api.ImageSource, error) {
 		return -1, api.ImageSource{}, err
 	}
 
-	protocol, found := dbImageSourceProtocol[protocolInt]
+	protocol, found := ImageSourceProtocol[protocolInt]
 	if !found {
 		return -1, api.ImageSource{}, fmt.Errorf("Invalid protocol: %d", protocolInt)
 	}
@@ -106,7 +106,7 @@ func dbImageSourceGet(db *sql.DB, imageId int) (int, api.ImageSource, error) {
 }
 
 // Whether an image with the given fingerprint exists.
-func dbImageExists(db *sql.DB, fingerprint string) (bool, error) {
+func ImageExists(db *sql.DB, fingerprint string) (bool, error) {
 	var exists bool
 	var err error
 	query := "SELECT COUNT(*) > 0 FROM images WHERE fingerprint=?"
@@ -116,12 +116,12 @@ func dbImageExists(db *sql.DB, fingerprint string) (bool, error) {
 	return exists, err
 }
 
-// dbImageGet gets an Image object from the database.
+// ImageGet gets an Image object from the database.
 // If strictMatching is false, The fingerprint argument will be queried with a LIKE query, means you can
 // pass a shortform and will get the full fingerprint.
 // There can never be more than one image with a given fingerprint, as it is
 // enforced by a UNIQUE constraint in the schema.
-func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool) (int, *api.Image, error) {
+func ImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool) (int, *api.Image, error) {
 	var err error
 	var create, expire, used, upload *time.Time // These hold the db-returned times
 
@@ -203,7 +203,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 	var key, value, name, desc string
 	inargs = []interface{}{id}
 	outfmt = []interface{}{key, value}
-	results, err := dbQueryScan(db, q, inargs, outfmt)
+	results, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return -1, nil, err
 	}
@@ -221,7 +221,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 	q = "SELECT name, description FROM images_aliases WHERE image_id=?"
 	inargs = []interface{}{id}
 	outfmt = []interface{}{name, desc}
-	results, err = dbQueryScan(db, q, inargs, outfmt)
+	results, err = QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return -1, nil, err
 	}
@@ -236,7 +236,7 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 
 	image.Aliases = aliases
 
-	_, source, err := dbImageSourceGet(db, id)
+	_, source, err := ImageSourceGet(db, id)
 	if err == nil {
 		image.UpdateSource = &source
 	}
@@ -244,8 +244,8 @@ func dbImageGet(db *sql.DB, fingerprint string, public bool, strictMatching bool
 	return id, &image, nil
 }
 
-func dbImageDelete(db *sql.DB, id int) error {
-	_, err := dbExec(db, "DELETE FROM images WHERE id=?", id)
+func ImageDelete(db *sql.DB, id int) error {
+	_, err := Exec(db, "DELETE FROM images WHERE id=?", id)
 	if err != nil {
 		return err
 	}
@@ -253,7 +253,7 @@ func dbImageDelete(db *sql.DB, id int) error {
 	return nil
 }
 
-func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, api.ImageAliasesEntry, error) {
+func ImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, api.ImageAliasesEntry, error) {
 	q := `SELECT images_aliases.id, images.fingerprint, images_aliases.description
 			 FROM images_aliases
 			 INNER JOIN images
@@ -285,53 +285,53 @@ func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, api.Im
 	return id, entry, nil
 }
 
-func dbImageAliasRename(db *sql.DB, id int, name string) error {
-	_, err := dbExec(db, "UPDATE images_aliases SET name=? WHERE id=?", name, id)
+func ImageAliasRename(db *sql.DB, id int, name string) error {
+	_, err := Exec(db, "UPDATE images_aliases SET name=? WHERE id=?", name, id)
 	return err
 }
 
-func dbImageAliasDelete(db *sql.DB, name string) error {
-	_, err := dbExec(db, "DELETE FROM images_aliases WHERE name=?", name)
+func ImageAliasDelete(db *sql.DB, name string) error {
+	_, err := Exec(db, "DELETE FROM images_aliases WHERE name=?", name)
 	return err
 }
 
-func dbImageAliasesMove(db *sql.DB, source int, destination int) error {
-	_, err := dbExec(db, "UPDATE images_aliases SET image_id=? WHERE image_id=?", destination, source)
+func ImageAliasesMove(db *sql.DB, source int, destination int) error {
+	_, err := Exec(db, "UPDATE images_aliases SET image_id=? WHERE image_id=?", destination, source)
 	return err
 }
 
 // Insert an alias ento the database.
-func dbImageAliasAdd(db *sql.DB, name string, imageID int, desc string) error {
+func ImageAliasAdd(db *sql.DB, name string, imageID int, desc string) error {
 	stmt := `INSERT INTO images_aliases (name, image_id, description) values (?, ?, ?)`
-	_, err := dbExec(db, stmt, name, imageID, desc)
+	_, err := Exec(db, stmt, name, imageID, desc)
 	return err
 }
 
-func dbImageAliasUpdate(db *sql.DB, id int, imageID int, desc string) error {
+func ImageAliasUpdate(db *sql.DB, id int, imageID int, desc string) error {
 	stmt := `UPDATE images_aliases SET image_id=?, description=? WHERE id=?`
-	_, err := dbExec(db, stmt, imageID, desc, id)
+	_, err := Exec(db, stmt, imageID, desc, id)
 	return err
 }
 
-func dbImageLastAccessUpdate(db *sql.DB, fingerprint string, date time.Time) error {
+func ImageLastAccessUpdate(db *sql.DB, fingerprint string, date time.Time) error {
 	stmt := `UPDATE images SET last_use_date=? WHERE fingerprint=?`
-	_, err := dbExec(db, stmt, date, fingerprint)
+	_, err := Exec(db, stmt, date, fingerprint)
 	return err
 }
 
-func dbImageLastAccessInit(db *sql.DB, fingerprint string) error {
+func ImageLastAccessInit(db *sql.DB, fingerprint string) error {
 	stmt := `UPDATE images SET cached=1, last_use_date=strftime("%s") WHERE fingerprint=?`
-	_, err := dbExec(db, stmt, fingerprint)
+	_, err := Exec(db, stmt, fingerprint)
 	return err
 }
 
-func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
+func ImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
 	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
 	}
 
-	tx, err := dbBegin(db)
+	tx, err := Begin(db)
 	if err != nil {
 		return err
 	}
@@ -375,20 +375,20 @@ func dbImageUpdate(db *sql.DB, id int, fname string, sz int64, public bool, auto
 		}
 	}
 
-	if err := txCommit(tx); err != nil {
+	if err := TxCommit(tx); err != nil {
 		return err
 	}
 
 	return nil
 }
 
-func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
+func ImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
 	arch, err := osarch.ArchitectureId(architecture)
 	if err != nil {
 		arch = 0
 	}
 
-	tx, err := dbBegin(db)
+	tx, err := Begin(db)
 	if err != nil {
 		return err
 	}
@@ -444,7 +444,7 @@ func dbImageInsert(db *sql.DB, fp string, fname string, sz int64, public bool, a
 
 	}
 
-	if err := txCommit(tx); err != nil {
+	if err := TxCommit(tx); err != nil {
 		return err
 	}
 
diff --git a/lxd/db_patches.go b/lxd/db/patches.go
similarity index 71%
rename from lxd/db_patches.go
rename to lxd/db/patches.go
index cd2876aac..c79498644 100644
--- a/lxd/db_patches.go
+++ b/lxd/db/patches.go
@@ -1,16 +1,16 @@
-package main
+package db
 
 import (
 	"database/sql"
 	"fmt"
 )
 
-func dbPatches(db *sql.DB) ([]string, error) {
+func Patches(db *sql.DB) ([]string, error) {
 	inargs := []interface{}{}
 	outfmt := []interface{}{""}
 
 	query := fmt.Sprintf("SELECT name FROM patches")
-	result, err := dbQueryScan(db, query, inargs, outfmt)
+	result, err := QueryScan(db, query, inargs, outfmt)
 	if err != nil {
 		return []string{}, err
 	}
@@ -23,7 +23,7 @@ func dbPatches(db *sql.DB) ([]string, error) {
 	return response, nil
 }
 
-func dbPatchesMarkApplied(db *sql.DB, patch string) error {
+func PatchesMarkApplied(db *sql.DB, patch string) error {
 	stmt := `INSERT INTO patches (name, applied_at) VALUES (?, strftime("%s"));`
 	_, err := db.Exec(stmt, patch)
 	return err
diff --git a/lxd/db_profiles.go b/lxd/db/profiles.go
similarity index 73%
rename from lxd/db_profiles.go
rename to lxd/db/profiles.go
index 4217aaea3..c89f6fd5c 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db/profiles.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -10,13 +10,13 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
-// dbProfiles returns a string list of profiles.
-func dbProfiles(db *sql.DB) ([]string, error) {
+// Profiles returns a string list of profiles.
+func Profiles(db *sql.DB) ([]string, error) {
 	q := fmt.Sprintf("SELECT name FROM profiles")
 	inargs := []interface{}{}
 	var name string
 	outfmt := []interface{}{name}
-	result, err := dbQueryScan(db, q, inargs, outfmt)
+	result, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return []string{}, err
 	}
@@ -29,7 +29,7 @@ func dbProfiles(db *sql.DB) ([]string, error) {
 	return response, nil
 }
 
-func dbProfileGet(db *sql.DB, name string) (int64, *api.Profile, error) {
+func ProfileGet(db *sql.DB, name string) (int64, *api.Profile, error) {
 	id := int64(-1)
 	description := sql.NullString{}
 
@@ -41,12 +41,12 @@ func dbProfileGet(db *sql.DB, name string) (int64, *api.Profile, error) {
 		return -1, nil, err
 	}
 
-	config, err := dbProfileConfig(db, name)
+	config, err := ProfileConfig(db, name)
 	if err != nil {
 		return -1, nil, err
 	}
 
-	devices, err := dbDevices(db, name, true)
+	devices, err := Devices(db, name, true)
 	if err != nil {
 		return -1, nil, err
 	}
@@ -62,10 +62,10 @@ func dbProfileGet(db *sql.DB, name string) (int64, *api.Profile, error) {
 	return id, &profile, nil
 }
 
-func dbProfileCreate(db *sql.DB, profile string, description string, config map[string]string,
+func ProfileCreate(db *sql.DB, profile string, description string, config map[string]string,
 	devices types.Devices) (int64, error) {
 
-	tx, err := dbBegin(db)
+	tx, err := Begin(db)
 	if err != nil {
 		return -1, err
 	}
@@ -80,19 +80,19 @@ func dbProfileCreate(db *sql.DB, profile string, description string, config map[
 		return -1, err
 	}
 
-	err = dbProfileConfigAdd(tx, id, config)
+	err = ProfileConfigAdd(tx, id, config)
 	if err != nil {
 		tx.Rollback()
 		return -1, err
 	}
 
-	err = dbDevicesAdd(tx, "profile", id, devices)
+	err = DevicesAdd(tx, "profile", id, devices)
 	if err != nil {
 		tx.Rollback()
 		return -1, err
 	}
 
-	err = txCommit(tx)
+	err = TxCommit(tx)
 	if err != nil {
 		return -1, err
 	}
@@ -100,8 +100,8 @@ func dbProfileCreate(db *sql.DB, profile string, description string, config map[
 	return id, nil
 }
 
-func dbProfileCreateDefault(db *sql.DB) error {
-	id, _, _ := dbProfileGet(db, "default")
+func ProfileCreateDefault(db *sql.DB) error {
+	id, _, _ := ProfileGet(db, "default")
 
 	if id != -1 {
 		// default profile already exists
@@ -115,7 +115,7 @@ func dbProfileCreateDefault(db *sql.DB) error {
 			"type":    "nic",
 			"nictype": "bridged",
 			"parent":  "lxdbr0"}}
-	_, err := dbProfileCreate(db, "default", "Default LXD profile", map[string]string{}, devices)
+	_, err := ProfileCreate(db, "default", "Default LXD profile", map[string]string{}, devices)
 	if err != nil {
 		return err
 	}
@@ -124,7 +124,7 @@ func dbProfileCreateDefault(db *sql.DB) error {
 }
 
 func dbProfileCreateDocker(db *sql.DB) error {
-	id, _, err := dbProfileGet(db, "docker")
+	id, _, err := ProfileGet(db, "docker")
 
 	if id != -1 {
 		// docker profile already exists
@@ -141,12 +141,12 @@ func dbProfileCreateDocker(db *sql.DB) error {
 	}
 	devices := map[string]map[string]string{"aadisable": aadisable}
 
-	_, err = dbProfileCreate(db, "docker", "Profile supporting docker in containers", config, devices)
+	_, err = ProfileCreate(db, "docker", "Profile supporting docker in containers", config, devices)
 	return err
 }
 
 // Get the profile configuration map from the DB
-func dbProfileConfig(db *sql.DB, name string) (map[string]string, error) {
+func ProfileConfig(db *sql.DB, name string) (map[string]string, error) {
 	var key, value string
 	query := `
         SELECT
@@ -156,7 +156,7 @@ func dbProfileConfig(db *sql.DB, name string) (map[string]string, error) {
 		WHERE name=?`
 	inargs := []interface{}{name}
 	outfmt := []interface{}{key, value}
-	results, err := dbQueryScan(db, query, inargs, outfmt)
+	results, err := QueryScan(db, query, inargs, outfmt)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to get profile '%s'", name)
 	}
@@ -168,7 +168,7 @@ func dbProfileConfig(db *sql.DB, name string) (map[string]string, error) {
 		 */
 		query := "SELECT id FROM profiles WHERE name=?"
 		var id int
-		results, err := dbQueryScan(db, query, []interface{}{name}, []interface{}{id})
+		results, err := QueryScan(db, query, []interface{}{name}, []interface{}{id})
 		if err != nil {
 			return nil, err
 		}
@@ -190,13 +190,13 @@ func dbProfileConfig(db *sql.DB, name string) (map[string]string, error) {
 	return config, nil
 }
 
-func dbProfileDelete(db *sql.DB, name string) error {
-	id, _, err := dbProfileGet(db, name)
+func ProfileDelete(db *sql.DB, name string) error {
+	id, _, err := ProfileGet(db, name)
 	if err != nil {
 		return err
 	}
 
-	_, err = dbExec(db, "DELETE FROM profiles WHERE id=?", id)
+	_, err = Exec(db, "DELETE FROM profiles WHERE id=?", id)
 	if err != nil {
 		return err
 	}
@@ -204,8 +204,8 @@ func dbProfileDelete(db *sql.DB, name string) error {
 	return nil
 }
 
-func dbProfileUpdate(db *sql.DB, name string, newName string) error {
-	tx, err := dbBegin(db)
+func ProfileUpdate(db *sql.DB, name string, newName string) error {
+	tx, err := Begin(db)
 	if err != nil {
 		return err
 	}
@@ -216,17 +216,17 @@ func dbProfileUpdate(db *sql.DB, name string, newName string) error {
 		return err
 	}
 
-	err = txCommit(tx)
+	err = TxCommit(tx)
 
 	return err
 }
 
-func dbProfileDescriptionUpdate(tx *sql.Tx, id int64, description string) error {
+func ProfileDescriptionUpdate(tx *sql.Tx, id int64, description string) error {
 	_, err := tx.Exec("UPDATE profiles SET description=? WHERE id=?", description, id)
 	return err
 }
 
-func dbProfileConfigClear(tx *sql.Tx, id int64) error {
+func ProfileConfigClear(tx *sql.Tx, id int64) error {
 	_, err := tx.Exec("DELETE FROM profiles_config WHERE profile_id=?", id)
 	if err != nil {
 		return err
@@ -247,7 +247,7 @@ func dbProfileConfigClear(tx *sql.Tx, id int64) error {
 	return nil
 }
 
-func dbProfileConfigAdd(tx *sql.Tx, id int64, config map[string]string) error {
+func ProfileConfigAdd(tx *sql.Tx, id int64, config map[string]string) error {
 	str := fmt.Sprintf("INSERT INTO profiles_config (profile_id, key, value) VALUES(?, ?, ?)")
 	stmt, err := tx.Prepare(str)
 	defer stmt.Close()
@@ -262,7 +262,7 @@ func dbProfileConfigAdd(tx *sql.Tx, id int64, config map[string]string) error {
 	return nil
 }
 
-func dbProfileContainersGet(db *sql.DB, profile string) ([]string, error) {
+func ProfileContainersGet(db *sql.DB, profile string) ([]string, error) {
 	q := `SELECT containers.name FROM containers JOIN containers_profiles
 		ON containers.id == containers_profiles.container_id
 		JOIN profiles ON containers_profiles.profile_id == profiles.id
@@ -273,7 +273,7 @@ func dbProfileContainersGet(db *sql.DB, profile string) ([]string, error) {
 	var name string
 	outfmt := []interface{}{name}
 
-	output, err := dbQueryScan(db, q, inargs, outfmt)
+	output, err := QueryScan(db, q, inargs, outfmt)
 	if err != nil {
 		return results, err
 	}
diff --git a/lxd/db_update.go b/lxd/db/update.go
similarity index 98%
rename from lxd/db_update.go
rename to lxd/db/update.go
index 70c5a913f..417774922 100644
--- a/lxd/db_update.go
+++ b/lxd/db/update.go
@@ -1,4 +1,4 @@
-package main
+package db
 
 import (
 	"database/sql"
@@ -94,8 +94,8 @@ func (u *dbUpdate) apply(currentVersion int, db *sql.DB) error {
 // of now "postApply" is only used by the daemon as a mean to apply
 // the legacy V10 and V15 non-db updates during the database upgrade
 // sequence to, avoid changing semantics see PR #3322).
-func dbUpdatesApplyAll(db *sql.DB, doBackup bool, postApply func(int) error) error {
-	currentVersion := dbGetSchema(db)
+func UpdatesApplyAll(db *sql.DB, doBackup bool, postApply func(int) error) error {
+	currentVersion := GetSchema(db)
 
 	backup := false
 	for _, update := range dbUpdates {
@@ -255,7 +255,7 @@ func dbUpdateFromV18(currentVersion int, version int, db *sql.DB) error {
 	var value string
 
 	// Update container config
-	rows, err := dbQueryScan(db, "SELECT id, value FROM containers_config WHERE key='limits.memory'", nil, []interface{}{id, value})
+	rows, err := QueryScan(db, "SELECT id, value FROM containers_config WHERE key='limits.memory'", nil, []interface{}{id, value})
 	if err != nil {
 		return err
 	}
@@ -292,7 +292,7 @@ func dbUpdateFromV18(currentVersion int, version int, db *sql.DB) error {
 	}
 
 	// Update profiles config
-	rows, err = dbQueryScan(db, "SELECT id, value FROM profiles_config WHERE key='limits.memory'", nil, []interface{}{id, value})
+	rows, err = QueryScan(db, "SELECT id, value FROM profiles_config WHERE key='limits.memory'", nil, []interface{}{id, value})
 	if err != nil {
 		return err
 	}
diff --git a/lxd/devices.go b/lxd/devices.go
index c7ad0a481..4f92bfe56 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -18,6 +18,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 
@@ -240,7 +241,7 @@ func deviceTaskBalance(d *Daemon) {
 	}
 
 	// Iterate through the containers
-	containers, err := dbContainersList(d.db, cTypeRegular)
+	containers, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		logger.Error("problem loading containers list", log.Ctx{"err": err})
 		return
@@ -366,7 +367,7 @@ func deviceNetworkPriority(d *Daemon, netif string) {
 		return
 	}
 
-	containers, err := dbContainersList(d.db, cTypeRegular)
+	containers, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return
 	}
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index c2ece9d30..5dd17b454 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
@@ -371,7 +372,7 @@ func findContainerForPid(pid int32, d *Daemon) (container, error) {
 		return nil, err
 	}
 
-	containers, err := dbContainersList(d.db, cTypeRegular)
+	containers, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/images.go b/lxd/images.go
index 12ea930fe..2240edaf2 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -22,6 +22,7 @@ import (
 	"github.com/gorilla/mux"
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -297,7 +298,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, builddir st
 
 	info.Fingerprint = fmt.Sprintf("%x", sha256.Sum(nil))
 
-	_, _, err = dbImageGet(d.db, info.Fingerprint, false, true)
+	_, _, err = db.ImageGet(d.db, info.Fingerprint, false, true)
 	if err == nil {
 		return nil, fmt.Errorf("The image already exists: %s", info.Fingerprint)
 	}
@@ -319,7 +320,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, builddir st
 	}
 
 	// Create the database entry
-	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+	err = db.ImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
 		return nil, err
 	}
@@ -344,7 +345,7 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image
 		return nil, err
 	}
 
-	id, info, err := dbImageGet(d.db, info.Fingerprint, false, true)
+	id, info, err := db.ImageGet(d.db, info.Fingerprint, false, true)
 	if err != nil {
 		return nil, err
 	}
@@ -356,7 +357,7 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image
 
 	// Update the DB record if needed
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
-		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+		err = db.ImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
 			return nil, err
 		}
@@ -413,7 +414,7 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, e
 		return nil, err
 	}
 
-	id, info, err := dbImageGet(d.db, info.Fingerprint, false, false)
+	id, info, err := db.ImageGet(d.db, info.Fingerprint, false, false)
 	if err != nil {
 		return nil, err
 	}
@@ -424,7 +425,7 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, e
 	}
 
 	if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
-		err = dbImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+		err = db.ImageUpdate(d.db, id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 		if err != nil {
 			return nil, err
 		}
@@ -627,7 +628,7 @@ func getImgPostInfo(d *Daemon, r *http.Request, builddir string, post *os.File)
 	}
 
 	// Check if the image already exists
-	exists, err := dbImageExists(d.db, info.Fingerprint)
+	exists, err := db.ImageExists(d.db, info.Fingerprint)
 	if err != nil {
 		return nil, err
 	}
@@ -635,7 +636,7 @@ func getImgPostInfo(d *Daemon, r *http.Request, builddir string, post *os.File)
 		return nil, fmt.Errorf("Image with same fingerprint already exists")
 	}
 	// Create the database entry
-	err = dbImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+	err = db.ImageInsert(d.db, info.Fingerprint, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
 		return nil, err
 	}
@@ -783,7 +784,7 @@ func getImageMetadata(fname string) (*imageMetadata, error) {
 }
 
 func doImagesGet(d *Daemon, recursion bool, public bool) (interface{}, error) {
-	results, err := dbImagesGet(d.db, public)
+	results, err := db.ImagesGet(d.db, public)
 	if err != nil {
 		return []string{}, err
 	}
@@ -828,14 +829,14 @@ var imagesCmd = Command{name: "images", post: imagesPost, untrustedGet: true, ge
 func autoUpdateImages(d *Daemon) {
 	logger.Infof("Updating images")
 
-	images, err := dbImagesGet(d.db, false)
+	images, err := db.ImagesGet(d.db, false)
 	if err != nil {
 		logger.Error("Unable to retrieve the list of images", log.Ctx{"err": err})
 		return
 	}
 
 	for _, fingerprint := range images {
-		id, info, err := dbImageGet(d.db, fingerprint, false, true)
+		id, info, err := db.ImageGet(d.db, fingerprint, false, true)
 		if err != nil {
 			logger.Error("Error loading image", log.Ctx{"err": err, "fp": fingerprint})
 			continue
@@ -855,7 +856,7 @@ func autoUpdateImages(d *Daemon) {
 // Returns whether the image has been updated.
 func autoUpdateImage(d *Daemon, op *operation, id int, info *api.Image) error {
 	fingerprint := info.Fingerprint
-	_, source, err := dbImageSourceGet(d.db, id)
+	_, source, err := db.ImageSourceGet(d.db, id)
 	if err != nil {
 		logger.Error("Error getting source image", log.Ctx{"err": err, "fp": fingerprint})
 		return err
@@ -886,27 +887,27 @@ func autoUpdateImage(d *Daemon, op *operation, id int, info *api.Image) error {
 		return nil
 	}
 
-	newId, _, err := dbImageGet(d.db, hash, false, true)
+	newId, _, err := db.ImageGet(d.db, hash, false, true)
 	if err != nil {
 		logger.Error("Error loading image", log.Ctx{"err": err, "fp": hash})
 		return err
 	}
 
 	if info.Cached {
-		err = dbImageLastAccessInit(d.db, hash)
+		err = db.ImageLastAccessInit(d.db, hash)
 		if err != nil {
 			logger.Error("Error setting cached flag", log.Ctx{"err": err, "fp": hash})
 			return err
 		}
 	}
 
-	err = dbImageLastAccessUpdate(d.db, hash, info.LastUsedAt)
+	err = db.ImageLastAccessUpdate(d.db, hash, info.LastUsedAt)
 	if err != nil {
 		logger.Error("Error setting last use date", log.Ctx{"err": err, "fp": hash})
 		return err
 	}
 
-	err = dbImageAliasesMove(d.db, id, newId)
+	err = db.ImageAliasesMove(d.db, id, newId)
 	if err != nil {
 		logger.Error("Error moving aliases", log.Ctx{"err": err, "fp": hash})
 		return err
@@ -926,7 +927,7 @@ func pruneExpiredImages(d *Daemon) {
 
 	// Get the list of expires images
 	expiry := daemonConfig["images.remote_cache_expiry"].GetInt64()
-	images, err := dbImagesGetExpired(d.db, expiry)
+	images, err := db.ImagesGetExpired(d.db, expiry)
 	if err != nil {
 		logger.Error("Unable to retrieve the list of expired images", log.Ctx{"err": err})
 		return
@@ -943,7 +944,7 @@ func pruneExpiredImages(d *Daemon) {
 }
 
 func doDeleteImage(d *Daemon, fingerprint string) error {
-	id, imgInfo, err := dbImageGet(d.db, fingerprint, false, false)
+	id, imgInfo, err := db.ImageGet(d.db, fingerprint, false, false)
 	if err != nil {
 		return err
 	}
@@ -979,7 +980,7 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	}
 
 	// Remove the DB entry
-	if err = dbImageDelete(d.db, id); err != nil {
+	if err = db.ImageDelete(d.db, id); err != nil {
 		return err
 	}
 
@@ -1005,7 +1006,7 @@ func imageDelete(d *Daemon, r *http.Request) Response {
 }
 
 func doImageGet(d *Daemon, fingerprint string, public bool) (*api.Image, Response) {
-	_, imgInfo, err := dbImageGet(d.db, fingerprint, public, false)
+	_, imgInfo, err := db.ImageGet(d.db, fingerprint, public, false)
 	if err != nil {
 		return nil, SmartError(err)
 	}
@@ -1068,12 +1069,12 @@ func imagePut(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
-	id, info, err := dbImageGet(d.db, fingerprint, false, false)
+	id, info, err := db.ImageGet(d.db, fingerprint, false, false)
 	if err != nil {
 		return SmartError(err)
 	}
 
-	err = dbImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties)
+	err = db.ImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1094,17 +1095,17 @@ func aliasesPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// This is just to see if the alias name already exists.
-	_, _, err := dbImageAliasGet(d.db, req.Name, true)
+	_, _, err := db.ImageAliasGet(d.db, req.Name, true)
 	if err == nil {
 		return Conflict
 	}
 
-	id, _, err := dbImageGet(d.db, req.Target, false, false)
+	id, _, err := db.ImageGet(d.db, req.Target, false, false)
 	if err != nil {
 		return SmartError(err)
 	}
 
-	err = dbImageAliasAdd(d.db, req.Name, id, req.Description)
+	err = db.ImageAliasAdd(d.db, req.Name, id, req.Description)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1119,7 +1120,7 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 	var name string
 	inargs := []interface{}{}
 	outfmt := []interface{}{name}
-	results, err := dbQueryScan(d.db, q, inargs, outfmt)
+	results, err := db.QueryScan(d.db, q, inargs, outfmt)
 	if err != nil {
 		return BadRequest(err)
 	}
@@ -1132,7 +1133,7 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 			responseStr = append(responseStr, url)
 
 		} else {
-			_, alias, err := dbImageAliasGet(d.db, name, d.isTrustedClient(r))
+			_, alias, err := db.ImageAliasGet(d.db, name, d.isTrustedClient(r))
 			if err != nil {
 				continue
 			}
@@ -1150,7 +1151,7 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 func aliasGet(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	_, alias, err := dbImageAliasGet(d.db, name, d.isTrustedClient(r))
+	_, alias, err := db.ImageAliasGet(d.db, name, d.isTrustedClient(r))
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1160,12 +1161,12 @@ func aliasGet(d *Daemon, r *http.Request) Response {
 
 func aliasDelete(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
-	_, _, err := dbImageAliasGet(d.db, name, true)
+	_, _, err := db.ImageAliasGet(d.db, name, true)
 	if err != nil {
 		return SmartError(err)
 	}
 
-	err = dbImageAliasDelete(d.db, name)
+	err = db.ImageAliasDelete(d.db, name)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1185,17 +1186,17 @@ func aliasPut(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("The target field is required"))
 	}
 
-	id, _, err := dbImageAliasGet(d.db, name, true)
+	id, _, err := db.ImageAliasGet(d.db, name, true)
 	if err != nil {
 		return SmartError(err)
 	}
 
-	imageId, _, err := dbImageGet(d.db, req.Target, false, false)
+	imageId, _, err := db.ImageGet(d.db, req.Target, false, false)
 	if err != nil {
 		return SmartError(err)
 	}
 
-	err = dbImageAliasUpdate(d.db, id, imageId, req.Description)
+	err = db.ImageAliasUpdate(d.db, id, imageId, req.Description)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1212,17 +1213,17 @@ func aliasPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Check that the name isn't already in use
-	id, _, _ := dbImageAliasGet(d.db, req.Name, true)
+	id, _, _ := db.ImageAliasGet(d.db, req.Name, true)
 	if id > 0 {
 		return Conflict
 	}
 
-	id, _, err := dbImageAliasGet(d.db, name, true)
+	id, _, err := db.ImageAliasGet(d.db, name, true)
 	if err != nil {
 		return SmartError(err)
 	}
 
-	err = dbImageAliasRename(d.db, id, req.Name)
+	err = db.ImageAliasRename(d.db, id, req.Name)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1236,7 +1237,7 @@ func imageExport(d *Daemon, r *http.Request) Response {
 	public := !d.isTrustedClient(r)
 	secret := r.FormValue("secret")
 
-	_, imgInfo, err := dbImageGet(d.db, fingerprint, false, false)
+	_, imgInfo, err := db.ImageGet(d.db, fingerprint, false, false)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1286,7 +1287,7 @@ func imageExport(d *Daemon, r *http.Request) Response {
 
 func imageSecret(d *Daemon, r *http.Request) Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
-	_, imgInfo, err := dbImageGet(d.db, fingerprint, false, false)
+	_, imgInfo, err := db.ImageGet(d.db, fingerprint, false, false)
 	if err != nil {
 		return SmartError(err)
 	}
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index 753d25dc8..fb85f6b2f 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -5,6 +5,7 @@ import (
 	"os"
 
 	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -51,7 +52,7 @@ func cmdActivateIfNeeded() error {
 	}
 
 	// Look for auto-started or previously started containers
-	result, err := dbContainersList(d.db, cTypeRegular)
+	result, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/networks.go b/lxd/networks.go
index afc05a367..f5335f79e 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
@@ -96,7 +97,7 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 	n.UsedBy = []string{}
 
 	// Look for containers using the interface
-	cts, err := dbContainersList(d.db, cTypeRegular)
+	cts, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return api.Network{}, err
 	}
diff --git a/lxd/patches.go b/lxd/patches.go
index f6c899dcb..0fe64ce27 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 	"syscall"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 
@@ -49,7 +50,7 @@ func (p *patch) apply(d *Daemon) error {
 		return err
 	}
 
-	err = dbPatchesMarkApplied(d.db, p.name)
+	err = db.PatchesMarkApplied(d.db, p.name)
 	if err != nil {
 		return err
 	}
@@ -57,8 +58,17 @@ func (p *patch) apply(d *Daemon) error {
 	return nil
 }
 
+// Return the names of all available patches.
+func patchesGetNames() []string {
+	names := make([]string, len(patches))
+	for i, patch := range patches {
+		names[i] = patch.name
+	}
+	return names
+}
+
 func patchesApplyAll(d *Daemon) error {
-	appliedPatches, err := dbPatches(d.db)
+	appliedPatches, err := db.Patches(d.db)
 	if err != nil {
 		return err
 	}
@@ -94,7 +104,7 @@ DELETE FROM profiles_devices_config WHERE profile_device_id NOT IN (SELECT id FR
 }
 
 func patchInvalidProfileNames(name string, d *Daemon) error {
-	profiles, err := dbProfiles(d.db)
+	profiles, err := db.Profiles(d.db)
 	if err != nil {
 		return err
 	}
@@ -102,7 +112,7 @@ func patchInvalidProfileNames(name string, d *Daemon) error {
 	for _, profile := range profiles {
 		if strings.Contains(profile, "/") || shared.StringInSlice(profile, []string{".", ".."}) {
 			logger.Info("Removing unreachable profile (invalid name)", log.Ctx{"name": profile})
-			err := dbProfileDelete(d.db, profile)
+			err := db.ProfileDelete(d.db, profile)
 			if err != nil {
 				return err
 			}
@@ -148,7 +158,7 @@ func patchUpdateFromV10(d *Daemon) error {
 }
 
 func patchUpdateFromV11(d *Daemon) error {
-	cNames, err := dbContainersList(d.db, cTypeSnapshot)
+	cNames, err := db.ContainersList(d.db, db.CTypeSnapshot)
 	if err != nil {
 		return err
 	}
@@ -219,7 +229,7 @@ func patchUpdateFromV15(d *Daemon) error {
 	// munge all LVM-backed containers' LV names to match what is
 	// required for snapshot support
 
-	cNames, err := dbContainersList(d.db, cTypeRegular)
+	cNames, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 2dabcf68c..285f393c6 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -9,6 +9,7 @@ import (
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -19,7 +20,7 @@ import (
 
 /* This is used for both profiles post and profile put */
 func profilesGet(d *Daemon, r *http.Request) Response {
-	results, err := dbProfiles(d.db)
+	results, err := db.Profiles(d.db)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -62,7 +63,7 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("No name provided"))
 	}
 
-	_, profile, _ := dbProfileGet(d.db, req.Name)
+	_, profile, _ := db.ProfileGet(d.db, req.Name)
 	if profile != nil {
 		return BadRequest(fmt.Errorf("The profile already exists"))
 	}
@@ -86,7 +87,7 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Update DB entry
-	_, err = dbProfileCreate(d.db, req.Name, req.Description, req.Config, req.Devices)
+	_, err = db.ProfileCreate(d.db, req.Name, req.Description, req.Config, req.Devices)
 	if err != nil {
 		return SmartError(
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
@@ -101,7 +102,7 @@ var profilesCmd = Command{
 	post: profilesPost}
 
 func doProfileGet(d *Daemon, name string) (*api.Profile, error) {
-	_, profile, err := dbProfileGet(d.db, name)
+	_, profile, err := db.ProfileGet(d.db, name)
 	return profile, err
 }
 
@@ -119,7 +120,7 @@ func profileGet(d *Daemon, r *http.Request) Response {
 func getContainersWithProfile(d *Daemon, profile string) []container {
 	results := []container{}
 
-	output, err := dbProfileContainersGet(d.db, profile)
+	output, err := db.ProfileContainersGet(d.db, profile)
 	if err != nil {
 		return results
 	}
@@ -139,7 +140,7 @@ func getContainersWithProfile(d *Daemon, profile string) []container {
 func profilePut(d *Daemon, r *http.Request) Response {
 	// Get the profile
 	name := mux.Vars(r)["name"]
-	id, profile, err := dbProfileGet(d.db, name)
+	id, profile, err := db.ProfileGet(d.db, name)
 	if err != nil {
 		return SmartError(fmt.Errorf("Failed to retrieve profile='%s'", name))
 	}
@@ -167,7 +168,7 @@ func profilePost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Check that the name isn't already in use
-	id, _, _ := dbProfileGet(d.db, req.Name)
+	id, _, _ := db.ProfileGet(d.db, req.Name)
 	if id > 0 {
 		return Conflict
 	}
@@ -180,7 +181,7 @@ func profilePost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
 	}
 
-	err := dbProfileUpdate(d.db, name, req.Name)
+	err := db.ProfileUpdate(d.db, name, req.Name)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -202,7 +203,7 @@ func profileDelete(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("Profile is currently in use"))
 	}
 
-	err = dbProfileDelete(d.db, name)
+	err = db.ProfileDelete(d.db, name)
 	if err != nil {
 		return SmartError(err)
 	}
diff --git a/lxd/profiles_test.go b/lxd/profiles_test.go
index 34d375684..888379682 100644
--- a/lxd/profiles_test.go
+++ b/lxd/profiles_test.go
@@ -3,6 +3,8 @@ package main
 import (
 	"database/sql"
 	"testing"
+
+	dbapi "github.com/lxc/lxd/lxd/db"
 )
 
 func Test_removing_a_profile_deletes_associated_configuration_entries(t *testing.T) {
@@ -28,14 +30,14 @@ func Test_removing_a_profile_deletes_associated_configuration_entries(t *testing
 		t.Fatal(err)
 	}
 
-	// Delete the profile we just created with dbProfileDelete
-	err = dbProfileDelete(db, "theprofile")
+	// Delete the profile we just created with dbapi.ProfileDelete
+	err = dbapi.ProfileDelete(db, "theprofile")
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	// Make sure there are 0 profiles_devices entries left.
-	devices, err := dbDevices(d.db, "theprofile", true)
+	devices, err := dbapi.Devices(d.db, "theprofile", true)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -44,7 +46,7 @@ func Test_removing_a_profile_deletes_associated_configuration_entries(t *testing
 	}
 
 	// Make sure there are 0 profiles_config entries left.
-	config, err := dbProfileConfig(d.db, "theprofile")
+	config, err := dbapi.ProfileConfig(d.db, "theprofile")
 	if err == nil {
 		t.Fatal("found the profile!")
 	}
diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index 355f53e0a..e3d6f537b 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"reflect"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared/api"
 )
 
@@ -22,13 +23,13 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 	containers := getContainersWithProfile(d, name)
 
 	// Update the database
-	tx, err := dbBegin(d.db)
+	tx, err := db.Begin(d.db)
 	if err != nil {
 		return SmartError(err)
 	}
 
 	if profile.Description != req.Description {
-		err = dbProfileDescriptionUpdate(tx, id, req.Description)
+		err = db.ProfileDescriptionUpdate(tx, id, req.Description)
 		if err != nil {
 			tx.Rollback()
 			return SmartError(err)
@@ -37,7 +38,7 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 
 	// Optimize for description-only changes
 	if reflect.DeepEqual(profile.Config, req.Config) && reflect.DeepEqual(profile.Devices, req.Devices) {
-		err = txCommit(tx)
+		err = db.TxCommit(tx)
 		if err != nil {
 			return SmartError(err)
 		}
@@ -45,33 +46,33 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req
 		return EmptySyncResponse
 	}
 
-	err = dbProfileConfigClear(tx, id)
+	err = db.ProfileConfigClear(tx, id)
 	if err != nil {
 		tx.Rollback()
 		return SmartError(err)
 	}
 
-	err = dbProfileConfigAdd(tx, id, req.Config)
+	err = db.ProfileConfigAdd(tx, id, req.Config)
 	if err != nil {
 		tx.Rollback()
 		return SmartError(err)
 	}
 
-	err = dbDevicesAdd(tx, "profile", id, req.Devices)
+	err = db.DevicesAdd(tx, "profile", id, req.Devices)
 	if err != nil {
 		tx.Rollback()
 		return SmartError(err)
 	}
 
-	err = txCommit(tx)
+	err = db.TxCommit(tx)
 	if err != nil {
 		return SmartError(err)
 	}
 
-	// Update all the containers using the profile. Must be done after txCommit due to DB lock.
+	// Update all the containers using the profile. Must be done after db.TxCommit due to DB lock.
 	failures := map[string]error{}
 	for _, c := range containers {
-		err = c.Update(containerArgs{
+		err = c.Update(db.ContainerArgs{
 			Architecture: c.Architecture(),
 			Ephemeral:    c.IsEphemeral(),
 			Config:       c.LocalConfig(),
diff --git a/lxd/response.go b/lxd/response.go
index e238a1d98..2209b3c3b 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -13,6 +13,7 @@ import (
 
 	"github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -291,11 +292,11 @@ func SmartError(err error) Response {
 		return NotFound
 	case sql.ErrNoRows:
 		return NotFound
-	case NoSuchObjectError:
+	case db.NoSuchObjectError:
 		return NotFound
 	case os.ErrPermission:
 		return Forbidden
-	case DbErrAlreadyDefined:
+	case db.DbErrAlreadyDefined:
 		return Conflict
 	case sqlite3.ErrConstraintUnique:
 		return Conflict
diff --git a/lxd/storage.go b/lxd/storage.go
index 3629bdf4f..08caadb58 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -662,7 +663,7 @@ func rsyncMigrationSource(container container) (MigrationStorageSourceDriver, er
 	return rsyncStorageSourceDriver{container, snapshots}, nil
 }
 
-func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) containerArgs {
+func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) db.ContainerArgs {
 	config := map[string]string{}
 
 	for _, ent := range snap.LocalConfig {
@@ -680,9 +681,9 @@ func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) conta
 	}
 
 	name := containerName + shared.SnapshotDelimiter + snap.GetName()
-	return containerArgs{
+	return db.ContainerArgs{
 		Name:         name,
-		Ctype:        cTypeSnapshot,
+		Ctype:        db.CTypeSnapshot,
 		Config:       config,
 		Profiles:     snap.Profiles,
 		Ephemeral:    snap.GetEphemeral(),
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index cac29f91c..e6de86076 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 
@@ -56,7 +57,7 @@ func storageLVMGetThinPoolUsers(d *Daemon) ([]string, error) {
 		return results, nil
 	}
 
-	cNames, err := dbContainersList(d.db, cTypeRegular)
+	cNames, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
 		return results, err
 	}
@@ -74,7 +75,7 @@ func storageLVMGetThinPoolUsers(d *Daemon) ([]string, error) {
 		}
 	}
 
-	imageNames, err := dbImagesGet(d.db, false)
+	imageNames, err := db.ImagesGet(d.db, false)
 	if err != nil {
 		return results, err
 	}

From 6e68f2b8210b0fef9e7d4e8e6c6c19effc518c4d Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:21:28 +0000
Subject: [PATCH 1164/1193] Move lxd/util.go into its own lxd/util/ sub-package

Move all the code currently in lxd/util.go to a new lxd/util
sub-package, and split it into two files according to the
functionality (http.go for http-related utils, kernel.go for
kernel-related utils).

In detail:

- Add a new lxd/util sub-package.
- Rename lxd/util.go to lxd/util/http.go, changing its package name
  from "main" to "util".
- Move loadModule from lxd/util/http.go to lxd/util/kernel.go.
- Make all functions public.
- Update call sites.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/container_lxc.go          |  5 +++--
 lxd/devlxd.go                 |  3 ++-
 lxd/main_init.go              |  3 ++-
 lxd/response.go               |  5 +++--
 lxd/storage_zfs.go            |  3 ++-
 lxd/{util.go => util/http.go} | 14 ++------------
 lxd/util/kernel.go            | 18 ++++++++++++++++++
 7 files changed, 32 insertions(+), 19 deletions(-)
 rename lxd/{util.go => util/http.go} (56%)
 create mode 100644 lxd/util/kernel.go

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ef616ffd1..3f72cb20a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -26,6 +26,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/types"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -1522,7 +1523,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	if kernelModules != "" {
 		for _, module := range strings.Split(kernelModules, ",") {
 			module = strings.TrimPrefix(module, " ")
-			err := loadModule(module)
+			err := util.LoadModule(module)
 			if err != nil {
 				return "", fmt.Errorf("Failed to load kernel module '%s': %s", module, err)
 			}
@@ -2908,7 +2909,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 			} else if key == "linux.kernel_modules" && value != "" {
 				for _, module := range strings.Split(value, ",") {
 					module = strings.TrimPrefix(module, " ")
-					err := loadModule(module)
+					err := util.LoadModule(module)
 					if err != nil {
 						return fmt.Errorf("Failed to load kernel module '%s': %s", module, err)
 					}
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index 5dd17b454..68f2ca518 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -16,6 +16,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
@@ -119,7 +120,7 @@ func hoistReq(f func(container, *http.Request) *devLxdResponse, d *Daemon) func(
 			http.Error(w, fmt.Sprintf("%s", resp.content), resp.code)
 		} else if resp.ctype == "json" {
 			w.Header().Set("Content-Type", "application/json")
-			WriteJSON(w, resp.content)
+			util.WriteJSON(w, resp.content, debug)
 		} else {
 			w.Header().Set("Content-Type", "application/octet-stream")
 			fmt.Fprintf(w, resp.content.(string))
diff --git a/lxd/main_init.go b/lxd/main_init.go
index a7ffbf5b4..f1cbfb8bb 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -12,6 +12,7 @@ import (
 	"golang.org/x/crypto/ssh/terminal"
 
 	"github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/cmd"
@@ -445,7 +446,7 @@ func (cmd *CmdInit) availableStoragePoolsDrivers() []string {
 	// Detect zfs
 	out, err := exec.LookPath("zfs")
 	if err == nil && len(out) != 0 && !cmd.RunningInUserns {
-		_ = loadModule("zfs")
+		_ = util.LoadModule("zfs")
 
 		_, err := shared.RunCommand("zpool", "list")
 		if err == nil {
diff --git a/lxd/response.go b/lxd/response.go
index 2209b3c3b..93157a7eb 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -14,6 +14,7 @@ import (
 	"github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -49,7 +50,7 @@ func (r *syncResponse) Render(w http.ResponseWriter) error {
 		Metadata: r.metadata,
 	}
 
-	return WriteJSON(w, resp)
+	return util.WriteJSON(w, resp, debug)
 }
 
 func (r *syncResponse) String() string {
@@ -212,7 +213,7 @@ func (r *operationResponse) Render(w http.ResponseWriter) error {
 	w.Header().Set("Location", url)
 	w.WriteHeader(202)
 
-	return WriteJSON(w, body)
+	return util.WriteJSON(w, body, debug)
 }
 
 func (r *operationResponse) String() string {
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 58ede0a53..c204d0fb4 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 
@@ -54,7 +55,7 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 	err = s.zfsCheckPool(s.zfsPool)
 	if err != nil {
 		if shared.PathExists(shared.VarPath("zfs.img")) {
-			_ = loadModule("zfs")
+			_ = util.LoadModule("zfs")
 
 			output, err := shared.RunCommand("zpool", "import",
 				"-d", shared.VarPath(), s.zfsPool)
diff --git a/lxd/util.go b/lxd/util/http.go
similarity index 56%
rename from lxd/util.go
rename to lxd/util/http.go
index 1eccf0d44..c276915e6 100644
--- a/lxd/util.go
+++ b/lxd/util/http.go
@@ -1,16 +1,15 @@
-package main
+package util
 
 import (
 	"bytes"
 	"encoding/json"
-	"fmt"
 	"io"
 	"net/http"
 
 	"github.com/lxc/lxd/shared"
 )
 
-func WriteJSON(w http.ResponseWriter, body interface{}) error {
+func WriteJSON(w http.ResponseWriter, body interface{}, debug bool) error {
 	var output io.Writer
 	var captured *bytes.Buffer
 
@@ -28,12 +27,3 @@ func WriteJSON(w http.ResponseWriter, body interface{}) error {
 
 	return err
 }
-
-func loadModule(module string) error {
-	if shared.PathExists(fmt.Sprintf("/sys/module/%s", module)) {
-		return nil
-	}
-
-	_, err := shared.RunCommand("modprobe", module)
-	return err
-}
diff --git a/lxd/util/kernel.go b/lxd/util/kernel.go
new file mode 100644
index 000000000..6f75915ba
--- /dev/null
+++ b/lxd/util/kernel.go
@@ -0,0 +1,18 @@
+package util
+
+import (
+	"fmt"
+
+	"github.com/lxc/lxd/shared"
+)
+
+// LoadModule loads the kernel module with the given name, by invoking
+// modprobe.
+func LoadModule(module string) error {
+	if shared.PathExists(fmt.Sprintf("/sys/module/%s", module)) {
+		return nil
+	}
+
+	_, err := shared.RunCommand("modprobe", module)
+	return err
+}

From df9b75d6815e89e3564bbc8994791ba9f5035135 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:23:18 +0000
Subject: [PATCH 1165/1193] Extract Daemon.httpClient into a standalone
 HTTPClient function

Extract the Daemon.httpClient method, which really depends only on the
Daemon.proxy attribute and nothing else from Deamon, into a standalone
HTTPClient function part of the new lxd/util package.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/container_instance_types.go |  3 ++-
 lxd/daemon.go                   | 44 -----------------------------------
 lxd/daemon_images.go            |  3 ++-
 lxd/images.go                   |  3 ++-
 lxd/util/http.go                | 51 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 57 insertions(+), 47 deletions(-)

diff --git a/lxd/container_instance_types.go b/lxd/container_instance_types.go
index 77259e3bf..c5225b7f8 100644
--- a/lxd/container_instance_types.go
+++ b/lxd/container_instance_types.go
@@ -9,6 +9,7 @@ import (
 
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
@@ -67,7 +68,7 @@ func instanceRefreshTypes(d *Daemon) error {
 	downloadParse := func(filename string, target interface{}) error {
 		url := fmt.Sprintf("https://images.linuxcontainers.org/meta/instance-types/%s", filename)
 
-		httpClient, err := d.httpClient("")
+		httpClient, err := util.HTTPClient("", d.proxy)
 		if err != nil {
 			return err
 		}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 6338d90b2..c503f80c4 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -6,7 +6,6 @@ import (
 	"crypto/x509"
 	"database/sql"
 	"encoding/hex"
-	"encoding/pem"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -107,49 +106,6 @@ type Command struct {
 	delete        func(d *Daemon, r *http.Request) Response
 }
 
-func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
-	var err error
-	var cert *x509.Certificate
-
-	if certificate != "" {
-		certBlock, _ := pem.Decode([]byte(certificate))
-		if certBlock == nil {
-			return nil, fmt.Errorf("Invalid certificate")
-		}
-
-		cert, err = x509.ParseCertificate(certBlock.Bytes)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	tlsConfig, err := shared.GetTLSConfig("", "", "", cert)
-	if err != nil {
-		return nil, err
-	}
-
-	tr := &http.Transport{
-		TLSClientConfig:   tlsConfig,
-		Dial:              shared.RFC3493Dialer,
-		Proxy:             d.proxy,
-		DisableKeepAlives: true,
-	}
-
-	myhttp := http.Client{
-		Transport: tr,
-	}
-
-	// Setup redirect policy
-	myhttp.CheckRedirect = func(req *http.Request, via []*http.Request) error {
-		// Replicate the headers
-		req.Header = via[len(via)-1].Header
-
-		return nil
-	}
-
-	return &myhttp, nil
-}
-
 func readMyCert() (string, string, error) {
 	certf := shared.VarPath("server.crt")
 	keyf := shared.VarPath("server.key")
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index f8dc4ac24..7eeaf3155 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/cancel"
@@ -391,7 +392,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 		}
 	} else if protocol == "direct" {
 		// Setup HTTP client
-		httpClient, err := d.httpClient(certificate)
+		httpClient, err := util.HTTPClient(certificate, d.proxy)
 		if err != nil {
 			return nil, err
 		}
diff --git a/lxd/images.go b/lxd/images.go
index 2240edaf2..1fe504976 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -23,6 +23,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -373,7 +374,7 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, e
 		return nil, fmt.Errorf("Missing URL")
 	}
 
-	myhttp, err := d.httpClient("")
+	myhttp, err := util.HTTPClient("", d.proxy)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/util/http.go b/lxd/util/http.go
index c276915e6..245c8fc28 100644
--- a/lxd/util/http.go
+++ b/lxd/util/http.go
@@ -2,9 +2,13 @@ package util
 
 import (
 	"bytes"
+	"crypto/x509"
 	"encoding/json"
+	"encoding/pem"
+	"fmt"
 	"io"
 	"net/http"
+	"net/url"
 
 	"github.com/lxc/lxd/shared"
 )
@@ -27,3 +31,50 @@ func WriteJSON(w http.ResponseWriter, body interface{}, debug bool) error {
 
 	return err
 }
+
+// HTTPClient returns an http.Client using the given certificate and proxy.
+func HTTPClient(certificate string, proxy proxyFunc) (*http.Client, error) {
+	var err error
+	var cert *x509.Certificate
+
+	if certificate != "" {
+		certBlock, _ := pem.Decode([]byte(certificate))
+		if certBlock == nil {
+			return nil, fmt.Errorf("Invalid certificate")
+		}
+
+		cert, err = x509.ParseCertificate(certBlock.Bytes)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	tlsConfig, err := shared.GetTLSConfig("", "", "", cert)
+	if err != nil {
+		return nil, err
+	}
+
+	tr := &http.Transport{
+		TLSClientConfig:   tlsConfig,
+		Dial:              shared.RFC3493Dialer,
+		Proxy:             proxy,
+		DisableKeepAlives: true,
+	}
+
+	myhttp := http.Client{
+		Transport: tr,
+	}
+
+	// Setup redirect policy
+	myhttp.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+		// Replicate the headers
+		req.Header = via[len(via)-1].Header
+
+		return nil
+	}
+
+	return &myhttp, nil
+}
+
+// A function capable of proxing an HTTP request.
+type proxyFunc func(req *http.Request) (*url.URL, error)

From df4df4829ead7a11e6be5ffca7cdf9717bd183d0 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:29:29 +0000
Subject: [PATCH 1166/1193] Move Deamon.CheckTrustState and
 Deamon.isTrustedClient to lxd/util

Extract the CheckTrustState and isTrustedClient methods of Daemon to
standalone functions living in the lxd/util sub-package.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/api_1.0.go      |  3 ++-
 lxd/certificates.go |  3 ++-
 lxd/daemon.go       | 31 ++-----------------------------
 lxd/images.go       | 11 ++++++-----
 lxd/util/http.go    | 44 ++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 56 insertions(+), 36 deletions(-)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 54f9548b7..dea90e5cf 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -11,6 +11,7 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
@@ -70,7 +71,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 	}
 
 	// If untrusted, return now
-	if !d.isTrustedClient(r) {
+	if !util.IsTrustedClient(r, d.clientCerts) {
 		return SyncResponse(true, srv)
 	}
 
diff --git a/lxd/certificates.go b/lxd/certificates.go
index c175487ff..2501e988e 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -11,6 +11,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -95,7 +96,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Access check
-	if !d.isTrustedClient(r) && d.PasswordCheck(req.Password) != nil {
+	if !util.IsTrustedClient(r, d.clientCerts) && d.PasswordCheck(req.Password) != nil {
 		return Forbidden
 	}
 
diff --git a/lxd/daemon.go b/lxd/daemon.go
index c503f80c4..f27d0d14a 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -30,6 +30,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
@@ -115,22 +116,6 @@ func readMyCert() (string, string, error) {
 	return certf, keyf, err
 }
 
-func (d *Daemon) isTrustedClient(r *http.Request) bool {
-	if r.RemoteAddr == "@" {
-		// Unix socket
-		return true
-	}
-	if r.TLS == nil {
-		return false
-	}
-	for i := range r.TLS.PeerCertificates {
-		if d.CheckTrustState(*r.TLS.PeerCertificates[i]) {
-			return true
-		}
-	}
-	return false
-}
-
 func isJSONRequest(r *http.Request) bool {
 	for k, vs := range r.Header {
 		if strings.ToLower(k) == "content-type" &&
@@ -163,7 +148,7 @@ func (d *Daemon) createCmd(version string, c Command) {
 	d.mux.HandleFunc(uri, func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/json")
 
-		if d.isTrustedClient(r) {
+		if util.IsTrustedClient(r, d.clientCerts) {
 			logger.Debug(
 				"handling",
 				log.Ctx{"method": r.Method, "url": r.URL.RequestURI(), "ip": r.RemoteAddr})
@@ -1012,18 +997,6 @@ func (d *Daemon) Ready() error {
 	return nil
 }
 
-// CheckTrustState returns True if the client is trusted else false.
-func (d *Daemon) CheckTrustState(cert x509.Certificate) bool {
-	for k, v := range d.clientCerts {
-		if bytes.Compare(cert.Raw, v.Raw) == 0 {
-			logger.Debug("Found cert", log.Ctx{"k": k})
-			return true
-		}
-		logger.Debug("Client cert != key", log.Ctx{"k": k})
-	}
-	return false
-}
-
 func (d *Daemon) numRunningContainers() (int, error) {
 	results, err := db.ContainersList(d.db, db.CTypeRegular)
 	if err != nil {
diff --git a/lxd/images.go b/lxd/images.go
index 1fe504976..cec730bed 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -816,7 +816,7 @@ func doImagesGet(d *Daemon, recursion bool, public bool) (interface{}, error) {
 }
 
 func imagesGet(d *Daemon, r *http.Request) Response {
-	public := !d.isTrustedClient(r)
+	public := !util.IsTrustedClient(r, d.clientCerts)
 
 	result, err := doImagesGet(d, d.isRecursionRequest(r), public)
 	if err != nil {
@@ -1047,7 +1047,7 @@ func imageValidSecret(fingerprint string, secret string) bool {
 
 func imageGet(d *Daemon, r *http.Request) Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
-	public := !d.isTrustedClient(r)
+	public := !util.IsTrustedClient(r, d.clientCerts)
 	secret := r.FormValue("secret")
 
 	info, response := doImageGet(d, fingerprint, false)
@@ -1134,7 +1134,8 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 			responseStr = append(responseStr, url)
 
 		} else {
-			_, alias, err := db.ImageAliasGet(d.db, name, d.isTrustedClient(r))
+			isTrustedClient := util.IsTrustedClient(r, d.clientCerts)
+			_, alias, err := db.ImageAliasGet(d.db, name, isTrustedClient)
 			if err != nil {
 				continue
 			}
@@ -1152,7 +1153,7 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 func aliasGet(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
-	_, alias, err := db.ImageAliasGet(d.db, name, d.isTrustedClient(r))
+	_, alias, err := db.ImageAliasGet(d.db, name, util.IsTrustedClient(r, d.clientCerts))
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1235,7 +1236,7 @@ func aliasPost(d *Daemon, r *http.Request) Response {
 func imageExport(d *Daemon, r *http.Request) Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
-	public := !d.isTrustedClient(r)
+	public := !util.IsTrustedClient(r, d.clientCerts)
 	secret := r.FormValue("secret")
 
 	_, imgInfo, err := db.ImageGet(d.db, fingerprint, false, false)
diff --git a/lxd/util/http.go b/lxd/util/http.go
index 245c8fc28..6d66370ee 100644
--- a/lxd/util/http.go
+++ b/lxd/util/http.go
@@ -9,8 +9,12 @@ import (
 	"io"
 	"net/http"
 	"net/url"
+	"time"
+
+	log "gopkg.in/inconshreveable/log15.v2"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 func WriteJSON(w http.ResponseWriter, body interface{}, debug bool) error {
@@ -78,3 +82,43 @@ func HTTPClient(certificate string, proxy proxyFunc) (*http.Client, error) {
 
 // A function capable of proxing an HTTP request.
 type proxyFunc func(req *http.Request) (*url.URL, error)
+
+// IsTrustedClient checks if the given HTTP request comes from a trusted client
+// (i.e. either it's received via Unix socket, or via TLS with a trusted
+// certificate).
+func IsTrustedClient(r *http.Request, trustedCerts []x509.Certificate) bool {
+	if r.RemoteAddr == "@" {
+		// Unix socket
+		return true
+	}
+
+	if r.TLS == nil {
+		return false
+	}
+
+	for i := range r.TLS.PeerCertificates {
+		if checkTrustState(*r.TLS.PeerCertificates[i], trustedCerts) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Check whether the given client certificate is trusted (i.e. it has a valid
+// time span and it belongs to the given list of trusted certificates).
+func checkTrustState(cert x509.Certificate, trustedCerts []x509.Certificate) bool {
+	// Extra validity check (should have been caught by TLS stack)
+	if time.Now().Before(cert.NotBefore) || time.Now().After(cert.NotAfter) {
+		return false
+	}
+
+	for k, v := range trustedCerts {
+		if bytes.Compare(cert.Raw, v.Raw) == 0 {
+			logger.Debug("Found cert", log.Ctx{"k": k})
+			return true
+		}
+	}
+
+	return false
+}

From db2ebfa01b5ae72e331b11a7bb5f4ac807b644db Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:32:22 +0000
Subject: [PATCH 1167/1193] Make lxd/.dir-locals.el play nice with flycheck

Pass it -tags libsqlite3, otherwise it would compile the vendored
sqlite C code in the go-sqlite3 package each time the Go checker runs.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/.dir-locals.el | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lxd/.dir-locals.el b/lxd/.dir-locals.el
index 9bebcc48c..300939c29 100644
--- a/lxd/.dir-locals.el
+++ b/lxd/.dir-locals.el
@@ -1,3 +1,8 @@
 ;;; Directory Local Variables
 ;;; For more information see (info "(emacs) Directory Variables")
-((go-mode . ((go-test-args . "-tags libsqlite3"))))
+((go-mode
+  . ((go-test-args . "-tags libsqlite3 -timeout 10s")
+     (eval
+      . (set
+	 (make-local-variable 'flycheck-go-build-tags)
+	 '("libsqlite3"))))))

From 345530b39a0c8356be15b762a05932971c61e21f Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:34:31 +0000
Subject: [PATCH 1168/1193] Move Daemon.isRecursionRequest to the lxd/util
 sub-package

Extract the Daemon.isRecursionRequest method into a standalone
function in the lxd/util package.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/certificates.go   |  2 +-
 lxd/containers_get.go |  3 ++-
 lxd/daemon.go         | 10 ----------
 lxd/images.go         |  4 ++--
 lxd/operations.go     |  3 ++-
 lxd/profiles.go       |  3 ++-
 lxd/util/http.go      | 14 ++++++++++++++
 7 files changed, 23 insertions(+), 16 deletions(-)

diff --git a/lxd/certificates.go b/lxd/certificates.go
index 2501e988e..7fc88c256 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -19,7 +19,7 @@ import (
 )
 
 func certificatesGet(d *Daemon, r *http.Request) Response {
-	recursion := d.isRecursionRequest(r)
+	recursion := util.IsRecursionRequest(r)
 
 	if recursion {
 		certResponses := []api.Certificate{}
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index adf6d384c..89564ed61 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/version"
@@ -13,7 +14,7 @@ import (
 
 func containersGet(d *Daemon, r *http.Request) Response {
 	for i := 0; i < 100; i++ {
-		result, err := doContainersGet(d, d.isRecursionRequest(r))
+		result, err := doContainersGet(d, util.IsRecursionRequest(r))
 		if err == nil {
 			return SyncResponse(true, result)
 		}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index f27d0d14a..80b6270ef 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -127,16 +127,6 @@ func isJSONRequest(r *http.Request) bool {
 	return false
 }
 
-func (d *Daemon) isRecursionRequest(r *http.Request) bool {
-	recursionStr := r.FormValue("recursion")
-	recursion, err := strconv.Atoi(recursionStr)
-	if err != nil {
-		return false
-	}
-
-	return recursion == 1
-}
-
 func (d *Daemon) createCmd(version string, c Command) {
 	var uri string
 	if c.name == "" {
diff --git a/lxd/images.go b/lxd/images.go
index cec730bed..5a5d1659e 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -818,7 +818,7 @@ func doImagesGet(d *Daemon, recursion bool, public bool) (interface{}, error) {
 func imagesGet(d *Daemon, r *http.Request) Response {
 	public := !util.IsTrustedClient(r, d.clientCerts)
 
-	result, err := doImagesGet(d, d.isRecursionRequest(r), public)
+	result, err := doImagesGet(d, util.IsRecursionRequest(r), public)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -1115,7 +1115,7 @@ func aliasesPost(d *Daemon, r *http.Request) Response {
 }
 
 func aliasesGet(d *Daemon, r *http.Request) Response {
-	recursion := d.isRecursionRequest(r)
+	recursion := util.IsRecursionRequest(r)
 
 	q := "SELECT name FROM images_aliases"
 	var name string
diff --git a/lxd/operations.go b/lxd/operations.go
index 043181d81..8353f22e5 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -11,6 +11,7 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/pborman/uuid"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/cancel"
@@ -476,7 +477,7 @@ var operationCmd = Command{name: "operations/{id}", get: operationAPIGet, delete
 func operationsAPIGet(d *Daemon, r *http.Request) Response {
 	var md shared.Jmap
 
-	recursion := d.isRecursionRequest(r)
+	recursion := util.IsRecursionRequest(r)
 
 	md = shared.Jmap{}
 
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 285f393c6..e23080f64 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -10,6 +10,7 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -25,7 +26,7 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	recursion := d.isRecursionRequest(r)
+	recursion := util.IsRecursionRequest(r)
 
 	resultString := make([]string, len(results))
 	resultMap := make([]*api.Profile, len(results))
diff --git a/lxd/util/http.go b/lxd/util/http.go
index 6d66370ee..010f7d637 100644
--- a/lxd/util/http.go
+++ b/lxd/util/http.go
@@ -9,6 +9,7 @@ import (
 	"io"
 	"net/http"
 	"net/url"
+	"strconv"
 	"time"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -122,3 +123,16 @@ func checkTrustState(cert x509.Certificate, trustedCerts []x509.Certificate) boo
 
 	return false
 }
+
+// IsRecursionRequest checks whether the given HTTP request is marked with the
+// "recursion" flag in its form values.
+func IsRecursionRequest(r *http.Request) bool {
+	recursionStr := r.FormValue("recursion")
+
+	recursion, err := strconv.Atoi(recursionStr)
+	if err != nil {
+		return false
+	}
+
+	return recursion == 1
+}

From 5798bf110f6166d73fe7e901c086cd2a942f19b4 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:39:16 +0000
Subject: [PATCH 1169/1193] Extract Daemon.SetupStorageDriver into a standalone
 function

Convert the Daemon.SetupStorageDriver method into a standalone
function accepting a Daemon parameter and move it to the storage.go
file. Later down the road it should be possible to make the function
accept a more narrow API as parameter than the whole Daemon structure,
for decoupling, easier dependency management and unit-testability.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon.go        | 36 +-----------------------------------
 lxd/daemon_config.go |  2 +-
 lxd/storage.go       | 34 ++++++++++++++++++++++++++++++++++
 3 files changed, 36 insertions(+), 36 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 80b6270ef..2705db09c 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -216,40 +216,6 @@ func (d *Daemon) createCmd(version string, c Command) {
 	})
 }
 
-func (d *Daemon) SetupStorageDriver() error {
-	var err error
-
-	lvmVgName := daemonConfig["storage.lvm_vg_name"].Get()
-	zfsPoolName := daemonConfig["storage.zfs_pool_name"].Get()
-
-	if lvmVgName != "" {
-		d.Storage, err = newStorage(d, storageTypeLvm)
-		if err != nil {
-			logger.Errorf("Could not initialize storage type LVM: %s - falling back to dir", err)
-		} else {
-			return nil
-		}
-	} else if zfsPoolName != "" {
-		d.Storage, err = newStorage(d, storageTypeZfs)
-		if err != nil {
-			logger.Errorf("Could not initialize storage type ZFS: %s - falling back to dir", err)
-		} else {
-			return nil
-		}
-	} else if d.BackingFs == "btrfs" {
-		d.Storage, err = newStorage(d, storageTypeBtrfs)
-		if err != nil {
-			logger.Errorf("Could not initialize storage type btrfs: %s - falling back to dir", err)
-		} else {
-			return nil
-		}
-	}
-
-	d.Storage, err = newStorage(d, storageTypeDir)
-
-	return err
-}
-
 // have we setup shared mounts?
 var sharedMounted bool
 var sharedMountsLock sync.Mutex
@@ -683,7 +649,7 @@ func (d *Daemon) Init() error {
 
 	if !d.MockMode {
 		/* Setup the storage driver */
-		err = d.SetupStorageDriver()
+		err = SetupStorageDriver(d)
 		if err != nil {
 			return fmt.Errorf("Failed to setup storage: %s", err)
 		}
diff --git a/lxd/daemon_config.go b/lxd/daemon_config.go
index 14ae6cf1a..9f58e97ca 100644
--- a/lxd/daemon_config.go
+++ b/lxd/daemon_config.go
@@ -273,7 +273,7 @@ func daemonConfigSetStorage(d *Daemon, key string, value string) (string, error)
 	}()
 
 	// Update the current storage driver
-	err := d.SetupStorageDriver()
+	err := SetupStorageDriver(d)
 	if err != nil {
 		return "", err
 	}
diff --git a/lxd/storage.go b/lxd/storage.go
index 08caadb58..b92dbc9e3 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -762,3 +762,37 @@ func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, c
 
 	return nil
 }
+
+func SetupStorageDriver(d *Daemon) error {
+	var err error
+
+	lvmVgName := daemonConfig["storage.lvm_vg_name"].Get()
+	zfsPoolName := daemonConfig["storage.zfs_pool_name"].Get()
+
+	if lvmVgName != "" {
+		d.Storage, err = newStorage(d, storageTypeLvm)
+		if err != nil {
+			logger.Errorf("Could not initialize storage type LVM: %s - falling back to dir", err)
+		} else {
+			return nil
+		}
+	} else if zfsPoolName != "" {
+		d.Storage, err = newStorage(d, storageTypeZfs)
+		if err != nil {
+			logger.Errorf("Could not initialize storage type ZFS: %s - falling back to dir", err)
+		} else {
+			return nil
+		}
+	} else if d.BackingFs == "btrfs" {
+		d.Storage, err = newStorage(d, storageTypeBtrfs)
+		if err != nil {
+			logger.Errorf("Could not initialize storage type btrfs: %s - falling back to dir", err)
+		} else {
+			return nil
+		}
+	}
+
+	d.Storage, err = newStorage(d, storageTypeDir)
+
+	return err
+}

From 1a4e1d01d68a069d4d78654d8c980d959f7d00c7 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:46:22 +0000
Subject: [PATCH 1170/1193] Extract Daemon.ListenAddresses into a standalone
 function

Move the Daemon.ListenAddresses method which has no dependency on
Daemon into a standalone function in the lxd/util sub-package.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/api_1.0.go   |  2 +-
 lxd/daemon.go    | 60 -------------------------------------------------------
 lxd/util/http.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+), 61 deletions(-)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index dea90e5cf..145da2e1e 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -113,7 +113,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		kernelArchitecture += string(byte(c))
 	}
 
-	addresses, err := d.ListenAddresses()
+	addresses, err := util.ListenAddresses(daemonConfig["core.https_address"].Get())
 	if err != nil {
 		return InternalError(err)
 	}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 2705db09c..84990b7fb 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -252,66 +252,6 @@ func setupSharedMounts() error {
 	return nil
 }
 
-func (d *Daemon) ListenAddresses() ([]string, error) {
-	addresses := make([]string, 0)
-
-	value := daemonConfig["core.https_address"].Get()
-	if value == "" {
-		return addresses, nil
-	}
-
-	localHost, localPort, err := net.SplitHostPort(value)
-	if err != nil {
-		localHost = value
-		localPort = shared.DefaultPort
-	}
-
-	if localHost == "0.0.0.0" || localHost == "::" || localHost == "[::]" {
-		ifaces, err := net.Interfaces()
-		if err != nil {
-			return addresses, err
-		}
-
-		for _, i := range ifaces {
-			addrs, err := i.Addrs()
-			if err != nil {
-				continue
-			}
-
-			for _, addr := range addrs {
-				var ip net.IP
-				switch v := addr.(type) {
-				case *net.IPNet:
-					ip = v.IP
-				case *net.IPAddr:
-					ip = v.IP
-				}
-
-				if !ip.IsGlobalUnicast() {
-					continue
-				}
-
-				if ip.To4() == nil {
-					if localHost == "0.0.0.0" {
-						continue
-					}
-					addresses = append(addresses, fmt.Sprintf("[%s]:%s", ip, localPort))
-				} else {
-					addresses = append(addresses, fmt.Sprintf("%s:%s", ip, localPort))
-				}
-			}
-		}
-	} else {
-		if strings.Contains(localHost, ":") {
-			addresses = append(addresses, fmt.Sprintf("[%s]:%s", localHost, localPort))
-		} else {
-			addresses = append(addresses, fmt.Sprintf("%s:%s", localHost, localPort))
-		}
-	}
-
-	return addresses, nil
-}
-
 func (d *Daemon) UpdateHTTPsPort(newAddress string) error {
 	oldAddress := daemonConfig["core.https_address"].Get()
 
diff --git a/lxd/util/http.go b/lxd/util/http.go
index 010f7d637..66732f0e8 100644
--- a/lxd/util/http.go
+++ b/lxd/util/http.go
@@ -7,9 +7,11 @@ import (
 	"encoding/pem"
 	"fmt"
 	"io"
+	"net"
 	"net/http"
 	"net/url"
 	"strconv"
+	"strings"
 	"time"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -136,3 +138,62 @@ func IsRecursionRequest(r *http.Request) bool {
 
 	return recursion == 1
 }
+
+func ListenAddresses(value string) ([]string, error) {
+	addresses := make([]string, 0)
+
+	if value == "" {
+		return addresses, nil
+	}
+
+	localHost, localPort, err := net.SplitHostPort(value)
+	if err != nil {
+		localHost = value
+		localPort = shared.DefaultPort
+	}
+
+	if localHost == "0.0.0.0" || localHost == "::" || localHost == "[::]" {
+		ifaces, err := net.Interfaces()
+		if err != nil {
+			return addresses, err
+		}
+
+		for _, i := range ifaces {
+			addrs, err := i.Addrs()
+			if err != nil {
+				continue
+			}
+
+			for _, addr := range addrs {
+				var ip net.IP
+				switch v := addr.(type) {
+				case *net.IPNet:
+					ip = v.IP
+				case *net.IPAddr:
+					ip = v.IP
+				}
+
+				if !ip.IsGlobalUnicast() {
+					continue
+				}
+
+				if ip.To4() == nil {
+					if localHost == "0.0.0.0" {
+						continue
+					}
+					addresses = append(addresses, fmt.Sprintf("[%s]:%s", ip, localPort))
+				} else {
+					addresses = append(addresses, fmt.Sprintf("%s:%s", ip, localPort))
+				}
+			}
+		}
+	} else {
+		if strings.Contains(localHost, ":") {
+			addresses = append(addresses, fmt.Sprintf("[%s]:%s", localHost, localPort))
+		} else {
+			addresses = append(addresses, fmt.Sprintf("%s:%s", localHost, localPort))
+		}
+	}
+
+	return addresses, nil
+}

From bc658259fbc0344aedc5129a19cf1a9ed5d6f1e6 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:47:31 +0000
Subject: [PATCH 1171/1193] Extract Daemon.PasswordCheck into a standalone
 function

Turn the Daemon.PasswordCheck method, which has effectively no
dependency on Daemon, into a standalone function in the lxd/util
sub-package.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/certificates.go    |  3 ++-
 lxd/daemon.go          | 30 ------------------------------
 lxd/main_init_test.go  |  2 +-
 lxd/util/encryption.go | 34 ++++++++++++++++++++++++++++++++++
 4 files changed, 37 insertions(+), 32 deletions(-)
 create mode 100644 lxd/util/encryption.go

diff --git a/lxd/certificates.go b/lxd/certificates.go
index 7fc88c256..7b9bfed1b 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -96,7 +96,8 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Access check
-	if !util.IsTrustedClient(r, d.clientCerts) && d.PasswordCheck(req.Password) != nil {
+	secret := daemonConfig["core.trust_password"].Get()
+	if !util.IsTrustedClient(r, d.clientCerts) && util.PasswordCheck(secret, req.Password) != nil {
 		return Forbidden
 	}
 
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 84990b7fb..d8a5646ff 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -5,7 +5,6 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"database/sql"
-	"encoding/hex"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -21,8 +20,6 @@ import (
 	"syscall"
 	"time"
 
-	"golang.org/x/crypto/scrypt"
-
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
 	"github.com/syndtr/gocapability/capability"
@@ -970,33 +967,6 @@ func (d *Daemon) Stop() error {
 	return err
 }
 
-func (d *Daemon) PasswordCheck(password string) error {
-	value := daemonConfig["core.trust_password"].Get()
-
-	// No password set
-	if value == "" {
-		return fmt.Errorf("No password is set")
-	}
-
-	// Compare the password
-	buff, err := hex.DecodeString(value)
-	if err != nil {
-		return err
-	}
-
-	salt := buff[0:32]
-	hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, 64)
-	if err != nil {
-		return err
-	}
-
-	if !bytes.Equal(hash, buff[32:]) {
-		return fmt.Errorf("Bad password provided")
-	}
-
-	return nil
-}
-
 func (d *Daemon) ExpireLogs() error {
 	entries, err := ioutil.ReadDir(shared.LogPath())
 	if err != nil {
diff --git a/lxd/main_init_test.go b/lxd/main_init_test.go
index b02313c07..b5249f3a3 100644
--- a/lxd/main_init_test.go
+++ b/lxd/main_init_test.go
@@ -3,7 +3,7 @@ package main
 import (
 	"testing"
 
-	lxd "github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/shared/cmd"
 	"github.com/stretchr/testify/suite"
 )
diff --git a/lxd/util/encryption.go b/lxd/util/encryption.go
new file mode 100644
index 000000000..a015bf514
--- /dev/null
+++ b/lxd/util/encryption.go
@@ -0,0 +1,34 @@
+package util
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+
+	"golang.org/x/crypto/scrypt"
+)
+
+func PasswordCheck(secret, password string) error {
+	// No password set
+	if secret == "" {
+		return fmt.Errorf("No password is set")
+	}
+
+	// Compare the password
+	buff, err := hex.DecodeString(secret)
+	if err != nil {
+		return err
+	}
+
+	salt := buff[0:32]
+	hash, err := scrypt.Key([]byte(password), salt, 1<<14, 8, 1, 64)
+	if err != nil {
+		return err
+	}
+
+	if !bytes.Equal(hash, buff[32:]) {
+		return fmt.Errorf("Bad password provided")
+	}
+
+	return nil
+}

From 4e7ff43faa6b4aa8a09ffa05908f2afbad652510 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:48:10 +0000
Subject: [PATCH 1172/1193] Extract Daemon.ExpireLogs into a standalone
 function

Turn the Daemon.ExpireLogs method, which only depends on sql.DB, into
a standalone function.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon.go  | 84 +---------------------------------------------------
 lxd/logging.go | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 94 insertions(+), 83 deletions(-)
 create mode 100644 lxd/logging.go

diff --git a/lxd/daemon.go b/lxd/daemon.go
index d8a5646ff..9d0149c38 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -610,7 +610,7 @@ func (d *Daemon) Init() error {
 		for {
 			logger.Infof("Expiring log files")
 
-			err := d.ExpireLogs()
+			err := ExpireLogs(d.db)
 			if err != nil {
 				logger.Error("Failed to expire logs", log.Ctx{"err": err})
 			}
@@ -967,88 +967,6 @@ func (d *Daemon) Stop() error {
 	return err
 }
 
-func (d *Daemon) ExpireLogs() error {
-	entries, err := ioutil.ReadDir(shared.LogPath())
-	if err != nil {
-		return err
-	}
-
-	result, err := db.ContainersList(d.db, db.CTypeRegular)
-	if err != nil {
-		return err
-	}
-
-	newestFile := func(path string, dir os.FileInfo) time.Time {
-		newest := dir.ModTime()
-
-		entries, err := ioutil.ReadDir(path)
-		if err != nil {
-			return newest
-		}
-
-		for _, entry := range entries {
-			if entry.ModTime().After(newest) {
-				newest = entry.ModTime()
-			}
-		}
-
-		return newest
-	}
-
-	for _, entry := range entries {
-		// Check if the container still exists
-		if shared.StringInSlice(entry.Name(), result) {
-			// Remove any log file which wasn't modified in the past 48 hours
-			logs, err := ioutil.ReadDir(shared.LogPath(entry.Name()))
-			if err != nil {
-				return err
-			}
-
-			for _, logfile := range logs {
-				path := shared.LogPath(entry.Name(), logfile.Name())
-
-				// Always keep the LXC config
-				if logfile.Name() == "lxc.conf" {
-					continue
-				}
-
-				// Deal with directories (snapshots)
-				if logfile.IsDir() {
-					newest := newestFile(path, logfile)
-					if time.Since(newest).Hours() >= 48 {
-						os.RemoveAll(path)
-						if err != nil {
-							return err
-						}
-					}
-
-					continue
-				}
-
-				// Individual files
-				if time.Since(logfile.ModTime()).Hours() >= 48 {
-					err := os.Remove(path)
-					if err != nil {
-						return err
-					}
-				}
-			}
-		} else {
-			// Empty directory if unchanged in the past 24 hours
-			path := shared.LogPath(entry.Name())
-			newest := newestFile(path, entry)
-			if time.Since(newest).Hours() >= 24 {
-				err := os.RemoveAll(path)
-				if err != nil {
-					return err
-				}
-			}
-		}
-	}
-
-	return nil
-}
-
 func (d *Daemon) GetListeners() []net.Listener {
 	defer func() {
 		os.Unsetenv("LISTEN_PID")
diff --git a/lxd/logging.go b/lxd/logging.go
new file mode 100644
index 000000000..3c5c120a0
--- /dev/null
+++ b/lxd/logging.go
@@ -0,0 +1,93 @@
+package main
+
+import (
+	"database/sql"
+	"io/ioutil"
+	"os"
+	"time"
+
+	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/shared"
+)
+
+func ExpireLogs(dbObj *sql.DB) error {
+	entries, err := ioutil.ReadDir(shared.LogPath())
+	if err != nil {
+		return err
+	}
+
+	result, err := db.ContainersList(dbObj, db.CTypeRegular)
+	if err != nil {
+		return err
+	}
+
+	newestFile := func(path string, dir os.FileInfo) time.Time {
+		newest := dir.ModTime()
+
+		entries, err := ioutil.ReadDir(path)
+		if err != nil {
+			return newest
+		}
+
+		for _, entry := range entries {
+			if entry.ModTime().After(newest) {
+				newest = entry.ModTime()
+			}
+		}
+
+		return newest
+	}
+
+	for _, entry := range entries {
+		// Check if the container still exists
+		if shared.StringInSlice(entry.Name(), result) {
+			// Remove any log file which wasn't modified in the past 48 hours
+			logs, err := ioutil.ReadDir(shared.LogPath(entry.Name()))
+			if err != nil {
+				return err
+			}
+
+			for _, logfile := range logs {
+				path := shared.LogPath(entry.Name(), logfile.Name())
+
+				// Always keep the LXC config
+				if logfile.Name() == "lxc.conf" {
+					continue
+				}
+
+				// Deal with directories (snapshots)
+				if logfile.IsDir() {
+					newest := newestFile(path, logfile)
+					if time.Since(newest).Hours() >= 48 {
+						os.RemoveAll(path)
+						if err != nil {
+							return err
+						}
+					}
+
+					continue
+				}
+
+				// Individual files
+				if time.Since(logfile.ModTime()).Hours() >= 48 {
+					err := os.Remove(path)
+					if err != nil {
+						return err
+					}
+				}
+			}
+		} else {
+			// Empty directory if unchanged in the past 24 hours
+			path := shared.LogPath(entry.Name())
+			newest := newestFile(path, entry)
+			if time.Since(newest).Hours() >= 24 {
+				err := os.RemoveAll(path)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	return nil
+}

From 8b3e361f334968aad6aca3cd496a27d00397be2f Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:48:25 +0000
Subject: [PATCH 1173/1193] Extract Daemon.GetListeners into a standalone
 function

Turn Daemon.GetListeners, which has no dependency on Daemon, into a
standalone function under the lxd/util sub-package.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon.go    | 39 +--------------------------------------
 lxd/util/http.go | 39 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 38 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 9d0149c38..783a71f4e 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -712,7 +712,7 @@ func (d *Daemon) Init() error {
 	})
 
 	// Prepare the list of listeners
-	listeners := d.GetListeners()
+	listeners := util.GetListeners()
 	if len(listeners) > 0 {
 		logger.Infof("LXD is socket activated")
 
@@ -967,43 +967,6 @@ func (d *Daemon) Stop() error {
 	return err
 }
 
-func (d *Daemon) GetListeners() []net.Listener {
-	defer func() {
-		os.Unsetenv("LISTEN_PID")
-		os.Unsetenv("LISTEN_FDS")
-	}()
-
-	pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
-	if err != nil {
-		return nil
-	}
-
-	if pid != os.Getpid() {
-		return nil
-	}
-
-	fds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
-	if err != nil {
-		return nil
-	}
-
-	listeners := []net.Listener{}
-
-	for i := 3; i < 3+fds; i++ {
-		syscall.CloseOnExec(i)
-
-		file := os.NewFile(uintptr(i), fmt.Sprintf("inherited-fd%d", i))
-		listener, err := net.FileListener(file)
-		if err != nil {
-			continue
-		}
-
-		listeners = append(listeners, listener)
-	}
-
-	return listeners
-}
-
 type lxdHttpServer struct {
 	r *mux.Router
 	d *Daemon
diff --git a/lxd/util/http.go b/lxd/util/http.go
index 66732f0e8..e422383aa 100644
--- a/lxd/util/http.go
+++ b/lxd/util/http.go
@@ -10,8 +10,10 @@ import (
 	"net"
 	"net/http"
 	"net/url"
+	"os"
 	"strconv"
 	"strings"
+	"syscall"
 	"time"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -197,3 +199,40 @@ func ListenAddresses(value string) ([]string, error) {
 
 	return addresses, nil
 }
+
+func GetListeners() []net.Listener {
+	defer func() {
+		os.Unsetenv("LISTEN_PID")
+		os.Unsetenv("LISTEN_FDS")
+	}()
+
+	pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
+	if err != nil {
+		return nil
+	}
+
+	if pid != os.Getpid() {
+		return nil
+	}
+
+	fds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
+	if err != nil {
+		return nil
+	}
+
+	listeners := []net.Listener{}
+
+	for i := 3; i < 3+fds; i++ {
+		syscall.CloseOnExec(i)
+
+		file := os.NewFile(uintptr(i), fmt.Sprintf("inherited-fd%d", i))
+		listener, err := net.FileListener(file)
+		if err != nil {
+			continue
+		}
+
+		listeners = append(listeners, listener)
+	}
+
+	return listeners
+}

From 7a1c7e03ac004110dab43ef9c5982c5932a30bad Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:49:36 +0000
Subject: [PATCH 1174/1193] Add initial lxd/sys sub-package and OperatingSystem
 structure

Introduce an initial implementation of the sys.OS struct, which is
meant to be a high-level API to access system-related functionality.

For now it has a single attribute, OS.Architectures, to access the
available architectures (whose core generic logic has been moved to
the lxd/util sub-package).

The Daemon.architectures attribute has been dropped and replaced with
an instance of the OS struct. Further parts of Daemon will be factored
out down the road.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/api_1.0.go       |  2 +-
 lxd/container.go     |  4 ++--
 lxd/container_lxc.go |  6 +++---
 lxd/daemon.go        | 26 +++++---------------------
 lxd/images.go        |  2 +-
 lxd/sys/os.go        | 23 +++++++++++++++++++++++
 lxd/util/sys.go      | 30 ++++++++++++++++++++++++++++++
 7 files changed, 65 insertions(+), 28 deletions(-)
 create mode 100644 lxd/sys/os.go
 create mode 100644 lxd/util/sys.go

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 145da2e1e..7da9f584d 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -130,7 +130,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 
 	architectures := []string{}
 
-	for _, architecture := range d.architectures {
+	for _, architecture := range d.os.Architectures {
 		architectureName, err := osarch.ArchitectureName(architecture)
 		if err != nil {
 			return InternalError(err)
diff --git a/lxd/container.go b/lxd/container.go
index f208ddadb..e2b20b9cd 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -729,7 +729,7 @@ func containerCreateInternal(d *Daemon, args db.ContainerArgs) (container, error
 	}
 
 	if args.Architecture == 0 {
-		args.Architecture = d.architectures[0]
+		args.Architecture = d.os.Architectures[0]
 	}
 
 	// Validate container name
@@ -758,7 +758,7 @@ func containerCreateInternal(d *Daemon, args db.ContainerArgs) (container, error
 		return nil, err
 	}
 
-	if !shared.IntInSlice(args.Architecture, d.architectures) {
+	if !shared.IntInSlice(args.Architecture, d.os.Architectures) {
 		return nil, fmt.Errorf("Requested architecture isn't supported by this host")
 	}
 
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 3f72cb20a..108ed4a3e 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -936,7 +936,7 @@ func (c *containerLXC) initLXC() error {
 	// Setup architecture
 	personality, err := osarch.ArchitecturePersonality(c.architecture)
 	if err != nil {
-		personality, err = osarch.ArchitecturePersonality(c.daemon.architectures[0])
+		personality, err = osarch.ArchitecturePersonality(c.daemon.os.Architectures[0])
 		if err != nil {
 			return err
 		}
@@ -3409,7 +3409,7 @@ func (c *containerLXC) Export(w io.Writer, properties map[string]string) error {
 		}
 
 		if arch == "" {
-			arch, err = osarch.ArchitectureName(c.daemon.architectures[0])
+			arch, err = osarch.ArchitectureName(c.daemon.os.Architectures[0])
 			if err != nil {
 				logger.Error("Failed exporting container", ctxMap)
 				return err
@@ -3841,7 +3841,7 @@ func (c *containerLXC) templateApplyNow(trigger string) error {
 		// Figure out the architecture
 		arch, err := osarch.ArchitectureName(c.architecture)
 		if err != nil {
-			arch, err = osarch.ArchitectureName(c.daemon.architectures[0])
+			arch, err = osarch.ArchitectureName(c.daemon.os.Architectures[0])
 			if err != nil {
 				return err
 			}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 783a71f4e..724964245 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -27,11 +27,11 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
-	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -64,9 +64,9 @@ type Socket struct {
 
 // A Daemon can respond to requests from a shared client.
 type Daemon struct {
-	architectures       []int
 	BackingFs           string
 	clientCerts         []x509.Certificate
+	os                  *sys.OS
 	db                  *sql.DB
 	group               string
 	IdmapSet            *shared.IdmapSet
@@ -466,28 +466,12 @@ func (d *Daemon) Init() error {
 		logger.Warnf("CGroup memory swap accounting is disabled, swap limits will be ignored.")
 	}
 
-	/* Get the list of supported architectures */
-	var architectures = []int{}
-
-	architectureName, err := osarch.ArchitectureGetLocal()
-	if err != nil {
-		return err
-	}
-
-	architecture, err := osarch.ArchitectureId(architectureName)
+	/* Initialize the operating system facade */
+	d.os = &sys.OS{}
+	err = d.os.Init()
 	if err != nil {
 		return err
 	}
-	architectures = append(architectures, architecture)
-
-	personalities, err := osarch.ArchitecturePersonalities(architecture)
-	if err != nil {
-		return err
-	}
-	for _, personality := range personalities {
-		architectures = append(architectures, personality)
-	}
-	d.architectures = architectures
 
 	/* Set container path */
 	d.lxcpath = shared.VarPath("containers")
diff --git a/lxd/images.go b/lxd/images.go
index 5a5d1659e..8e80fa834 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -386,7 +386,7 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation) (*api.Image, e
 	}
 
 	architecturesStr := []string{}
-	for _, arch := range d.architectures {
+	for _, arch := range d.os.Architectures {
 		architecturesStr = append(architecturesStr, fmt.Sprintf("%d", arch))
 	}
 
diff --git a/lxd/sys/os.go b/lxd/sys/os.go
new file mode 100644
index 000000000..e312b3224
--- /dev/null
+++ b/lxd/sys/os.go
@@ -0,0 +1,23 @@
+package sys
+
+import (
+	"github.com/lxc/lxd/lxd/util"
+)
+
+// OS is a high-level facade for accessing all operating-system
+// level functionality that LXD uses.
+type OS struct {
+	Architectures []int // Cache of detected system architectures
+}
+
+// Init our internal data structures.
+func (s *OS) Init() error {
+	var err error
+
+	s.Architectures, err = util.GetArchitectures()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/lxd/util/sys.go b/lxd/util/sys.go
new file mode 100644
index 000000000..d1cc9f519
--- /dev/null
+++ b/lxd/util/sys.go
@@ -0,0 +1,30 @@
+package util
+
+import (
+	"github.com/lxc/lxd/shared/osarch"
+)
+
+// GetArchitectures returns the list of supported architectures.
+func GetArchitectures() ([]int, error) {
+	architectures := []int{}
+
+	architectureName, err := osarch.ArchitectureGetLocal()
+	if err != nil {
+		return nil, err
+	}
+
+	architecture, err := osarch.ArchitectureId(architectureName)
+	if err != nil {
+		return nil, err
+	}
+	architectures = append(architectures, architecture)
+
+	personalities, err := osarch.ArchitecturePersonalities(architecture)
+	if err != nil {
+		return nil, err
+	}
+	for _, personality := range personalities {
+		architectures = append(architectures, personality)
+	}
+	return architectures, nil
+}

From 91ab4b193c98dc6179e26325c46080593fbc1b32 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:53:05 +0000
Subject: [PATCH 1175/1193] Move filesystemDetect function into lxd/util
 subpackage.

This will make it possible to use util.FilesystemDetect from other
sub-packages, in particular the lxd/sys one.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon.go                              |  2 +-
 lxd/devices.go                             |  3 +-
 lxd/storage.go                             | 44 ++----------------------------
 lxd/util/fs.go                             | 44 ++++++++++++++++++++++++++++++
 lxd/{storage_32bit.go => util/fs_32bit.go} |  4 +--
 lxd/{storage_64bit.go => util/fs_64bit.go} |  4 +--
 6 files changed, 53 insertions(+), 48 deletions(-)
 create mode 100644 lxd/util/fs.go
 rename lxd/{storage_32bit.go => util/fs_32bit.go} (73%)
 rename lxd/{storage_64bit.go => util/fs_64bit.go} (50%)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 724964245..2d088f491 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -509,7 +509,7 @@ func (d *Daemon) Init() error {
 	}
 
 	/* Detect the filesystem */
-	d.BackingFs, err = filesystemDetect(d.lxcpath)
+	d.BackingFs, err = util.FilesystemDetect(d.lxcpath)
 	if err != nil {
 		logger.Error("Error detecting backing fs", log.Ctx{"err": err})
 	}
diff --git a/lxd/devices.go b/lxd/devices.go
index 4f92bfe56..bde9cfc7a 100644
--- a/lxd/devices.go
+++ b/lxd/devices.go
@@ -19,6 +19,7 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 
@@ -734,7 +735,7 @@ func deviceGetParentBlocks(path string) ([]string, error) {
 
 	// Deal with per-filesystem oddities. We don't care about failures here
 	// because any non-special filesystem => directory backend.
-	fs, _ := filesystemDetect(expPath)
+	fs, _ := util.FilesystemDetect(expPath)
 
 	if fs == "zfs" && shared.PathExists("/dev/zfs") {
 		// Accessible zfs filesystems
diff --git a/lxd/storage.go b/lxd/storage.go
index b92dbc9e3..c7dbdb557 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -6,12 +6,12 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
-	"syscall"
 
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/types"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -20,46 +20,6 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-/* Some interesting filesystems */
-const (
-	filesystemSuperMagicTmpfs = 0x01021994
-	filesystemSuperMagicExt4  = 0xEF53
-	filesystemSuperMagicXfs   = 0x58465342
-	filesystemSuperMagicNfs   = 0x6969
-	filesystemSuperMagicZfs   = 0x2fc12fc1
-)
-
-/*
- * filesystemDetect returns the filesystem on which
- * the passed-in path sits
- */
-func filesystemDetect(path string) (string, error) {
-	fs := syscall.Statfs_t{}
-
-	err := syscall.Statfs(path, &fs)
-	if err != nil {
-		return "", err
-	}
-
-	switch fs.Type {
-	case filesystemSuperMagicBtrfs:
-		return "btrfs", nil
-	case filesystemSuperMagicZfs:
-		return "zfs", nil
-	case filesystemSuperMagicTmpfs:
-		return "tmpfs", nil
-	case filesystemSuperMagicExt4:
-		return "ext4", nil
-	case filesystemSuperMagicXfs:
-		return "xfs", nil
-	case filesystemSuperMagicNfs:
-		return "nfs", nil
-	default:
-		logger.Debugf("Unknown backing filesystem type: 0x%x", fs.Type)
-		return string(fs.Type), nil
-	}
-}
-
 // storageRsyncCopy copies a directory using rsync (with the --devices option).
 func storageRsyncCopy(source string, dest string) (string, error) {
 	if err := os.MkdirAll(dest, 0755); err != nil {
@@ -251,7 +211,7 @@ func storageForFilename(d *Daemon, filename string) (storage, error) {
 	}
 
 	if shared.PathExists(filename) {
-		filesystem, err = filesystemDetect(filename)
+		filesystem, err = util.FilesystemDetect(filename)
 		if err != nil {
 			return nil, fmt.Errorf("couldn't detect filesystem for '%s': %v", filename, err)
 		}
diff --git a/lxd/util/fs.go b/lxd/util/fs.go
new file mode 100644
index 000000000..a09da3fa7
--- /dev/null
+++ b/lxd/util/fs.go
@@ -0,0 +1,44 @@
+package util
+
+import (
+	"syscall"
+
+	"github.com/lxc/lxd/shared/logger"
+)
+
+// Filesystem magic numbers
+const (
+	FilesystemSuperMagicTmpfs = 0x01021994
+	FilesystemSuperMagicExt4  = 0xEF53
+	FilesystemSuperMagicXfs   = 0x58465342
+	FilesystemSuperMagicNfs   = 0x6969
+	FilesystemSuperMagicZfs   = 0x2fc12fc1
+)
+
+// FilesystemDetect returns the filesystem on which the passed-in path sits.
+func FilesystemDetect(path string) (string, error) {
+	fs := syscall.Statfs_t{}
+
+	err := syscall.Statfs(path, &fs)
+	if err != nil {
+		return "", err
+	}
+
+	switch fs.Type {
+	case FilesystemSuperMagicBtrfs:
+		return "btrfs", nil
+	case FilesystemSuperMagicZfs:
+		return "zfs", nil
+	case FilesystemSuperMagicTmpfs:
+		return "tmpfs", nil
+	case FilesystemSuperMagicExt4:
+		return "ext4", nil
+	case FilesystemSuperMagicXfs:
+		return "xfs", nil
+	case FilesystemSuperMagicNfs:
+		return "nfs", nil
+	default:
+		logger.Debugf("Unknown backing filesystem type: 0x%x", fs.Type)
+		return string(fs.Type), nil
+	}
+}
diff --git a/lxd/storage_32bit.go b/lxd/util/fs_32bit.go
similarity index 73%
rename from lxd/storage_32bit.go
rename to lxd/util/fs_32bit.go
index 42985f0d6..9e39f83fb 100644
--- a/lxd/storage_32bit.go
+++ b/lxd/util/fs_32bit.go
@@ -1,9 +1,9 @@
 // +build 386 arm ppc s390
 
-package main
+package util
 
 const (
 	/* This is really 0x9123683E, go wants us to give it in signed form
 	 * since we use it as a signed constant. */
-	filesystemSuperMagicBtrfs = -1859950530
+	FilesystemSuperMagicBtrfs = -1859950530
 )
diff --git a/lxd/storage_64bit.go b/lxd/util/fs_64bit.go
similarity index 50%
rename from lxd/storage_64bit.go
rename to lxd/util/fs_64bit.go
index d3c42a862..9ff591362 100644
--- a/lxd/storage_64bit.go
+++ b/lxd/util/fs_64bit.go
@@ -1,7 +1,7 @@
 // +build amd64 ppc64 ppc64le arm64 s390x
 
-package main
+package util
 
 const (
-	filesystemSuperMagicBtrfs = 0x9123683E
+	FilesystemSuperMagicBtrfs = 0x9123683E
 )

From 2773e1b1b44c8fa6544ddc6672000b38b50b9c3a Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:54:34 +0000
Subject: [PATCH 1176/1193] Move Daemon.lxcpath to OS.LxcPath

Move the Daemon.lxcpath attribute to the OS struct, as it's a
system-specific bit of info. It can now be accessed via
deamon.os.LxcPath(), which also prevents accidental mutation.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/container_lxc.go         | 16 ++++++++--------
 lxd/daemon.go                |  8 ++------
 lxd/main_activateifneeded.go |  4 +++-
 lxd/sys/os.go                | 12 +++++++++++-
 4 files changed, 24 insertions(+), 16 deletions(-)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 108ed4a3e..17b4495bd 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -771,7 +771,7 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Load the go-lxc struct
-	cc, err := lxc.NewContainer(c.Name(), c.daemon.lxcpath)
+	cc, err := lxc.NewContainer(c.Name(), c.daemon.os.LxcPath)
 	if err != nil {
 		return err
 	}
@@ -1766,7 +1766,7 @@ func (c *containerLXC) Start(stateful bool) error {
 		execPath,
 		"forkstart",
 		c.name,
-		c.daemon.lxcpath,
+		c.daemon.os.LxcPath,
 		configPath)
 
 	// Capture debug output
@@ -1810,7 +1810,7 @@ func (c *containerLXC) Start(stateful bool) error {
 		return fmt.Errorf(
 			"Error calling 'lxd forkstart %s %s %s': err='%v'%s",
 			c.name,
-			c.daemon.lxcpath,
+			c.daemon.os.LxcPath,
 			filepath.Join(c.LogPath(), "lxc.conf"),
 			err, lxcLog)
 	}
@@ -3658,7 +3658,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 			execPath,
 			"forkmigrate",
 			c.name,
-			c.daemon.lxcpath,
+			c.daemon.os.LxcPath,
 			configPath,
 			stateDir,
 			fmt.Sprintf("%v", preservesInodes))
@@ -3673,7 +3673,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 			migrateErr = fmt.Errorf(
 				"Error calling 'lxd forkmigrate %s %s %s %s': err='%v' out='%v'",
 				c.name,
-				c.daemon.lxcpath,
+				c.daemon.os.LxcPath,
 				filepath.Join(c.LogPath(), "lxc.conf"),
 				stateDir,
 				err,
@@ -4206,7 +4206,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
 	}
 
-	args := []string{execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
+	args := []string{execPath, "forkexec", c.name, c.daemon.os.LxcPath, filepath.Join(c.LogPath(), "lxc.conf")}
 
 	args = append(args, "--")
 	args = append(args, "env")
@@ -5044,7 +5044,7 @@ func (c *containerLXC) fillNetworkDevice(name string, m types.Device) (types.Dev
 		}
 
 		// Attempt to include all existing interfaces
-		cc, err := lxc.NewContainer(c.Name(), c.daemon.lxcpath)
+		cc, err := lxc.NewContainer(c.Name(), c.daemon.os.LxcPath)
 		if err == nil {
 			interfaces, err := cc.Interfaces()
 			if err == nil {
@@ -5223,7 +5223,7 @@ func (c *containerLXC) removeNetworkDevice(name string, m types.Device) error {
 	}
 
 	// For some reason, having network config confuses detach, so get our own go-lxc struct
-	cc, err := lxc.NewContainer(c.Name(), c.daemon.lxcpath)
+	cc, err := lxc.NewContainer(c.Name(), c.daemon.os.LxcPath)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 2d088f491..d55bbe612 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -70,7 +70,6 @@ type Daemon struct {
 	db                  *sql.DB
 	group               string
 	IdmapSet            *shared.IdmapSet
-	lxcpath             string
 	mux                 *mux.Router
 	tomb                tomb.Tomb
 	readyChan           chan bool
@@ -467,15 +466,12 @@ func (d *Daemon) Init() error {
 	}
 
 	/* Initialize the operating system facade */
-	d.os = &sys.OS{}
+	d.os = sys.NewOS()
 	err = d.os.Init()
 	if err != nil {
 		return err
 	}
 
-	/* Set container path */
-	d.lxcpath = shared.VarPath("containers")
-
 	/* Make sure all our directories are available */
 	if err := os.MkdirAll(shared.VarPath(), 0711); err != nil {
 		return err
@@ -509,7 +505,7 @@ func (d *Daemon) Init() error {
 	}
 
 	/* Detect the filesystem */
-	d.BackingFs, err = util.FilesystemDetect(d.lxcpath)
+	d.BackingFs, err = util.FilesystemDetect(d.os.LxcPath)
 	if err != nil {
 		logger.Error("Error detecting backing fs", log.Ctx{"err": err})
 	}
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index fb85f6b2f..203459dbe 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -18,8 +19,9 @@ func cmdActivateIfNeeded() error {
 
 	// Don't start a full daemon, we just need DB access
 	d := &Daemon{
-		lxcpath: shared.VarPath("containers"),
+		os: sys.NewOS(),
 	}
+	d.os.LxcPath = shared.VarPath("containers")
 
 	if !shared.PathExists(shared.VarPath("lxd.db")) {
 		logger.Debugf("No DB, so no need to start the daemon now.")
diff --git a/lxd/sys/os.go b/lxd/sys/os.go
index e312b3224..3f564febd 100644
--- a/lxd/sys/os.go
+++ b/lxd/sys/os.go
@@ -2,12 +2,21 @@ package sys
 
 import (
 	"github.com/lxc/lxd/lxd/util"
+	"github.com/lxc/lxd/shared"
 )
 
 // OS is a high-level facade for accessing all operating-system
 // level functionality that LXD uses.
 type OS struct {
-	Architectures []int // Cache of detected system architectures
+
+	// Caches of system characteristics detected at Init() time.
+	Architectures []int  // Cache of detected system architectures
+	LxcPath       string // Path to the $LXD_DIR/containers directory
+}
+
+// NewOS returns a fresh uninitialized OS instance.
+func NewOS() *OS {
+	return &OS{}
 }
 
 // Init our internal data structures.
@@ -19,5 +28,6 @@ func (s *OS) Init() error {
 		return err
 	}
 
+	s.LxcPath = shared.VarPath("containers")
 	return nil
 }

From 8169a9815d8957349ead50261b5b7e136b77d108 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 10:55:35 +0000
Subject: [PATCH 1177/1193] Move Daemon.BackingFs to the OS struct

Move the Daemon.BackingFs attribute to the OS struct,
since it's system-specific information.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon.go  |  7 -------
 lxd/storage.go |  2 +-
 lxd/sys/os.go  | 10 ++++++++++
 3 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index d55bbe612..d50044d2f 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -64,7 +64,6 @@ type Socket struct {
 
 // A Daemon can respond to requests from a shared client.
 type Daemon struct {
-	BackingFs           string
 	clientCerts         []x509.Certificate
 	os                  *sys.OS
 	db                  *sql.DB
@@ -504,12 +503,6 @@ func (d *Daemon) Init() error {
 		return err
 	}
 
-	/* Detect the filesystem */
-	d.BackingFs, err = util.FilesystemDetect(d.os.LxcPath)
-	if err != nil {
-		logger.Error("Error detecting backing fs", log.Ctx{"err": err})
-	}
-
 	/* Read the uid/gid allocation */
 	d.IdmapSet, err = shared.DefaultIdmapSet()
 	if err != nil {
diff --git a/lxd/storage.go b/lxd/storage.go
index c7dbdb557..ba18ff350 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -743,7 +743,7 @@ func SetupStorageDriver(d *Daemon) error {
 		} else {
 			return nil
 		}
-	} else if d.BackingFs == "btrfs" {
+	} else if d.os.BackingFS == "btrfs" {
 		d.Storage, err = newStorage(d, storageTypeBtrfs)
 		if err != nil {
 			logger.Errorf("Could not initialize storage type btrfs: %s - falling back to dir", err)
diff --git a/lxd/sys/os.go b/lxd/sys/os.go
index 3f564febd..f877762f1 100644
--- a/lxd/sys/os.go
+++ b/lxd/sys/os.go
@@ -1,8 +1,11 @@
 package sys
 
 import (
+	log "gopkg.in/inconshreveable/log15.v2"
+
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 )
 
 // OS is a high-level facade for accessing all operating-system
@@ -12,6 +15,7 @@ type OS struct {
 	// Caches of system characteristics detected at Init() time.
 	Architectures []int  // Cache of detected system architectures
 	LxcPath       string // Path to the $LXD_DIR/containers directory
+	BackingFS     string // Backing filesystem of $LXD_DIR/containers
 }
 
 // NewOS returns a fresh uninitialized OS instance.
@@ -29,5 +33,11 @@ func (s *OS) Init() error {
 	}
 
 	s.LxcPath = shared.VarPath("containers")
+
+	s.BackingFS, err = util.FilesystemDetect(s.LxcPath)
+	if err != nil {
+		logger.Error("Error detecting backing fs", log.Ctx{"err": err})
+	}
+
 	return nil
 }

From 11dfbb5a783ab0f8f1c8c384879472ab33340cec Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:00:08 +0000
Subject: [PATCH 1178/1193] Move Daemon.IdmapSet to OS.IdmapSet

Move the Daemon.IdmapSet attribute to the OS struct, since it's
system-specific information.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/container.go             |  2 +-
 lxd/container_lxc.go         | 18 ++++++++--------
 lxd/container_test.go        |  6 +++---
 lxd/daemon.go                | 43 -------------------------------------
 lxd/main_activateifneeded.go |  2 +-
 lxd/main_test.go             |  2 +-
 lxd/sys/os.go                |  3 +++
 lxd/util/sys.go              | 51 ++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 69 insertions(+), 58 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index e2b20b9cd..252d7e759 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -328,7 +328,7 @@ func containerValidConfig(d *Daemon, config map[string]string, profile bool, exp
 		}
 	}
 
-	if expanded && (config["security.privileged"] == "" || !shared.IsTrue(config["security.privileged"])) && d.IdmapSet == nil {
+	if expanded && (config["security.privileged"] == "" || !shared.IsTrue(config["security.privileged"])) && d.os.IdmapSet == nil {
 		return fmt.Errorf("LXD doesn't have a uid/gid allocation. In this mode, only privileged containers are supported.")
 	}
 
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 17b4495bd..0060aeb1d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -507,11 +507,11 @@ func idmapSize(daemon *Daemon, isolatedStr string, size string) (int64, error) {
 		if isolated {
 			idMapSize = 65536
 		} else {
-			if len(daemon.IdmapSet.Idmap) != 2 {
-				return 0, fmt.Errorf("bad initial idmap: %v", daemon.IdmapSet)
+			if len(daemon.os.IdmapSet.Idmap) != 2 {
+				return 0, fmt.Errorf("bad initial idmap: %v", daemon.os.IdmapSet)
 			}
 
-			idMapSize = daemon.IdmapSet.Idmap[0].Maprange
+			idMapSize = daemon.os.IdmapSet.Idmap[0].Maprange
 		}
 	} else {
 		size, err := strconv.ParseInt(size, 10, 64)
@@ -625,8 +625,8 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configBase stri
 	}
 
 	if !isolated {
-		newIdmapset := shared.IdmapSet{Idmap: make([]shared.IdmapEntry, len(daemon.IdmapSet.Idmap))}
-		copy(newIdmapset.Idmap, daemon.IdmapSet.Idmap)
+		newIdmapset := shared.IdmapSet{Idmap: make([]shared.IdmapEntry, len(daemon.os.IdmapSet.Idmap))}
+		copy(newIdmapset.Idmap, daemon.os.IdmapSet.Idmap)
 
 		for _, ent := range rawMaps {
 			newIdmapset.AddSafe(ent)
@@ -670,7 +670,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configBase stri
 		return nil, 0, err
 	}
 
-	offset := daemon.IdmapSet.Idmap[0].Hostid + 65536
+	offset := daemon.os.IdmapSet.Idmap[0].Hostid + 65536
 
 	mapentries := shared.ByHostid{}
 	for _, name := range cs {
@@ -732,7 +732,7 @@ func findIdmap(daemon *Daemon, cName string, isolatedStr string, configBase stri
 		offset = mapentries[i].Hostid + mapentries[i].Maprange
 	}
 
-	if offset+size < daemon.IdmapSet.Idmap[0].Hostid+daemon.IdmapSet.Idmap[0].Maprange {
+	if offset+size < daemon.os.IdmapSet.Idmap[0].Hostid+daemon.os.IdmapSet.Idmap[0].Maprange {
 		return mkIdmap(offset, size), offset, nil
 	}
 
@@ -5878,8 +5878,8 @@ func (c *containerLXC) NextIdmapSet() (*shared.IdmapSet, error) {
 		return c.idmapsetFromConfig("volatile.idmap.next")
 	} else if c.IsPrivileged() {
 		return nil, nil
-	} else if c.daemon.IdmapSet != nil {
-		return c.daemon.IdmapSet, nil
+	} else if c.daemon.os.IdmapSet != nil {
+		return c.daemon.os.IdmapSet, nil
 	}
 
 	return nil, fmt.Errorf("Unable to determine the idmap")
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 53b292b2f..99bf7b4e6 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -258,7 +258,7 @@ func (suite *containerTestSuite) TestContainer_findIdmap_isolated() {
 	map2, err := c2.(*containerLXC).NextIdmapSet()
 	suite.Req.Nil(err)
 
-	host := suite.d.IdmapSet.Idmap[0]
+	host := suite.d.os.IdmapSet.Idmap[0]
 
 	for i := 0; i < 2; i++ {
 		suite.Req.Equal(host.Hostid+65536, map1.Idmap[i].Hostid, "hostids don't match %d", i)
@@ -299,7 +299,7 @@ func (suite *containerTestSuite) TestContainer_findIdmap_mixed() {
 	map2, err := c2.(*containerLXC).NextIdmapSet()
 	suite.Req.Nil(err)
 
-	host := suite.d.IdmapSet.Idmap[0]
+	host := suite.d.os.IdmapSet.Idmap[0]
 
 	for i := 0; i < 2; i++ {
 		suite.Req.Equal(host.Hostid, map1.Idmap[i].Hostid, "hostids don't match %d", i)
@@ -329,7 +329,7 @@ func (suite *containerTestSuite) TestContainer_findIdmap_raw() {
 	map1, err := c1.(*containerLXC).NextIdmapSet()
 	suite.Req.Nil(err)
 
-	host := suite.d.IdmapSet.Idmap[0]
+	host := suite.d.os.IdmapSet.Idmap[0]
 
 	for _, i := range []int{0, 3} {
 		suite.Req.Equal(host.Hostid, map1.Idmap[i].Hostid, "hostids don't match")
diff --git a/lxd/daemon.go b/lxd/daemon.go
index d50044d2f..7885c16ed 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -68,7 +68,6 @@ type Daemon struct {
 	os                  *sys.OS
 	db                  *sql.DB
 	group               string
-	IdmapSet            *shared.IdmapSet
 	mux                 *mux.Router
 	tomb                tomb.Tomb
 	readyChan           chan bool
@@ -503,48 +502,6 @@ func (d *Daemon) Init() error {
 		return err
 	}
 
-	/* Read the uid/gid allocation */
-	d.IdmapSet, err = shared.DefaultIdmapSet()
-	if err != nil {
-		logger.Warn("Error reading default uid/gid map", log.Ctx{"err": err.Error()})
-		logger.Warnf("Only privileged containers will be able to run")
-		d.IdmapSet = nil
-	} else {
-		kernelIdmapSet, err := shared.CurrentIdmapSet()
-		if err == nil {
-			logger.Infof("Kernel uid/gid map:")
-			for _, lxcmap := range kernelIdmapSet.ToLxcString() {
-				logger.Infof(strings.TrimRight(" - "+lxcmap, "\n"))
-			}
-		}
-
-		if len(d.IdmapSet.Idmap) == 0 {
-			logger.Warnf("No available uid/gid map could be found")
-			logger.Warnf("Only privileged containers will be able to run")
-			d.IdmapSet = nil
-		} else {
-			logger.Infof("Configured LXD uid/gid map:")
-			for _, lxcmap := range d.IdmapSet.Idmap {
-				suffix := ""
-
-				if lxcmap.Usable() != nil {
-					suffix = " (unusable)"
-				}
-
-				for _, lxcEntry := range lxcmap.ToLxcString() {
-					logger.Infof(" - %s%s", strings.TrimRight(lxcEntry, "\n"), suffix)
-				}
-			}
-
-			err = d.IdmapSet.Usable()
-			if err != nil {
-				logger.Warnf("One or more uid/gid map entry isn't usable (typically due to nesting)")
-				logger.Warnf("Only privileged containers will be able to run")
-				d.IdmapSet = nil
-			}
-		}
-	}
-
 	/* Initialize the database */
 	err = initializeDbObject(d, shared.VarPath("lxd.db"))
 	if err != nil {
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index 203459dbe..c3620149e 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -48,7 +48,7 @@ func cmdActivateIfNeeded() error {
 	}
 
 	// Load the idmap for unprivileged containers
-	d.IdmapSet, err = shared.DefaultIdmapSet()
+	d.os.IdmapSet, err = shared.DefaultIdmapSet()
 	if err != nil {
 		return err
 	}
diff --git a/lxd/main_test.go b/lxd/main_test.go
index ea95ec4d2..e93472beb 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -21,7 +21,7 @@ func mockStartDaemon() (*Daemon, error) {
 		return nil, err
 	}
 
-	d.IdmapSet = &shared.IdmapSet{Idmap: []shared.IdmapEntry{
+	d.os.IdmapSet = &shared.IdmapSet{Idmap: []shared.IdmapEntry{
 		{Isuid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
 		{Isgid: true, Hostid: 100000, Nsid: 0, Maprange: 500000},
 	}}
diff --git a/lxd/sys/os.go b/lxd/sys/os.go
index f877762f1..2e47217ad 100644
--- a/lxd/sys/os.go
+++ b/lxd/sys/os.go
@@ -16,6 +16,7 @@ type OS struct {
 	Architectures []int  // Cache of detected system architectures
 	LxcPath       string // Path to the $LXD_DIR/containers directory
 	BackingFS     string // Backing filesystem of $LXD_DIR/containers
+	IdmapSet      *shared.IdmapSet
 }
 
 // NewOS returns a fresh uninitialized OS instance.
@@ -39,5 +40,7 @@ func (s *OS) Init() error {
 		logger.Error("Error detecting backing fs", log.Ctx{"err": err})
 	}
 
+	s.IdmapSet = util.GetIdmapSet()
+
 	return nil
 }
diff --git a/lxd/util/sys.go b/lxd/util/sys.go
index d1cc9f519..005a8554a 100644
--- a/lxd/util/sys.go
+++ b/lxd/util/sys.go
@@ -1,6 +1,12 @@
 package util
 
 import (
+	"strings"
+
+	log "gopkg.in/inconshreveable/log15.v2"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
@@ -28,3 +34,48 @@ func GetArchitectures() ([]int, error) {
 	}
 	return architectures, nil
 }
+
+// GetIdmapSet reads the uid/gid allocation.
+func GetIdmapSet() *shared.IdmapSet {
+	idmapSet, err := shared.DefaultIdmapSet()
+	if err != nil {
+		logger.Warn("Error reading default uid/gid map", log.Ctx{"err": err.Error()})
+		logger.Warnf("Only privileged containers will be able to run")
+		idmapSet = nil
+	} else {
+		kernelIdmapSet, err := shared.CurrentIdmapSet()
+		if err == nil {
+			logger.Infof("Kernel uid/gid map:")
+			for _, lxcmap := range kernelIdmapSet.ToLxcString() {
+				logger.Infof(strings.TrimRight(" - "+lxcmap, "\n"))
+			}
+		}
+
+		if len(idmapSet.Idmap) == 0 {
+			logger.Warnf("No available uid/gid map could be found")
+			logger.Warnf("Only privileged containers will be able to run")
+			idmapSet = nil
+		} else {
+			logger.Infof("Configured LXD uid/gid map:")
+			for _, lxcmap := range idmapSet.Idmap {
+				suffix := ""
+
+				if lxcmap.Usable() != nil {
+					suffix = " (unusable)"
+				}
+
+				for _, lxcEntry := range lxcmap.ToLxcString() {
+					logger.Infof(" - %s%s", strings.TrimRight(lxcEntry, "\n"), suffix)
+				}
+			}
+
+			err = idmapSet.Usable()
+			if err != nil {
+				logger.Warnf("One or more uid/gid map entry isn't usable (typically due to nesting)")
+				logger.Warnf("Only privileged containers will be able to run")
+				idmapSet = nil
+			}
+		}
+	}
+	return idmapSet
+}

From 5fafa10fdde66442169e362bf801ad1ee48f57aa Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:04:35 +0000
Subject: [PATCH 1179/1193] Move Daemon.MockMode to OS.MockMode

Move the Daemon.MockMode attribute to sys.OS, since system-level
functionality is really what we want to mock (everything else can be
run for real does not need to be mocked).

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/daemon.go                | 21 +++++++++++++--------
 lxd/devlxd_test.go           |  2 +-
 lxd/main_activateifneeded.go |  5 +----
 lxd/main_daemon.go           |  6 +++---
 lxd/main_test.go             |  7 +++----
 lxd/profiles_test.go         |  2 +-
 lxd/storage.go               |  4 ++--
 lxd/sys/os.go                |  2 ++
 8 files changed, 26 insertions(+), 23 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 7885c16ed..ff570bab2 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -82,7 +82,6 @@ type Daemon struct {
 
 	devlxd *net.UnixListener
 
-	MockMode  bool
 	SetupMode bool
 
 	tlsConfig *tls.Config
@@ -90,6 +89,13 @@ type Daemon struct {
 	proxy func(req *http.Request) (*url.URL, error)
 }
 
+// NewDaemon returns a new, un-initialized Daemon object.
+func NewDaemon() *Daemon {
+	return &Daemon{
+		os: sys.NewOS(),
+	}
+}
+
 // Command is the basic structure for every API call.
 type Command struct {
 	name          string
@@ -321,7 +327,7 @@ func (d *Daemon) Init() error {
 	}
 
 	/* Print welcome message */
-	if d.MockMode {
+	if d.os.MockMode {
 		logger.Info(fmt.Sprintf("LXD %s is starting in mock mode", version.Version),
 			log.Ctx{"path": shared.VarPath("")})
 	} else if d.SetupMode {
@@ -464,7 +470,6 @@ func (d *Daemon) Init() error {
 	}
 
 	/* Initialize the operating system facade */
-	d.os = sys.NewOS()
 	err = d.os.Init()
 	if err != nil {
 		return err
@@ -514,7 +519,7 @@ func (d *Daemon) Init() error {
 		return err
 	}
 
-	if !d.MockMode {
+	if !d.os.MockMode {
 		/* Setup the storage driver */
 		err = SetupStorageDriver(d)
 		if err != nil {
@@ -558,7 +563,7 @@ func (d *Daemon) Init() error {
 	)
 
 	/* Setup some mounts (nice to have) */
-	if !d.MockMode {
+	if !d.os.MockMode {
 		// Attempt to mount the shmounts tmpfs
 		setupSharedMounts()
 
@@ -580,7 +585,7 @@ func (d *Daemon) Init() error {
 		return server.Serve(d.devlxd)
 	})
 
-	if !d.MockMode {
+	if !d.os.MockMode {
 		/* Start the scheduler */
 		go deviceEventListener(d)
 
@@ -739,7 +744,7 @@ func (d *Daemon) Init() error {
 	}
 
 	// Run the post initialization actions
-	if !d.MockMode && !d.SetupMode {
+	if !d.os.MockMode && !d.SetupMode {
 		err := d.Ready()
 		if err != nil {
 			return err
@@ -885,7 +890,7 @@ func (d *Daemon) Stop() error {
 	imageSaveStreamCache()
 	logger.Infof("Saved simplestreams cache")
 
-	if d.MockMode || forceStop {
+	if d.os.MockMode || forceStop {
 		return nil
 	}
 
diff --git a/lxd/devlxd_test.go b/lxd/devlxd_test.go
index 62d200be7..2453e3832 100644
--- a/lxd/devlxd_test.go
+++ b/lxd/devlxd_test.go
@@ -120,7 +120,7 @@ func TestHttpRequest(t *testing.T) {
 	}
 	defer os.RemoveAll(testDir)
 
-	d := &Daemon{}
+	d := NewDaemon()
 	err := d.Init()
 	if err != nil {
 		t.Fatal(err)
diff --git a/lxd/main_activateifneeded.go b/lxd/main_activateifneeded.go
index c3620149e..1b8f74c21 100644
--- a/lxd/main_activateifneeded.go
+++ b/lxd/main_activateifneeded.go
@@ -6,7 +6,6 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -18,9 +17,7 @@ func cmdActivateIfNeeded() error {
 	}
 
 	// Don't start a full daemon, we just need DB access
-	d := &Daemon{
-		os: sys.NewOS(),
-	}
+	d := NewDaemon()
 	d.os.LxcPath = shared.VarPath("containers")
 
 	if !shared.PathExists(shared.VarPath("lxd.db")) {
diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
index 39e92085d..c49ef127d 100644
--- a/lxd/main_daemon.go
+++ b/lxd/main_daemon.go
@@ -50,9 +50,9 @@ func cmdDaemon() error {
 		}()
 	}
 
-	d := &Daemon{
-		group:     *argGroup,
-		SetupMode: shared.PathExists(shared.VarPath(".setup_mode"))}
+	d := NewDaemon()
+	d.group = *argGroup
+	d.SetupMode = shared.PathExists(shared.VarPath(".setup_mode"))
 	err := d.Init()
 	if err != nil {
 		if d != nil && d.db != nil {
diff --git a/lxd/main_test.go b/lxd/main_test.go
index e93472beb..4f974106b 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -12,10 +12,9 @@ import (
 )
 
 func mockStartDaemon() (*Daemon, error) {
-	d := &Daemon{
-		MockMode:  true,
-		tlsConfig: &tls.Config{},
-	}
+	d := NewDaemon()
+	d.os.MockMode = true
+	d.tlsConfig = &tls.Config{}
 
 	if err := d.Init(); err != nil {
 		return nil, err
diff --git a/lxd/profiles_test.go b/lxd/profiles_test.go
index 888379682..db0246fca 100644
--- a/lxd/profiles_test.go
+++ b/lxd/profiles_test.go
@@ -11,7 +11,7 @@ func Test_removing_a_profile_deletes_associated_configuration_entries(t *testing
 	var db *sql.DB
 	var err error
 
-	d := &Daemon{}
+	d := NewDaemon()
 	err = initializeDbObject(d, ":memory:")
 	db = d.db
 
diff --git a/lxd/storage.go b/lxd/storage.go
index ba18ff350..209fad436 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -163,7 +163,7 @@ func newStorage(d *Daemon, sType storageType) (storage, error) {
 }
 
 func newStorageWithConfig(d *Daemon, sType storageType, config map[string]interface{}) (storage, error) {
-	if d.MockMode {
+	if d.os.MockMode {
 		return d.Storage, nil
 	}
 
@@ -206,7 +206,7 @@ func storageForFilename(d *Daemon, filename string) (storage, error) {
 	config := make(map[string]interface{})
 	storageType := storageTypeDir
 
-	if d.MockMode {
+	if d.os.MockMode {
 		return newStorageWithConfig(d, storageTypeMock, config)
 	}
 
diff --git a/lxd/sys/os.go b/lxd/sys/os.go
index 2e47217ad..d699ad878 100644
--- a/lxd/sys/os.go
+++ b/lxd/sys/os.go
@@ -17,6 +17,8 @@ type OS struct {
 	LxcPath       string // Path to the $LXD_DIR/containers directory
 	BackingFS     string // Backing filesystem of $LXD_DIR/containers
 	IdmapSet      *shared.IdmapSet
+
+	MockMode bool // If true some APIs will be mocked (for testing)
 }
 
 // NewOS returns a fresh uninitialized OS instance.

From c895bde3478cfb885dc90c5e1c457ff2269aed7c Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:16:49 +0000
Subject: [PATCH 1180/1193] Use sql.DB or sys.OS instead of Daemon where
 possible

Change a few occurrences of functions where a Daemon reference is
either replaceable with sql.DB or sys.OS, or not needed
at all.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/certificates.go   | 13 +++++++------
 lxd/container.go      |  7 ++++---
 lxd/container_lxc.go  |  6 +++---
 lxd/images.go         | 19 ++++++++++---------
 lxd/profiles.go       |  2 +-
 lxd/profiles_utils.go |  2 +-
 lxd/storage_btrfs.go  |  2 +-
 lxd/storage_dir.go    |  2 +-
 lxd/storage_lvm.go    |  2 +-
 lxd/storage_zfs.go    |  2 +-
 10 files changed, 30 insertions(+), 27 deletions(-)

diff --git a/lxd/certificates.go b/lxd/certificates.go
index 7b9bfed1b..4543a54c1 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"crypto/x509"
+	"database/sql"
 	"encoding/base64"
 	"encoding/pem"
 	"fmt"
@@ -76,7 +77,7 @@ func readSavedClientCAList(d *Daemon) {
 	}
 }
 
-func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
+func saveCert(dbObj *sql.DB, host string, cert *x509.Certificate) error {
 	baseCert := new(db.CertInfo)
 	baseCert.Fingerprint = shared.CertFingerprint(cert)
 	baseCert.Type = 1
@@ -85,7 +86,7 @@ func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
 		pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}),
 	)
 
-	return db.CertSave(d.db, baseCert)
+	return db.CertSave(dbObj, baseCert)
 }
 
 func certificatesPost(d *Daemon, r *http.Request) Response {
@@ -142,7 +143,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 		}
 	}
 
-	err := saveCert(d, name, cert)
+	err := saveCert(d.db, name, cert)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -165,7 +166,7 @@ var certificatesCmd = Command{
 func certificateFingerprintGet(d *Daemon, r *http.Request) Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
-	cert, err := doCertificateGet(d, fingerprint)
+	cert, err := doCertificateGet(d.db, fingerprint)
 	if err != nil {
 		return SmartError(err)
 	}
@@ -173,10 +174,10 @@ func certificateFingerprintGet(d *Daemon, r *http.Request) Response {
 	return SyncResponse(true, cert)
 }
 
-func doCertificateGet(d *Daemon, fingerprint string) (api.Certificate, error) {
+func doCertificateGet(dbObj *sql.DB, fingerprint string) (api.Certificate, error) {
 	resp := api.Certificate{}
 
-	dbCertInfo, err := db.CertGet(d.db, fingerprint)
+	dbCertInfo, err := db.CertGet(dbObj, fingerprint)
 	if err != nil {
 		return resp, err
 	}
diff --git a/lxd/container.go b/lxd/container.go
index 252d7e759..04b88324a 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -12,6 +12,7 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -312,7 +313,7 @@ func containerValidDeviceConfigKey(t, k string) bool {
 	}
 }
 
-func containerValidConfig(d *Daemon, config map[string]string, profile bool, expanded bool) error {
+func containerValidConfig(os *sys.OS, config map[string]string, profile bool, expanded bool) error {
 	if config == nil {
 		return nil
 	}
@@ -328,7 +329,7 @@ func containerValidConfig(d *Daemon, config map[string]string, profile bool, exp
 		}
 	}
 
-	if expanded && (config["security.privileged"] == "" || !shared.IsTrue(config["security.privileged"])) && d.os.IdmapSet == nil {
+	if expanded && (config["security.privileged"] == "" || !shared.IsTrue(config["security.privileged"])) && os.IdmapSet == nil {
 		return fmt.Errorf("LXD doesn't have a uid/gid allocation. In this mode, only privileged containers are supported.")
 	}
 
@@ -741,7 +742,7 @@ func containerCreateInternal(d *Daemon, args db.ContainerArgs) (container, error
 	}
 
 	// Validate container config
-	err := containerValidConfig(d, args.Config, false, false)
+	err := containerValidConfig(d.os, args.Config, false, false)
 	if err != nil {
 		return nil, err
 	}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 0060aeb1d..fcca47a8d 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -302,7 +302,7 @@ func containerLXCCreate(d *Daemon, args db.ContainerArgs) (container, error) {
 	}
 
 	// Validate expanded config
-	err = containerValidConfig(c.daemon, c.expandedConfig, false, true)
+	err = containerValidConfig(c.daemon.os, c.expandedConfig, false, true)
 	if err != nil {
 		c.Delete()
 		logger.Error("Failed creating container", ctxMap)
@@ -2635,7 +2635,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 	}
 
 	// Validate the new config
-	err := containerValidConfig(c.daemon, args.Config, false, false)
+	err := containerValidConfig(c.daemon.os, args.Config, false, false)
 	if err != nil {
 		return err
 	}
@@ -2784,7 +2784,7 @@ func (c *containerLXC) Update(args db.ContainerArgs, userRequested bool) error {
 	removeDevices, addDevices, updateDevices := oldExpandedDevices.Update(c.expandedDevices)
 
 	// Do some validation of the config diff
-	err = containerValidConfig(c.daemon, c.expandedConfig, false, true)
+	err = containerValidConfig(c.daemon.os, c.expandedConfig, false, true)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/images.go b/lxd/images.go
index 8e80fa834..5d5f9ab39 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -3,6 +3,7 @@ package main
 import (
 	"bytes"
 	"crypto/sha256"
+	"database/sql"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -83,7 +84,7 @@ func detectCompression(fname string) ([]string, string, error) {
 
 }
 
-func unpack(d *Daemon, file string, path string) error {
+func unpack(file string, path string, sType storageType) error {
 	extractArgs, extension, err := detectCompression(file)
 	if err != nil {
 		return err
@@ -132,7 +133,7 @@ func unpack(d *Daemon, file string, path string) error {
 
 		// Check if we're running out of space
 		if int64(fs.Bfree) < int64(2*fs.Bsize) {
-			if d.Storage.GetStorageType() == storageTypeLvm {
+			if sType == storageTypeLvm {
 				return fmt.Errorf("Unable to unpack image, run out of disk space (consider increasing storage.lvm_volume_size).")
 			} else {
 				return fmt.Errorf("Unable to unpack image, run out of disk space.")
@@ -152,8 +153,8 @@ func unpack(d *Daemon, file string, path string) error {
 	return nil
 }
 
-func unpackImage(d *Daemon, imagefname string, destpath string) error {
-	err := unpack(d, imagefname, destpath)
+func unpackImage(imagefname string, destpath string, sType storageType) error {
+	err := unpack(imagefname, destpath, sType)
 	if err != nil {
 		return err
 	}
@@ -165,7 +166,7 @@ func unpackImage(d *Daemon, imagefname string, destpath string) error {
 			return fmt.Errorf("Error creating rootfs directory")
 		}
 
-		err = unpack(d, imagefname+".rootfs", rootfsPath)
+		err = unpack(imagefname+".rootfs", rootfsPath, sType)
 		if err != nil {
 			return err
 		}
@@ -798,7 +799,7 @@ func doImagesGet(d *Daemon, recursion bool, public bool) (interface{}, error) {
 			url := fmt.Sprintf("/%s/images/%s", version.APIVersion, name)
 			resultString[i] = url
 		} else {
-			image, response := doImageGet(d, name, public)
+			image, response := doImageGet(d.db, name, public)
 			if response != nil {
 				continue
 			}
@@ -1006,8 +1007,8 @@ func imageDelete(d *Daemon, r *http.Request) Response {
 	return OperationResponse(op)
 }
 
-func doImageGet(d *Daemon, fingerprint string, public bool) (*api.Image, Response) {
-	_, imgInfo, err := db.ImageGet(d.db, fingerprint, public, false)
+func doImageGet(dbObj *sql.DB, fingerprint string, public bool) (*api.Image, Response) {
+	_, imgInfo, err := db.ImageGet(dbObj, fingerprint, public, false)
 	if err != nil {
 		return nil, SmartError(err)
 	}
@@ -1050,7 +1051,7 @@ func imageGet(d *Daemon, r *http.Request) Response {
 	public := !util.IsTrustedClient(r, d.clientCerts)
 	secret := r.FormValue("secret")
 
-	info, response := doImageGet(d, fingerprint, false)
+	info, response := doImageGet(d.db, fingerprint, false)
 	if response != nil {
 		return response
 	}
diff --git a/lxd/profiles.go b/lxd/profiles.go
index e23080f64..1b5736fcd 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -77,7 +77,7 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
 	}
 
-	err := containerValidConfig(d, req.Config, true, false)
+	err := containerValidConfig(d.os, req.Config, true, false)
 	if err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index e3d6f537b..c4d7bcf5f 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -10,7 +10,7 @@ import (
 
 func doProfileUpdate(d *Daemon, name string, id int64, profile *api.Profile, req api.ProfilePut) Response {
 	// Sanity checks
-	err := containerValidConfig(d, req.Config, true, false)
+	err := containerValidConfig(d.os, req.Config, true, false)
 	if err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 1c915e0bd..5712be4bd 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -415,7 +415,7 @@ func (s *storageBtrfs) ImageCreate(fingerprint string) error {
 		return err
 	}
 
-	if err := unpackImage(s.d, imagePath, subvol); err != nil {
+	if err := unpackImage(imagePath, subvol, s.d.Storage.GetStorageType()); err != nil {
 		s.subvolsDelete(subvol)
 		return err
 	}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 346b36c57..ffbb5c199 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -69,7 +69,7 @@ func (s *storageDir) ContainerCreateFromImage(
 	}
 
 	imagePath := shared.VarPath("images", imageFingerprint)
-	if err := unpackImage(s.d, imagePath, container.Path()); err != nil {
+	if err := unpackImage(imagePath, container.Path(), s.d.Storage.GetStorageType()); err != nil {
 		s.ContainerDelete(container)
 		return err
 	}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index e6de86076..9ddbcab8c 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -738,7 +738,7 @@ func (s *storageLvm) ImageCreate(fingerprint string) error {
 		return fmt.Errorf("Error mounting image LV: %v", err)
 	}
 
-	unpackErr := unpackImage(s.d, finalName, tempLVMountPoint)
+	unpackErr := unpackImage(finalName, tempLVMountPoint, s.d.Storage.GetStorageType())
 
 	err = tryUnmount(tempLVMountPoint, 0)
 	if err != nil {
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index c204d0fb4..32f424c06 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -630,7 +630,7 @@ func (s *storageZfs) ImageCreate(fingerprint string) error {
 		return err
 	}
 
-	err = unpackImage(s.d, imagePath, subvol)
+	err = unpackImage(imagePath, subvol, s.d.Storage.GetStorageType())
 	if err != nil {
 		return cleanup(err)
 	}

From 35df1edead5203a3ce6d744fd375784f9dccc969 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:54:44 +0000
Subject: [PATCH 1181/1193] d.os.Init must be run after all paths are created
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/daemon.go | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index ff570bab2..9f214189e 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -469,12 +469,6 @@ func (d *Daemon) Init() error {
 		logger.Warnf("CGroup memory swap accounting is disabled, swap limits will be ignored.")
 	}
 
-	/* Initialize the operating system facade */
-	err = d.os.Init()
-	if err != nil {
-		return err
-	}
-
 	/* Make sure all our directories are available */
 	if err := os.MkdirAll(shared.VarPath(), 0711); err != nil {
 		return err
@@ -507,6 +501,12 @@ func (d *Daemon) Init() error {
 		return err
 	}
 
+	/* Initialize the operating system facade */
+	err = d.os.Init()
+	if err != nil {
+		return err
+	}
+
 	/* Initialize the database */
 	err = initializeDbObject(d, shared.VarPath("lxd.db"))
 	if err != nil {

From 8a10704cbfa571216bec8712c48204c5b66f4bd2 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:55:01 +0000
Subject: [PATCH 1182/1193] Add db/query sub-package with common query helpers

Introduce helpers around database/sql to execute various kinds of very
common SQL queries. In particular add SelectStrings and SelectIntegers
to fetch rows wuth a single string or int column.

This package will grow as needed in follow-up branches.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/query/doc.go             |  3 ++
 lxd/db/query/slices.go          | 81 +++++++++++++++++++++++++++++++++++++++++
 lxd/db/query/slices_1_6.go      | 15 ++++++++
 lxd/db/query/slices_1_6_test.go | 18 +++++++++
 lxd/db/query/slices_1_8.go      | 26 +++++++++++++
 lxd/db/query/slices_1_8_test.go | 21 +++++++++++
 lxd/db/query/slices_test.go     | 70 +++++++++++++++++++++++++++++++++++
 7 files changed, 234 insertions(+)
 create mode 100644 lxd/db/query/doc.go
 create mode 100644 lxd/db/query/slices.go
 create mode 100644 lxd/db/query/slices_1_6.go
 create mode 100644 lxd/db/query/slices_1_6_test.go
 create mode 100644 lxd/db/query/slices_1_8.go
 create mode 100644 lxd/db/query/slices_1_8_test.go
 create mode 100644 lxd/db/query/slices_test.go

diff --git a/lxd/db/query/doc.go b/lxd/db/query/doc.go
new file mode 100644
index 000000000..eef2fc7bf
--- /dev/null
+++ b/lxd/db/query/doc.go
@@ -0,0 +1,3 @@
+// Package query implements helpers around database/sql to execute various
+// kinds of very common SQL read queries.
+package query
diff --git a/lxd/db/query/slices.go b/lxd/db/query/slices.go
new file mode 100644
index 000000000..4e58126c7
--- /dev/null
+++ b/lxd/db/query/slices.go
@@ -0,0 +1,81 @@
+package query
+
+import (
+	"database/sql"
+)
+
+// SelectStrings executes a statement which must yield rows with a single string
+// column. It returns the list of column values.
+func SelectStrings(tx *sql.Tx, query string) ([]string, error) {
+	values := []string{}
+	scan := func(rows *sql.Rows) error {
+		var value string
+		err := rows.Scan(&value)
+		if err != nil {
+			return err
+		}
+		values = append(values, value)
+		return nil
+	}
+
+	err := scanSingleColumn(tx, query, "TEXT", scan)
+	if err != nil {
+		return nil, err
+	}
+
+	return values, nil
+}
+
+// SelectIntegers executes a statement which must yield rows with a single integer
+// column. It returns the list of column values.
+func SelectIntegers(tx *sql.Tx, query string) ([]int, error) {
+	values := []int{}
+	scan := func(rows *sql.Rows) error {
+		var value int
+		err := rows.Scan(&value)
+		if err != nil {
+			return err
+		}
+		values = append(values, value)
+		return nil
+	}
+
+	err := scanSingleColumn(tx, query, "INTEGER", scan)
+	if err != nil {
+		return nil, err
+	}
+
+	return values, nil
+}
+
+// Execute the given query and ensure that it yields rows with a single column
+// of the given database type. For every row yielded, execute the given
+// scanner.
+func scanSingleColumn(tx *sql.Tx, query string, typeName string, scan scanFunc) error {
+	rows, err := tx.Query(query)
+	if err != nil {
+		return err
+	}
+	defer rows.Close()
+
+	err = checkRowsHaveOneColumnOfSpecificType(rows, typeName)
+	if err != nil {
+		return err
+	}
+
+	for rows.Next() {
+		err := scan(rows)
+		if err != nil {
+			return err
+		}
+	}
+
+	err = rows.Err()
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// Function to scan a single row.
+type scanFunc func(*sql.Rows) error
diff --git a/lxd/db/query/slices_1_6.go b/lxd/db/query/slices_1_6.go
new file mode 100644
index 000000000..652147b70
--- /dev/null
+++ b/lxd/db/query/slices_1_6.go
@@ -0,0 +1,15 @@
+// +build !go1.8
+
+package query
+
+import "database/sql"
+
+// Check that the given result set yields rows with a single column of a
+// specific type.
+func checkRowsHaveOneColumnOfSpecificType(rows *sql.Rows, typeName string) error {
+	// The Rows.ColumnTypes() method is available only since Go 1.8, so we
+	// just return nil for <1.8. This is safe to do since if the returned
+	// rows are not of the expected type, call sites will still fail at
+	// Rows.Scan() time, although the error message will be less clear.
+	return nil
+}
diff --git a/lxd/db/query/slices_1_6_test.go b/lxd/db/query/slices_1_6_test.go
new file mode 100644
index 000000000..d508edecf
--- /dev/null
+++ b/lxd/db/query/slices_1_6_test.go
@@ -0,0 +1,18 @@
+// +build !go1.8
+
+package query_test
+
+var testStringsErrorCases = []struct {
+	query string
+	error string
+}{
+	{"garbage", "near \"garbage\": syntax error"},
+	{"SELECT id, name FROM test", "sql: expected 2 destination arguments in Scan, not 1"},
+}
+
+var testIntegersErrorCases = []struct {
+	query string
+	error string
+}{
+	{"garbage", "near \"garbage\": syntax error"},
+}
diff --git a/lxd/db/query/slices_1_8.go b/lxd/db/query/slices_1_8.go
new file mode 100644
index 000000000..140de7c2a
--- /dev/null
+++ b/lxd/db/query/slices_1_8.go
@@ -0,0 +1,26 @@
+// +build go1.8
+
+package query
+
+import (
+	"database/sql"
+	"fmt"
+	"strings"
+)
+
+// Check that the given result set yields rows with a single column of a
+// specific type.
+func checkRowsHaveOneColumnOfSpecificType(rows *sql.Rows, typeName string) error {
+	types, err := rows.ColumnTypes()
+	if err != nil {
+		return err
+	}
+	if len(types) != 1 {
+		return fmt.Errorf("query yields %d columns, not 1", len(types))
+	}
+	actualTypeName := strings.ToUpper(types[0].DatabaseTypeName())
+	if actualTypeName != typeName {
+		return fmt.Errorf("query yields %s column, not %s", actualTypeName, typeName)
+	}
+	return nil
+}
diff --git a/lxd/db/query/slices_1_8_test.go b/lxd/db/query/slices_1_8_test.go
new file mode 100644
index 000000000..dae33c2ab
--- /dev/null
+++ b/lxd/db/query/slices_1_8_test.go
@@ -0,0 +1,21 @@
+// +build go1.8
+
+package query_test
+
+var testStringsErrorCases = []struct {
+	query string
+	error string
+}{
+	{"garbage", "near \"garbage\": syntax error"},
+	{"SELECT id, name FROM test", "query yields 2 columns, not 1"},
+	{"SELECT id FROM test", "query yields INTEGER column, not TEXT"},
+}
+
+var testIntegersErrorCases = []struct {
+	query string
+	error string
+}{
+	{"garbage", "near \"garbage\": syntax error"},
+	{"SELECT id, name FROM test", "query yields 2 columns, not 1"},
+	{"SELECT name FROM test", "query yields TEXT column, not INTEGER"},
+}
diff --git a/lxd/db/query/slices_test.go b/lxd/db/query/slices_test.go
new file mode 100644
index 000000000..47e4a3eb0
--- /dev/null
+++ b/lxd/db/query/slices_test.go
@@ -0,0 +1,70 @@
+package query_test
+
+import (
+	"database/sql"
+	"testing"
+
+	_ "github.com/mattn/go-sqlite3"
+	"github.com/mpvl/subtest"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/lxc/lxd/lxd/db/query"
+)
+
+// Exercise possible failure modes.
+func TestStrings_Error(t *testing.T) {
+	for _, c := range testStringsErrorCases {
+		subtest.Run(t, c.query, func(t *testing.T) {
+			tx := newTxForSlices(t)
+			values, err := query.SelectStrings(tx, c.query)
+			assert.EqualError(t, err, c.error)
+			assert.Nil(t, values)
+		})
+	}
+}
+
+// All values yield by the query are returned.
+func TestStrings(t *testing.T) {
+	tx := newTxForSlices(t)
+	values, err := query.SelectStrings(tx, "SELECT name FROM test ORDER BY name")
+	assert.Nil(t, err)
+	assert.Equal(t, []string{"bar", "foo"}, values)
+}
+
+// Exercise possible failure modes.
+func TestIntegers_Error(t *testing.T) {
+	for _, c := range testIntegersErrorCases {
+		subtest.Run(t, c.query, func(t *testing.T) {
+			tx := newTxForSlices(t)
+			values, err := query.SelectIntegers(tx, c.query)
+			assert.EqualError(t, err, c.error)
+			assert.Nil(t, values)
+		})
+	}
+}
+
+// All values yield by the query are returned.
+func TestIntegers(t *testing.T) {
+	tx := newTxForSlices(t)
+	values, err := query.SelectIntegers(tx, "SELECT id FROM test ORDER BY id")
+	assert.Nil(t, err)
+	assert.Equal(t, []int{0, 1}, values)
+}
+
+// Return a new transaction against an in-memory SQLite database with a single
+// test table populated with a few rows.
+func newTxForSlices(t *testing.T) *sql.Tx {
+	db, err := sql.Open("sqlite3", ":memory:")
+	assert.NoError(t, err)
+
+	_, err = db.Exec("CREATE TABLE test (id INTEGER, name TEXT)")
+	assert.NoError(t, err)
+
+	_, err = db.Exec("INSERT INTO test VALUES (0, 'foo'), (1, 'bar')")
+	assert.NoError(t, err)
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	return tx
+}

From afe2815de4ecb67bc98eae4773d377bd6f2abca0 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:55:14 +0000
Subject: [PATCH 1183/1193] Add query.Transaction

The query.Transaction function is a convenience to execute a given
function within transaction boundaries.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/query/doc.go              |  2 +-
 lxd/db/query/transaction.go      | 37 +++++++++++++++++++++++++++++++
 lxd/db/query/transaction_test.go | 48 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 86 insertions(+), 1 deletion(-)
 create mode 100644 lxd/db/query/transaction.go
 create mode 100644 lxd/db/query/transaction_test.go

diff --git a/lxd/db/query/doc.go b/lxd/db/query/doc.go
index eef2fc7bf..37bb2e2f5 100644
--- a/lxd/db/query/doc.go
+++ b/lxd/db/query/doc.go
@@ -1,3 +1,3 @@
 // Package query implements helpers around database/sql to execute various
-// kinds of very common SQL read queries.
+// kinds of very common SQL queries.
 package query
diff --git a/lxd/db/query/transaction.go b/lxd/db/query/transaction.go
new file mode 100644
index 000000000..845b25a9a
--- /dev/null
+++ b/lxd/db/query/transaction.go
@@ -0,0 +1,37 @@
+package query
+
+import (
+	"database/sql"
+	"fmt"
+
+	"github.com/pkg/errors"
+)
+
+// Transaction executes the given function within a database transaction.
+func Transaction(db *sql.DB, f func(*sql.Tx) error) error {
+	tx, err := db.Begin()
+	if err != nil {
+		return errors.Wrap(err, "failed to begin transaction")
+	}
+
+	err = f(tx)
+	if err != nil {
+		return rollback(tx, err)
+	}
+
+	return tx.Commit()
+}
+
+// Rollback a transaction after the given error occured. If the rollback
+// succeeds the given error is returned, otherwise a new error that wraps it
+// gets generated and returned.
+func rollback(tx *sql.Tx, reason error) error {
+	err := tx.Rollback()
+	if err != nil {
+		return errors.Wrap(
+			reason,
+			fmt.Sprintf("failed to rollback transaction after error (%v)", reason))
+	}
+
+	return reason
+}
diff --git a/lxd/db/query/transaction_test.go b/lxd/db/query/transaction_test.go
new file mode 100644
index 000000000..3aebe186d
--- /dev/null
+++ b/lxd/db/query/transaction_test.go
@@ -0,0 +1,48 @@
+package query_test
+
+import (
+	"database/sql"
+	"fmt"
+	"testing"
+
+	"github.com/lxc/lxd/lxd/db/query"
+	_ "github.com/mattn/go-sqlite3"
+	"github.com/stretchr/testify/assert"
+)
+
+// Any error happening when beginning the transaction will be propagated.
+func TestTransaction_BeginError(t *testing.T) {
+	db := newDB(t)
+	db.Close()
+
+	err := query.Transaction(db, func(*sql.Tx) error { return nil })
+	assert.NotNil(t, err)
+	assert.Contains(t, err.Error(), "failed to begin transaction")
+}
+
+// Any error happening when in the transaction function will cause a rollback.
+func TestTransaction_FunctionError(t *testing.T) {
+	db := newDB(t)
+
+	err := query.Transaction(db, func(tx *sql.Tx) error {
+		_, err := tx.Exec("CREATE TABLE test (id INTEGER)")
+		assert.NoError(t, err)
+		return fmt.Errorf("boom")
+
+	})
+	assert.EqualError(t, err, "boom")
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	tables, err := query.SelectStrings(tx, "SELECT name FROM sqlite_master WHERE type = 'table'")
+	assert.NoError(t, err)
+	assert.NotContains(t, tables, "test")
+}
+
+// Return a new in-memory SQLite database.
+func newDB(t *testing.T) *sql.DB {
+	db, err := sql.Open("sqlite3", ":memory:")
+	assert.NoError(t, err)
+	return db
+}

From 8f8a0d71e49b039f8ec231d5997aa6e31c44445f Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:55:28 +0000
Subject: [PATCH 1184/1193] Add db/schema sub-package for managing database
 schemas

The Schema class encapsulate all logic needed to create and maintain a
database schema, defined by a series of incremental database schema
updates, migrating from one version of the schema to the next.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/schema/doc.go         |   2 +
 lxd/db/schema/query.go       |  70 +++++++++++++++++
 lxd/db/schema/schema.go      | 157 +++++++++++++++++++++++++++++++++++++++
 lxd/db/schema/schema_test.go | 173 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 402 insertions(+)
 create mode 100644 lxd/db/schema/doc.go
 create mode 100644 lxd/db/schema/query.go
 create mode 100644 lxd/db/schema/schema.go
 create mode 100644 lxd/db/schema/schema_test.go

diff --git a/lxd/db/schema/doc.go b/lxd/db/schema/doc.go
new file mode 100644
index 000000000..fefa02e3e
--- /dev/null
+++ b/lxd/db/schema/doc.go
@@ -0,0 +1,2 @@
+// Package schema offers utilities to create and maintain a database schema.
+package schema
diff --git a/lxd/db/schema/query.go b/lxd/db/schema/query.go
new file mode 100644
index 000000000..d9c79587d
--- /dev/null
+++ b/lxd/db/schema/query.go
@@ -0,0 +1,70 @@
+package schema
+
+import (
+	"database/sql"
+	"fmt"
+
+	"github.com/lxc/lxd/lxd/db/query"
+)
+
+// Return whether the schema table is present in the database.
+func doesSchemaTableExist(tx *sql.Tx) (bool, error) {
+	statement := `
+SELECT COUNT(name) FROM sqlite_master WHERE type = 'table' AND name = 'schema'
+`
+	rows, err := tx.Query(statement)
+	if err != nil {
+		return false, err
+	}
+	defer rows.Close()
+	if !rows.Next() {
+		return false, fmt.Errorf("schema table query returned no rows")
+	}
+
+	var count int
+	err = rows.Scan(&count)
+	if err != nil {
+		return false, err
+	}
+	return count == 1, nil
+}
+
+// Return all versions in the schema table, in increasing order.
+func selectSchemaVersions(tx *sql.Tx) ([]int, error) {
+	statement := `
+SELECT version FROM schema ORDER BY version
+`
+	return query.SelectIntegers(tx, statement)
+}
+
+// Create the schema table.
+func createSchemaTable(tx *sql.Tx) error {
+	statement := `
+CREATE TABLE schema (
+    id         INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    version    INTEGER NOT NULL,
+    updated_at DATETIME NOT NULL,
+    UNIQUE (version)
+)
+`
+	_, err := tx.Exec(statement, nil)
+	return err
+}
+
+// Insert a new version into the schema table.
+func insertSchemaVersion(tx *sql.Tx, new int) error {
+	statement := `
+INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"))
+`
+	_, err := tx.Exec(statement, new)
+	return err
+}
+
+// Update a version in the schema table.
+func updateSchemaVersion(tx *sql.Tx, old, new int) error {
+	statement := `
+UPDATE schema SET version=?, updated_at=strftime("%s") WHERE version=?
+`
+	_, err := tx.Exec(statement, new, old)
+	return err
+}
diff --git a/lxd/db/schema/schema.go b/lxd/db/schema/schema.go
new file mode 100644
index 000000000..77339c9f3
--- /dev/null
+++ b/lxd/db/schema/schema.go
@@ -0,0 +1,157 @@
+package schema
+
+import (
+	"database/sql"
+	"fmt"
+
+	"github.com/pkg/errors"
+
+	"github.com/lxc/lxd/lxd/db/query"
+)
+
+// Schema captures the schema of a database in terms of a series of ordered
+// updates.
+type Schema struct {
+	updates []Update // Ordered series of updates making up the schema
+	hook    Hook     // Optional hook to execute whenever a update gets applied
+}
+
+// Update applies a specific schema change to a database, and returns an error
+// if anything goes wrong.
+type Update func(*sql.Tx) error
+
+// Hook is a callback that gets fired when a update gets applied.
+type Hook func(int, *sql.Tx) error
+
+// New creates a new schema Schema with the given updates.
+func New(updates []Update) *Schema {
+	return &Schema{
+		updates: updates,
+	}
+}
+
+// Empty creates a new schema with no updates.
+func Empty() *Schema {
+	return New([]Update{})
+}
+
+// Add a new update to the schema. It will be appended at the end of the
+// existing series.
+func (s *Schema) Add(update Update) {
+	s.updates = append(s.updates, update)
+}
+
+// Hook instructs the schema to invoke the given function whenever a update is
+// about to be applied. The function gets passed the update version number and
+// the running transaction, and if it returns an error it will cause the schema
+// transaction to be rolled back. Any previously installed hook will be
+// replaced.
+func (s *Schema) Hook(hook Hook) {
+	s.hook = hook
+}
+
+// Ensure makes sure that the actual schema in the given database matches the
+// one defined by our updates.
+//
+// All updates are applied transactionally. In case any error occurs the
+// transaction will be rolled back and the database will remain unchanged.
+//
+// A update will be applied only if it hasn't been before (currently applied
+// updates are tracked in the a 'shema' table, which gets automatically
+// created).
+func (s *Schema) Ensure(db *sql.DB) error {
+	return query.Transaction(db, func(tx *sql.Tx) error {
+		err := ensureSchemaTableExists(tx)
+		if err != nil {
+			return err
+		}
+
+		err = ensureUpdatesAreApplied(tx, s.updates, s.hook)
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+}
+
+// Ensure that the schema exists.
+func ensureSchemaTableExists(tx *sql.Tx) error {
+	exists, err := doesSchemaTableExist(tx)
+	if err != nil {
+		return errors.Wrap(err, "failed to check if schema table is there")
+	}
+	if !exists {
+		err := createSchemaTable(tx)
+		if err != nil {
+			return errors.Wrap(err, "failed to create schema table")
+		}
+	}
+	return nil
+}
+
+// Apply any pending update that was not yet applied.
+func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
+	current := 0 // Current update level in the database
+
+	versions, err := selectSchemaVersions(tx)
+	if err != nil {
+		return errors.Wrap(err, "failed to fetch update versions")
+	}
+	if len(versions) > 1 {
+		return fmt.Errorf(
+			"schema table contains %d rows, expected at most one", len(versions))
+	}
+
+	// If this is a fresh database insert a row with this schema's update
+	// level, otherwise update the existing row (it's okay to do this
+	// before actually running the updates since the transaction will be
+	// rolled back in case of errors).
+	if len(versions) == 0 {
+		err := insertSchemaVersion(tx, len(updates))
+		if err != nil {
+			return errors.Wrap(
+				err,
+				fmt.Sprintf("failed to insert version %d", len(updates)))
+		}
+	} else {
+		current = versions[0]
+		if current > len(updates) {
+			return fmt.Errorf(
+				"schema version '%d' is more recent than expected '%d'",
+				current, len(updates))
+		}
+		err := updateSchemaVersion(tx, current, len(updates))
+		if err != nil {
+			return errors.Wrap(
+				err,
+				fmt.Sprintf("failed to update version %d", current))
+		}
+	}
+
+	// If there are no updates, there's nothing to do.
+	if len(updates) == 0 {
+		return nil
+	}
+
+	// Apply missing updates.
+	for _, update := range updates[current:] {
+		if hook != nil {
+			err := hook(current, tx)
+			if err != nil {
+				return errors.Wrap(
+					err,
+					fmt.Sprintf("failed to execute hook (version %d)", current))
+			}
+		}
+		err := update(tx)
+		if err != nil {
+			return errors.Wrap(
+				err,
+				fmt.Sprintf("failed to apply update %d", current))
+		}
+		current++
+	}
+
+	return nil
+}
diff --git a/lxd/db/schema/schema_test.go b/lxd/db/schema/schema_test.go
new file mode 100644
index 000000000..bde5868f0
--- /dev/null
+++ b/lxd/db/schema/schema_test.go
@@ -0,0 +1,173 @@
+package schema_test
+
+import (
+	"database/sql"
+	"fmt"
+	"testing"
+
+	_ "github.com/mattn/go-sqlite3"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/lxc/lxd/lxd/db/query"
+	"github.com/lxc/lxd/lxd/db/schema"
+)
+
+// If the database schema version is more recent than our update series, an
+// error is returned.
+func TestSchemaEnsure_VersionMoreRecentThanExpected(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	schema.Add(updateNoop)
+	assert.NoError(t, schema.Ensure(db))
+
+	schema, _ = newSchemaAndDB(t)
+	err := schema.Ensure(db)
+	assert.NotNil(t, err)
+	assert.EqualError(t, err, "schema version '1' is more recent than expected '0'")
+}
+
+// If there's more than one row in the schema table, an error is returned.
+func TestSchemaEnsure_ExtraVersions(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	assert.NoError(t, schema.Ensure(db))
+
+	_, err := db.Exec(`INSERT INTO schema (version, updated_at) VALUES (2, strftime("%s"))`)
+	assert.NoError(t, err)
+
+	err = schema.Ensure(db)
+	assert.EqualError(t, err, "schema table contains 2 rows, expected at most one")
+}
+
+// If the schema has no update, the schema table gets created and has version 0.
+func TestSchemaEnsure_ZeroUpdates(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+
+	err := schema.Ensure(db)
+	assert.NoError(t, err)
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
+	assert.NoError(t, err)
+	assert.Equal(t, []int{0}, versions)
+}
+
+// If the schema has updates and no one was applied yet, all of them get
+// applied.
+func TestSchemaEnsure_ApplyAllUpdates(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	schema.Add(updateCreateTable)
+	schema.Add(updateInsertValue)
+
+	err := schema.Ensure(db)
+	assert.NoError(t, err)
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	// THe update version is recorded.
+	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
+	assert.NoError(t, err)
+	assert.Equal(t, []int{2}, versions)
+
+	// The two updates have been applied in order.
+	ids, err := query.SelectIntegers(tx, "SELECT id FROM test")
+	assert.NoError(t, err)
+	assert.Equal(t, []int{1}, ids)
+}
+
+// If the schema has updates and part of them were already applied, only the
+// missing ones are applied.
+func TestSchemaEnsure_OnlyApplyMissing(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	schema.Add(updateCreateTable)
+	assert.NoError(t, schema.Ensure(db))
+
+	schema.Add(updateInsertValue)
+	assert.NoError(t, schema.Ensure(db))
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	// All update versions are recorded.
+	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
+	assert.NoError(t, err)
+	assert.Equal(t, []int{2}, versions)
+
+	// The two updates have been applied in order.
+	ids, err := query.SelectIntegers(tx, "SELECT id FROM test")
+	assert.NoError(t, err)
+	assert.Equal(t, []int{1}, ids)
+}
+
+// If a update fails, an error is returned, and all previous changes are rolled
+// back.
+func TestSchemaEnsure_FailingUpdate(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	schema.Add(updateCreateTable)
+	schema.Add(updateBoom)
+	err := schema.Ensure(db)
+	assert.EqualError(t, err, "failed to apply update 1: boom")
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	// Not update was applied.
+	tables, err := query.SelectStrings(tx, "SELECT name FROM sqlite_master WHERE type = 'table'")
+	assert.NoError(t, err)
+	assert.NotContains(t, tables, "schema")
+	assert.NotContains(t, tables, "test")
+}
+
+// If a hook fails, an error is returned, and all previous changes are rolled
+// back.
+func TestSchemaEnsure_FailingHook(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	schema.Add(updateCreateTable)
+	schema.Hook(func(int, *sql.Tx) error { return fmt.Errorf("boom") })
+	err := schema.Ensure(db)
+	assert.EqualError(t, err, "failed to execute hook (version 0): boom")
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	// Not update was applied.
+	tables, err := query.SelectStrings(tx, "SELECT name FROM sqlite_master WHERE type = 'table'")
+	assert.NoError(t, err)
+	assert.NotContains(t, tables, "schema")
+	assert.NotContains(t, tables, "test")
+}
+
+// Return a new in-memory SQLite database.
+func newDB(t *testing.T) *sql.DB {
+	db, err := sql.Open("sqlite3", ":memory:")
+	assert.NoError(t, err)
+	return db
+}
+
+// Return both an empty schema and a test database.
+func newSchemaAndDB(t *testing.T) (*schema.Schema, *sql.DB) {
+	return schema.Empty(), newDB(t)
+}
+
+// An update that does nothing.
+func updateNoop(*sql.Tx) error {
+	return nil
+}
+
+// An update that creates a test table.
+func updateCreateTable(tx *sql.Tx) error {
+	_, err := tx.Exec("CREATE TABLE test (id INTEGER)")
+	return err
+}
+
+// An update that inserts a value into the test table.
+func updateInsertValue(tx *sql.Tx) error {
+	_, err := tx.Exec("INSERT INTO test VALUES (1)")
+	return err
+}
+
+// An update that unconditionally fails with an error.
+func updateBoom(tx *sql.Tx) error {
+	return fmt.Errorf("boom")
+}

From 6278d2e33962105906df17dc041dffc55f208285 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:55:37 +0000
Subject: [PATCH 1185/1193] Add schema.NewFromMap convenience to create a
 schema from a map.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/schema/schema.go      | 36 ++++++++++++++++++++++++++++++++++++
 lxd/db/schema/schema_test.go | 21 +++++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/lxd/db/schema/schema.go b/lxd/db/schema/schema.go
index 77339c9f3..a969c7d48 100644
--- a/lxd/db/schema/schema.go
+++ b/lxd/db/schema/schema.go
@@ -3,6 +3,7 @@ package schema
 import (
 	"database/sql"
 	"fmt"
+	"sort"
 
 	"github.com/pkg/errors"
 
@@ -30,6 +31,41 @@ func New(updates []Update) *Schema {
 	}
 }
 
+// NewFromMap creates a new schema Schema with the updates specified in the
+// given map. The keys of the map are schema versions that when upgraded will
+// trigger the associated Update value. It's required that the minimum key in
+// the map is 1, and if key N is present then N-1 is present too, with N>1
+// (i.e. there are no missing versions).
+//
+// NOTE: the regular New() constructor would be formally enough, but for extra
+//       clarity we also support a map that indicates the version explicitely,
+//       see also PR #3704.
+func NewFromMap(versionsToUpdates map[int]Update) *Schema {
+	// Collect all version keys.
+	versions := []int{}
+	for version := range versionsToUpdates {
+		versions = append(versions, version)
+	}
+
+	// Sort the versions,
+	sort.Sort(sort.IntSlice(versions))
+
+	// Build the updates slice.
+	updates := []Update{}
+	for i, version := range versions {
+		// Assert that we start from 1 and there are no gaps.
+		if version != i+1 {
+			panic(fmt.Sprintf("updates map misses version %d", i+1))
+		}
+
+		updates = append(updates, versionsToUpdates[version])
+	}
+
+	return &Schema{
+		updates: updates,
+	}
+}
+
 // Empty creates a new schema with no updates.
 func Empty() *Schema {
 	return New([]Update{})
diff --git a/lxd/db/schema/schema_test.go b/lxd/db/schema/schema_test.go
index bde5868f0..fc4c797cd 100644
--- a/lxd/db/schema/schema_test.go
+++ b/lxd/db/schema/schema_test.go
@@ -12,6 +12,27 @@ import (
 	"github.com/lxc/lxd/lxd/db/schema"
 )
 
+// Create a new Schema by specifying an explicit map from versions to Update
+// functions.
+func TestNewFromMap(t *testing.T) {
+	db := newDB(t)
+	schema := schema.NewFromMap(map[int]schema.Update{
+		1: updateCreateTable,
+		2: updateInsertValue,
+	})
+	assert.NoError(t, schema.Ensure(db))
+}
+
+// Panic if there are missing versions in the map.
+func TestNewFromMap_MissingVersions(t *testing.T) {
+	assert.Panics(t, func() {
+		schema.NewFromMap(map[int]schema.Update{
+			1: updateCreateTable,
+			3: updateInsertValue,
+		})
+	}, "updates map misses version 2")
+}
+
 // If the database schema version is more recent than our update series, an
 // error is returned.
 func TestSchemaEnsure_VersionMoreRecentThanExpected(t *testing.T) {

From 8547aad1e6e3adb6f781234576c860947d32f7e2 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 11:59:10 +0000
Subject: [PATCH 1186/1193] Wire new schema code into db.go

Handle db updates using the new schema.Schema helper.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/db.go      |  19 ++--
 lxd/db/db_test.go |   3 +-
 lxd/db/update.go  | 276 +++++++++++++++++++++++++-----------------------------
 3 files changed, 140 insertions(+), 158 deletions(-)

diff --git a/lxd/db/db.go b/lxd/db/db.go
index 3ba85661b..b44879f35 100644
--- a/lxd/db/db.go
+++ b/lxd/db/db.go
@@ -233,11 +233,7 @@ func GetSchema(db *sql.DB) (v int) {
 }
 
 func GetLatestSchema() int {
-	if len(dbUpdates) == 0 {
-		return 0
-	}
-
-	return dbUpdates[len(dbUpdates)-1].version
+	return len(updates)
 }
 
 func IsDbLockedError(err error) bool {
@@ -337,8 +333,8 @@ func dbQuery(db *sql.DB, q string, args ...interface{}) (*sql.Rows, error) {
 	return nil, fmt.Errorf("DB is locked")
 }
 
-func doDbQueryScan(db *sql.DB, q string, args []interface{}, outargs []interface{}) ([][]interface{}, error) {
-	rows, err := db.Query(q, args...)
+func doDbQueryScan(qi queryer, q string, args []interface{}, outargs []interface{}) ([][]interface{}, error) {
+	rows, err := qi.Query(q, args...)
 	if err != nil {
 		return [][]interface{}{}, err
 	}
@@ -387,7 +383,12 @@ func doDbQueryScan(db *sql.DB, q string, args []interface{}, outargs []interface
 	return result, nil
 }
 
+type queryer interface {
+	Query(query string, args ...interface{}) (*sql.Rows, error)
+}
+
 /*
+ * . qi anything implementing the querier interface (i.e. either sql.DB or sql.Tx)
  * . q is the database query
  * . inargs is an array of interfaces containing the query arguments
  * . outfmt is an array of interfaces containing the right types of output
@@ -399,9 +400,9 @@ func doDbQueryScan(db *sql.DB, q string, args []interface{}, outargs []interface
  * The result will be an array (one per output row) of arrays (one per output argument)
  * of interfaces, containing pointers to the actual output arguments.
  */
-func QueryScan(db *sql.DB, q string, inargs []interface{}, outfmt []interface{}) ([][]interface{}, error) {
+func QueryScan(qi queryer, q string, inargs []interface{}, outfmt []interface{}) ([][]interface{}, error) {
 	for i := 0; i < 1000; i++ {
-		result, err := doDbQueryScan(db, q, inargs, outfmt)
+		result, err := doDbQueryScan(qi, q, inargs, outfmt)
 		if err == nil {
 			return result, nil
 		}
diff --git a/lxd/db/db_test.go b/lxd/db/db_test.go
index bac862791..1ddc0428b 100644
--- a/lxd/db/db_test.go
+++ b/lxd/db/db_test.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/stretchr/testify/suite"
 
+	"github.com/lxc/lxd/lxd/db/query"
 	"github.com/lxc/lxd/lxd/types"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -215,7 +216,7 @@ INSERT INTO containers_config (container_id, key, value) VALUES (1, 'thekey', 't
 	s.Nil(err)
 
 	// Run the upgrade from V6 code
-	err = dbUpdateFromV6(5, 6, db)
+	err = query.Transaction(db, updateFromV6)
 	s.Nil(err)
 
 	// Make sure the inserted data is still there.
diff --git a/lxd/db/update.go b/lxd/db/update.go
index 417774922..c1eb25a1c 100644
--- a/lxd/db/update.go
+++ b/lxd/db/update.go
@@ -8,6 +8,7 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/lxc/lxd/lxd/db/schema"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -29,62 +30,39 @@ import (
    Only append to the updates list, never remove entries and never re-order them.
 */
 
-var dbUpdates = []dbUpdate{
-	{version: 1, run: dbUpdateFromV0},
-	{version: 2, run: dbUpdateFromV1},
-	{version: 3, run: dbUpdateFromV2},
-	{version: 4, run: dbUpdateFromV3},
-	{version: 5, run: dbUpdateFromV4},
-	{version: 6, run: dbUpdateFromV5},
-	{version: 7, run: dbUpdateFromV6},
-	{version: 8, run: dbUpdateFromV7},
-	{version: 9, run: dbUpdateFromV8},
-	{version: 10, run: dbUpdateFromV9},
-	{version: 11, run: dbUpdateFromV10},
-	{version: 12, run: dbUpdateFromV11},
-	{version: 13, run: dbUpdateFromV12},
-	{version: 14, run: dbUpdateFromV13},
-	{version: 15, run: dbUpdateFromV14},
-	{version: 16, run: dbUpdateFromV15},
-	{version: 17, run: dbUpdateFromV16},
-	{version: 18, run: dbUpdateFromV17},
-	{version: 19, run: dbUpdateFromV18},
-	{version: 20, run: dbUpdateFromV19},
-	{version: 21, run: dbUpdateFromV20},
-	{version: 22, run: dbUpdateFromV21},
-	{version: 23, run: dbUpdateFromV22},
-	{version: 24, run: dbUpdateFromV23},
-	{version: 25, run: dbUpdateFromV24},
-	{version: 26, run: dbUpdateFromV25},
-	{version: 27, run: dbUpdateFromV26},
-	{version: 28, run: dbUpdateFromV27},
-	{version: 29, run: dbUpdateFromV28},
-	{version: 30, run: dbUpdateFromV29},
-	{version: 31, run: dbUpdateFromV30},
-	{version: 32, run: dbUpdateFromV31},
-}
-
-type dbUpdate struct {
-	version int
-	run     func(previousVersion int, version int, db *sql.DB) error
-}
-
-func (u *dbUpdate) apply(currentVersion int, db *sql.DB) error {
-	// Get the current schema version
-
-	logger.Debugf("Updating DB schema from %d to %d", currentVersion, u.version)
-
-	err := u.run(currentVersion, u.version, db)
-	if err != nil {
-		return err
-	}
-
-	_, err = db.Exec("INSERT INTO schema (version, updated_at) VALUES (?, strftime(\"%s\"));", u.version)
-	if err != nil {
-		return err
-	}
-
-	return nil
+var updates = map[int]schema.Update{
+	1:  updateFromV0,
+	2:  updateFromV1,
+	3:  updateFromV2,
+	4:  updateFromV3,
+	5:  updateFromV4,
+	6:  updateFromV5,
+	7:  updateFromV6,
+	8:  updateFromV7,
+	9:  updateFromV8,
+	10: updateFromV9,
+	11: updateFromV10,
+	12: updateFromV11,
+	13: updateFromV12,
+	14: updateFromV13,
+	15: updateFromV14,
+	16: updateFromV15,
+	17: updateFromV16,
+	18: updateFromV17,
+	19: updateFromV18,
+	20: updateFromV19,
+	21: updateFromV20,
+	22: updateFromV21,
+	23: updateFromV22,
+	24: updateFromV23,
+	25: updateFromV24,
+	26: updateFromV25,
+	27: updateFromV26,
+	28: updateFromV27,
+	29: updateFromV28,
+	30: updateFromV29,
+	31: updateFromV30,
+	32: updateFromV31,
 }
 
 // Apply all possible database patches. If "doBackup" is true, the
@@ -95,14 +73,10 @@ func (u *dbUpdate) apply(currentVersion int, db *sql.DB) error {
 // the legacy V10 and V15 non-db updates during the database upgrade
 // sequence to, avoid changing semantics see PR #3322).
 func UpdatesApplyAll(db *sql.DB, doBackup bool, postApply func(int) error) error {
-	currentVersion := GetSchema(db)
-
 	backup := false
-	for _, update := range dbUpdates {
-		if update.version <= currentVersion {
-			continue
-		}
 
+	schema := schema.NewFromMap(updates)
+	schema.Hook(func(version int, tx *sql.Tx) error {
 		if doBackup && !backup {
 			logger.Infof("Updating the LXD database schema. Backup made as \"lxd.db.bak\"")
 			err := shared.FileCopy(shared.VarPath("lxd.db"), shared.VarPath("lxd.db.bak"))
@@ -112,26 +86,39 @@ func UpdatesApplyAll(db *sql.DB, doBackup bool, postApply func(int) error) error
 
 			backup = true
 		}
+		logger.Debugf("Updating DB schema from %d to %d", version, version+1)
 
-		err := update.apply(currentVersion, db)
-		if err != nil {
-			return err
-		}
 		if postApply != nil {
-			err = postApply(update.version)
+			legacy := []int{11, 12, 16} // Legacy patches doing DB work
+			if shared.IntInSlice(version, legacy) {
+				// FIXME We need to commit the transaction before the
+				// hook and then open it again afterwards because this
+				// legacy patch pokes with the database and would fail
+				// with a lock error otherwise.
+				_, err := tx.Exec("COMMIT")
+				if err != nil {
+					return err
+				}
+			}
+			err := postApply(version + 1)
 			if err != nil {
 				return err
 			}
+			if shared.IntInSlice(version, legacy) {
+				_, err := tx.Exec("BEGIN")
+				if err != nil {
+					return err
+				}
+			}
+			return nil
 		}
-
-		currentVersion = update.version
-	}
-
-	return nil
+		return nil
+	})
+	return schema.Ensure(db)
 }
 
 // Schema updates begin here
-func dbUpdateFromV31(currentVersion int, version int, db *sql.DB) error {
+func updateFromV31(tx *sql.Tx) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS patches (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@@ -139,17 +126,17 @@ CREATE TABLE IF NOT EXISTS patches (
     applied_at DATETIME NOT NULL,
     UNIQUE (name)
 );`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV30(currentVersion int, version int, db *sql.DB) error {
+func updateFromV30(tx *sql.Tx) error {
 	// NOTE: this database update contained daemon-level logic which
 	//       was been moved to patchUpdateFromV15 in patches.go.
 	return nil
 }
 
-func dbUpdateFromV29(currentVersion int, version int, db *sql.DB) error {
+func updateFromV29(tx *sql.Tx) error {
 	if shared.PathExists(shared.VarPath("zfs.img")) {
 		err := os.Chmod(shared.VarPath("zfs.img"), 0600)
 		if err != nil {
@@ -160,22 +147,22 @@ func dbUpdateFromV29(currentVersion int, version int, db *sql.DB) error {
 	return nil
 }
 
-func dbUpdateFromV28(currentVersion int, version int, db *sql.DB) error {
+func updateFromV28(tx *sql.Tx) error {
 	stmt := `
 INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "aadisable", 2 FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "source", "/dev/null" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker" AND profiles_devices.name = "aadisable";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "path", "/sys/module/apparmor/parameters/enabled" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker" AND profiles_devices.name = "aadisable";`
-	db.Exec(stmt)
+	tx.Exec(stmt)
 
 	return nil
 }
 
-func dbUpdateFromV27(currentVersion int, version int, db *sql.DB) error {
-	_, err := db.Exec("UPDATE profiles_devices SET type=3 WHERE type='unix-char';")
+func updateFromV27(tx *sql.Tx) error {
+	_, err := tx.Exec("UPDATE profiles_devices SET type=3 WHERE type='unix-char';")
 	return err
 }
 
-func dbUpdateFromV26(currentVersion int, version int, db *sql.DB) error {
+func updateFromV26(tx *sql.Tx) error {
 	stmt := `
 ALTER TABLE images ADD COLUMN auto_update INTEGER NOT NULL DEFAULT 0;
 CREATE TABLE IF NOT EXISTS images_source (
@@ -187,58 +174,58 @@ CREATE TABLE IF NOT EXISTS images_source (
     alias VARCHAR(255) NOT NULL,
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
 );`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV25(currentVersion int, version int, db *sql.DB) error {
+func updateFromV25(tx *sql.Tx) error {
 	stmt := `
 INSERT INTO profiles (name, description) VALUES ("docker", "Profile supporting docker in containers");
 INSERT INTO profiles_config (profile_id, key, value) SELECT id, "security.nesting", "true" FROM profiles WHERE name="docker";
 INSERT INTO profiles_config (profile_id, key, value) SELECT id, "linux.kernel_modules", "overlay, nf_nat" FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices (profile_id, name, type) SELECT id, "fuse", "unix-char" FROM profiles WHERE name="docker";
 INSERT INTO profiles_devices_config (profile_device_id, key, value) SELECT profiles_devices.id, "path", "/dev/fuse" FROM profiles_devices LEFT JOIN profiles WHERE profiles_devices.profile_id = profiles.id AND profiles.name = "docker";`
-	db.Exec(stmt)
+	tx.Exec(stmt)
 
 	return nil
 }
 
-func dbUpdateFromV24(currentVersion int, version int, db *sql.DB) error {
-	_, err := db.Exec("ALTER TABLE containers ADD COLUMN stateful INTEGER NOT NULL DEFAULT 0;")
+func updateFromV24(tx *sql.Tx) error {
+	_, err := tx.Exec("ALTER TABLE containers ADD COLUMN stateful INTEGER NOT NULL DEFAULT 0;")
 	return err
 }
 
-func dbUpdateFromV23(currentVersion int, version int, db *sql.DB) error {
-	_, err := db.Exec("ALTER TABLE profiles ADD COLUMN description TEXT;")
+func updateFromV23(tx *sql.Tx) error {
+	_, err := tx.Exec("ALTER TABLE profiles ADD COLUMN description TEXT;")
 	return err
 }
 
-func dbUpdateFromV22(currentVersion int, version int, db *sql.DB) error {
+func updateFromV22(tx *sql.Tx) error {
 	stmt := `
 DELETE FROM containers_devices_config WHERE key='type';
 DELETE FROM profiles_devices_config WHERE key='type';`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV21(currentVersion int, version int, db *sql.DB) error {
-	_, err := db.Exec("ALTER TABLE containers ADD COLUMN creation_date DATETIME NOT NULL DEFAULT 0;")
+func updateFromV21(tx *sql.Tx) error {
+	_, err := tx.Exec("ALTER TABLE containers ADD COLUMN creation_date DATETIME NOT NULL DEFAULT 0;")
 	return err
 }
 
-func dbUpdateFromV20(currentVersion int, version int, db *sql.DB) error {
+func updateFromV20(tx *sql.Tx) error {
 	stmt := `
 UPDATE containers_devices SET name='__lxd_upgrade_root' WHERE name='root';
 UPDATE profiles_devices SET name='__lxd_upgrade_root' WHERE name='root';
 
 INSERT INTO containers_devices (container_id, name, type) SELECT id, "root", 2 FROM containers;
 INSERT INTO containers_devices_config (container_device_id, key, value) SELECT id, "path", "/" FROM containers_devices WHERE name='root';`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 
 	return err
 }
 
-func dbUpdateFromV19(currentVersion int, version int, db *sql.DB) error {
+func updateFromV19(tx *sql.Tx) error {
 	stmt := `
 DELETE FROM containers_config WHERE container_id NOT IN (SELECT id FROM containers);
 DELETE FROM containers_devices_config WHERE container_device_id NOT IN (SELECT id FROM containers_devices WHERE container_id IN (SELECT id FROM containers));
@@ -246,16 +233,16 @@ DELETE FROM containers_devices WHERE container_id NOT IN (SELECT id FROM contain
 DELETE FROM containers_profiles WHERE container_id NOT IN (SELECT id FROM containers);
 DELETE FROM images_aliases WHERE image_id NOT IN (SELECT id FROM images);
 DELETE FROM images_properties WHERE image_id NOT IN (SELECT id FROM images);`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV18(currentVersion int, version int, db *sql.DB) error {
+func updateFromV18(tx *sql.Tx) error {
 	var id int
 	var value string
 
 	// Update container config
-	rows, err := QueryScan(db, "SELECT id, value FROM containers_config WHERE key='limits.memory'", nil, []interface{}{id, value})
+	rows, err := QueryScan(tx, "SELECT id, value FROM containers_config WHERE key='limits.memory'", nil, []interface{}{id, value})
 	if err != nil {
 		return err
 	}
@@ -278,21 +265,21 @@ func dbUpdateFromV18(currentVersion int, version int, db *sql.DB) error {
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
 			logger.Debugf("Invalid container memory limit, id=%d value=%s, removing.", id, value)
-			_, err = db.Exec("DELETE FROM containers_config WHERE id=?;", id)
+			_, err = tx.Exec("DELETE FROM containers_config WHERE id=?;", id)
 			if err != nil {
 				return err
 			}
 		}
 
 		// Set the new value
-		_, err = db.Exec("UPDATE containers_config SET value=? WHERE id=?", value, id)
+		_, err = tx.Exec("UPDATE containers_config SET value=? WHERE id=?", value, id)
 		if err != nil {
 			return err
 		}
 	}
 
 	// Update profiles config
-	rows, err = QueryScan(db, "SELECT id, value FROM profiles_config WHERE key='limits.memory'", nil, []interface{}{id, value})
+	rows, err = QueryScan(tx, "SELECT id, value FROM profiles_config WHERE key='limits.memory'", nil, []interface{}{id, value})
 	if err != nil {
 		return err
 	}
@@ -315,14 +302,14 @@ func dbUpdateFromV18(currentVersion int, version int, db *sql.DB) error {
 		_, err = shared.ParseByteSizeString(value)
 		if err != nil {
 			logger.Debugf("Invalid profile memory limit, id=%d value=%s, removing.", id, value)
-			_, err = db.Exec("DELETE FROM profiles_config WHERE id=?;", id)
+			_, err = tx.Exec("DELETE FROM profiles_config WHERE id=?;", id)
 			if err != nil {
 				return err
 			}
 		}
 
 		// Set the new value
-		_, err = db.Exec("UPDATE profiles_config SET value=? WHERE id=?", value, id)
+		_, err = tx.Exec("UPDATE profiles_config SET value=? WHERE id=?", value, id)
 		if err != nil {
 			return err
 		}
@@ -331,30 +318,30 @@ func dbUpdateFromV18(currentVersion int, version int, db *sql.DB) error {
 	return nil
 }
 
-func dbUpdateFromV17(currentVersion int, version int, db *sql.DB) error {
+func updateFromV17(tx *sql.Tx) error {
 	stmt := `
 DELETE FROM profiles_config WHERE key LIKE 'volatile.%';
 UPDATE containers_config SET key='limits.cpu' WHERE key='limits.cpus';
 UPDATE profiles_config SET key='limits.cpu' WHERE key='limits.cpus';`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV16(currentVersion int, version int, db *sql.DB) error {
+func updateFromV16(tx *sql.Tx) error {
 	stmt := `
 UPDATE config SET key='storage.lvm_vg_name' WHERE key = 'core.lvm_vg_name';
 UPDATE config SET key='storage.lvm_thinpool_name' WHERE key = 'core.lvm_thinpool_name';`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV15(currentVersion int, version int, db *sql.DB) error {
+func updateFromV15(tx *sql.Tx) error {
 	// NOTE: this database update contained daemon-level logic which
 	//       was been moved to patchUpdateFromV15 in patches.go.
 	return nil
 }
 
-func dbUpdateFromV14(currentVersion int, version int, db *sql.DB) error {
+func updateFromV14(tx *sql.Tx) error {
 	stmt := `
 PRAGMA foreign_keys=OFF; -- So that integrity doesn't get in the way for now
 
@@ -383,38 +370,38 @@ DROP TABLE containers;
 ALTER TABLE tmp RENAME TO containers;
 
 PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV13(currentVersion int, version int, db *sql.DB) error {
+func updateFromV13(tx *sql.Tx) error {
 	stmt := `
 UPDATE containers_config SET key='volatile.base_image' WHERE key = 'volatile.baseImage';`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV12(currentVersion int, version int, db *sql.DB) error {
+func updateFromV12(tx *sql.Tx) error {
 	stmt := `
 ALTER TABLE images ADD COLUMN cached INTEGER NOT NULL DEFAULT 0;
 ALTER TABLE images ADD COLUMN last_use_date DATETIME;`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV11(currentVersion int, version int, db *sql.DB) error {
+func updateFromV11(tx *sql.Tx) error {
 	// NOTE: this database update contained daemon-level logic which
 	//       was been moved to patchUpdateFromV15 in patches.go.
 	return nil
 }
 
-func dbUpdateFromV10(currentVersion int, version int, db *sql.DB) error {
+func updateFromV10(tx *sql.Tx) error {
 	// NOTE: this database update contained daemon-level logic which
 	//       was been moved to patchUpdateFromV10 in patches.go.
 	return nil
 }
 
-func dbUpdateFromV9(currentVersion int, version int, db *sql.DB) error {
+func updateFromV9(tx *sql.Tx) error {
 	stmt := `
 CREATE TABLE tmp (
     id INTEGER primary key AUTOINCREMENT NOT NULL,
@@ -453,26 +440,26 @@ UPDATE profiles_devices SET type=3 WHERE id IN (SELECT id FROM tmp WHERE type="u
 UPDATE profiles_devices SET type=4 WHERE id IN (SELECT id FROM tmp WHERE type="unix-block");
 
 DROP TABLE tmp;`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV8(currentVersion int, version int, db *sql.DB) error {
+func updateFromV8(tx *sql.Tx) error {
 	stmt := `
 UPDATE certificates SET fingerprint = replace(fingerprint, " ", "");`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV7(currentVersion int, version int, db *sql.DB) error {
+func updateFromV7(tx *sql.Tx) error {
 	stmt := `
 UPDATE config SET key='core.trust_password' WHERE key IN ('password', 'trust_password', 'trust-password', 'core.trust-password');
 DELETE FROM config WHERE key != 'core.trust_password';`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV6(currentVersion int, version int, db *sql.DB) error {
+func updateFromV6(tx *sql.Tx) error {
 	// This update recreates the schemas that need an ON DELETE CASCADE foreign
 	// key.
 	stmt := `
@@ -597,13 +584,13 @@ INSERT INTO profiles_devices_config SELECT * FROM tmp;
 DROP TABLE tmp;
 
 PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	if err != nil {
 		return err
 	}
 
 	// Get the rows with broken foreign keys an nuke them
-	rows, err := db.Query("PRAGMA foreign_key_check;")
+	rows, err := tx.Query("PRAGMA foreign_key_check;")
 	if err != nil {
 		return err
 	}
@@ -625,7 +612,7 @@ PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
 	rows.Close()
 
 	for i := range tablestodelete {
-		_, err = db.Exec(fmt.Sprintf("DELETE FROM %s WHERE rowid = %d;", tablestodelete[i], rowidtodelete[i]))
+		_, err = tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE rowid = %d;", tablestodelete[i], rowidtodelete[i]))
 		if err != nil {
 			return err
 		}
@@ -634,15 +621,15 @@ PRAGMA foreign_keys=ON; -- Make sure we turn integrity checks back on.`
 	return err
 }
 
-func dbUpdateFromV5(currentVersion int, version int, db *sql.DB) error {
+func updateFromV5(tx *sql.Tx) error {
 	stmt := `
 ALTER TABLE containers ADD COLUMN power_state INTEGER NOT NULL DEFAULT 0;
 ALTER TABLE containers ADD COLUMN ephemeral INTEGER NOT NULL DEFAULT 0;`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV4(currentVersion int, version int, db *sql.DB) error {
+func updateFromV4(tx *sql.Tx) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS config (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@@ -651,7 +638,7 @@ CREATE TABLE IF NOT EXISTS config (
     UNIQUE (key)
 );`
 
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	if err != nil {
 		return err
 	}
@@ -670,7 +657,7 @@ CREATE TABLE IF NOT EXISTS config (
 		oldPassword = hex.EncodeToString(buff)
 		stmt := `INSERT INTO config (key, value) VALUES ("core.trust_password", ?);`
 
-		_, err := db.Exec(stmt, oldPassword)
+		_, err := tx.Exec(stmt, oldPassword)
 		if err != nil {
 			return err
 		}
@@ -681,14 +668,14 @@ CREATE TABLE IF NOT EXISTS config (
 	return nil
 }
 
-func dbUpdateFromV3(currentVersion int, version int, db *sql.DB) error {
+func updateFromV3(tx *sql.Tx) error {
 	// Attempt to create a default profile (but don't fail if already there)
-	db.Exec("INSERT INTO profiles (name) VALUES (\"default\");")
+	tx.Exec("INSERT INTO profiles (name) VALUES (\"default\");")
 
 	return nil
 }
 
-func dbUpdateFromV2(currentVersion int, version int, db *sql.DB) error {
+func updateFromV2(tx *sql.Tx) error {
 	stmt := `
 CREATE TABLE IF NOT EXISTS containers_devices (
     id INTEGER primary key AUTOINCREMENT NOT NULL,
@@ -744,11 +731,11 @@ CREATE TABLE IF NOT EXISTS profiles_devices_config (
     UNIQUE (profile_device_id, key),
     FOREIGN KEY (profile_device_id) REFERENCES profiles_devices (id)
 );`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV1(currentVersion int, version int, db *sql.DB) error {
+func updateFromV1(tx *sql.Tx) error {
 	// v1..v2 adds images aliases
 	stmt := `
 CREATE TABLE IF NOT EXISTS images_aliases (
@@ -759,19 +746,12 @@ CREATE TABLE IF NOT EXISTS images_aliases (
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
     UNIQUE (name)
 );`
-	_, err := db.Exec(stmt)
+	_, err := tx.Exec(stmt)
 	return err
 }
 
-func dbUpdateFromV0(currentVersion int, version int, db *sql.DB) error {
-	// v0..v1 adds schema table
-	stmt := `
-CREATE TABLE IF NOT EXISTS schema (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    version INTEGER NOT NULL,
-    updated_at DATETIME NOT NULL,
-    UNIQUE (version)
-);`
-	_, err := db.Exec(stmt)
-	return err
+func updateFromV0(tx *sql.Tx) error {
+	// v0..v1 is a noop. It used to add the schema table, but that's now
+	// done by Schema.Ensure().
+	return nil
 }

From 029cfe84e2f0216be2ed3c32d1239d53ee4ef835 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 12:28:37 +0000
Subject: [PATCH 1187/1193] Add Schema.Dump() method for flattening a series of
 schema updates

The new Schema.Dump() will apply all available schema updates to a
fresh empty database and then dump the resulting schema as text of SQL
statements. This effectively "flattens" the schema updates into a
single blob of SQL statements that can be used to initialize new
databases.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/schema/query.go       |  9 ++++++
 lxd/db/schema/schema.go      | 65 ++++++++++++++++++++++++++++++++++++++++++++
 lxd/db/schema/schema_test.go | 46 +++++++++++++++++++++++++++++++
 3 files changed, 120 insertions(+)

diff --git a/lxd/db/schema/query.go b/lxd/db/schema/query.go
index d9c79587d..dd40680dc 100644
--- a/lxd/db/schema/query.go
+++ b/lxd/db/schema/query.go
@@ -37,6 +37,15 @@ SELECT version FROM schema ORDER BY version
 	return query.SelectIntegers(tx, statement)
 }
 
+// Return a list of SQL statements that can be used to create all tables in the
+// database.
+func selectTablesSQL(tx *sql.Tx) ([]string, error) {
+	statement := `
+SELECT sql FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name
+`
+	return query.SelectStrings(tx, statement)
+}
+
 // Create the schema table.
 func createSchemaTable(tx *sql.Tx) error {
 	statement := `
diff --git a/lxd/db/schema/schema.go b/lxd/db/schema/schema.go
index a969c7d48..d0d83ae28 100644
--- a/lxd/db/schema/schema.go
+++ b/lxd/db/schema/schema.go
@@ -4,6 +4,7 @@ import (
 	"database/sql"
 	"fmt"
 	"sort"
+	"strings"
 
 	"github.com/pkg/errors"
 
@@ -111,6 +112,38 @@ func (s *Schema) Ensure(db *sql.DB) error {
 	})
 }
 
+// Dump returns a text of SQL commands that can be used to create this schema
+// from scratch in one go, without going thorugh individual patches
+// (essentially flattening them).
+//
+// It requires that all patches in this schema have been applied, otherwise an
+// error will be returned.
+func (s *Schema) Dump(db *sql.DB) (string, error) {
+	var statements []string
+	err := query.Transaction(db, func(tx *sql.Tx) error {
+		err := checkAllUpdatesAreApplied(tx, s.updates)
+		if err != nil {
+			return err
+		}
+		statements, err = selectTablesSQL(tx)
+		return err
+	})
+	if err != nil {
+		return "", err
+	}
+	for i, statement := range statements {
+		statements[i] = formatSQL(statement)
+	}
+
+	// Add a statement for inserting the current schema version row.
+	statements = append(
+		statements,
+		fmt.Sprintf(`
+INSERT INTO schema (version, updated_at) VALUES (%d, strftime("%%s"))
+`, len(s.updates)))
+	return strings.Join(statements, ";\n"), nil
+}
+
 // Ensure that the schema exists.
 func ensureSchemaTableExists(tx *sql.Tx) error {
 	exists, err := doesSchemaTableExist(tx)
@@ -191,3 +224,35 @@ func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
 
 	return nil
 }
+
+// Check that all the given updates are applied.
+func checkAllUpdatesAreApplied(tx *sql.Tx, updates []Update) error {
+	versions, err := selectSchemaVersions(tx)
+	if err != nil {
+		return errors.Wrap(err, "failed to fetch update versions")
+	}
+	if len(versions) != 1 {
+		return fmt.Errorf("schema table contains %d rows, expected 1", len(versions))
+	}
+	if versions[0] != len(updates) {
+		return fmt.Errorf("update level is %d, expected %d", versions[0], len(updates))
+	}
+	return nil
+}
+
+// Format the given SQL statement in a human-readable way.
+//
+// In particular make sure that each column definition in a CREATE TABLE clause
+// is in its own row, since SQLite dumps occasionally stuff more than one
+// column in the same line.
+func formatSQL(statement string) string {
+	lines := strings.Split(statement, "\n")
+	for i, line := range lines {
+		if strings.Contains(line, "UNIQUE") {
+			// Let UNIQUE(x, y) constraints alone.
+			continue
+		}
+		lines[i] = strings.Replace(line, ", ", ",\n    ", -1)
+	}
+	return strings.Join(lines, "\n")
+}
diff --git a/lxd/db/schema/schema_test.go b/lxd/db/schema/schema_test.go
index fc4c797cd..e251573b6 100644
--- a/lxd/db/schema/schema_test.go
+++ b/lxd/db/schema/schema_test.go
@@ -159,6 +159,46 @@ func TestSchemaEnsure_FailingHook(t *testing.T) {
 	assert.NotContains(t, tables, "test")
 }
 
+// The SQL text returns by Dump() can be used to create the schema from
+// scratch, without applying each individual update.
+func TestSchemaDump(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	schema.Add(updateCreateTable)
+	schema.Add(updateAddColumn)
+	assert.NoError(t, schema.Ensure(db))
+
+	dump, err := schema.Dump(db)
+	assert.NoError(t, err)
+
+	_, db = newSchemaAndDB(t)
+	_, err = db.Exec(dump)
+	assert.NoError(t, err)
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	// All update versions are in place.
+	versions, err := query.SelectIntegers(tx, "SELECT version FROM schema")
+	assert.NoError(t, err)
+	assert.Equal(t, []int{2}, versions)
+
+	// Both the table added by the first update and the extra column added
+	// by the second update are there.
+	_, err = tx.Exec("SELECT id, name FROM test")
+	assert.NoError(t, err)
+}
+
+// If not all updates are applied, Dump() returns an error.
+func TestSchemaDump_MissingUpdatees(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	schema.Add(updateCreateTable)
+	assert.NoError(t, schema.Ensure(db))
+	schema.Add(updateAddColumn)
+
+	_, err := schema.Dump(db)
+	assert.EqualError(t, err, "update level is 1, expected 2")
+}
+
 // Return a new in-memory SQLite database.
 func newDB(t *testing.T) *sql.DB {
 	db, err := sql.Open("sqlite3", ":memory:")
@@ -188,6 +228,12 @@ func updateInsertValue(tx *sql.Tx) error {
 	return err
 }
 
+// An update that adds a column to the test tabble.
+func updateAddColumn(tx *sql.Tx) error {
+	_, err := tx.Exec("ALTER TABLE test ADD COLUMN name TEXT")
+	return err
+}
+
 // An update that unconditionally fails with an error.
 func updateBoom(tx *sql.Tx) error {
 	return fmt.Errorf("boom")

From 3035799a50fd773d27f2ea7dea2b5a98616134d9 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 12:31:38 +0000
Subject: [PATCH 1188/1193] Automatically generate database schema from
 database updates

This change integrates the new Schema.Dump() method in the LXD
development process. From now one there's a single source of truth of
database schema, the database updates, and there's no need to keep
updates and CURRENT_SCHEMA declarations in sync, since CURRENT_SCHEMA
is generated automatically.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 Makefile         |   5 ++
 lxd/daemon.go    |  20 ++++---
 lxd/db/db.go     | 149 ---------------------------------------------------
 lxd/db/schema.go | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 lxd/db/update.go | 158 ++++++++++++++++++++++++++++++++++++++++++++++---------
 lxd/patches.go   |   3 +-
 lxd/schema.go    |  22 ++++++++
 7 files changed, 325 insertions(+), 182 deletions(-)
 create mode 100644 lxd/db/schema.go
 create mode 100644 lxd/schema.go

diff --git a/Makefile b/Makefile
index a2e77ec15..545cd482f 100644
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,11 @@ update:
 	go get -t -v -d -u ./...
 	@echo "Dependencies updated"
 
+.PHONY: update
+update-schema:
+	go run -v $(TAGS) ./lxd/schema.go
+	@echo "Schema source code updated"
+
 .PHONY: debug
 debug:
 	go get -t -v -d ./...
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 9f214189e..c4e2d54bf 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -956,17 +956,23 @@ func initializeDbObject(d *Daemon, path string) error {
 
 	// Apply any database update.
 	//
-	// NOTE: we use the postApply parameter to run a couple of
-	// legacy non-db updates that were introduced before the
+	// NOTE: we use the legacyPatches parameter to run a few
+	// legacy non-db updates that were in place before the
 	// patches mechanism was introduced in lxd/patches.go. The
 	// rest of non-db patches will be applied separately via
 	// patchesApplyAll. See PR #3322 for more details.
-	err = db.UpdatesApplyAll(d.db, true, func(version int) error {
-		if legacyPatch, ok := legacyPatches[version]; ok {
-			return legacyPatch(d)
+	legacy := map[int]*db.LegacyPatch{}
+	for i, patch := range legacyPatches {
+		legacy[i] = &db.LegacyPatch{
+			Hook: func() error {
+				return patch(d)
+			},
 		}
-		return nil
-	})
+	}
+	for _, i := range legacyPatchesNeedingDB {
+		legacy[i].NeedsDB = true
+	}
+	err = db.UpdatesApplyAll(d.db, true, legacy)
 	if err != nil {
 		return err
 	}
diff --git a/lxd/db/db.go b/lxd/db/db.go
index b44879f35..b3dcb148c 100644
--- a/lxd/db/db.go
+++ b/lxd/db/db.go
@@ -25,148 +25,6 @@ var (
 	NoSuchObjectError = fmt.Errorf("No such object")
 )
 
-// CURRENT_SCHEMA contains the current SQLite SQL Schema.
-const CURRENT_SCHEMA string = `
-CREATE TABLE IF NOT EXISTS certificates (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    fingerprint VARCHAR(255) NOT NULL,
-    type INTEGER NOT NULL,
-    name VARCHAR(255) NOT NULL,
-    certificate TEXT NOT NULL,
-    UNIQUE (fingerprint)
-);
-CREATE TABLE IF NOT EXISTS config (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    key VARCHAR(255) NOT NULL,
-    value TEXT,
-    UNIQUE (key)
-);
-CREATE TABLE IF NOT EXISTS containers (
-    id INTEGER primary key AUTOINCREMENT NOT NULL,
-    name VARCHAR(255) NOT NULL,
-    architecture INTEGER NOT NULL,
-    type INTEGER NOT NULL,
-    ephemeral INTEGER NOT NULL DEFAULT 0,
-    stateful INTEGER NOT NULL DEFAULT 0,
-    creation_date DATETIME,
-    UNIQUE (name)
-);
-CREATE TABLE IF NOT EXISTS containers_config (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    container_id INTEGER NOT NULL,
-    key VARCHAR(255) NOT NULL,
-    value TEXT,
-    FOREIGN KEY (container_id) REFERENCES containers (id) ON DELETE CASCADE,
-    UNIQUE (container_id, key)
-);
-CREATE TABLE IF NOT EXISTS containers_devices (
-    id INTEGER primary key AUTOINCREMENT NOT NULL,
-    container_id INTEGER NOT NULL,
-    name VARCHAR(255) NOT NULL,
-    type INTEGER NOT NULL default 0,
-    FOREIGN KEY (container_id) REFERENCES containers (id) ON DELETE CASCADE,
-    UNIQUE (container_id, name)
-);
-CREATE TABLE IF NOT EXISTS containers_devices_config (
-    id INTEGER primary key AUTOINCREMENT NOT NULL,
-    container_device_id INTEGER NOT NULL,
-    key VARCHAR(255) NOT NULL,
-    value TEXT,
-    FOREIGN KEY (container_device_id) REFERENCES containers_devices (id) ON DELETE CASCADE,
-    UNIQUE (container_device_id, key)
-);
-CREATE TABLE IF NOT EXISTS containers_profiles (
-    id INTEGER primary key AUTOINCREMENT NOT NULL,
-    container_id INTEGER NOT NULL,
-    profile_id INTEGER NOT NULL,
-    apply_order INTEGER NOT NULL default 0,
-    UNIQUE (container_id, profile_id),
-    FOREIGN KEY (container_id) REFERENCES containers(id) ON DELETE CASCADE,
-    FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
-);
-CREATE TABLE IF NOT EXISTS images (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    cached INTEGER NOT NULL DEFAULT 0,
-    fingerprint VARCHAR(255) NOT NULL,
-    filename VARCHAR(255) NOT NULL,
-    size INTEGER NOT NULL,
-    public INTEGER NOT NULL DEFAULT 0,
-    auto_update INTEGER NOT NULL DEFAULT 0,
-    architecture INTEGER NOT NULL,
-    creation_date DATETIME,
-    expiry_date DATETIME,
-    upload_date DATETIME NOT NULL,
-    last_use_date DATETIME,
-    UNIQUE (fingerprint)
-);
-CREATE TABLE IF NOT EXISTS images_aliases (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    name VARCHAR(255) NOT NULL,
-    image_id INTEGER NOT NULL,
-    description VARCHAR(255),
-    FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
-    UNIQUE (name)
-);
-CREATE TABLE IF NOT EXISTS images_properties (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    image_id INTEGER NOT NULL,
-    type INTEGER NOT NULL,
-    key VARCHAR(255) NOT NULL,
-    value TEXT,
-    FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
-);
-CREATE TABLE IF NOT EXISTS images_source (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    image_id INTEGER NOT NULL,
-    server TEXT NOT NULL,
-    protocol INTEGER NOT NULL,
-    certificate TEXT NOT NULL,
-    alias VARCHAR(255) NOT NULL,
-    FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
-);
-CREATE TABLE IF NOT EXISTS patches (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    name VARCHAR(255) NOT NULL,
-    applied_at DATETIME NOT NULL,
-    UNIQUE (name)
-);
-CREATE TABLE IF NOT EXISTS profiles (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    name VARCHAR(255) NOT NULL,
-    description TEXT,
-    UNIQUE (name)
-);
-CREATE TABLE IF NOT EXISTS profiles_config (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    profile_id INTEGER NOT NULL,
-    key VARCHAR(255) NOT NULL,
-    value VARCHAR(255),
-    UNIQUE (profile_id, key),
-    FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
-);
-CREATE TABLE IF NOT EXISTS profiles_devices (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    profile_id INTEGER NOT NULL,
-    name VARCHAR(255) NOT NULL,
-    type INTEGER NOT NULL default 0,
-    UNIQUE (profile_id, name),
-    FOREIGN KEY (profile_id) REFERENCES profiles (id) ON DELETE CASCADE
-);
-CREATE TABLE IF NOT EXISTS profiles_devices_config (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    profile_device_id INTEGER NOT NULL,
-    key VARCHAR(255) NOT NULL,
-    value TEXT,
-    UNIQUE (profile_device_id, key),
-    FOREIGN KEY (profile_device_id) REFERENCES profiles_devices (id) ON DELETE CASCADE
-);
-CREATE TABLE IF NOT EXISTS schema (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    version INTEGER NOT NULL,
-    updated_at DATETIME NOT NULL,
-    UNIQUE (version)
-);`
-
 func enableForeignKeys(conn *sqlite3.SQLiteConn) error {
 	_, err := conn.Exec("PRAGMA foreign_keys=ON;", nil)
 	return err
@@ -201,13 +59,6 @@ func CreateDb(db *sql.DB, patchNames []string) (err error) {
 		return err
 	}
 
-	// There isn't an entry for schema version, let's put it in.
-	insertStmt := `INSERT INTO schema (version, updated_at) values (?, strftime("%s"));`
-	_, err = db.Exec(insertStmt, GetLatestSchema())
-	if err != nil {
-		return err
-	}
-
 	// Mark all existing patches as applied
 	for _, patchName := range patchNames {
 		PatchesMarkApplied(db, patchName)
diff --git a/lxd/db/schema.go b/lxd/db/schema.go
new file mode 100644
index 000000000..d6aca4711
--- /dev/null
+++ b/lxd/db/schema.go
@@ -0,0 +1,150 @@
+package db
+
+// DO NOT EDIT BY HAND
+//
+// This code was generated by the UpdateSchemaDotGo function. If you need to
+// modify the database schema, please add a new schema update to update.go
+// and the run 'make update-schema'.
+const CURRENT_SCHEMA = `
+CREATE TABLE certificates (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    fingerprint VARCHAR(255) NOT NULL,
+    type INTEGER NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    certificate TEXT NOT NULL,
+    UNIQUE (fingerprint)
+);
+CREATE TABLE config (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    UNIQUE (key)
+);
+CREATE TABLE "containers" (
+    id INTEGER primary key AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    architecture INTEGER NOT NULL,
+    type INTEGER NOT NULL,
+    ephemeral INTEGER NOT NULL DEFAULT 0,
+    creation_date DATETIME NOT NULL DEFAULT 0,
+    stateful INTEGER NOT NULL DEFAULT 0,
+    UNIQUE (name)
+);
+CREATE TABLE containers_config (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    container_id INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    FOREIGN KEY (container_id) REFERENCES containers (id) ON DELETE CASCADE,
+    UNIQUE (container_id, key)
+);
+CREATE TABLE containers_devices (
+    id INTEGER primary key AUTOINCREMENT NOT NULL,
+    container_id INTEGER NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    type INTEGER NOT NULL default 0,
+    FOREIGN KEY (container_id) REFERENCES containers (id) ON DELETE CASCADE,
+    UNIQUE (container_id, name)
+);
+CREATE TABLE containers_devices_config (
+    id INTEGER primary key AUTOINCREMENT NOT NULL,
+    container_device_id INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    FOREIGN KEY (container_device_id) REFERENCES containers_devices (id) ON DELETE CASCADE,
+    UNIQUE (container_device_id, key)
+);
+CREATE TABLE containers_profiles (
+    id INTEGER primary key AUTOINCREMENT NOT NULL,
+    container_id INTEGER NOT NULL,
+    profile_id INTEGER NOT NULL,
+    apply_order INTEGER NOT NULL default 0,
+    UNIQUE (container_id, profile_id),
+    FOREIGN KEY (container_id) REFERENCES containers(id) ON DELETE CASCADE,
+    FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
+);
+CREATE TABLE images (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    fingerprint VARCHAR(255) NOT NULL,
+    filename VARCHAR(255) NOT NULL,
+    size INTEGER NOT NULL,
+    public INTEGER NOT NULL DEFAULT 0,
+    architecture INTEGER NOT NULL,
+    creation_date DATETIME,
+    expiry_date DATETIME,
+    upload_date DATETIME NOT NULL,
+    cached INTEGER NOT NULL DEFAULT 0,
+    last_use_date DATETIME,
+    auto_update INTEGER NOT NULL DEFAULT 0,
+    UNIQUE (fingerprint)
+);
+CREATE TABLE images_aliases (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    image_id INTEGER NOT NULL,
+    description VARCHAR(255),
+    FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
+    UNIQUE (name)
+);
+CREATE TABLE images_properties (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    image_id INTEGER NOT NULL,
+    type INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
+);
+CREATE TABLE images_source (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    image_id INTEGER NOT NULL,
+    server TEXT NOT NULL,
+    protocol INTEGER NOT NULL,
+    certificate TEXT NOT NULL,
+    alias VARCHAR(255) NOT NULL,
+    FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE
+);
+CREATE TABLE patches (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    applied_at DATETIME NOT NULL,
+    UNIQUE (name)
+);
+CREATE TABLE profiles (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    description TEXT,
+    UNIQUE (name)
+);
+CREATE TABLE profiles_config (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    profile_id INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value VARCHAR(255),
+    UNIQUE (profile_id, key),
+    FOREIGN KEY (profile_id) REFERENCES profiles(id) ON DELETE CASCADE
+);
+CREATE TABLE profiles_devices (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    profile_id INTEGER NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    type INTEGER NOT NULL default 0,
+    UNIQUE (profile_id, name),
+    FOREIGN KEY (profile_id) REFERENCES profiles (id) ON DELETE CASCADE
+);
+CREATE TABLE profiles_devices_config (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    profile_device_id INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    UNIQUE (profile_device_id, key),
+    FOREIGN KEY (profile_device_id) REFERENCES profiles_devices (id) ON DELETE CASCADE
+);
+CREATE TABLE schema (
+    id         INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    version    INTEGER NOT NULL,
+    updated_at DATETIME NOT NULL,
+    UNIQUE (version)
+);
+
+INSERT INTO schema (version, updated_at) VALUES (32, strftime("%s"))
+`
diff --git a/lxd/db/update.go b/lxd/db/update.go
index c1eb25a1c..32d38dc59 100644
--- a/lxd/db/update.go
+++ b/lxd/db/update.go
@@ -5,6 +5,8 @@ import (
 	"encoding/hex"
 	"fmt"
 	"os"
+	"path"
+	"runtime"
 	"strconv"
 	"strings"
 
@@ -27,6 +29,10 @@ import (
    DO NOT USE this mechanism for one-time actions which do not involve
    changes to the database schema. Use patches instead (see lxd/patches.go).
 
+   REMEMBER to run "make update-schema" after you add a new update function to
+   this slice. That will refresh the schema declaration in lxd/db/schema.go and
+   include the effect of applying your patch as well.
+
    Only append to the updates list, never remove entries and never re-order them.
 */
 
@@ -65,14 +71,21 @@ var updates = map[int]schema.Update{
 	32: updateFromV31,
 }
 
-// Apply all possible database patches. If "doBackup" is true, the
-// sqlite file will be backed up before any update is applied. If
-// "postApply" it's passed, it will be called after each database
-// update gets successfully applied, and be passed the its version (as
-// of now "postApply" is only used by the daemon as a mean to apply
-// the legacy V10 and V15 non-db updates during the database upgrade
-// sequence to, avoid changing semantics see PR #3322).
-func UpdatesApplyAll(db *sql.DB, doBackup bool, postApply func(int) error) error {
+// LegacyPatch is a "database" update that performs non-database work. They
+// are needed for historical reasons, since there was a time were db updates
+// could do non-db work and depend on functionality external to the db
+// package. See UpdatesApplyAll below.
+type LegacyPatch struct {
+	NeedsDB bool         // Whether the patch does any DB-related work
+	Hook    func() error // The actual patch logic
+}
+
+// UpdatesApplyAll applies all possible database patches. If "doBackup" is
+// true, the sqlite file will be backed up before any update is applied. The
+// legacyPatches parameter is used by the Daemon as a mean to apply the legacy
+// V10, V11, V15, V29 and V30 non-db updates during the database upgrade
+// sequence, to avoid any change in semantics wrt the old logic (see PR #3322).
+func UpdatesApplyAll(db *sql.DB, doBackup bool, legacyPatches map[int]*LegacyPatch) error {
 	backup := false
 
 	schema := schema.NewFromMap(updates)
@@ -88,35 +101,86 @@ func UpdatesApplyAll(db *sql.DB, doBackup bool, postApply func(int) error) error
 		}
 		logger.Debugf("Updating DB schema from %d to %d", version, version+1)
 
-		if postApply != nil {
-			legacy := []int{11, 12, 16} // Legacy patches doing DB work
-			if shared.IntInSlice(version, legacy) {
-				// FIXME We need to commit the transaction before the
-				// hook and then open it again afterwards because this
-				// legacy patch pokes with the database and would fail
-				// with a lock error otherwise.
+		legacyPatch, ok := legacyPatches[version]
+		if ok {
+			// FIXME We need to commit the transaction before the
+			// hook and then open it again afterwards because this
+			// legacy patch pokes with the database and would fail
+			// with a lock error otherwise.
+			if legacyPatch.NeedsDB {
 				_, err := tx.Exec("COMMIT")
 				if err != nil {
 					return err
 				}
 			}
-			err := postApply(version + 1)
+			err := legacyPatch.Hook()
 			if err != nil {
 				return err
 			}
-			if shared.IntInSlice(version, legacy) {
-				_, err := tx.Exec("BEGIN")
-				if err != nil {
-					return err
-				}
+			if legacyPatch.NeedsDB {
+				_, err = tx.Exec("BEGIN")
 			}
-			return nil
+
+			return err
 		}
 		return nil
 	})
 	return schema.Ensure(db)
 }
 
+// UpdateSchemaDotGo rewrites the 'schema.go' source file in this package to
+// match the current schema updates.
+//
+// The schema.go file contains a "flattened" render of all schema updates
+// defined in this file, and it's used to initialize brand new databases.
+func UpdateSchemaDotGo() error {
+	// Apply all the updates that we have on a pristine database and dump
+	// the resulting schema.
+	db, err := sql.Open("sqlite3", ":memory:")
+	if err != nil {
+		return fmt.Errorf("failed to open schema.go for writing: %v", err)
+	}
+
+	schema := schema.NewFromMap(updates)
+
+	err = schema.Ensure(db)
+	if err != nil {
+		return err
+	}
+
+	dump, err := schema.Dump(db)
+	if err != nil {
+		return err
+	}
+
+	// Passing 1 to runtime.Caller identifies the caller of runtime.Caller,
+	// that means us.
+	_, filename, _, _ := runtime.Caller(0)
+
+	file, err := os.Create(path.Join(path.Dir(filename), "schema.go"))
+	if err != nil {
+		return fmt.Errorf("failed to open schema.go for writing: %v", err)
+	}
+
+	_, err = file.Write([]byte(fmt.Sprintf(schemaDotGo, dump)))
+	if err != nil {
+		return fmt.Errorf("failed to write to schema.go: %v", err)
+	}
+
+	return nil
+}
+
+// Template for schema.go (can't use backticks since we need to use backticks
+// inside the template itself).
+const schemaDotGo = "package db\n\n" +
+	"// DO NOT EDIT BY HAND\n" +
+	"//\n" +
+	"// This code was generated by the UpdateSchemaDotGo function. If you need to\n" +
+	"// modify the database schema, please add a new schema update to update.go\n" +
+	"// and the run 'make update-schema'.\n" +
+	"const CURRENT_SCHEMA = `\n" +
+	"%s`\n"
+
 // Schema updates begin here
 func updateFromV31(tx *sql.Tx) error {
 	stmt := `
@@ -751,7 +815,51 @@ CREATE TABLE IF NOT EXISTS images_aliases (
 }
 
 func updateFromV0(tx *sql.Tx) error {
-	// v0..v1 is a noop. It used to add the schema table, but that's now
-	// done by Schema.Ensure().
-	return nil
+	// v0..v1 the dawn of containers
+	stmt := `
+CREATE TABLE certificates (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    fingerprint VARCHAR(255) NOT NULL,
+    type INTEGER NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    certificate TEXT NOT NULL,
+    UNIQUE (fingerprint)
+);
+CREATE TABLE containers (
+    id INTEGER primary key AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    architecture INTEGER NOT NULL,
+    type INTEGER NOT NULL,
+    UNIQUE (name)
+);
+CREATE TABLE containers_config (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    container_id INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    FOREIGN KEY (container_id) REFERENCES containers (id),
+    UNIQUE (container_id, key)
+);
+CREATE TABLE images (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    fingerprint VARCHAR(255) NOT NULL,
+    filename VARCHAR(255) NOT NULL,
+    size INTEGER NOT NULL,
+    public INTEGER NOT NULL DEFAULT 0,
+    architecture INTEGER NOT NULL,
+    creation_date DATETIME,
+    expiry_date DATETIME,
+    upload_date DATETIME NOT NULL,
+    UNIQUE (fingerprint)
+);
+CREATE TABLE images_properties (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    image_id INTEGER NOT NULL,
+    type INTEGER NOT NULL,
+    key VARCHAR(255) NOT NULL,
+    value TEXT,
+    FOREIGN KEY (image_id) REFERENCES images (id)
+);`
+	_, err := tx.Exec(stmt)
+	return err
 }
diff --git a/lxd/patches.go b/lxd/patches.go
index 0fe64ce27..b75cbe03d 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -138,9 +138,10 @@ var legacyPatches = map[int](func(d *Daemon) error){
 	11: patchUpdateFromV10,
 	12: patchUpdateFromV11,
 	16: patchUpdateFromV15,
-	31: patchUpdateFromV30,
 	30: patchUpdateFromV29,
+	31: patchUpdateFromV30,
 }
+var legacyPatchesNeedingDB = []int{11, 12, 16} // Legacy patches doing DB work
 
 func patchUpdateFromV10(d *Daemon) error {
 	if shared.PathExists(shared.VarPath("lxc")) {
diff --git a/lxd/schema.go b/lxd/schema.go
new file mode 100644
index 000000000..f1ed49482
--- /dev/null
+++ b/lxd/schema.go
@@ -0,0 +1,22 @@
+// +build never
+//
+// We use the tag 'never' because this utility shouldn't normally be built,
+// unless you're running the 'update-schema' target of the Makefile.
+
+package main
+
+import (
+	"log"
+
+	"github.com/lxc/lxd/lxd/db"
+)
+
+// Entry point for the "schema" development utility, which updates the content
+// of lxd/db/schema.go according to the current schema updates declared in
+// updates.go in the same package.
+func main() {
+	err := db.UpdateSchemaDotGo()
+	if err != nil {
+		log.Fatal(err)
+	}
+}

From 64008c9c31c18eef108905f56070a378e656a7b7 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 12:32:09 +0000
Subject: [PATCH 1189/1193] db: Revert the use of the "pkg/errors" package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/db/query/transaction.go |  8 ++------
 lxd/db/schema/schema.go     | 26 ++++++++------------------
 2 files changed, 10 insertions(+), 24 deletions(-)

diff --git a/lxd/db/query/transaction.go b/lxd/db/query/transaction.go
index 845b25a9a..122c3af2a 100644
--- a/lxd/db/query/transaction.go
+++ b/lxd/db/query/transaction.go
@@ -3,15 +3,13 @@ package query
 import (
 	"database/sql"
 	"fmt"
-
-	"github.com/pkg/errors"
 )
 
 // Transaction executes the given function within a database transaction.
 func Transaction(db *sql.DB, f func(*sql.Tx) error) error {
 	tx, err := db.Begin()
 	if err != nil {
-		return errors.Wrap(err, "failed to begin transaction")
+		return fmt.Errorf("failed to begin transaction: %v", err)
 	}
 
 	err = f(tx)
@@ -28,9 +26,7 @@ func Transaction(db *sql.DB, f func(*sql.Tx) error) error {
 func rollback(tx *sql.Tx, reason error) error {
 	err := tx.Rollback()
 	if err != nil {
-		return errors.Wrap(
-			reason,
-			fmt.Sprintf("failed to rollback transaction after error (%v)", reason))
+		return fmt.Errorf("failed to rollback transaction after error (%v)", reason)
 	}
 
 	return reason
diff --git a/lxd/db/schema/schema.go b/lxd/db/schema/schema.go
index d0d83ae28..b21592706 100644
--- a/lxd/db/schema/schema.go
+++ b/lxd/db/schema/schema.go
@@ -6,8 +6,6 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/pkg/errors"
-
 	"github.com/lxc/lxd/lxd/db/query"
 )
 
@@ -148,12 +146,12 @@ INSERT INTO schema (version, updated_at) VALUES (%d, strftime("%%s"))
 func ensureSchemaTableExists(tx *sql.Tx) error {
 	exists, err := doesSchemaTableExist(tx)
 	if err != nil {
-		return errors.Wrap(err, "failed to check if schema table is there")
+		return fmt.Errorf("failed to check if schema table is there: %v", err)
 	}
 	if !exists {
 		err := createSchemaTable(tx)
 		if err != nil {
-			return errors.Wrap(err, "failed to create schema table")
+			return fmt.Errorf("failed to create schema table: %v", err)
 		}
 	}
 	return nil
@@ -165,7 +163,7 @@ func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
 
 	versions, err := selectSchemaVersions(tx)
 	if err != nil {
-		return errors.Wrap(err, "failed to fetch update versions")
+		return fmt.Errorf("failed to fetch update versions: %v", err)
 	}
 	if len(versions) > 1 {
 		return fmt.Errorf(
@@ -179,9 +177,7 @@ func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
 	if len(versions) == 0 {
 		err := insertSchemaVersion(tx, len(updates))
 		if err != nil {
-			return errors.Wrap(
-				err,
-				fmt.Sprintf("failed to insert version %d", len(updates)))
+			return fmt.Errorf("failed to insert version %d: %v", len(updates), err)
 		}
 	} else {
 		current = versions[0]
@@ -192,9 +188,7 @@ func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
 		}
 		err := updateSchemaVersion(tx, current, len(updates))
 		if err != nil {
-			return errors.Wrap(
-				err,
-				fmt.Sprintf("failed to update version %d", current))
+			return fmt.Errorf("failed to update version %d: %v", current, err)
 		}
 	}
 
@@ -208,16 +202,12 @@ func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
 		if hook != nil {
 			err := hook(current, tx)
 			if err != nil {
-				return errors.Wrap(
-					err,
-					fmt.Sprintf("failed to execute hook (version %d)", current))
+				return fmt.Errorf("failed to execute hook (version %d): %v", current, err)
 			}
 		}
 		err := update(tx)
 		if err != nil {
-			return errors.Wrap(
-				err,
-				fmt.Sprintf("failed to apply update %d", current))
+			return fmt.Errorf("failed to apply update %d: %v", current, err)
 		}
 		current++
 	}
@@ -229,7 +219,7 @@ func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
 func checkAllUpdatesAreApplied(tx *sql.Tx, updates []Update) error {
 	versions, err := selectSchemaVersions(tx)
 	if err != nil {
-		return errors.Wrap(err, "failed to fetch update versions")
+		return fmt.Errorf("failed to fetch update versions: %v", err)
 	}
 	if len(versions) != 1 {
 		return fmt.Errorf("schema table contains %d rows, expected 1", len(versions))

From 0ab8be91d85cf2819b85aa71e1a66e293ac6c875 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 12:32:18 +0000
Subject: [PATCH 1190/1193] Fix database upgrade logic not inserting interim
 versions

The new schema.Schema class was not matching existing logic in master,
in that it was only inserting the last schema version, deleting
previous ones.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/schema/query.go       |  9 ------
 lxd/db/schema/schema.go      | 69 ++++++++++++++++++++++++++------------------
 lxd/db/schema/schema_test.go | 52 +++++++++++++++++++++++++++------
 3 files changed, 84 insertions(+), 46 deletions(-)

diff --git a/lxd/db/schema/query.go b/lxd/db/schema/query.go
index dd40680dc..beec95ace 100644
--- a/lxd/db/schema/query.go
+++ b/lxd/db/schema/query.go
@@ -68,12 +68,3 @@ INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"))
 	_, err := tx.Exec(statement, new)
 	return err
 }
-
-// Update a version in the schema table.
-func updateSchemaVersion(tx *sql.Tx, old, new int) error {
-	statement := `
-UPDATE schema SET version=?, updated_at=strftime("%s") WHERE version=?
-`
-	_, err := tx.Exec(statement, new, old)
-	return err
-}
diff --git a/lxd/db/schema/schema.go b/lxd/db/schema/schema.go
index b21592706..d80f37db7 100644
--- a/lxd/db/schema/schema.go
+++ b/lxd/db/schema/schema.go
@@ -159,37 +159,23 @@ func ensureSchemaTableExists(tx *sql.Tx) error {
 
 // Apply any pending update that was not yet applied.
 func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
-	current := 0 // Current update level in the database
-
 	versions, err := selectSchemaVersions(tx)
 	if err != nil {
 		return fmt.Errorf("failed to fetch update versions: %v", err)
 	}
-	if len(versions) > 1 {
-		return fmt.Errorf(
-			"schema table contains %d rows, expected at most one", len(versions))
-	}
 
-	// If this is a fresh database insert a row with this schema's update
-	// level, otherwise update the existing row (it's okay to do this
-	// before actually running the updates since the transaction will be
-	// rolled back in case of errors).
-	if len(versions) == 0 {
-		err := insertSchemaVersion(tx, len(updates))
-		if err != nil {
-			return fmt.Errorf("failed to insert version %d: %v", len(updates), err)
-		}
-	} else {
-		current = versions[0]
-		if current > len(updates) {
-			return fmt.Errorf(
-				"schema version '%d' is more recent than expected '%d'",
-				current, len(updates))
-		}
-		err := updateSchemaVersion(tx, current, len(updates))
+	current := 0
+	if len(versions) > 0 {
+		err = checkSchemaVersionsHaveNoHoles(versions)
 		if err != nil {
-			return fmt.Errorf("failed to update version %d: %v", current, err)
+			return err
 		}
+		current = versions[len(versions)-1] // Highest recorded version
+	}
+	if current > len(updates) {
+		return fmt.Errorf(
+			"schema version '%d' is more recent than expected '%d'",
+			current, len(updates))
 	}
 
 	// If there are no updates, there's nothing to do.
@@ -210,22 +196,49 @@ func ensureUpdatesAreApplied(tx *sql.Tx, updates []Update, hook Hook) error {
 			return fmt.Errorf("failed to apply update %d: %v", current, err)
 		}
 		current++
+
+		err = insertSchemaVersion(tx, current)
+		if err != nil {
+			return fmt.Errorf("failed to insert version %d", current)
+		}
 	}
 
 	return nil
 }
 
+// Check that the given list of update version numbers doesn't have "holes",
+// that is each version equal the preceeding version plus 1.
+func checkSchemaVersionsHaveNoHoles(versions []int) error {
+	// Sanity check that there are no "holes" in the recorded
+	// versions.
+	for i := range versions[:len(versions)-1] {
+		if versions[i+1] != versions[i]+1 {
+			return fmt.Errorf(
+				"missing updates: %d -> %d", versions[i], versions[i+1])
+		}
+	}
+	return nil
+}
+
 // Check that all the given updates are applied.
 func checkAllUpdatesAreApplied(tx *sql.Tx, updates []Update) error {
 	versions, err := selectSchemaVersions(tx)
 	if err != nil {
 		return fmt.Errorf("failed to fetch update versions: %v", err)
 	}
-	if len(versions) != 1 {
-		return fmt.Errorf("schema table contains %d rows, expected 1", len(versions))
+
+	if len(versions) == 0 {
+		return fmt.Errorf("expected schema table to contain at least one row")
+	}
+
+	err = checkSchemaVersionsHaveNoHoles(versions)
+	if err != nil {
+		return err
 	}
-	if versions[0] != len(updates) {
-		return fmt.Errorf("update level is %d, expected %d", versions[0], len(updates))
+
+	current := versions[len(versions)-1]
+	if current != len(updates) {
+		return fmt.Errorf("update level is %d, expected %d", current, len(updates))
 	}
 	return nil
 }
diff --git a/lxd/db/schema/schema_test.go b/lxd/db/schema/schema_test.go
index e251573b6..d811a498b 100644
--- a/lxd/db/schema/schema_test.go
+++ b/lxd/db/schema/schema_test.go
@@ -46,19 +46,24 @@ func TestSchemaEnsure_VersionMoreRecentThanExpected(t *testing.T) {
 	assert.EqualError(t, err, "schema version '1' is more recent than expected '0'")
 }
 
-// If there's more than one row in the schema table, an error is returned.
-func TestSchemaEnsure_ExtraVersions(t *testing.T) {
+// If the database schema contains "holes" in the applied versions, an error is
+// returned.
+func TestSchemaEnsure_MissingVersion(t *testing.T) {
 	schema, db := newSchemaAndDB(t)
+	schema.Add(updateNoop)
 	assert.NoError(t, schema.Ensure(db))
 
-	_, err := db.Exec(`INSERT INTO schema (version, updated_at) VALUES (2, strftime("%s"))`)
-	assert.NoError(t, err)
+	_, err := db.Exec(`INSERT INTO schema (version, updated_at) VALUES (3, strftime("%s"))`)
+
+	schema.Add(updateNoop)
+	schema.Add(updateNoop)
 
 	err = schema.Ensure(db)
-	assert.EqualError(t, err, "schema table contains 2 rows, expected at most one")
+	assert.NotNil(t, err)
+	assert.EqualError(t, err, "missing updates: 1 -> 3")
 }
 
-// If the schema has no update, the schema table gets created and has version 0.
+// If the schema has no update, the schema table gets created and has no version.
 func TestSchemaEnsure_ZeroUpdates(t *testing.T) {
 	schema, db := newSchemaAndDB(t)
 
@@ -70,7 +75,7 @@ func TestSchemaEnsure_ZeroUpdates(t *testing.T) {
 
 	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
 	assert.NoError(t, err)
-	assert.Equal(t, []int{0}, versions)
+	assert.Equal(t, []int{}, versions)
 }
 
 // If the schema has updates and no one was applied yet, all of them get
@@ -89,7 +94,7 @@ func TestSchemaEnsure_ApplyAllUpdates(t *testing.T) {
 	// THe update version is recorded.
 	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
 	assert.NoError(t, err)
-	assert.Equal(t, []int{2}, versions)
+	assert.Equal(t, []int{1, 2}, versions)
 
 	// The two updates have been applied in order.
 	ids, err := query.SelectIntegers(tx, "SELECT id FROM test")
@@ -97,6 +102,35 @@ func TestSchemaEnsure_ApplyAllUpdates(t *testing.T) {
 	assert.Equal(t, []int{1}, ids)
 }
 
+// If the schema schema has been created using a dump, the schema table will
+// contain just one row with the update level associated with the dump. It's
+// possible to apply further updates from there, and only these new ones will
+// be inserted in the schema table.
+func TestSchemaEnsure_ApplyAfterInitialDumpCreation(t *testing.T) {
+	schema, db := newSchemaAndDB(t)
+	schema.Add(updateCreateTable)
+	schema.Add(updateAddColumn)
+	assert.NoError(t, schema.Ensure(db))
+
+	dump, err := schema.Dump(db)
+	assert.NoError(t, err)
+
+	_, db = newSchemaAndDB(t)
+	_, err = db.Exec(dump)
+	assert.NoError(t, err)
+
+	schema.Add(updateNoop)
+	assert.NoError(t, schema.Ensure(db))
+
+	tx, err := db.Begin()
+	assert.NoError(t, err)
+
+	// Only updates starting from the initial dump are recorded.
+	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
+	assert.NoError(t, err)
+	assert.Equal(t, []int{2, 3}, versions)
+}
+
 // If the schema has updates and part of them were already applied, only the
 // missing ones are applied.
 func TestSchemaEnsure_OnlyApplyMissing(t *testing.T) {
@@ -113,7 +147,7 @@ func TestSchemaEnsure_OnlyApplyMissing(t *testing.T) {
 	// All update versions are recorded.
 	versions, err := query.SelectIntegers(tx, "SELECT version FROM SCHEMA")
 	assert.NoError(t, err)
-	assert.Equal(t, []int{2}, versions)
+	assert.Equal(t, []int{1, 2}, versions)
 
 	// The two updates have been applied in order.
 	ids, err := query.SelectIntegers(tx, "SELECT id FROM test")

From 06a936361e8182c987b59652a42fdee01a10c70d Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 12:32:35 +0000
Subject: [PATCH 1191/1193] Add a cmd.Parser helper for parsing command line
 flags

This helper parses a command line according to the attributes defined
on a given object, which will be populated with the parsed values.

It will be used to replace the global command line variables
defined in main.go, making it easier to create unit tests that
exercise whole commands.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 shared/cmd/parser.go      | 142 ++++++++++++++++++++++++++++++++++++++++
 shared/cmd/parser_test.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 304 insertions(+)
 create mode 100644 shared/cmd/parser.go
 create mode 100644 shared/cmd/parser_test.go

diff --git a/shared/cmd/parser.go b/shared/cmd/parser.go
new file mode 100644
index 000000000..ce71eaeeb
--- /dev/null
+++ b/shared/cmd/parser.go
@@ -0,0 +1,142 @@
+package cmd
+
+import (
+	"reflect"
+	"strings"
+	"unsafe"
+
+	"github.com/lxc/lxd/shared/gnuflag"
+)
+
+// Parser for command line arguments.
+type Parser struct {
+	Context      *Context
+	UsageMessage string
+	ExitOnError  bool
+}
+
+// NewParser returns a Parser connected to the given I/O context and printing
+// the given usage message when '--help' or '-h' are passed.
+func NewParser(context *Context, usage string) *Parser {
+	return &Parser{
+		Context:      context,
+		UsageMessage: usage,
+		ExitOnError:  true,
+	}
+}
+
+// Parse a command line populating the given args object accordingly.
+//
+// The command line format is expected to be:
+//
+// <cmd> [subcmd [params]] [flags] [-- [extra]]
+//
+// The args object may have Subcommand, Params and Extra attributes
+// (respectively of type string, []string and []string), which will be
+// populated with the subcommand, its params and any extra argument (if
+// present).
+//
+// The type of the args object must have one attribute for each supported
+// command line flag, annotated with a tag like `flag:"<name>"`, where <name>
+// is the name of the command line flag.
+//
+// In case of parsing error (e.g. unknown command line flag) the default
+// behavior is to call os.Exit() with a non-zero value. This can be disabled by
+// setting the ExitOnError attribute to false, in which case the error will be
+// returned.
+func (p *Parser) Parse(line []string, args interface{}) error {
+	val := reflect.ValueOf(args).Elem()
+
+	if err := p.parseFlags(line, val); err != nil {
+		return err
+	}
+
+	p.parseRest(line, val)
+
+	return nil
+}
+
+// Populate the given FlagSet by introspecting the given object, adding a new
+// flag variable for each annotated attribute.
+func (p *Parser) parseFlags(line []string, val reflect.Value) error {
+	mode := gnuflag.ContinueOnError
+	if p.ExitOnError {
+		mode = gnuflag.ExitOnError
+	}
+
+	flags := gnuflag.NewFlagSet(line[0], mode)
+	flags.SetOutput(p.Context.stderr)
+
+	if p.UsageMessage != "" {
+		// Since usage will be printed only if "-h" or "--help" are
+		// explicitly set in the command line, use stdout for it.
+		flags.Usage = func() {
+			p.Context.Output(p.UsageMessage)
+		}
+	}
+
+	typ := val.Type()
+	for i := 0; i < typ.NumField(); i++ {
+		name := typ.Field(i).Tag.Get("flag")
+		if name == "" {
+			continue
+		}
+		kind := typ.Field(i).Type.Kind()
+		addr := val.Field(i).Addr()
+		switch kind {
+		case reflect.Bool:
+			pointer := (*bool)(unsafe.Pointer(addr.Pointer()))
+			flags.BoolVar(pointer, name, false, "")
+		case reflect.String:
+			pointer := (*string)(unsafe.Pointer(addr.Pointer()))
+			flags.StringVar(pointer, name, "", "")
+		case reflect.Int:
+			pointer := (*int)(unsafe.Pointer(addr.Pointer()))
+			flags.IntVar(pointer, name, -1, "")
+		case reflect.Int64:
+			pointer := (*int64)(unsafe.Pointer(addr.Pointer()))
+			flags.Int64Var(pointer, name, -1, "")
+		}
+	}
+
+	return flags.Parse(true, line[1:])
+}
+
+// Parse any non-flag argument, i.e. the subcommand, its parameters and any
+// extra argument following "--".
+func (p *Parser) parseRest(line []string, val reflect.Value) {
+	subcommand := ""
+	params := []string{}
+	extra := []string{}
+	if len(line) > 1 {
+		rest := line[1:]
+		for i, token := range rest {
+			if token == "--" {
+				// Set extra to anything left, excluding the token.
+				if i < len(rest)-1 {
+					extra = rest[i+1:]
+				}
+				break
+			}
+			if strings.HasPrefix(token, "-") {
+				// Subcommand and parameters must both come
+				// before any flag.
+				break
+			}
+			if i == 0 {
+				subcommand = token
+				continue
+			}
+			params = append(params, token)
+		}
+	}
+	if field := val.FieldByName("Subcommand"); field.IsValid() {
+		field.SetString(subcommand)
+	}
+	if field := val.FieldByName("Params"); field.IsValid() {
+		field.Set(reflect.ValueOf(params))
+	}
+	if field := val.FieldByName("Extra"); field.IsValid() {
+		field.Set(reflect.ValueOf(extra))
+	}
+}
diff --git a/shared/cmd/parser_test.go b/shared/cmd/parser_test.go
new file mode 100644
index 000000000..1111a168e
--- /dev/null
+++ b/shared/cmd/parser_test.go
@@ -0,0 +1,162 @@
+package cmd_test
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/mpvl/subtest"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/lxc/lxd/shared/cmd"
+)
+
+// Sample command line arguments specification.
+type Args struct {
+	Subcommand string
+	Params     []string
+	Extra      []string
+
+	Help      bool   `flag:"help"`
+	Text      string `flag:"text"`
+	Number    int    `flag:"number"`
+	BigNumber int64  `flag:"big-number"`
+}
+
+// Check the default values of all command line args.
+func TestParser_ArgsDefaults(t *testing.T) {
+	line := []string{"cmd"}
+	args := &Args{}
+	parser := newParser()
+
+	assert.NoError(t, parser.Parse(line, args))
+
+	assert.Equal(t, "", args.Text)
+	assert.Equal(t, false, args.Help)
+	assert.Equal(t, -1, args.Number)
+	assert.Equal(t, int64(-1), args.BigNumber)
+}
+
+// Check that parsing the command line results in the correct attributes
+// being set.
+func TestParser_ArgsCustom(t *testing.T) {
+	line := []string{
+		"cmd",
+		"--text", "hello",
+		"--help",
+		"--number", "10",
+		"--big-number", "666",
+	}
+	args := &Args{}
+	parser := newParser()
+
+	assert.NoError(t, parser.Parse(line, args))
+
+	assert.Equal(t, "hello", args.Text)
+	assert.Equal(t, true, args.Help)
+	assert.Equal(t, 10, args.Number)
+	assert.Equal(t, int64(666), args.BigNumber)
+}
+
+// Check that the subcommand is properly set.
+func TestParser_Subcommand(t *testing.T) {
+	cases := []struct {
+		line       []string
+		subcommand string
+	}{
+		{[]string{"cmd"}, ""},
+		{[]string{"cmd", "--help"}, ""},
+		{[]string{"cmd", "subcmd"}, "subcmd"},
+		{[]string{"cmd", "subcmd", "--help"}, "subcmd"},
+		{[]string{"cmd", "--help", "subcmd"}, ""},
+	}
+	for _, c := range cases {
+		subtest.Run(t, strings.Join(c.line, "_"), func(t *testing.T) {
+			args := &Args{}
+			parser := newParser()
+			assert.NoError(t, parser.Parse(c.line, args))
+			assert.Equal(t, c.subcommand, args.Subcommand)
+		})
+	}
+}
+
+// Check that subcommand params are properly set.
+func TestParser_Params(t *testing.T) {
+	cases := []struct {
+		line   []string
+		params []string
+	}{
+		{[]string{"cmd"}, []string{}},
+		{[]string{"cmd", "--help"}, []string{}},
+		{[]string{"cmd", "subcmd"}, []string{}},
+		{[]string{"cmd", "subcmd", "param"}, []string{"param"}},
+		{[]string{"cmd", "subcmd", "param1", "param2"}, []string{"param1", "param2"}},
+		{[]string{"cmd", "subcmd", "param", "--help"}, []string{"param"}},
+		{[]string{"cmd", "subcmd", "--help", "param"}, []string{}},
+	}
+	for _, c := range cases {
+		subtest.Run(t, strings.Join(c.line, "_"), func(t *testing.T) {
+			args := &Args{}
+			parser := newParser()
+			assert.NoError(t, parser.Parse(c.line, args))
+			assert.Equal(t, c.params, args.Params)
+		})
+	}
+}
+
+// Check that extra params are properly set.
+func TestParser_Extra(t *testing.T) {
+	cases := []struct {
+		line  []string
+		extra []string
+	}{
+		{[]string{"cmd"}, []string{}},
+		{[]string{"cmd", "--help"}, []string{}},
+		{[]string{"cmd", "subcmd"}, []string{}},
+		{[]string{"cmd", "subcmd", "--"}, []string{}},
+		{[]string{"cmd", "subcmd", "--", "extra"}, []string{"extra"}},
+		{[]string{"cmd", "subcmd", "--", "extra1", "--extra2"}, []string{"extra1", "--extra2"}},
+	}
+	for _, c := range cases {
+		subtest.Run(t, strings.Join(c.line, "_"), func(t *testing.T) {
+			args := &Args{}
+			parser := newParser()
+			assert.NoError(t, parser.Parse(c.line, args))
+			assert.Equal(t, c.extra, args.Extra)
+		})
+	}
+}
+
+// If a flag doesn't exist, an error is returned.
+func TestParser_Error(t *testing.T) {
+	line := []string{"cmd", "--boom"}
+	args := &Args{}
+	parser := newParser()
+
+	assert.Error(t, parser.Parse(line, args))
+}
+
+// If a usage string is passed, and the command line has the help flag, the
+// message is printed out.
+func TestParser_Usage(t *testing.T) {
+	line := []string{"cmd", "-h"}
+	args := &Args{}
+	streams := cmd.NewMemoryStreams("")
+
+	parser := newParserWithStreams(streams)
+	parser.UsageMessage = "usage message"
+
+	assert.Error(t, parser.Parse(line, args))
+	assert.Equal(t, parser.UsageMessage, streams.Out())
+}
+
+// Return a new test parser
+func newParser() *cmd.Parser {
+	return newParserWithStreams(cmd.NewMemoryStreams(""))
+}
+
+// Return a new test parser using the given streams for its context.
+func newParserWithStreams(streams *cmd.MemoryStreams) *cmd.Parser {
+	return &cmd.Parser{
+		Context: cmd.NewMemoryContext(streams),
+	}
+}

From d2e69c27db89bb191af6c556bda9d394c6fe7562 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 12:40:06 +0000
Subject: [PATCH 1192/1193] Plug cmd.Parser into main.go

Replace global command line variables in main.go with a new Args
structure, populated using cmd.Parser.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/main.go                    | 181 +++++++----------------------------------
 lxd/main_args.go               | 130 +++++++++++++++++++++++++++++
 lxd/main_args_test.go          |  89 ++++++++++++++++++++
 lxd/main_callhook.go           |  10 +--
 lxd/main_daemon.go             |  16 ++--
 lxd/main_forkexec.go           |  17 ++--
 lxd/main_forkmigrate.go        |  16 ++--
 lxd/main_forkstart.go          |  12 +--
 lxd/main_init.go               |  33 ++------
 lxd/main_init_test.go          |   5 +-
 lxd/main_migratedumpsuccess.go |  10 +--
 lxd/main_netcat.go             |  18 ++--
 lxd/main_shutdown.go           |   8 +-
 lxd/main_waitready.go          |   6 +-
 shared/cmd/context.go          |   7 ++
 15 files changed, 324 insertions(+), 234 deletions(-)
 create mode 100644 lxd/main_args.go
 create mode 100644 lxd/main_args_test.go

diff --git a/lxd/main.go b/lxd/main.go
index afcc7b378..022a0535e 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -7,33 +7,12 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/gnuflag"
+	"github.com/lxc/lxd/shared/cmd"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/version"
 )
 
-// Global arguments
-var argAuto = gnuflag.Bool("auto", false, "")
-var argCPUProfile = gnuflag.String("cpuprofile", "", "")
-var argDebug = gnuflag.Bool("debug", false, "")
-var argGroup = gnuflag.String("group", "", "")
-var argHelp = gnuflag.Bool("help", false, "")
-var argLogfile = gnuflag.String("logfile", "", "")
-var argMemProfile = gnuflag.String("memprofile", "", "")
-var argNetworkAddress = gnuflag.String("network-address", "", "")
-var argNetworkPort = gnuflag.Int64("network-port", -1, "")
-var argPrintGoroutinesEvery = gnuflag.Int("print-goroutines-every", -1, "")
-var argStorageBackend = gnuflag.String("storage-backend", "", "")
-var argStorageCreateDevice = gnuflag.String("storage-create-device", "", "")
-var argStorageCreateLoop = gnuflag.Int64("storage-create-loop", -1, "")
-var argStoragePool = gnuflag.String("storage-pool", "", "")
-var argSyslog = gnuflag.Bool("syslog", false, "")
-var argTimeout = gnuflag.Int("timeout", -1, "")
-var argTrustPassword = gnuflag.String("trust-password", "", "")
-var argVerbose = gnuflag.Bool("verbose", false, "")
-var argVersion = gnuflag.Bool("version", false, "")
-
 // Global variables
 var debug bool
 var verbose bool
@@ -56,118 +35,24 @@ func main() {
 }
 
 func run() error {
-	// Our massive custom usage
-	gnuflag.Usage = func() {
-		fmt.Printf("Usage: lxd [command] [options]\n")
-
-		fmt.Printf("\nCommands:\n")
-		fmt.Printf("    activateifneeded\n")
-		fmt.Printf("        Check if LXD should be started (at boot) and if so, spawns it through socket activation\n")
-		fmt.Printf("    daemon [--group=lxd] (default command)\n")
-		fmt.Printf("        Start the main LXD daemon\n")
-		fmt.Printf("    init [--auto] [--network-address=IP] [--network-port=8443] [--storage-backend=dir]\n")
-		fmt.Printf("         [--storage-create-device=DEVICE] [--storage-create-loop=SIZE] [--storage-pool=POOL]\n")
-		fmt.Printf("         [--trust-password=]\n")
-		fmt.Printf("        Setup storage and networking\n")
-		fmt.Printf("    ready\n")
-		fmt.Printf("        Tells LXD that any setup-mode configuration has been done and that it can start containers.\n")
-		fmt.Printf("    shutdown [--timeout=60]\n")
-		fmt.Printf("        Perform a clean shutdown of LXD and all running containers\n")
-		fmt.Printf("    waitready [--timeout=15]\n")
-		fmt.Printf("        Wait until LXD is ready to handle requests\n")
-
-		fmt.Printf("\n\nCommon options:\n")
-		fmt.Printf("    --debug\n")
-		fmt.Printf("        Enable debug mode\n")
-		fmt.Printf("    --help\n")
-		fmt.Printf("        Print this help message\n")
-		fmt.Printf("    --logfile FILE\n")
-		fmt.Printf("        Logfile to log to (e.g., /var/log/lxd/lxd.log)\n")
-		fmt.Printf("    --syslog\n")
-		fmt.Printf("        Enable syslog logging\n")
-		fmt.Printf("    --verbose\n")
-		fmt.Printf("        Enable verbose mode\n")
-		fmt.Printf("    --version\n")
-		fmt.Printf("        Print LXD's version number and exit\n")
-
-		fmt.Printf("\nDaemon options:\n")
-		fmt.Printf("    --group GROUP\n")
-		fmt.Printf("        Group which owns the shared socket\n")
-
-		fmt.Printf("\nDaemon debug options:\n")
-		fmt.Printf("    --cpuprofile FILE\n")
-		fmt.Printf("        Enable cpu profiling into the specified file\n")
-		fmt.Printf("    --memprofile FILE\n")
-		fmt.Printf("        Enable memory profiling into the specified file\n")
-		fmt.Printf("    --print-goroutines-every SECONDS\n")
-		fmt.Printf("        For debugging, print a complete stack trace every n seconds\n")
-
-		fmt.Printf("\nInit options:\n")
-		fmt.Printf("    --auto\n")
-		fmt.Printf("        Automatic (non-interactive) mode\n")
-
-		fmt.Printf("\nInit options for non-interactive mode (--auto):\n")
-		fmt.Printf("    --network-address ADDRESS\n")
-		fmt.Printf("        Address to bind LXD to (default: none)\n")
-		fmt.Printf("    --network-port PORT\n")
-		fmt.Printf("        Port to bind LXD to (default: 8443)\n")
-		fmt.Printf("    --storage-backend NAME\n")
-		fmt.Printf("        Storage backend to use (zfs or dir, default: dir)\n")
-		fmt.Printf("    --storage-create-device DEVICE\n")
-		fmt.Printf("        Setup device based storage using DEVICE\n")
-		fmt.Printf("    --storage-create-loop SIZE\n")
-		fmt.Printf("        Setup loop based storage with SIZE in GB\n")
-		fmt.Printf("    --storage-pool NAME\n")
-		fmt.Printf("        Storage pool to use or create\n")
-		fmt.Printf("    --trust-password PASSWORD\n")
-		fmt.Printf("        Password required to add new clients\n")
-
-		fmt.Printf("\nShutdown options:\n")
-		fmt.Printf("    --timeout SECONDS\n")
-		fmt.Printf("        How long to wait before failing\n")
-
-		fmt.Printf("\nWaitready options:\n")
-		fmt.Printf("    --timeout SECONDS\n")
-		fmt.Printf("        How long to wait before failing\n")
-
-		fmt.Printf("\n\nInternal commands (don't call these directly):\n")
-		fmt.Printf("    forkexec\n")
-		fmt.Printf("        Execute a command in a container\n")
-		fmt.Printf("    forkgetnet\n")
-		fmt.Printf("        Get container network information\n")
-		fmt.Printf("    forkgetfile\n")
-		fmt.Printf("        Grab a file from a running container\n")
-		fmt.Printf("    forkmigrate\n")
-		fmt.Printf("        Restore a container after migration\n")
-		fmt.Printf("    forkputfile\n")
-		fmt.Printf("        Push a file to a running container\n")
-		fmt.Printf("    forkstart\n")
-		fmt.Printf("        Start a container\n")
-		fmt.Printf("    callhook\n")
-		fmt.Printf("        Call a container hook\n")
-		fmt.Printf("    migratedumpsuccess\n")
-		fmt.Printf("        Indicate that a migration dump was successful\n")
-		fmt.Printf("    netcat\n")
-		fmt.Printf("        Mirror a unix socket to stdin/stdout\n")
-	}
-
-	// Parse the arguments
-	gnuflag.Parse(true)
+	// Pupulate a new Args instance by parsing the command line arguments
+	// passed.
+	context := cmd.DefaultContext()
+	args := &Args{}
+	parser := cmd.NewParser(context, usage)
+	parser.Parse(os.Args, args)
 
 	// Set the global variables
-	debug = *argDebug
-	verbose = *argVerbose
+	debug = args.Debug
+	verbose = args.Verbose
 
-	if *argHelp {
-		// The user asked for help via --help, so we shouldn't print to
-		// stderr.
-		gnuflag.SetOut(os.Stdout)
-		gnuflag.Usage()
+	if args.Help {
+		context.Output(usage)
 		return nil
 	}
 
 	// Deal with --version right here
-	if *argVersion {
+	if args.Version {
 		fmt.Println(version.Version)
 		return nil
 	}
@@ -178,64 +63,60 @@ func run() error {
 
 	// Configure logging
 	syslog := ""
-	if *argSyslog {
+	if args.Syslog {
 		syslog = "lxd"
 	}
 
 	handler := eventsHandler{}
 	var err error
-	logger.Log, err = logging.GetLogger(syslog, *argLogfile, *argVerbose, *argDebug, handler)
+	logger.Log, err = logging.GetLogger(syslog, args.Logfile, args.Verbose, args.Debug, handler)
 	if err != nil {
 		fmt.Printf("%s", err)
 		return nil
 	}
 
 	// Process sub-commands
-	if len(os.Args) > 1 {
-		// "forkputfile", "forkgetfile", "forkmount" and "forkumount" are handled specially in nsexec.go
+	if args.Subcommand != "" {
+		// "forkputfile", "forkgetfile", "forkmount" and "forkumount" are handled specially in main_nsexec.go
 		// "forkgetnet" is partially handled in nsexec.go (setns)
-		switch os.Args[1] {
+		switch args.Subcommand {
 		// Main commands
 		case "activateifneeded":
 			return cmdActivateIfNeeded()
 		case "daemon":
-			return cmdDaemon()
+			return cmdDaemon(args)
 		case "callhook":
-			return cmdCallHook(os.Args[1:])
+			return cmdCallHook(args)
 		case "init":
-			return cmdInit()
+			return cmdInit(args)
 		case "ready":
 			return cmdReady()
 		case "shutdown":
-			return cmdShutdown()
+			return cmdShutdown(args)
 		case "waitready":
-			return cmdWaitReady()
-
+			return cmdWaitReady(args)
 		// Internal commands
 		case "forkgetnet":
 			return cmdForkGetNet()
 		case "forkmigrate":
-			return cmdForkMigrate(os.Args[1:])
+			return cmdForkMigrate(args)
 		case "forkstart":
-			return cmdForkStart(os.Args[1:])
+			return cmdForkStart(args)
 		case "forkexec":
-			ret, err := cmdForkExec(os.Args[1:])
+			ret, err := cmdForkExec(args)
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "error: %v\n", err)
 			}
 			os.Exit(ret)
 		case "netcat":
-			return cmdNetcat(os.Args[1:])
+			return cmdNetcat(args)
 		case "migratedumpsuccess":
-			return cmdMigrateDumpSuccess(os.Args[1:])
+			return cmdMigrateDumpSuccess(args)
 		}
+	} else {
+		return cmdDaemon(args) // Default subcommand
 	}
 
-	// Fail if some other command is passed
-	if gnuflag.NArg() > 0 {
-		gnuflag.Usage()
-		return fmt.Errorf("Unknown arguments")
-	}
-
-	return cmdDaemon()
+	context.Output(usage)
+	return fmt.Errorf("Unknown arguments")
 }
diff --git a/lxd/main_args.go b/lxd/main_args.go
new file mode 100644
index 000000000..02a054dcd
--- /dev/null
+++ b/lxd/main_args.go
@@ -0,0 +1,130 @@
+package main
+
+// Args contains all supported LXD command line flags.
+type Args struct {
+	Auto                 bool   `flag:"auto"`
+	CPUProfile           string `flag:"cpuprofile"`
+	Debug                bool   `flag:"debug"`
+	Group                string `flag:"group"`
+	Help                 bool   `flag:"help"`
+	Logfile              string `flag:"logfile"`
+	MemProfile           string `flag:"memprofile"`
+	NetworkAddress       string `flag:"network-address"`
+	NetworkPort          int64  `flag:"network-port"`
+	PrintGoroutinesEvery int    `flag:"print-goroutines-every"`
+	StorageBackend       string `flag:"storage-backend"`
+	StorageCreateDevice  string `flag:"storage-create-device"`
+	StorageCreateLoop    int64  `flag:"storage-create-loop"`
+	StorageDataset       string `flag:"storage-pool"`
+	Syslog               bool   `flag:"syslog"`
+	Timeout              int    `flag:"timeout"`
+	TrustPassword        string `flag:"trust-password"`
+	Verbose              bool   `flag:"verbose"`
+	Version              bool   `flag:"version"`
+
+	// The LXD subcommand, if any (e.g. "init" for "lxd init")
+	Subcommand string
+
+	// The subcommand parameters (e.g. []string{"foo"} for "lxd import foo").
+	Params []string
+
+	// Any extra arguments following the "--" separator.
+	Extra []string
+}
+
+const usage = `Usage: lxd [command] [options]
+
+Commands:
+    activateifneeded
+        Check if LXD should be started (at boot) and if so, spawns it through socket activation
+    daemon [--group=lxd] (default command)
+        Start the main LXD daemon
+    init [--auto] [--network-address=IP] [--network-port=8443] [--storage-backend=dir]
+         [--storage-create-device=DEVICE] [--storage-create-loop=SIZE] [--storage-pool=POOL]
+         [--trust-password=] [--preseed]
+        Setup storage and networking
+    ready
+        Tells LXD that any setup-mode configuration has been done and that it can start containers.
+    shutdown [--timeout=60]
+        Perform a clean shutdown of LXD and all running containers
+    waitready [--timeout=15]
+        Wait until LXD is ready to handle requests
+    import <container name> [--force]
+        Import a pre-existing container from storage
+
+
+Common options:
+    --debug
+        Enable debug mode
+    --help
+        Print this help message
+    --logfile FILE
+        Logfile to log to (e.g., /var/log/lxd/lxd.log)
+    --syslog
+        Enable syslog logging
+    --verbose
+        Enable verbose mode
+    --version
+        Print LXD's version number and exit
+
+Daemon options:
+    --group GROUP
+        Group which owns the shared socket
+
+Daemon debug options:
+    --cpuprofile FILE
+        Enable cpu profiling into the specified file
+    --memprofile FILE
+        Enable memory profiling into the specified file
+    --print-goroutines-every SECONDS
+        For debugging, print a complete stack trace every n seconds
+
+Init options:
+    --auto
+        Automatic (non-interactive) mode
+
+Init options for non-interactive mode (--auto):
+    --network-address ADDRESS
+        Address to bind LXD to (default: none)
+    --network-port PORT
+        Port to bind LXD to (default: 8443)
+    --storage-backend NAME
+        Storage backend to use (btrfs, dir, lvm or zfs, default: dir)
+    --storage-create-device DEVICE
+        Setup device based storage using DEVICE
+    --storage-create-loop SIZE
+        Setup loop based storage with SIZE in GB
+    --storage-pool NAME
+        Storage pool to use or create
+    --trust-password PASSWORD
+        Password required to add new clients
+
+Shutdown options:
+    --timeout SECONDS
+        How long to wait before failing
+
+Waitready options:
+    --timeout SECONDS
+        How long to wait before failing
+
+
+Internal commands (don't call these directly):
+    forkexec
+        Execute a command in a container
+    forkgetnet
+        Get container network information
+    forkgetfile
+        Grab a file from a running container
+    forkmigrate
+        Restore a container after migration
+    forkputfile
+        Push a file to a running container
+    forkstart
+        Start a container
+    callhook
+        Call a container hook
+    migratedumpsuccess
+        Indicate that a migration dump was successful
+    netcat
+        Mirror a unix socket to stdin/stdout
+`
diff --git a/lxd/main_args_test.go b/lxd/main_args_test.go
new file mode 100644
index 000000000..0c34ca34c
--- /dev/null
+++ b/lxd/main_args_test.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/lxc/lxd/shared/cmd"
+)
+
+// Check the default values of all command line arguments.
+func TestParse_ArgsDefaults(t *testing.T) {
+	context := cmd.NewMemoryContext(cmd.NewMemoryStreams(""))
+	line := []string{"lxd"}
+	args := &Args{}
+	parser := cmd.NewParser(context, "")
+	parser.Parse(line, args)
+
+	assert.Equal(t, false, args.Auto)
+	assert.Equal(t, "", args.CPUProfile)
+	assert.Equal(t, false, args.Debug)
+	assert.Equal(t, "", args.Group)
+	assert.Equal(t, false, args.Help)
+	assert.Equal(t, "", args.Logfile)
+	assert.Equal(t, "", args.MemProfile)
+	assert.Equal(t, "", args.NetworkAddress)
+	assert.Equal(t, int64(-1), args.NetworkPort)
+	assert.Equal(t, -1, args.PrintGoroutinesEvery)
+	assert.Equal(t, "", args.StorageBackend)
+	assert.Equal(t, "", args.StorageCreateDevice)
+	assert.Equal(t, int64(-1), args.StorageCreateLoop)
+	assert.Equal(t, "", args.StorageDataset)
+	assert.Equal(t, false, args.Syslog)
+	assert.Equal(t, -1, args.Timeout)
+	assert.Equal(t, "", args.TrustPassword)
+	assert.Equal(t, false, args.Verbose)
+	assert.Equal(t, false, args.Version)
+}
+
+// Check that parsing the command line results in the correct attributes
+// being set.
+func TestParse_ArgsCustom(t *testing.T) {
+	context := cmd.NewMemoryContext(cmd.NewMemoryStreams(""))
+	line := []string{
+		"lxd",
+		"--auto",
+		"--cpuprofile", "lxd.cpu",
+		"--debug",
+		"--group", "lxd",
+		"--help",
+		"--logfile", "lxd.log",
+		"--memprofile", "lxd.mem",
+		"--network-address", "127.0.0.1",
+		"--network-port", "666",
+		"--print-goroutines-every", "10",
+		"--storage-backend", "btrfs",
+		"--storage-create-device", "/dev/sda2",
+		"--storage-create-loop", "8192",
+		"--storage-pool", "default",
+		"--syslog",
+		"--timeout", "30",
+		"--trust-password", "sekret",
+		"--verbose",
+		"--version",
+	}
+	args := &Args{}
+	parser := cmd.NewParser(context, "")
+	parser.Parse(line, args)
+
+	assert.Equal(t, true, args.Auto)
+	assert.Equal(t, "lxd.cpu", args.CPUProfile)
+	assert.Equal(t, true, args.Debug)
+	assert.Equal(t, "lxd", args.Group)
+	assert.Equal(t, true, args.Help)
+	assert.Equal(t, "lxd.log", args.Logfile)
+	assert.Equal(t, "lxd.mem", args.MemProfile)
+	assert.Equal(t, "127.0.0.1", args.NetworkAddress)
+	assert.Equal(t, int64(666), args.NetworkPort)
+	assert.Equal(t, 10, args.PrintGoroutinesEvery)
+	assert.Equal(t, "btrfs", args.StorageBackend)
+	assert.Equal(t, "/dev/sda2", args.StorageCreateDevice)
+	assert.Equal(t, int64(8192), args.StorageCreateLoop)
+	assert.Equal(t, "default", args.StorageDataset)
+	assert.Equal(t, true, args.Syslog)
+	assert.Equal(t, 30, args.Timeout)
+	assert.Equal(t, "sekret", args.TrustPassword)
+	assert.Equal(t, true, args.Verbose)
+	assert.Equal(t, true, args.Version)
+}
diff --git a/lxd/main_callhook.go b/lxd/main_callhook.go
index c8507fd83..cc0b5c858 100644
--- a/lxd/main_callhook.go
+++ b/lxd/main_callhook.go
@@ -8,15 +8,15 @@ import (
 	"github.com/lxc/lxd/client"
 )
 
-func cmdCallHook(args []string) error {
+func cmdCallHook(args *Args) error {
 	// Parse the arguments
-	if len(args) < 4 {
+	if len(args.Params) < 3 {
 		return fmt.Errorf("Invalid arguments")
 	}
 
-	path := args[1]
-	id := args[2]
-	state := args[3]
+	path := args.Params[0]
+	id := args.Params[1]
+	state := args.Params[2]
 	target := ""
 
 	// Connect to LXD
diff --git a/lxd/main_daemon.go b/lxd/main_daemon.go
index c49ef127d..b4058eec9 100644
--- a/lxd/main_daemon.go
+++ b/lxd/main_daemon.go
@@ -13,14 +13,14 @@ import (
 	"github.com/lxc/lxd/shared/logger"
 )
 
-func cmdDaemon() error {
+func cmdDaemon(args *Args) error {
 	// Only root should run this
 	if os.Geteuid() != 0 {
 		return fmt.Errorf("This must be run as root")
 	}
 
-	if *argCPUProfile != "" {
-		f, err := os.Create(*argCPUProfile)
+	if args.CPUProfile != "" {
+		f, err := os.Create(args.CPUProfile)
 		if err != nil {
 			fmt.Printf("Error opening cpu profile file: %s\n", err)
 			return nil
@@ -29,8 +29,8 @@ func cmdDaemon() error {
 		defer pprof.StopCPUProfile()
 	}
 
-	if *argMemProfile != "" {
-		go memProfiler(*argMemProfile)
+	if args.MemProfile != "" {
+		go memProfiler(args.MemProfile)
 	}
 
 	neededPrograms := []string{"setfacl", "rsync", "tar", "unsquashfs", "xz"}
@@ -41,17 +41,17 @@ func cmdDaemon() error {
 		}
 	}
 
-	if *argPrintGoroutinesEvery > 0 {
+	if args.PrintGoroutinesEvery > 0 {
 		go func() {
 			for {
-				time.Sleep(time.Duration(*argPrintGoroutinesEvery) * time.Second)
+				time.Sleep(time.Duration(args.PrintGoroutinesEvery) * time.Second)
 				logger.Debugf(logger.GetStack())
 			}
 		}()
 	}
 
 	d := NewDaemon()
-	d.group = *argGroup
+	d.group = args.Group
 	d.SetupMode = shared.PathExists(shared.VarPath(".setup_mode"))
 	err := d.Init()
 	if err != nil {
diff --git a/lxd/main_forkexec.go b/lxd/main_forkexec.go
index bdbdcac64..ab1638a69 100644
--- a/lxd/main_forkexec.go
+++ b/lxd/main_forkexec.go
@@ -15,14 +15,17 @@ import (
 /*
  * This is called by lxd when called as "lxd forkexec <container>"
  */
-func cmdForkExec(args []string) (int, error) {
-	if len(args) < 6 {
-		return -1, fmt.Errorf("Bad arguments: %q", args)
+func cmdForkExec(args *Args) (int, error) {
+	if len(args.Params) < 3 {
+		return -1, fmt.Errorf("Bad params: %q", args.Params)
+	}
+	if len(args.Extra) < 1 {
+		return -1, fmt.Errorf("Bad extra: %q", args.Extra)
 	}
 
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
+	name := args.Params[0]
+	lxcpath := args.Params[1]
+	configPath := args.Params[2]
 
 	c, err := lxc.NewContainer(name, lxcpath)
 	if err != nil {
@@ -63,7 +66,7 @@ func cmdForkExec(args []string) (int, error) {
 	cmd := []string{}
 
 	section := ""
-	for _, arg := range args[5:] {
+	for _, arg := range args.Extra {
 		// The "cmd" section must come last as it may contain a --
 		if arg == "--" && section != "cmd" {
 			section = ""
diff --git a/lxd/main_forkmigrate.go b/lxd/main_forkmigrate.go
index 45e437aae..ab3191c38 100644
--- a/lxd/main_forkmigrate.go
+++ b/lxd/main_forkmigrate.go
@@ -19,16 +19,16 @@ import (
  * want to fork for the same reasons we do forkstart (i.e. reduced memory
  * footprint when we fork tasks that will never free golang's memory, etc.)
  */
-func cmdForkMigrate(args []string) error {
-	if len(args) != 6 {
-		return fmt.Errorf("Bad arguments %q", args)
+func cmdForkMigrate(args *Args) error {
+	if len(args.Params) != 5 {
+		return fmt.Errorf("Bad arguments %q", args.Params)
 	}
 
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
-	imagesDir := args[4]
-	preservesInodes, err := strconv.ParseBool(args[5])
+	name := args.Params[0]
+	lxcpath := args.Params[1]
+	configPath := args.Params[2]
+	imagesDir := args.Params[3]
+	preservesInodes, err := strconv.ParseBool(args.Params[4])
 
 	c, err := lxc.NewContainer(name, lxcpath)
 	if err != nil {
diff --git a/lxd/main_forkstart.go b/lxd/main_forkstart.go
index cc3d2ed1b..0c51daa1e 100644
--- a/lxd/main_forkstart.go
+++ b/lxd/main_forkstart.go
@@ -15,14 +15,14 @@ import (
  * 'forkstart' is used instead of just 'start' in the hopes that people
  * do not accidentally type 'lxd start' instead of 'lxc start'
  */
-func cmdForkStart(args []string) error {
-	if len(args) != 4 {
-		return fmt.Errorf("Bad arguments: %q", args)
+func cmdForkStart(args *Args) error {
+	if len(args.Params) != 3 {
+		return fmt.Errorf("Bad arguments: %q", args.Params)
 	}
 
-	name := args[1]
-	lxcpath := args[2]
-	configPath := args[3]
+	name := args.Params[0]
+	lxcpath := args.Params[1]
+	configPath := args.Params[2]
 
 	c, err := lxc.NewContainer(name, lxcpath)
 	if err != nil {
diff --git a/lxd/main_init.go b/lxd/main_init.go
index f1cbfb8bb..35087db3e 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -13,28 +13,17 @@ import (
 
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/util"
+
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/cmd"
 	"github.com/lxc/lxd/shared/logger"
 )
 
-// CmdInitArgs holds command line arguments for the "lxd init" command.
-type CmdInitArgs struct {
-	Auto                bool
-	StorageBackend      string
-	StorageCreateDevice string
-	StorageCreateLoop   int64
-	StoragePool         string
-	NetworkPort         int64
-	NetworkAddress      string
-	TrustPassword       string
-}
-
 // CmdInit implements the "lxd init" command line.
 type CmdInit struct {
 	Context         *cmd.Context
-	Args            *CmdInitArgs
+	Args            *Args
 	RunningInUserns bool
 	SocketPath      string
 	PasswordReader  func(int) ([]byte, error)
@@ -130,7 +119,7 @@ func (cmd *CmdInit) fillDataAuto(data *cmdInitData, client lxd.ContainerServer,
 			Backend:  cmd.Args.StorageBackend,
 			LoopSize: cmd.Args.StorageCreateLoop,
 			Device:   cmd.Args.StorageCreateDevice,
-			Pool:     cmd.Args.StoragePool,
+			Pool:     cmd.Args.StorageDataset,
 		}
 
 		if cmd.Args.StorageCreateDevice != "" {
@@ -400,7 +389,7 @@ func (cmd *CmdInit) initProfileUpdate(client lxd.ContainerServer, profile api.Pr
 // and no invalid combination is provided.
 func (cmd *CmdInit) validateArgs() error {
 	if !cmd.Args.Auto {
-		if cmd.Args.StorageBackend != "" || cmd.Args.StorageCreateDevice != "" || cmd.Args.StorageCreateLoop != -1 || cmd.Args.StoragePool != "" || cmd.Args.NetworkAddress != "" || cmd.Args.NetworkPort != -1 || cmd.Args.TrustPassword != "" {
+		if cmd.Args.StorageBackend != "" || cmd.Args.StorageCreateDevice != "" || cmd.Args.StorageCreateLoop != -1 || cmd.Args.StorageDataset != "" || cmd.Args.NetworkAddress != "" || cmd.Args.NetworkPort != -1 || cmd.Args.TrustPassword != "" {
 			return fmt.Errorf("Init configuration is only valid with --auto")
 		}
 	}
@@ -418,7 +407,7 @@ func (cmd *CmdInit) validateArgsAuto(availableStoragePoolsDrivers []string) erro
 	}
 
 	if cmd.Args.StorageBackend == "dir" {
-		if cmd.Args.StorageCreateLoop != -1 || cmd.Args.StorageCreateDevice != "" || cmd.Args.StoragePool != "" {
+		if cmd.Args.StorageCreateLoop != -1 || cmd.Args.StorageCreateDevice != "" || cmd.Args.StorageDataset != "" {
 			return fmt.Errorf("None of --storage-pool, --storage-create-device or --storage-create-loop may be used with the 'dir' backend.")
 		}
 	} else {
@@ -634,18 +623,8 @@ type cmdInitNetworkingParams struct {
 // some change, and that are passed around as parameters.
 type reverter func() error
 
-func cmdInit() error {
+func cmdInit(args *Args) error {
 	context := cmd.NewContext(os.Stdin, os.Stdout, os.Stderr)
-	args := &CmdInitArgs{
-		Auto:                *argAuto,
-		StorageBackend:      *argStorageBackend,
-		StorageCreateDevice: *argStorageCreateDevice,
-		StorageCreateLoop:   *argStorageCreateLoop,
-		StoragePool:         *argStoragePool,
-		NetworkPort:         *argNetworkPort,
-		NetworkAddress:      *argNetworkAddress,
-		TrustPassword:       *argTrustPassword,
-	}
 	command := &CmdInit{
 		Context:         context,
 		Args:            args,
diff --git a/lxd/main_init_test.go b/lxd/main_init_test.go
index b5249f3a3..1c5f12fc1 100644
--- a/lxd/main_init_test.go
+++ b/lxd/main_init_test.go
@@ -4,6 +4,7 @@ import (
 	"testing"
 
 	"github.com/lxc/lxd/client"
+
 	"github.com/lxc/lxd/shared/cmd"
 	"github.com/stretchr/testify/suite"
 )
@@ -12,7 +13,7 @@ type cmdInitTestSuite struct {
 	lxdTestSuite
 	streams *cmd.MemoryStreams
 	context *cmd.Context
-	args    *CmdInitArgs
+	args    *Args
 	command *CmdInit
 	client  lxd.ContainerServer
 }
@@ -21,7 +22,7 @@ func (suite *cmdInitTestSuite) SetupTest() {
 	suite.lxdTestSuite.SetupTest()
 	suite.streams = cmd.NewMemoryStreams("")
 	suite.context = cmd.NewMemoryContext(suite.streams)
-	suite.args = &CmdInitArgs{
+	suite.args = &Args{
 		NetworkPort:       -1,
 		StorageCreateLoop: -1,
 	}
diff --git a/lxd/main_migratedumpsuccess.go b/lxd/main_migratedumpsuccess.go
index 56fce2e11..85603534b 100644
--- a/lxd/main_migratedumpsuccess.go
+++ b/lxd/main_migratedumpsuccess.go
@@ -8,9 +8,9 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
-func cmdMigrateDumpSuccess(args []string) error {
-	if len(args) != 3 {
-		return fmt.Errorf("bad migrate dump success args %s", args)
+func cmdMigrateDumpSuccess(args *Args) error {
+	if len(args.Params) != 2 {
+		return fmt.Errorf("bad migrate dump success args %s", args.Params)
 	}
 
 	c, err := lxd.ConnectLXDUnix("", nil)
@@ -18,14 +18,14 @@ func cmdMigrateDumpSuccess(args []string) error {
 		return err
 	}
 
-	url := fmt.Sprintf("%s/websocket?secret=%s", strings.TrimPrefix(args[1], "/1.0"), args[2])
+	url := fmt.Sprintf("%s/websocket?secret=%s", strings.TrimPrefix(args.Params[0], "/1.0"), args.Params[1])
 	conn, err := c.RawWebsocket(url)
 	if err != nil {
 		return err
 	}
 	conn.Close()
 
-	resp, _, err := c.RawQuery("GET", fmt.Sprintf("%s/wait", args[1]), nil, "")
+	resp, _, err := c.RawQuery("GET", fmt.Sprintf("%s/wait", args.Params[0]), nil, "")
 	if err != nil {
 		return err
 	}
diff --git a/lxd/main_netcat.go b/lxd/main_netcat.go
index 1cf9ee0a1..2fac5b338 100644
--- a/lxd/main_netcat.go
+++ b/lxd/main_netcat.go
@@ -18,12 +18,12 @@ import (
 // and does unbuffered netcatting of to socket to stdin/stdout. Any arguments
 // after the path to the unix socket are ignored, so that this can be passed
 // directly to rsync as the sync command.
-func cmdNetcat(args []string) error {
-	if len(args) < 3 {
-		return fmt.Errorf("Bad arguments %q", args)
+func cmdNetcat(args *Args) error {
+	if len(args.Params) < 2 {
+		return fmt.Errorf("Bad arguments %q", args.Params)
 	}
 
-	logPath := shared.LogPath(args[2], "netcat.log")
+	logPath := shared.LogPath(args.Params[1], "netcat.log")
 	if shared.PathExists(logPath) {
 		os.Remove(logPath)
 	}
@@ -34,15 +34,15 @@ func cmdNetcat(args []string) error {
 	}
 	defer logFile.Close()
 
-	uAddr, err := net.ResolveUnixAddr("unix", args[1])
+	uAddr, err := net.ResolveUnixAddr("unix", args.Params[0])
 	if err != nil {
-		logFile.WriteString(fmt.Sprintf("Could not resolve unix domain socket \"%s\": %s.\n", args[1], err))
+		logFile.WriteString(fmt.Sprintf("Could not resolve unix domain socket \"%s\": %s.\n", args.Params[0], err))
 		return err
 	}
 
 	conn, err := net.DialUnix("unix", nil, uAddr)
 	if err != nil {
-		logFile.WriteString(fmt.Sprintf("Could not dial unix domain socket \"%s\": %s.\n", args[1], err))
+		logFile.WriteString(fmt.Sprintf("Could not dial unix domain socket \"%s\": %s.\n", args.Params[0], err))
 		return err
 	}
 
@@ -52,7 +52,7 @@ func cmdNetcat(args []string) error {
 	go func() {
 		_, err := io.Copy(eagainWriter{os.Stdout}, eagainReader{conn})
 		if err != nil {
-			logFile.WriteString(fmt.Sprintf("Error while copying from stdout to unix domain socket \"%s\": %s.\n", args[1], err))
+			logFile.WriteString(fmt.Sprintf("Error while copying from stdout to unix domain socket \"%s\": %s.\n", args.Params[0], err))
 		}
 		conn.Close()
 		wg.Done()
@@ -61,7 +61,7 @@ func cmdNetcat(args []string) error {
 	go func() {
 		_, err := io.Copy(eagainWriter{conn}, eagainReader{os.Stdin})
 		if err != nil {
-			logFile.WriteString(fmt.Sprintf("Error while copying from unix domain socket \"%s\" to stdin: %s.\n", args[1], err))
+			logFile.WriteString(fmt.Sprintf("Error while copying from unix domain socket \"%s\" to stdin: %s.\n", args.Params[0], err))
 		}
 	}()
 
diff --git a/lxd/main_shutdown.go b/lxd/main_shutdown.go
index 7c982fbb7..00654ff64 100644
--- a/lxd/main_shutdown.go
+++ b/lxd/main_shutdown.go
@@ -7,7 +7,7 @@ import (
 	"github.com/lxc/lxd/client"
 )
 
-func cmdShutdown() error {
+func cmdShutdown(args *Args) error {
 	c, err := lxd.ConnectLXDUnix("", nil)
 	if err != nil {
 		return err
@@ -30,12 +30,12 @@ func cmdShutdown() error {
 		close(chMonitor)
 	}()
 
-	if *argTimeout > 0 {
+	if args.Timeout > 0 {
 		select {
 		case <-chMonitor:
 			break
-		case <-time.After(time.Second * time.Duration(*argTimeout)):
-			return fmt.Errorf("LXD still running after %ds timeout.", *argTimeout)
+		case <-time.After(time.Second * time.Duration(args.Timeout)):
+			return fmt.Errorf("LXD still running after %ds timeout.", args.Timeout)
 		}
 	} else {
 		<-chMonitor
diff --git a/lxd/main_waitready.go b/lxd/main_waitready.go
index 057abe758..3edca01cd 100644
--- a/lxd/main_waitready.go
+++ b/lxd/main_waitready.go
@@ -7,13 +7,13 @@ import (
 	"github.com/lxc/lxd/client"
 )
 
-func cmdWaitReady() error {
+func cmdWaitReady(args *Args) error {
 	var timeout int
 
-	if *argTimeout == -1 {
+	if args.Timeout == -1 {
 		timeout = 15
 	} else {
-		timeout = *argTimeout
+		timeout = args.Timeout
 	}
 
 	finger := make(chan error, 1)
diff --git a/shared/cmd/context.go b/shared/cmd/context.go
index ef192ce31..925152d82 100644
--- a/shared/cmd/context.go
+++ b/shared/cmd/context.go
@@ -6,6 +6,7 @@ import (
 	"gopkg.in/yaml.v2"
 	"io"
 	"io/ioutil"
+	"os"
 	"strconv"
 	"strings"
 
@@ -20,6 +21,12 @@ type Context struct {
 	stderr io.Writer
 }
 
+// DefaultContext returns a new Context connected the stdin, stdout and stderr
+// streams.
+func DefaultContext() *Context {
+	return NewContext(os.Stdin, os.Stderr, os.Stdout)
+}
+
 // NewContext creates a new command context with the given parameters.
 func NewContext(stdin io.Reader, stdout, stderr io.Writer) *Context {
 	return &Context{

From 2b4e4d62686c6b4a4a169b605035e7e238f434ba Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanayaka at canonical.com>
Date: Wed, 4 Oct 2017 12:40:39 +0000
Subject: [PATCH 1193/1193] Vendor the subtest compatibility schim in
 shared/subtest

Since we can't add new dependencies (also for testing), the
github.com/mpvl/subtest package which offers a backward compatible
subtest API was vendored under shared/subtest.

Signed-off-by: Free Ekanayaka <free.ekanayaka at canonical.com>
---
 lxd/db/query/slices_test.go |  2 +-
 shared/cmd/parser_test.go   |  2 +-
 shared/subtest/doc.go       | 26 ++++++++++++++++++++++++++
 shared/subtest/go1_6.go     | 17 +++++++++++++++++
 shared/subtest/go1_7.go     | 16 ++++++++++++++++
 5 files changed, 61 insertions(+), 2 deletions(-)
 create mode 100644 shared/subtest/doc.go
 create mode 100644 shared/subtest/go1_6.go
 create mode 100644 shared/subtest/go1_7.go

diff --git a/lxd/db/query/slices_test.go b/lxd/db/query/slices_test.go
index 47e4a3eb0..f5bb6549a 100644
--- a/lxd/db/query/slices_test.go
+++ b/lxd/db/query/slices_test.go
@@ -5,10 +5,10 @@ import (
 	"testing"
 
 	_ "github.com/mattn/go-sqlite3"
-	"github.com/mpvl/subtest"
 	"github.com/stretchr/testify/assert"
 
 	"github.com/lxc/lxd/lxd/db/query"
+	"github.com/lxc/lxd/shared/subtest"
 )
 
 // Exercise possible failure modes.
diff --git a/shared/cmd/parser_test.go b/shared/cmd/parser_test.go
index 1111a168e..1b48a7eb8 100644
--- a/shared/cmd/parser_test.go
+++ b/shared/cmd/parser_test.go
@@ -4,10 +4,10 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/mpvl/subtest"
 	"github.com/stretchr/testify/assert"
 
 	"github.com/lxc/lxd/shared/cmd"
+	"github.com/lxc/lxd/shared/subtest"
 )
 
 // Sample command line arguments specification.
diff --git a/shared/subtest/doc.go b/shared/subtest/doc.go
new file mode 100644
index 000000000..e588491c1
--- /dev/null
+++ b/shared/subtest/doc.go
@@ -0,0 +1,26 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// See https://github.com/golang/go/blob/master/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://github.com/golang/go/blob/master/LICENSE
+
+// Package subtest provides a backwards-compatible way to run subtests.
+//
+// For Go 1.7 and higher, it uses testing.T's run method. For lower versions
+// it mimics subtests by logging additional information.
+//
+//    package foo
+//
+//    import "github.com/mpvl/subtest"
+//
+//    var testCases = ...
+//
+//    func TestFoo(t *testing.T) {
+//        for _, tc := range testCases {
+//            subtest.Run(t, tc.name, func(t *testing.T) {
+//                tc.doTest()
+//            })
+//        }
+//    }
+//
+package subtest
diff --git a/shared/subtest/go1_6.go b/shared/subtest/go1_6.go
new file mode 100644
index 000000000..c64d68f05
--- /dev/null
+++ b/shared/subtest/go1_6.go
@@ -0,0 +1,17 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// See https://github.com/golang/go/blob/master/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://github.com/golang/go/blob/master/LICENSE
+
+// +build !go1.7
+
+package subtest
+
+import "testing"
+
+// Run runs function f as a subtest of t.
+func Run(t *testing.T, name string, f func(t *testing.T)) {
+	t.Logf("Running %s...", name)
+	f(t)
+}
diff --git a/shared/subtest/go1_7.go b/shared/subtest/go1_7.go
new file mode 100644
index 000000000..55cd3b49a
--- /dev/null
+++ b/shared/subtest/go1_7.go
@@ -0,0 +1,16 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// See https://github.com/golang/go/blob/master/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://github.com/golang/go/blob/master/LICENSE
+
+// +build go1.7
+
+package subtest
+
+import "testing"
+
+// Run runs function f as a subtest of t.
+func Run(t *testing.T, name string, f func(t *testing.T)) {
+	t.Run(name, f)
+}


More information about the lxc-devel mailing list